操作系统-课堂笔记-系统调用(南航)

本文详细介绍了操作系统中的系统调用,包括系统调用的功能概述,如write/read等,以及其提供的安全性。深入探讨了系统调用的处理过程,如system_call()函数和sys_call_table,并分析了参数传递方式。同时,提到了如何在Linux中增加自定义系统调用,阐述了系统调用与普通函数、API和内核函数的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系统调用

系统调用功能概述

1.什么是系统调用?

  • OS内核中都有一组实现系统功能的过程,系统调用就是对上述过程的调用。
  • 编程人员利用系统调用,向OS提出服务请求,由OS代为完成。
  • 一般情况下,进程是不能够存取系统内核的,它不能存取内核使用的内存段,也不能调用内核函数,CPU的硬件结构保证了这一点,只是系统调用是个例外。

2.系统调用提供哪些功能?

系统调用是用户态进入内核态的唯一入口,常用的系统调用有:

  • write/read调用
  • 设置系统状态或读取内核数据:getpid(), getpriority(), setpriority(), sethostname()等
  • 进程管理相关:fork(), clone(), execve(), exit()等

3.提供系统调用有何优点?

系统调用其实和我们用的函数库一样,只不过这里的函数库很特殊,其涉及到硬件设备,所以系统调用的优点是:

  • 编程容易,从硬件设备的低级编程中解脱出来
  • 只有使用系统调用才能接触到内核,提高了系统的安全性,可以先检查请求的正确性,然后再执行

系统调用的处理过程

在这里插入图片描述

  • Linux实现系统调用利用了i386体系结构中的软中断,即调用了int 0x80汇编指令。
  • 这条汇编指令产生向量为128的异常,CPU便切换到内核状态执行内核函数:system_call()。
  • int 0x80指令将用户态的执行模式转变成内核态,并将控制权交给系统调用过程的起点system_call()处理函数。

system_call()函数

  • system_call()函数首先检查系统调用号,该号码告诉内核进程请求哪种服务。
  • 内核进程查看系统调用表sys_call_table找到所调用的内核函数入口地址。
  • 接着调用相应的函数,在返回后做一些系统检查,最后返回到进程。

文字描述太无聊了,翻了翻Linux0.11版内核,看看它们怎么实现的叭:

  • 我发现system_call是用汇编实现的,对应system_call.s文件,大概就是下面这样:
    在这里插入图片描述
    还有一个片段:
    在这里插入图片描述
    表示Linux0.11版内核有72个系统调用!

system_call具体的执行过程见下图,上面的代码就不解析了,还是比较容易理解的!
在这里插入图片描述在这里插入图片描述
系统调用返回时:

  • 当服务例程结束时,system_call()从eax获得系统调用的返回值,并把这个返回值保存起来,然后跳转到ret_from_sys_call(),终止系统调用处理程序的执行。
  • 当进程恢复它在用户态的执行前,先恢复保存到堆栈的寄存器值,其中eax返回时会带回系统调用的返回码(负数说明调用错误,0或正数说明正常完成)。

sys_call_table

上面说过从sys_call_table里面找内核函数入口,那sys_call_table里面有什么东西呢?
在这里插入图片描述
这里就简单看下sys_call_table长什么样子,想具体了解去读Linux内核源码哦~~!

上面还是有很多亲切的面孔的,比如exit, fork, write, read, open, dup2. getpid等等!

在看看系统调用号

在这里插入图片描述
就是酱紫~!

系统调用和普通函数调用对比

  • API是用于某种特定目的的函数,供应用程序调用,而系统调用供应用程序直接进入系统内核。
  • Linux内核提供了一些C语言函数库,这些库对系统调用进行了一些包装和扩展,因为这些库函数与系统调用的关系非常紧密,所以习惯上把这些函数也称为系统调用。
  • 有的API函数在用户空间就可以完成工作,如一些用于数学计算的函数,因此不需要使用系统调用,有的API函数可能会进行多次系统调用。
  • 不同的API 函数也可能会有相同的系统调用。比如malloc(),calloc(),free()等函数都使用相同的方法分配和释放内存。

系统命令和内核函数

系统调用与系统命令

  • 系统命令相对API来说,更高一层。每个系统命令都是一个执行程序,如ls命令等。这些命令的实现调用了系统调用。

系统调用与内核函数

  • 系统调用是用户进入内核的接口层,它本身并非内核函数,但是它由内核函数实现。
  • 进入内核后,不同的系统调用会找到各自对应的内核函数,这些内核函数被称为系统调用的“服务例程”。如系统调用getpid实际调用的服务例程为sys_getpid(),或者说系统调用getpid()是服务例程sys_getpid()的封装例程。

系统调用的实例分析

实例代码:

#include <syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main(void) {
 long ID;
 ID = getpid();
 printf ("getpid()=%ld\n", ID);
 return(0);
}

执行过程:

  • 该程序调用封装例程getpid()。该封装例程将系统调用号_NR_getpid(第20个)压入EAX寄存器
  • CPU通过int $0x80 进入内核,找到system_call(),并调用它
    (以下进入内核态)
  • 在内核中首先执行system_call(),接着执行根据系统调用号在调用表中查找到的对应的系统调用服务例程sys_getpid()。
  • 执行sys_getpid()服务例程。
  • 执行完毕后,转入ret_from_sys_call()例程,系统调用返回到用户态。

系统调用的参数传递

  • 很多系统调用需要不止一个参数,普通C函数的参数传递是通过把参数值写入堆栈(用户态堆栈或内核态堆栈)来实现的。
  • 但因为系统调用是一种特殊函数,它由用户态进入了内核态,所以既不能使用用户态的堆栈也不能直接使用内核态堆栈。
  • 在int $0x80汇编指令之前,系统调用的参数被写入CPU的寄存器。
  • 然后,在进入内核态调用系统调用服务例程之前,内核再把存放在CPU寄存器中的参数拷贝到内核态堆栈中。毕竟服务例程是C函数,它还是要到堆栈中去寻找参数的

系统调用使用寄存器来传递参数,要传递的参数有:系统调用号、系统调用所需的参数。

用于传递参数的寄存器有:eax保存系统调用号和系统调用返回值

系统调用参数保存在:ebx, ecx, edx, esi, edi

进入内核态后,system_call将上述值压栈,使其保存在内核态堆栈中,这一点在上面的代码截图中能直观的看到。

用寄存器传递参数必须满足两个条件:

  • 每个参数的长度不能超过寄存器的长度
  • 参数的个数不能超过6个(包括eax中传递的系统调用号);否则,需要用一个单独的寄存器指向进程地址空间中这些参数值所在的一个内存区即可

返回值必须写到eax寄存器中

如何增加一个系统调用

功能要求

  • 首先,自定义一个系统调用mysyscall ,它的功能是使用户的uid等于0 。然后,编写一段测试程序进行调用,思考将用户的uid设置成0意味着什么?
  • 意味着获取了管理员权限!

执行步骤如下,注笔者看的是Linux0.11版内核源码, 老师的ppt中用的是v86的,有点小区别!

  • 添加系统调用号:在unistd.h文件中
  • 在系统调用表中添加相应的表项:include/linux/sys.h中添加
  • 实现系统调用服务例程:kernal/sys.c中添加
    	int sys_mysyscall(void)
    	{
    		current->uid = current->euid = current->suid = current->fsuid = 0;
    		return 0;
    	}  
    
  • 重新编译内核,启动新内核
  • 编写一段测试程序检验实验结果
    #include <linux/unistd.h>
    _syscall0(int,mysyscall)/* 注意这里没有分号 */
    int main()
    { mysyscall();
    printf(“This is my uid: %d. \n”, getuid());
    }
    

这里笔者还没有亲手做实验,后面尝试自己做做!

小结

系统调用的过程:
1、程序调用库的封装函数
2、调用软中断 int $0x80 进入内核。
3、在内核中首先执行system_call()函数,接着根据系统调用号在系统调用表中查找到对应的系统调用服务例程
4、执行该服务例程
5、执行完毕后,转入ret_from_sys_call例程,从系统调用返回

尝试回答以下问题:
什么是系统调用?它与普通函数调用有何区别?
什么是系统调用号和系统调用表?
系统调用的参数如何传递到内核?

本系列博客目录
下一篇:strace的使用

实验报告撰写要求 实验报告要求具有以下内容: 一、实验目的 二、实验内容 三、实验要求 四、算法流程图 五、给出测试数据及运行结果 六、实验体会或对改进实验的建议 实验1 进程调度 一、实验目的 通过实验加强对进程调度算法的理解和掌握。 二、实验内容 编写程序实现基于优先级的时间片轮转调度算法。 三、实验要求 1、假定系统有5个进程,每个进程用一个进程控制块PCB来代表,进程控制块的结构如下图1.1所示: 进程名 优先级 要求运行时间 已运行时间 进程状态 指针 图1.1 其中: 进程名:作为进程的标识,假设五个进程的进程名分别为p1,p2,p3,p4,p5。 指针:进程按顺序排成循环链表,用指针指出下一个进程的进程控制块首地址,最后一个进程中的指针指出第一个进程的进程控制块首地址。 要求运行时间:假设进程需要运行的单位时间数。 已运行时间:假设进程已经运行的单位时间数,初值为0。 状态:可假设有两种状态,就绪状态和结束状态。进程的初始状态都为就绪状态。 2、每次运行所设计的处理器调度程序调度进程之前,为每个进程随机确定它的要求运行时间。 3、此程序是模拟处理器调度,因此,被选中的进程并不实际启动运行,而是执行 已运行时间+1 来模拟进程的一次运行,表示进程已经运行过一个单位时间。 4、在所设计的程序中应有显示语句,能显示每次被选中的进程名以及运行一次后进程队列的变化。 5、优先级可自己给出初始值,但要求采用动态优先级,可自己设计优先数如何变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值