第五章 系统调用
文章目录
内核提供了用户进程与内核进行交互的一组接口,可以让应用程序受限地访问硬件设备,提供了创建新进程并与已有进程通信的机制,也提供了申请操作系统资源的能力。实际上主要是为了保证系统的稳定可靠。
与内核通信
系统调用是用户空间进程和硬件设备之间的中间层。
作用:
- 为用户空间提供了硬件的抽象接口
- 保证了系统的稳定和安全
- 和虚拟系统有关
在Linux中,系统调用是用户空间访问内存的唯一手段;除异常和陷入外,他们是内核唯一的合法入口。
API、POSIX、C库
API定义了一组应用程序使用的编程接口。可以实现成系统调用,也可以调用系统调用来实现,还可以不使用系统调用。
POSIX是一种标准,一些函数之间的关系(如API和系统调用之间的关系)。
应用程序调用printf()时,会执行C库中的printf(),C库中的printf()又会调用C库中的write(),后者才调用内核提供的系统调用write()。
系统调用
系统调用在出错时会把错误码写入errno全局变量。通过perror()库函数,可以输出具体的错误信息。
为了保证32位和64位系统的兼容性,系统调用在用户空间和内核空间有不同的返回类型,在用户空间为int,在内核空间为long。
系统调用有命名规则,如sys_getpid(),sys_bar()。
系统调用号
每个系统调用被赋予一个系统调用号(相当于目录或者索引)。
系统调用号很重要,一旦分配不能有任何变更,也不能回收利用。sys_ni_syscall(),除了返回-ENOSYS外不做任何工作,针对无效的系统调用而设。
内核记录了系统调用表中所有已注册过的系统调用的列表,存储在sys_call_table中。
系统调用的性能
Linux的系统调用比其他系统要快。
- 上下文切换时间很短
- 系统调用处理程序和每个系统调用本身非常简洁。
系统调用处理程序
通知内核的机制是靠软中断实现的:通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序实际上就是系统调用处理程序。
在x86系统上预定义的软中断是中断号128,通过int $0x80指令触发该中断,执行第128号异常处理指令,system_call()。x86新增了sysenter的指令,更快、更专业。
指定恰当的系统调用
仅仅陷入内核空间是不够的,还要传递系统调用号给内核。在x86上,系统调用号是通过eax寄存器传递给内核的。其他体系结构上的实现也都类似。
系统调用号的有效性也要被检查。
参数传递
放在寄存器中。
在x86-32系统上,ebx、ecx、edx、esi、edi按照顺序存放前五个参数。如果需要6个及以上的参数,使用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。
给用户空间的返回值也通过寄存器传递。在x86系统上,存放在eax中。
系统调用的实现
设计和实现一个系统调用比较复杂,把它添加到内核相对容易。
实现系统调用
决定它的用途。一个系统调用有一个明确的用途。
参数、返回值、错误码。
设计接口的时候为将来多做考虑。
参数验证
系统调用必须仔细检查所有参数是否合法有效。
与文件I/O相关的系统调用必须检查文件描述符是否有效。与进程相关的系统调用必须检查提供的PID是否有效。等等…
还要检查用户提供的指针是否有效。内核提供了两个方法来完成必须的检查和内核空间与用户之间数据的来回拷贝。为了向用户空间写入数据,使用copy_to_user()。为了从用户空间读取数据,使用copy_fron_user()。如果执行失败,这两个函数都是返回未完成拷贝的字节数。成功的话返回0。出现错误,系统调用返回标准-EFAULT。
最后检查是否有合法权限。suser(),capable(),后者粒度更小,只是检查是否可以对指定的资源有操作权限。
系统调用上下文
内核在系统执行系统调用的时候处于进程上下文。current指针指向当前任务,即引发系统调用的那个进程。
绑定一个系统调用
- 首先,在系统调用表中的最后加入一个表项。每种支持的体系结构文件都必须做这样的工作。对于大多数的体系结构来说,该表位于entry.s。
- 系统调用号定义到<asm/unistd.h>。
- 系统调用必须被编译进内核映像(不能被编译成模块)。这只要把它放到kernel/下的一个相关文件即可。
从用户空间访问系统调用
Linux提供了一组宏,用于直接对系统调用进行访问。它会设置好寄存器并进行访问。这些宏是_syscalln(),n的范围是0-6,代表传递给系统调用的参数个数。
不要轻易创建新的系统调用
系统调用的优点:
- 创建容易,使用方便
- Linux的系统调用的性能较高
缺点:
- 需要一个系统调用号,需要官方开发版时分配(如果想要加入Linux的官方版本中)。
- 加入稳定内核就固化了,不允许改动。
- 需要注册到每个需要支持的体系结构中
- 在脚本和文件系统中不能直接访问系统调用
- 很难维护
替代方法:
实现一个设备节点,并对此实现open()和write()。使用itcol()对特定的设置进行操作或者特定的信息进行检索。
- 像信号量这样的某些接口,可以使用文件描述符来表示,因此也可以按上述方式进行操作。
- 把增加的信息作为一个文件放在sysfs的合适位置。