第五章系统调用
内核提供了用户进程与内核进行交互的一组接口
5.1与内核通信
系统调用在用户空间进程和硬件设备之间添加了一个中间层,该层有三个作用:
1) 为用户空间提供了一种硬件的抽象接口
2) 系统调用保证了系统的稳定和安全
3) 每个进程都运行在虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接口。
在Linux中系统调用是用户空间访问内核的唯一手段。除了异常和陷入外,系统调用是内核的唯一合法入口。
5.2API,POSIX,和C库
应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。
5.3系统调用
使用getpid()系统调用课返回当前的 PID。在内核中被定义为sys_getpid()。
5.3.1系统调用号
作用:指明到底是要执行哪个系统调用,进程不会提及系统调用的名称。
系统调用号一旦分配就不能再有任何变更。
5.3.2系统调用的性能
Linux系统调用比其他许多操作系统执行更快。 1,进出内核都被优化得简洁高效。2,系统调用处理程序和每个系统调用本身都很简洁。
5.4系统调用处理程序
用户空间的程序无法直接执行内核代码。
系统转换到内核态是通过软中断实现的:通过引发一个异常来促使操作系统切换到内核态去执行异常处理程序(系统调用处理程序)。
在X86系统上预定义的软中断是中断号128.通过int $0x80指令来触发该中断。该处理程序名字叫做system_call()。
5.4.1指定恰当的系统调用
所有的系统调用陷入内核的方式都一样,所以还需把系统调用号和陷入内核的空间一并传入内核。
在X86系统调用号是通过eax寄存器传递给内核的。
System_call()函数通过将给定的系统调用与NR_syscalls做比较来检查其有效性。如果它大于NR_syscalls时,该函数返回-ENOSYS.否则执行相应的系统调用
Call *sys_call_table(,%eax,8)
5.4.2参数传递
发生陷入时应该把外部的参数输入从用户空间传给内核。 把参数放在寄存器里。在X86-32系统上,按照ebx,ecx,edx,esi,edi存放前五个参数。需要六个及以上情况不多见。
给用户空间的返回值也通过寄存器传递。在X86中存放在eax中。
5.5系统调用的实现
5.5.1实现系统调用
步骤一:决定它的用途。新系统调用的参数,返回值,错误码。系统调用的接口应该力求简洁参数尽可能的少。设计接口时要尽量为将来多做考虑
记住Unix的格言:“提供机制而不是策略”。
5.5.2参数验证
在接受;一个用户空间的指针时,内核必须保证:
*指针指向的内存区域在进程的地址空间里。进程决不能哄骗内核去读内核空间的数据。
*指针指向的内存区域在进程的地址空间里。进程决不能哄骗内核去读其他进程的数据。
*如果是读。该内存应被标记为可读。如果是写,内存应该被标记为可写。如果是可执行该内存应该被标记为可执行。进程决不能绕过内存访问限制。
为了向用空间写入数据内核提供了copy_to_user()。三参数:进程空间中的目的内存地址。内核空间内的源地址。需要拷贝的数据长度(字节数)。
为了向用空间读取数据内核提供了copy_from_user()。三参数:进程空间中的目的内存地址。内核空间内的源地址。需要拷贝的数据长度(字节数)。
5.6系统调用上下文。
内核在执行系统调用时候处于进程上下文。在进程上下文中,内核可以休眠并且被抢占。当系统调用返回的时候。控制权仍在system_call()中,它最终会负责切换到用户空间。
5.6.1绑定一个系统调用的最后步骤
当编写完一个系统调用后。把它注册成一个正式的系统调用
1),在系统调用表的最后一个表中加入一个表项。从0开始,系统调用在该表中的的地位就是它的系统调用号。
2),对于所有支持的给种体系结构,系统调用号都必须定义在<asm/unistd.h>
3),系统调用必须被编译进内核影像。