1. 系统调用接口
1.1 Linux系统调用概念
- 系统调用(systemcall): 所有的操作系统在内核里都有一些内建的函数,这些函数完成对硬件的访问和对文件的打开、读、写、关闭等操作。 Linux 系统中称这些函数为系统调用。这些函数实现了将操作从用户空间转换到内核空间,有了这些接口函数,用户就可以方便地访问硬件。
一个已经安装的系统所支持的系统调用都可以在/usr/include/bits/syscall.h 文件里看到 。
- Linux 将程序的运行空间分为内核空间和用户空间,它们分别运行在不同的级别上。在逻辑上,它们是相互隔离的。 内核模式可以执行一些特权指令并进入用户模式,而用户模式不能直接进入内核模式,除非通过系统调用,此时,程序运行空间将会从用户空间转移到内核空间,处理完毕后再返回到用户空间。
1.2 用户编程接口(库函数)
- 概念:用户编程接口是为用户编程过程提供的各种功能库函数(API),包括网络编程接口、图形编程接口、数据库编程接口等,它们遵循POSIX 标准。但它不是系统调用,与系统调用之间存在一定的联系和区别。前者只是一个函数定义,说明了如何获得一个给定的服务;而后者是通过软中断向内核态发出一个明确的请求。如果按照层次关系来分,系统调用为底层,而用户编程接口为上层。一个用户编程接口由 0 个或者多个系统调用组成。
1.3 内核服务例程(sys_abc()
)
- 为了通过系统调用号来调用不同的内核服务例程,系统必须创建并管理好一张系统调用表。该表用于系统调用号与内核服务例程的映射。Linux 用数组
sys_call_table
表示这个表,该表的每个表项中存放着对应内核服务例程的指针,而该表项的下标就是该内核服务例程的系统调用号。 Linux 规定,在 i386 体系中,处理器的寄存器 eax 用来传递系统调用号。
1.4 系统调用过程
- 通常情况下,
abc()
系统调用对应的服务例程的名字是sys_abc()
。下图表示了系统调用和应用程序、对应的封装例程、系统调用处理程序及系统调用服务例程之间的关系。
- 下面使用一个例子来简单说明系统调用过程:
- 用户程序中调用库函数
abc()
; - 系统加载 libc 库调用索引和参数后,执行
int $0x80
或者sysenter
汇编指令进入系统调用(在老版本的 Linux 内核中只支持 int $0x80 中断方式),执行system_call()
函数; system_call()
函数根据传递过来的参数处理所有的系统调用。使用system_call_table[参数]
执行系统调用。- 系统调用返回;
- 执行
iret
或者sysexit
汇编指令两种方式退出系统调用,并调用resume_userspace()
函数进入用户空间。 - 继续在 libc 库中执行,执行完成后返回到用户应用程序中。
- 用户程序中调用库函数
1.5 系统调用传递的参数
- 系统调用中输入输出的参数为实际传递的值或者是用户态进程的地址,或者是指向用户态函数指针的数据结构地址。传递的参数放在寄存器 eax 中,即系统调用号。寄存器传递参数的个数满足两个条件:
- 参数的长度不超过寄存器的长度,如果是 32 位平台不超过 32 位, 64 位平台不超过 64 位。
如果是 32 位平台不超过 32 位, 64 位平台不超过 64 位。 - 不包括 eax (i386体系结构下)中的系统调用号,参数的个数不超过 6 个。
- 参数的长度不超过寄存器的长度,如果是 32 位平台不超过 32 位, 64 位平台不超过 64 位。