第5章 系统调用

来源:互联网 发布:win10升级助手windows 编辑:程序博客网 时间:2024/05/17 21:58

系统调用概述


一个稳定运行的Linux操作系统需要内核和用户应用程序之间的完美配合,内核提供各种各样的服务,然后用户应用程序通过某种途径使用这些服务,

进而契合用户的不同需求。


用户应用程序访问并使用内核所提供的各种服务的途径即是系统调用。在内核和用户应用程序相交界的地方,内核提供了一组系统调用接口,通过

这组接口,应用程序可以访问系统硬件和各种操作系统资源。


内核提供的这组系统调用通常也被称之为系统调用接口层。系统调用接口层作为内核和用户应用程序之间的中间层,扮演了一个桥梁,或者说中间

人的角色。系统调用把应用程序的请求传达给内核,待内核处理完请求后再将处理结果返回给应用程序。


系统调用、POSIX、C库、系统命令和内核函数


(1)系统调用和POSIX


系统调用虽然是内核和用户应用程序之间的沟通桥梁,是用户应用程序访问内核的入口点,但通常情况下,应用程序是通过操作系统提供的应用编程

接口(API)而不是直接通过系统调用来编程。


操作系统API的主要作用是把操作系统的功能完全展示出来,提供给应用程序,基于该操作系统,与文件、内存、时钟、网络、图形、各种外设等互操作

的能力。此外,操作系统API通常还提供许多工具类的功能,比如操纵字符串、各种数据类型、时间日期等。


POSIX标准定义了"POSIX兼容"的操作系统所必须提供的服务。Linux兼容于POSIX标准,提供了根据POSIX而定义的API函数。这些API函数和系统调用之间

有着直接的关系,一个API函数可以用一个系统调用实现,也可以通过调用多个系统调用来实现,还可以完全不使用任何系统调用。


(2)系统调用和C库


操作系统API通常都以C库的方式提供,Linux也是如此。C库提供了POSIX的绝大部分API,同时,内核提供的每个系统调用在C库中都具有相应的封装函数。

系统调用与其C库封装函数的名称常常相同。

C库中的系统调用封装函数在最终用到相应系统调用之前,往往不做多少额外的工作。不过,某些情况下会有些例外,比如对于两个相关的系统调用

truncate和truncate64,C库中的封装函数truncate函数即需要决定它们中的哪个应该最终被调用。


(3)系统调用与系统命令


系统命令位于C库的更上层,是利用C库实现的可执行程序,比如最为常用的ls、cd等命令。

strace工具可以跟踪命令的执行,使用希望跟踪的命令为参数,并显示出该命令执行过程中所使用到的所有系统调用。


(4)系统调用和内核函数


内核函数与C库函数的区别仅仅是内核函数在内核实现,因此必须遵守内核编程的规则。

系统调用最终必须具有明确的操作。用户应用程序通过系统调用进入内核后,会执行各个系统调用对应的内核函数,即系统调用服务历程。

系统调用服务历程之外,内核中存在着大量的内核函数。有些局限于某个内核文件自己使用,有些则是export出来供内核其他部分共同使用。对于

export出来的内核函数,可以使用ksyms命令或通过/proc/ksyms文件查看。


系统调用表


系统调用表sys_call_table存储了所有系统调用对应的服务例程的函数地址,在arch/i386/kernel/syscall_table.S文件中被定义。

有两个特别之处,首先,所有系统调用服务例程的命名遵守一定的规则,即在系统调用名称之前增加"sys_"前缀,比如open系统调用对应sys_open 函数。

其次,内核提供的系统调用数目非常有限。


系统调用号


既然系统调用表集中存放了所有系统调用服务例程的地址,那么系统调用在内核中的执行就可以转化为从该表获取对应的服务例程并执行的过程。

这个过程中一个很重要的环节就是系统调用号。每个系统调用都拥有一个独一无二的系统调用号,用户应用通过它,而不是系统调用的名称,来

指明要执行哪个系统调用。系统调用号的定义在include/asm-i386/unistd.h文件

将其与sys_call_table的定义相比较可以发现,每个系统调用号都依次对应了sys_call_table的定义相比较可以发现,每个系统调用号都依次对应了

sys_call_table中的某一项。内核正是将系统调用号作为下标去获取sys_call_table中的服务例程函数地址。

系统调用号与系统调用为相依相生的关系,一旦分配就不能再有任何变更,即使该系统调用被删除,它所拥有的系统调用号也不能被回收利用。


系统调用服务例程


系统调用最终由系统调用服务例程完成明确的操作。所有的系统调用服务例程集中声明在include/linux/syscalls.h文件,但分散定义在很多不同的文件。

除了都具有"sys_"前缀之外,所有的系统调用服务例程命名与定义还必须遵守其他的一些规则。首先,函数定义中必须添加asmlinkage标记,通知

编译器仅从堆栈中获取该函数的函数。

其次,必须返回一个long类型的返回值表示成功或错误,通常返回0表示成功,返回负值表示错误。当然,getpid系统调用非常简单,不可能会失败,

通过命名"man 2 getpid"可以查看它的手册,里面也明确指出了这一点。

每个系统调用的系统调用号、命名以及操作目的都是固定的,但内核如何去实现并没有明确规定,不同版本、不同架构的内核实现都有可能会有所变化。


如何使用系统调用


用户应用可以通过两种方式使用系统调用。第一种方式是通过C库函数,包括系统调用在C库中的封装函数和其他普通函数。

第二种方式是使用_syscall宏。


系统调用执行过程








系统调用的执行过程主要包括如图5.3与图5.4所示的两个阶段:用户空间到内核空间的转换阶段,以及系统调用处理程序system_call函数到系统调用服务例程

的阶段。

(1)用户空间到内核空间

如图5.3所示,系统调用的执行需要一个用户空间到内核空间的状态转换,不同的平台具有不同的指令可以完成这种转换,这种指令也被称作操作系统陷入指令。

(2)system_call函数到系统调用服务例程


系统调用的实现


如何实现一个新的系统调用


(1)编写系统调用服务例程

(2)添加系统调用号

(3)修改系统调用表

(4)重新编译内核并测试


什么时候需要添加新的系统调用


虽说添加一个新的系统调用非常简单,但这并不意味着用户有必要这么做。添加系统调用需要修改内核源代码、重新编译内核,如果更进一步希望自己添加的系统

调用能够得到广泛的应用,就需要得到官方的认可并分配一个固定的系统调用号,还需要将该系统调用在每个需要支持的体系结构上实现。因此我们最好使用其

他替代方法和内核交换信息,如下所示:

使用设备驱动程序。创建一个设备节点,通过read和write函数进行读写访问,使用ioctl函数进行设置操作和获取特定信息。这种方法最大的好处在于可以模块式

加载卸载,避免了编译内核等过程,而且调用接口固定,容易操作。

使用proc虚拟文件系统。利用proc接口获取系统运行信息和修订系统状态是一种很常见的手段,比如读取/proc/cpuinfo可以获得当前系统的CPU信息,通过设备

驱动提供的proc接口还可以设置硬件寄存器。

sysfs文件系统。sysfs文件系统在2.6内核被引入,是一个类似于proc文件系统的特殊文件系统,用于对系统的设备进行管理,它把实际连接到系统上的设备和

总线组织成层次结构,并向用户提供详细的内核数据结构信息,用户可以利用这些信息以实现和内核的交互。

0 0
原创粉丝点击