日期:2014-05-16  浏览次数:20831 次

深入理解Linux内核--系统调用(阅读笔记)(原创)

深入理解Linux内核--系统调用(阅读笔记)(原创)


?

由 王宇 原创并发布

?

第十一章系统调用

??? 操作系统为在用户态运行的进程与硬件设备进行交互提供了一组接口。优点:

??? ??? 首先这使得编程更加容易,把用户从学习硬件设备的低级编程特性中解放出来。其次这极大地提高了系统的安全性,因为内核在试图满足某个请求之前在接口级就可以检查这种请求的正确性。最后,更重要的是这些接口使得程序更具有可移植性

1、POSIX API和系统调用


??? 先强调一下应用编程接口(API)与系统调用之不同。前者只是一个函数定义,说明了如何获得一个给定的服务;而后者是通过软中断向内核态发出一个明确的请求。

??? Unix系统给程序员提供了很多API的库函数。libc的标准C库所定义的一些API引用了封装例程。通常情况下,每个系统调用对应一个封装例程,而封装例程定义了应用程序使用的API

??? 反之则不然,顺便说一句,一个API没必要对应一个特定的系统调用。首先,API可能直接提供用户态的服务,其次一个单独的API函数可能调用几个系统调用。此外,几个API函数可能调用封装了不同功能的同一个系统调用。

??? 从编程者的观点看,API和系统调用之间的差别是没有关系的:唯一相关的事情就是函数名、参数类型及返回代码的含义。然而,从内核设计者的观点看,这种差别确实有关系,因为系统调用属于内核,而用户态的库函数不属于内核。

??? 大部分封装返回一个整数,其值的含义依赖于相应的系统调用。返回值-1通常表示内核不能满足进程的请求。

??? 每个出错码都定义为一个常量宏。POSIX标准制定了很多出错码的宏名。

??? include/asm-i386/errno.h

??? /usr/include/error.h

??? include/asm-i386/error.h


2、系统调用处理程序及服务例程

??? 当用户态的进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核态函数。在80x86体系结构中,可以用两种不同的方式调用linux的系统调用。两种方式的最终结果都是跳转到所谓系统调用处理程序的汇编语言函数。

??? 因为内核实现了很多不同的系统调用,因此进程必须传递一个名为系统调用号的参数来识别所需的系统调用,eax寄存器就用作此目的。

??? 所有的系统调用都返回一个整数值。这些返回值与封装例程返回值的约定是不同的。在内核中,正数或0表示系统调用成功结束,而负数表示一个出错条件。在后一种情况下,这个值就是存放在error变量中必须返回给应用程序的负出错码。

??? 系统调用处理程序与其他异常处理程序的结构类似,执行下列操作

??? ??? 在内核态栈保存大多数寄存器的内容

??? ??? 调用名为系统调用服务例程

??? ??? 退出系统调用处理程序:用保存在内核栈中的值加载寄存器,CPU从内核态切换回到用户态

??? xyz()系统调用对应的服务例程的名字通常是sys_xyz()。不过也有一些例外???


?
??? **图:10-1调用一个系统调用

??? 为了把系统调用号与相应的服务例程关联起来,内核利用了一个系统调用分派表(dispatchtable)

3、进入和退出系统调用

??? 通过两种不同的方式调用系统调用:

??? ??? 执行int$0x80汇编语言指令,在Linux内核的老版本中,这是从用户态切换到内核态的唯一方式

??? ??? 执行sysenter汇编语言指令。在Inter Pentium II微处理器芯片中引入了这条指令,现在Linux2.6内核支持这条指令。

??? 通过两种不同的方式从系统调用退出,从而使CPU切换回到用户态:

??? ??? 执行iret汇编语言指令

??? ??? 执行sysexit汇编语言指令,它和sysenter指令同时在InterPentiumII微处理器中引入

??? 支持进入内核的两种不同方式并不像看起来那么简单,因为:

??? ??? 内核必须既支持只使用int$0x80指令的旧函数库,同时支持也可以使用sysenter指令的新函数库

??? ??? 使用sysenter指令的标准库必须能处理仅支持int$0x80指令的旧内核

??? ??? 内核和标准库必须既能运行在不包含sysenter指令的旧处理器上,也能运行在包含它的新处理器上

??? [1]通过int$0x80指令发出系统调用

??? ??? 调用系统调用的传统方法是使用汇编语言指令int

??? ??? 向量128(十六进制0x80)对应于内核入口点。在内核初始化期间调用的函数trap_init(),用下面的方式建立对应于向量128的中断描述符表表型:

??? ??? ??? set_system_gate(0x80,&system_call);

??? ??? 该调用把下列值存如这个门描述符的相应字段:

??? ??? ??? SegmentSelecor:内核代码段__KERNEL_CS的段选择符

??? ??? ??? Offset:指向system_call()系统调用处理程序的指针

??? ??? ??? Type:置15表示这个异常是一个陷阱,相应的处理程序不禁止可屏蔽中断

??? ??? 当用户态进程发出int$0x80指令时,CPU切换到内核态并开始从地址system_call处开始执行指令???

??? ??? (1)system_call()

??? ??? ??? system_call()函数首先把系统调用号和这个异常处理程序可以用到的所有CPU寄存器保存到相应的