系统调用是什么?
系统调用是用户进程与内核进行交互的一组接口,系统调用让应用程序受限的访问硬件设备,提供了创建新进程并与已有进程进行通信的机制,也提供了申请操作系统其他资源的能力。
系统调用在内核软件框架位置如下图:
从上图可见,系统调用是位于用户与内核之间的一组接口,所以系统调用层主要有以下功能:
- 它为用户空间提供了一种硬件的抽象接口。
- 系统调用保证了系统的稳定和安全。
- 系统调用的存在使得每个进程都运行在虚拟系统中,为实现多任务和虚拟内存提供了便利。
系统调用
Linux用户一般通过C库中定义的函数调用来访问系统调用,例如,写某个文件或向给定的指针拷贝数据等。
系统调用函数一般会有一个asmlinkage
限定词,asmlinkage
是个编译指令,通知编译器仅从栈中提取该函数的参数。
系统调用号
每个系统调用被赋予一个系统调用号,用于关联系统调用,通过系统调用号指明调用哪个系统调用。
系统调用号一旦分配就不再有任何变更,避免当该系统调用号删除后导致曾经使用该系统调用的函数调用失败。
内核记录了系统调用表中的所有已注册过的系统调用的列表,存储在kernel/syscall64.c
文件sys_call_table
中。
系统调用过程
由于Linux内核代码是位于受保护的地址上,所以用户通过系统调用来访问内核,而系统调用的实际过程是怎样的呢?
系统调用过程概括来说就是用户调用系统调用函数,陷入内核,传递系统调用号和参数,和内核执行对应的处理程序。
系统调用是通过软中断来通知内核的:引发一个异常来促使系统切换到内核态去执行异常处理程序。
例如x86系统上,软中断的中断号是128,通过int $0x80指令触发该中断,该指令会触发一个异常导致系统切换到内核态执行第128号异常处理程序,即系统调用处理程序system_call()。
只通知内核系统调用的发生是不够了,我们还需要传入系统调用号来区分是调用了哪个系统调用号,所以当系统调用时会把系统调用号通过eax
寄存器来传递给内核,除了系统调用号以外,还需把一些需要用到的外部的参数传给内核,也是放在寄存器里。
过程如下:
系统调用上下文
系统调用上下文即是进程上下文:内核在执行系统调用的时候。
进程上下文有以下特点:
在进程上下文中,内核可以休眠并且可以被抢占,说明系统调用可以使用内核提供的绝大部分功能,使得使用系统调用的当前进程可以被其他进程抢占,所以可能新的进程也调用相同的系统调用,则需要保证系统调用的可重入性。
当上下文返回的时候,控制权仍然在system_call()中,它最终会负责切换到用户空间,并让用户继续执行下去。