---恢复内容开始---
第五章 系统调用
一、与内核通信
1.系统调用在用户控件进程和硬件设备之间添加了一个中间层。
为用户空间提供了一种硬件的抽象接口 系统调用保证了系统的稳定和安全 每个进程都运行在虚拟系统中,而在用户控件和系统的其余部分提供这样一层公共接口
2.作用
在Linux中,系统调用是用户空间访问内核的唯一手段 。
二、API、POSIX和C库
Linux的系统调用作为C库的一部分提供。
C库:Unix系统的主要API,包括标准C库函数和系统调用接口,即POSIXdM大部分API。
Unix的接口设计——“提供机制而不是策略”
三、系统调用
要访问系统调用,通常通过C库中的定义函数进行。
通常定义1个,零个,或2个参数,可能有一些副作用。
例:
通过返回一个long型的返回值来表示成功或者错误(写某个文件或者给指定的指针拷贝数据)
用一个负的返回值来表明错误,返回0代表成功。 系统调用出现错误的时候会把错误码写入errno全局变量 通过perror()库函数可以把该变量翻译成用户可以理解的错误字符串
比如: SYSCALL_DEFINE0 只是一个宏,定义一个无参数的函数调用。
展开后的代码如下 asmlinkage long sys_getpid(void)。
定义系统调用:
asmlinkage long sys_getpid(void)
asmlgkage:编译指令,通知编译器仅从栈中提取该函数的参数。所有的系统调用都需要这个限定词。
返回值
long:
为了保证32和64位系统的兼容,系统调用在用户空间返回值int,内核空间
long
命名规则:sys_x
xx
a. 系统调用号
每个系统调用被赋予一个系统调用号,系统调用发生时,内核就是根据传入的系统调用号来知道是哪个系统调用的。
在x86架构中,用户空间将系统调用号是放在eax中。
每个系统调用号独一无二,一旦分配就不能再有任何变更。
执行系统调用时,通过系统调用号指明,进程不会提及系统调用的名称。
内核记录了系统调用表中的所有已经注册过的系统调用列表,存储在sys_call_table
中。
未实现系统调用——sys_ni_syscall(),返回-ENOSYS,针对无用的系统调用。
b.系统调用的性能——执行速度快
- 上下文切换时间短。设计原则:简洁、高效
- 系统调用处理程序和每个系统调用本身都很简洁
四、系统调用处理程序
1. 通知内核
用户程序无法直接执行内核代码,需要切换内核态,由于内核驻留在受保护的地址空间上,不能直接调用内核空间中的函数。 应用程序以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,需要切换内核态,这个通知内核的机制是软中断。 通知内核的机制是靠软中断实现的:通过引发异常来粗来系统切换内核态执行异常处理程序(系统调用处理程序)。 一个异常来促使系统切换到内核态中去执行异常处理程序,即系统调用处理程序system_call()。
2.指定恰当的系统调用
- ax寄存器:将系统调用号传递给内核
- system_call():与NR_syscall比较,检查有效性
- call *sys_call_table(,%rax,8):执行相应的系统调用
3.参数调用
- x86系统,ebx,ecx,edx,esi,edi按顺序存放前五个参数。
- 需要6个及以上参数,应用一个单独的寄存器存放指向这些参数在用户空间地址的指针。
- 返回值存放在eax。
system_call()函数通过将给定的系统调用号与NR_syscalls作比较来检查其有效性。
五、系统调用的实现
1.实现系统调用
a .决定它的用途 不提倡采用多用途的系统调用
b.原则:用途明确、简洁稳定、通用、可移植、健壮。
2.参数验证
必须检查每个参数,保证他们不但合法有效,而且正确: 不应让内核访问无权访问的资源.
最重要——检查用户提供的指针:
指针指向的内存区域必须属于用户空间指向的内存区域属于用户空间;
指针指向的内存区域在进程的地址空间内;
决不能绕过内存访问限制;指向的内存区在内存的访问权限范围中。
1.检查读写
(1)向用户空间写入数据——copy_to_user()
(2)从用户控件读取数据——copy_from_user()
针对是否有合法权限的检查
capable():是否有权对指定的资源进行操作
返回0:无权操作
以上两个函数成功返回0,失败返回没能完成拷贝的数据的字节数。
这两个函数都有可能引起阻塞——当包含用户数据的页被换出到硬盘上而不是物理内存上的时候。
六、系统调用上下文
内核在执行系统调用时处于进程上下文。
在进程上下文中,内核可以:
休眠:说明系统调用可以使用内核提供的绝大部分功能
被抢占:要求保证该系统调用是可重入的
current指针指向当前任务。
当系统调用返回时,控制权仍然在system_call()中,负责切换到用户空间,并让用户进程继续执行下去。
1.绑定一个系统调用的最后步骤
在系统调用表的最后加入一个表项。 对于所支持的各种体系结构,系统调用号都必须定义于<asm/unistd.h>中 系统调用必须被编译进内核映像,不能被编译成模块。
2.从用户空间访问系统调用
Linux本身提供了一组宏,如:_syscalln()。用于直接对系统调用进行访问。
_syscalln()
n的范围从0到6,代表需要传递给系统调用的参数个数。
对于每个宏来说,都有(2+2xn)个参数:
系统调用的返回值类型 系统调用的名称 按照系统调用参数的顺序排列每个参数的类型和名称。
3.提倡不通过系统调用的方式实现
建立一个系统调用的好处:
系统调用创建容易并且使用方便
linux系统调用的高性能
问题:
占用系统调用号
固化,不允许改动接口
需要分别注册到每个需要支持的体系结构中
脚本中不易调用,文件系统中也不能直接访问
在主内核树外难以维护使用
替代:
某些接口,例如信号量,用文件描述符表示
把增加的信息作为一个文件放在sysfs的合适位置。
七、 学习心得
看书的过程中,当时觉得理解,一旦应用到实践中,就会有混淆不理解的地方,还是在看书的过程中需要多理解问题。