Linu下的系统调用_第1页
Linu下的系统调用_第2页
Linu下的系统调用_第3页
Linu下的系统调用_第4页
Linu下的系统调用_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Linux 下的系统调用1 什么是系统调用系统调用,顾名思义,说的是操作系统提供给用户程序调用的一组“特殊” 接口。用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务,比 如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件, 可以通过时钟相关的系统调用获得系统时间或设置定时器等。从逻辑上来说,系统调用可被看成是一个内核与用户空间程序交互的接口 它好比一个中间人,把用户进程的请求传达给内核,待内核把请求处理完毕后 再将处理结果送回给用户空间。系统服务之所以需要通过系统调用来提供给用户空间的根本原因是为了对 系统进行“保护”,因为我们知道 Linux 的运行空间分为内核

2、空间与用户空间, 它们各自运行在不同的级别中,逻辑上相互隔离。所以用户进程在通常情况下不 允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据, 调用用户空间函数。比如我们熟悉的“hello world”程序(执行时)就是标准的用 户空间进程,它使用的打印函数printf就属于用户空间函数,打印的字符“ hello word”字符串也属于用户空间数据。但是很多情况下,用户进程需要获得系统服务(调用系统程序),这时就必 须利用系统提供给用户的“特殊接口”系统调用了,它的特殊性主要在于规 定了用户进程进入内核的具体位置;换句话说,用户访问内核的路径是事先规定 好的,只能从规定位置进

3、入内核,而不准许肆意跳入内核。有了这样的陷入内核 的统一访问路径限制才能保证内核安全无虞。我们可以形象地描述这种机制:作 为一个游客,你可以买票要求进入野生动物园,但你必须老老实实地坐在观光车 上,按照规定的路线观光游览。当然,不准下车,因为那样太危险,不是让你丢 掉小命,就是让你吓坏了野生动物。2 Linux的系统调用对于现代操作系统,系统调用是一种内核与用户空间通讯的普遍手段, Linux系统也不例外。但是Linux系统的系统调用相比很多Unix和windows等 系统具有一些独特之处,无处不体现出Linux的设计精髓一一简洁和高效。Linux系统调用很多地方继承了 Unix的系统调用(但

4、不是全部),但Linux 相比传统Unix的系统调用做了很多扬弃,它省去了许多Unix系统冗余的系统调 用,仅仅保留了最基本和最有用的系统调用,所以Linux全部系统调用只有250 个左右(而有些操作系统系统调用多达 1000个以上)。这些系统调用按照功能逻辑大致可分为“进程控制”、“文件系统控制”、“系统 控制” “存储管理” “网络管理” “socket控制” “用户管理” “进程间通信” 等几类,详细情况可参阅文章系统调用列表如果你想详细看看系统调用的说明,可以使用man 2 syscalls命令查看,或干 脆到 内核源码目录/include/asm-i386/unistd.h源文件中找

5、到它们的源本。熟练了解和掌握上面这些系统调用是对系统程序员的必备要求,但对于一个开 发内核的人员或内核开发者来11说,死记硬背下这些调用还远远不够。如果你 仅仅知道存在的调用而不知道为什么它们会存在,或只知道如何使用调用而不知 道这些调用在系统中的主要用途,那么你离驾驭系统还有不小距离。要弥补这个鸿沟,第一,你必须明白系统调用在内核里的主要用途。虽然上面 给出了数种分类,不过,总的概括来讲,系统调用在系统中的主要用途无非以下 几类:控制硬件一一系统调用往往作为硬件资源和用户空间的抽象接口,比 如读写文件时用到的 write/read 调用。设置系统状态或读取内核数据因为系统调用是用户空间和内核

6、的 唯一通讯手段22,所以用户设置系统状态,比如开/关某项内核服务(设 置某个内核变量) ,或读取内核数据都必须通过系统调用。比如 getpgid、getpriority、setpriority、sethostname进程管理一系统调用接口是用来保证系统中进程能以多任务在虚 拟内存环境下得以运行。比如 fork、clone、execve、exit 等第二,什么服务应该存在于内核;或者说什么功能应该实现在内核而不是 在用户空间。这个问题并没有明确的答案,有些服务你可以选择在内核完成,也 可以在用户空间完成。选择在内核完成通常基于以下考虑:服务必须获得内核数据,比如一些服务必须获得中断或系统时间等

7、内 核数据。从安全角度考虑,在内核中提供的服务相比用户空间提供的毫无疑问 更安全,很难被非法访问到。从效率考虑,在内核实现服务避免了和用户空间来回传递数据以及保护现场等步骤,因此效率往往要比在用户空间实现高许多。比如,httpd 等服务。如果内核和用户空间都需要使用该服务,那么最好实现在内核空间, 比如随机数产生。理解上述道理对掌握系统调用的本质意义很大,希望网友们能从使用中多总 结,多思考。3系统调用、用户编程接口 API).系统命令和内核函数的关系系统调用并非直接和程序员或系统管理员打交道,它仅仅是一个通过软中断 机制(我们后面讲述)向内核提交请求,获取内核服务的接口。而在实际使用中 程序

8、员调用的多是用户编程接口一一API,而管理员使用的则多是系统命令。用户编程接口其实是一个函数定义,说明了如何获得一个给定的服务,比如read()、malloc()、free ()、abs()等。它有可能和系统调用形式上一致,比如read() 接口就和 read 系统调用对应,但这种对应并非一一对应,往往会出现几种不同 的API内部用到同一个系统调用,比如malloc()、free ()内部利用brk()系统调 用来扩大或缩小进程的堆;或一个 API 利用了好几个系统调用组合完成服务。 更有些 API 甚至不需要任何系统调用因为它并不是必需要使用内核服务, 如计算整数绝对值的abs ()接口。另

9、外要补充的是Linux的用户编程接口遵循了在Unix世界中最流行的应用 编程界面标准POSIX标准,这套标准定义了一系列API。在Linux中(Unix 也如此),这些API主要是通过C库(libc)实现的,它除了定义的一些标准的C 函数外,一个很重要的任务就是提供了一套封装例程(wrapper routine)将系统 调用在用户空间包装后供用户编程使用。不过封装并非必须的,如果你愿意直接调用,Linux内核也提供了一个syscall() 函数来实现调用,我们看个例子来对比一下通过C库调用和直接调用的区别。#include #include #include #include int main

10、(void) long ID1, ID2; TOC o 1-5 h z /*/* 直接系统调用*/* SYS_getpid (func no. is 20) */*/ID1 = syscall(SYS_getpid);printf (syscall(SYS_getpid)=%ldn, ID1); /*/*使用libc封装的系统调用*/* SYS_getpid (Func No. is 20) */*/ID2 = getpid();printf (getpid()=%ldn, ID2); return(0);系统命令相对编程接口更高了一层,它是内部引用API的可执行程序,比如 我们常用的系统命令

11、Is、hostname等。Linux的系统命令格式遵循系统V的传统, 多数放在/bin和/sbin下(相关内容可看看shell等章节)。有兴趣的话,可以通过strace ls或strace hostname命令查看一下它们用到的 系统调用,你会发现诸如open、brk、fstat、ioctl等系统调用被用在系统命令中。下一个需要解释一下的问题是内核函数和系统调用的关系。大家不要把内核 函数想像的过于复杂,其实它们和普通函数很像,只不过在内核实现,因此要满 足一些内核编程的要求33。系统调用是一层用户进入内核的接口,它本身并非 内核函数,进入内核后,不同的系统调用会找到对应到各自的内核函数换个

12、专业说法就叫:系统调用服务例程。实际上针对请求提供服务的是内核函数而非 调用接口。比如系统调用 getpid 实际上就是调用内核函数 sys_getpid。 asmlinkage long sys_getpid(void)return current-tpid;Linux 系统中存在许多内核函数,有些是内核文件中自己使用的,有些则是 可以export出来供内核其他部分共同使用的,具体情况自己决定。内核公开的内核函数 export出来的 可以使用命令ksyms或cat /proc/ksyms来查看。另外,网上还有一本归纳分类内核函数的书叫作The Linux Kernel API Book,有兴

13、趣的读者可以去看看。总而言之,从用户角度向内核看,依次是系统命令、编程接口、系统调用和 内核函数。在讲述了系统调用实现后,我们会回过头来看看整个执行路径。4 系统调用的实现Linux 中实现系统调用利用了 0 x86 体系结构中的软件中断44。软件中断和 我们常说的中断(硬件中断)不同之处在于它是通过软件指令触发而并非外 设引发的中断,也就是说,又是编程人员开发出的一种异常,具体的讲就是调用 int $0 x80匚编指令,这条汇编指令将产生向量为128的编程异常。之所以系统调用需要借助异常来实现,是因为当用户态的进程调用一个系统 调用时,CPU便被切换到内核态执行内核函数55,而我们在i386

14、体系结构部分 已经讲述过了进入内核进入高特权级别必须经过系统的门机制,这里的 异常实际上就是通过系统门陷入内核(除了 int 0 x80外用户空间还可以通过int3 向量3、into向量4、bound向量5等异常指令进入内核,而其他异 常无法被用户空间程序利用,都是由系统使用的)。我们更详细地解释一下这个过程。int $0 x80指令的目的是产生一个编号为 128的编程异常,这个编程异常对应的是中断描述符表IDT中的第128项也 就是对应的系统门描述符。门描述符中含有一个预设的内核空间地址,它指向了 系统调用处理程序:system_call()(别和系统调用服务程序混淆,这个程序在entry.

15、S 文件中用汇编语言编写)。很显然,所有的系统调用都会统一地转到这个地址,但 Linux 一共有 2、3 百个系统调用都从这里进入内核后又该如何派发到它们到各自的服务程序去 呢?别发昏,解决这个问题的方法非常简单:首先Linux为每个系统调用都进行 了编号(0NR_syscall),同时在内核中保存了一张系统调用表,该表中保存了 系统调用编号和其对应的服务例程,因此在系统调入通过系统门陷入内核前,需 要把系统调用号一并传入内核,在 x86 上,这个传递动作是通过在执行 int0 x80 前把调用号装入eax寄存器实现的。这样系统调用处理程序一旦运行,就可以从 eax 中得到数据,然后再去系统调

16、用表中寻找相应服务例程了。除了需要传递系统调用号以外,许多系统调用还需要传递一些参数到内核, 比如 sys_write(unsigned int fd, const char * buf, size_t count)调用就需要传递文件 描述符fd、要写入的内容buf、以及写入字节数count等几个内容到内核。碰到 这种情况,Linux会有6个寄存器可被用来传递这些参数:eax (存放系统调用号)、 ebx、ecx、edx、esi及edi来存放这些额外的参数(以字母递增的顺序)。具体做 法是在 system_call( )中使用 SAVE_ALL 宏把这些寄存器的值保存在内核态堆栈 中。有始便有

17、终,当服务例程结束时,system_call()从eax获得系统调用的返 回值,并把这个返回值存放在曾保存用户态eax寄存器栈单元的那个位置上。 然后跳转到ret_from_sys_call(),终止系统调用处理程序的执行。当进程恢复它在用户态的执行前,RESTORE_ALL宏会恢复用户进入内核前 被保留到堆栈中的寄存器值。其中 eax 返回时会带回系统调用的返回码。(负数 说明调用错误,0 或正数说明正常完成)我们可以通过分析一下getpid系统调用的真是过程来将上述概念具体化,分 析 getpid 系统调用的一个办法是查看 entry.s 中的代码细节,逐步跟踪源码来分 析运行过程,另外就

18、是可借助一些内核调试工具,动态跟踪运行路径。假设我们的程序源文件名为getpid.c,内容是:#include #include #include #include int main(void) long ID;ID = getpid();printf (getpid()=%ldn, ID);return(0);将其编译成名为getpid的执行文件“gcc -o getpid 提示符下,执行bt命令观察堆栈,发现调用的嵌套路径,可以看到在sys_getpid 是在内核函数system_call中被嵌套调用的。在KDB提示符下,执行rd命令查看寄存器中的数值,可以看到eax中存放的getpid

19、调用号0 x00000014(=20).在KDB提示符下,执行ssb (或ss)命令跟踪内核代码执行路径,可以发现sys_getpid 执行后,会返回system_call函数,然后接者转入ret_from_sys_call例程。(再往后还有 些和调度有关其他例程,我们这里不说了它们了。)结合用户空间的执行路径,该程序大致可归结为以下几个步骤:1该程序调用libc库的封装函数getpid。该封装函数将系统调用号_NR_getpid (第20个) 压入EAX寄存器,2 调用软中断 int 0 x80 进入内核。(以下进入内核态)3在内核中首先执行system_call,接着执行根据系统调用号在调

20、用表中查找到的对应的系 统调用服务例程sys_getpid。4.执行sys_getpid服务例程。5执行完毕后,转入 ret_from_sys_call 例程,系统调用中返回。内核调试是一个很有趣的话题,方法多种多样,我个人认为比较好用的 是UML(user mode linux+gdb)和 KDB 这两个工具。尤其KDB对于调试小 规模内核模块或查看内核运行路径很有效,对于它的使用方法可以看看Linux 内核调试器内幕这篇文章。5.系统调用思考系统调用的内在过程并不复杂,我们不再多说了,下面这节我们主要就系统 调用所涉及的一些重要问题作一些讨论和分析,希望这样能更有助于了解系统调 用的精髓。

21、51. 调用上下文分析系统调用虽说是要进入内核执行,但它并非一个纯粹意义上的内核例程。首 先它是代表用户进程的,这点决定了虽然它会陷入内核执行,但是上下文仍然是 处于进程上下文中,因此可以访问进程的许多信息(比如 current 结构当前 进程的控制结构) ,而且可以被其他进程抢占(在从系统调用返回时,由 system_call 函数判断是否该再调度),可以休眠,还可接收信号66等等。所有这些特点都涉及到了进程调度的问题,我们这里不做深究,只要大家明 白系统调用完成后,再回到或者说把控制权交回到发起调用的用户进程前,内核 会有一次调度。如果发现有优先级别更高的进程或当前进程的时间片用完,那么

22、就会选择高优先级的进程或重新选择进程运行。除了再调度需要考虑外,再就是 内核需要检查是否有挂起的信号,如果发现当前进程有挂起的信号,那么还需要 先返回用户空间处理信号处理例程(处于用户空间),然后再回到内核,重新返 回用户空间,有些麻烦但这个反复过程是必须的。52 调用性能问题系统调用需要从用户空间陷入内核空间,处理完后,又需要返回用户空间。 其中除了系统调用服务例程的实际耗时外,陷入 /返回过程和系统调用处理程序 (查系统调用表、存储/恢复用户现场)也需要花费一些时间,这些时间加起来 就是一个系统调用的响应速度。系统调用不比别的用户程序,它对性能要求很苛 刻,因为它需要陷入内核执行,所以和其

23、他内核程序一样要求代码简洁、执行迅 速。幸好Linux具有令人难以置信的上下文切换速度,使得其进出内核都被优化 得简洁高效;同时所有Linux系统调用处理程序和每个系统调用本身也都非常简 洁。绝大多数情况下,Linux系统调用的性能是可以接受的,但是对于一些对性 能要求非常高的应用来说,它们虽然希望利用系统调用的服务,但却希望加快响 应速度,避免陷入/返回和系统调用处理程序带来的花销,因此采用由内核直接 调用系统调用服务例程,最好的例子就HTTPD它为了避免上述开销,从内 核调用socket等系统调用服务例程。53什么时候添加系统调用系统调用是用户空间和内核空间交互的唯一手段,但是这并不是说要

24、完成交 互功能就非要添加新系统调用不可。添加系统调用需要修改内核源代码、重新编 译内核,因此如果想灵活地和内核交互信息,最好使用以下几种方法:编写字符驱动程序利用字符驱动程序可以完成和内核交互数据的功能。它最大的好处在于可以 模块式加载,这样一来就避免了编译内核等手续,而且调用接口固定,容易 操作。使用 proc 文件系统利用proc文件系统修订系统状态是一种很常见的手段,比如通过修改proc 文件系统下的系统参数配置文件(/proc/sys),我们可以直接在运行时动态更 改内核参数;再如,通过下面这条指令:echo 1 /proc/sys/net/ip_v4/ip_forward 开启内核中

25、控制IP转发的开关。类似的,还有许多内核选项可以直接通过 proc 文件系统进行查询和调整。使用虚拟文件系统有些内核开发者认为利用ioctl ()系统调用(字符设备驱动接口)往往会使 得系统调用意义不明确,而且难以控制。而将信息放入到proc文件系统中会使 信息组织混乱,因此也不赞成过多使用。他们建议实现一种孤立的虚拟文件系统 来代替ioctl()和/proc,因为文件系统接口清楚,而且便于用户空间访问,同时, 利用虚拟文件系统使得利用脚本执行系统管理任务更加方便、有效。6试验部分61 试验概述 本章的试验,我们将编写一个搜集内核中系统调用发生序列信息的内核服 务,并利用一个新的系统调用为用户

26、程序取回这些信息。这个试验不但能教会大家如何为内核添加新系统调用,而且能教大家学会 用户程序如何使用系统调用获取内核服务。更进一步,大家可以通过观察系统调 用序列和发生频率更深入地理解系统调用和系统运行的关系。当然,这里除了系 统调用的知识外,还会涉及到一些有关内核编程,比如等待队列、内核模块等知 识,这些部分请大家查看相关资料。我们的新系统调用为audit (在2.4.18内核中系统调用号是223,置于调用表 最末尾),该调用对应的具体实现是内核函数sys_audit,它将把事先记录的系统 调用序列信息(比如系统调用的进程号,命令等相关信息)返回给用户空间。而 记录这些系统调用序列则是依靠另

27、一个内核服务函数syscall_audit,该函数负责 搜集系统调用数据并填充到一个自建的内核缓冲区中,等待系统调用 audit 将搜 集到的内核数据取回到用户空间。它的搜集方式很有意思。首先要修改系统调用处理程序system_call,在其中 需要监控的每个调用(在我们例子钟 222 个系统调用都监控了,当然你也可以根 据自己需求有选择的监控)执行完毕后都插入一个指令,该指令会转去调用内核 服务函数 syscall_audit 来记录该次调用的信息。因为任何一个系统调用都要经过 system_call 统一处理,所以任何一次系统调用的信息都可被 syscall_audit 记录下 来。Syscall_audit 内核服务函数要做的事情就是记录系统调用的信息,具体做法 是建立一个内核缓冲区来存放被记录的函数。当搜集的数据量到达一定阀值时 (比如设定为到达缓冲区总大小的 80,这样做可以避免再丢失新调用),唤醒 系统调用进程取回数据。否则继续搜集,这时,系统调用程序会堵塞在一个等待 队列上,直到被唤醒,也就是说,如果缓冲区还没接近满时,系统调用会等待它 被填充。Sys_audit 系统调用服务函数所做的事情很简单,就是从缓冲区中取数据返回 用户空间。如果缓冲还没满则挂起等待。道理就这么多了。不过为了方便调试,我们采用模块化方式来加载 sys_audit 和sysca

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论