《Linux内核设计与实现》第五章学习笔记
姓名:王玮怡 学号:20135116
一、与内核通信
在Linux中,系统调用是用户空间访问内核的唯一手段;除异常和陷入外,它们是内核的唯一合法入口。
二、API、POSIX、C库
1、API
一般情况下,应用程序通过在用户空间实现的应用编程接口(API)来编程,而不是直接通过系统调用。
2、POSIX
在Unix世界中,最流行的应用编程接口是基于POSIX标准的。
3、C库
C库实现了Unix系统的主要API,包括标准C库函数和系统调用接口。此外,C库提供了POSIX的绝大部分API。
4、POSIX、API、C库以及系统调用间的关系
三、系统调用(syscall)
1、系统调用号
在Linux中,每个系统调用被赋予一个系统调用号。当用户空间的进程执行一个系统调用时,用系统调用号来指明到底执行哪个系统调用。
*特点:
(1)一旦分配就不能再有任何改变,并且如果一个系统调用被删除,它所占有的系统调用号也不允许被回收利用,内核将系统调用表中已注册过的系统调用号记录存储在sys_call_table中
(2)“未实现”系统调用sys_ni_syscall(),只能返回-ENOSYS,专门针对无效的系统调用而设
2、Linux系统调用快速的原因
(1)Linux上下文切换时间短,进出内核简洁高效
(2)系统调用处理程序和每个系统调用本身十分简洁
四、系统调用处理程序
由于内核驻留在受保护的地址空间上,用户空间的程序无法直接执行内核代码,不能直接调用内核空间中的函数。应用程序通知内核的机制是靠软中断实现的:通过引发一个异常来促使系统切换到内核态去执行异常处理程序(系统调用处理程序)
在x86系统上预定义的软中断是中断号128,通过int $0x80指令触发该中断,切换到内核态执行第128号异常处理程序——system_call
1、指定恰当的系统调用
所有的系统调用陷入内核的方式为:陷入内核空间,并将系统调用号一并传给内核(x86上通过eax寄存器传递)。system_call()函数将系统调用号与NR_syscalls作比较:
(1)系统调用号大于或等于NR_syscalls:return -ENOSYS
(2)系统调用号小于NR_syscalls:执行相应的系统调用 call *sys_call_table(,%rax,8) (x86-64位乘8,x86-32位乘4)
2、参数传递
大部分系统调用除了系统调用号外还需要外部的参数输入,在x86-32系统上,ebx、ecx、edx、esi、edi按顺序存放前五个参数(需要六个及以上的情况不多见)。此外,给用户空间的返回值通过eax寄存器传递。
五、系统调用的实现
1、实现系统调用
(1)决定它的用途;(2)确定新系统调用的参数、返回值和错误码;(3)设计接口
*注意:可移植性和健壮性
2、参数验证
(1)系统调用必须检查每个参数,保证它们不仅合法有效,而且正确。例如,与文件I/O相关的系统调用必须检查文件描述符是否有效;与进程有关的函数必须检查提供的PID是否有效。最重要的一种检查是检查用户提供的指针是否有效。在接收一个用户空间的指针前,内核必须保证:
(2)内核提供了两个方法来完成必须的检查和内核空间与用户空间之间的数据来回拷贝:
*注意:copy_to_user()和copy_from_user()都有可能引起阻塞
(3)调用者可以使用capable()函数检查是否有权能对指定的资源进行操作,如果返回非0值则有权操作,返回0则无权操作
六、调用上下文
内核在执行系统调用时处于进程上下文。current指针指向当前任务,引发系统调用那个进程。在进程上下文中,内核可以休眠(例如在系统调用阻塞或显式调用schedule()时)并且可以被抢占。
系统调用返回的时候,控制权仍然在system_call()中,它最终会负责切换到用户空间,并让用户进程继续执行下去。
1、绑定一个系统调用的最后步骤
注册成正式的系统调用的步骤:
(1)在系统调用表的最后加一个表项(从0开始算起)
(2)系统调用号必须定义于<asm/unistd.h>中
(3)系统调用必须被编译进内核映象(不能被编译成模块)
2、从用户空间访问系统调用
Linux本身提供了一组宏,用于直接对系统调用进行访问:_syscalln(),其中n的范围为0到6,代表需要传递给系统调用的参数个数。对每个宏来说,都有2+2*n个参数,第一个参数对应着系统调用的返回值类型,第二个参数是系统调用名称,再以后是按照系统调用参数的顺序排列的每个参数的类型和名称。
3、采用系统调用作为实现方法的利弊和代替方法
(1)建立一个系统调用的好处:
(2)建立系统调用的弊端:
(3)代替方法: