前言
前两天小组开会,学长提问什么是系统调用,自己突然脑子一片空白,明明是一个很简单的问题,却回答不上来,所以记录一篇博客。
一. 什么是系统调用?
首先来自维基百科上的定义:系统调用指运行在使用者空间的程序向操作系统内核请求需要更高权限运行的服务。系统调用提供用户程序与操作系统之间的接口。大多数系统交互式操作需求在内核态执行。如设备IO操作或者进程间通信.
就如我们常常调用C库的函数,那么底层为我们切换到所调用的函数时所做的一些事情是什么呢?
由下面的图展现:
通过这个图可以简单的概括系统调用的流程:
- 调用中断指令从用户态进入内核态
- 在内核态中调用所需要的函数
- 返回用户态
这里我们可以看到这张图是以write()
函数来作为一个例子,那么下面我们一次来说说每个流程的具体步骤
具体流程
1.调用中断指令从用户态进入内核态
这个过程中中断就很有意思,首先一个很直观的问题,什么是中断?
我认为中断的本质就是两个:
- 保存上下文
- 执行跳转指令
而无论外部中断和内部中断都需要经过一下步骤:
请求中断-> 响应中断->关闭中断->保留断点->中断源识别->保护现场->中断服务子程序->恢复现场->中断返回
下面这张图简要的概括了一个中断的过程
下面这张图则解释了我们从高级语言到汇编执行系统调用的一个过程
之后在执行中断指令进入内核之前系统需要保存当前进程的上下文
从而达到从内核态切换到用户态后能够继续执行当前进程后面的指令
在这个过程中,还会涉及到特权级切换,可以参考下面的博客
特权级切换
2.在内核态中调用所需要的函数
在这之后linux会截获处理器的系统调用异常,将SystemCall()
作为系统调用异常处理函数,之后SystemCall()
函数会先检查传入的参数,判断是否我们自己定制的系统调用号。如果是,就会调用定制的函数后使用RFI
指令后返回,否则,调用途中的DoSyscall()
函数.
所有系统调用进入DoSyscall()
函数后,会使用sys_call_table
变量的值作为系统调用表的基地址,再加上根据系统调用号(寄存器 r0
的值)计算出的偏移量,得到对应的系统调用服务程序的地址并执行.
这里有一些细节问题(在 DoSyscall()
函数之前,参数都是通过寄存器传递的。其中, r0
用于传递系统调用号, r3
及其后几个通用寄存器用于传递真正的函数参数。系统调用服务程序(如 sys_write()
)的定义中,都加了 asmlinkage标记。这个标记的作用是,告诉编译器不要在寄存器中查找这个函数的参数,而只在栈中查找。在 DoSyscall
() 函数中,将各个参数压入栈中,并调用相应的系统调用服务程序。加了 asmlinkage 标记的系统调用服务程序就只从栈中查找各个参数)
3.返回用户态
自然而然,在系统调用结束后将关闭中断,从内核栈中将保护数据弹出,返回用户态。