系统调用
为了和用户空间的进程进行交互,内核提供了一组界面,应用程序可以通过此访问硬件设备和其他操作系统资源。
系统调用在用户空间进程和硬件设备之间添加了一个中间层,作用如下
- 为用户空间提供了硬件的抽象界面,当需要读文件时,应用程序可以不用管磁盘类型和介质,甚至不用去管文件所在的文件系统是哪种类型
- 系统调用保证了系统的稳定和安全,内核作为硬件设备和应用程序之间的中间人,可以基于权限和其他规则对需要进行的访问进行裁决
- 如果应用程序可以随意访问硬件而内核又对此一无所知的话,几乎无法实现多任务和虚拟内存(即内核需要有管理能力)。在
Linux
中,系统调用是用户空间访问内核的唯一手段(process
不能随便访问内核了,内核也只需处理system_call()
即可)
API、POSIX、和C库
一般情况下,应用程序通过API
而不是直接通过系统调用来编程。而API
不一定和内核提供的系统调用对应。
一个API定义了一组应用程序使用的编程接口。(可理解为实现某种功能的函数)它们可以实现成一个系统调用,也可以通过调用多个系统调用来实现,而完全不使用任何系统调用也不存在问题。
实际上,API可以在各种不同的操作系统上实现,给应用程序提供完全相同的接口,而它们本身在这些系统上的实现却可能迥异。
Linux的系统调用作为C库的一部分
- C库实现了Unix系统的主要
API
,包括标准C库函数和系统调用 - 程序员只需关心
API
,而内核只关心system_call
系统调用
系统调用通常通过函数进行调用,通常都需要定义一个或几个参数。一般返回0
表示成功,返回负值表示失败,Unix
系统调用在出现错误的时候会把错误码写入errno
全局变量,通过调用perror()
库函数,可以把该变量翻译成用户可以理解的错误字符串。
//getpid系统调用
asmlinkage long sys_getpid(void)
{
return current->tgid;
}
asmlinkage
限定词,通知编译器仅从栈中提取该函数的参数,所有系统调用都需此限定词- 系统调用
git_pid()
在内核中被定义成sys_getpid()
,这是Linux
中所有系统调用都要遵守的命名规则
系统调用号
在Linux
中,所有系统调用被赋予一个系统调用号,当用户空间进程执行一个系统调用,这个系统调用号就被用来指明到底执行哪个系统调用,进程不会提及系统调用的名称
- 系统调用号一经分配不能再有变更,否则编译好的程序会崩溃
- 如果系统调用被删除,那么它的系统调用号也不能被回收
Linux
有一个专门处理访问无效的系统调用情况的系统调用,sys_ni_syscall()
,如果某个系统调用被删除,这个函数就填补空位
系统调用处理程序
用户空间的程序无法执行内核代码,它们不可直接调用内核空间函数,因为内核空间地址受到保护。
应用程序应以某种方式通知系统自己需要执行一个系统调用,希望系统切换到内核态。
通知内核的机制是靠软中断实现的:
-
通过引发一个异常来促使系统切换到内核态去执行异常处理程序——系统调用处理程序
-
x86
系统的软中断由int $0x80
指令产生,这条指令触发一个异常导致系统切换到内核态并执行第128
号异常处理程序,该程序正是系统调用处理程序,该处理程序叫做system_call()
,与硬件体系结构关联紧密,由汇编语言编写 -
所以系统调用陷入内核方式都一样,我们还需把系统调用号传给内核才知道要调用哪个。这个传递动作通过在触发软中断前把调用号装入
eax
寄存器实现 -
除了系统调用号以外,大部分系统调用还需要一些参数输入,所以发生异常的时候,应该把这些参数由用户空间传给内核。也可以把这些参数也存放在寄存器中,给用户空间的返回值也存放在寄存器中。
该
read()
函数是API
,调用后通过软中断通知内核需要执行一个系统调用,切换到内核态后才可以调用系统调用sys_read()
系统调用上下文
内核执行系统调用时处于进程上下文(进程保存信息到PCB
中),current
指针指向当前触发系统调用那个进程
在进程上下文中,内核可以休眠并且可以被抢占
- 能够休眠说明系统调用可以使用内核提供的绝大部分功能
- 被抢占说明当前进程可被其他进程抢占,比如另一个进程也启动了相同的系统调用,所以系统调用需要可重入
- 当系统调用返回时,控制权仍在
system_call()
,它最终会负责切换到用户空间
参考
- [(83 封私信 / 83 条消息) Linux 系统调用 - 搜索结果 - 知乎 (zhihu.com)](https://www.zhihu.com/search?type=content&q=Linux 系统调用)