例子
首先看一个标准C库的例子:当我们程序中使用了C库中的printf()函数,实际在底层是在内核态中调用了write()函数。图中右侧则是将程序代码与C库都算到应用程序中,内核提供了一个系统调用接口。
从这个例子我们可以得到以下几点:
1. 系统调用是操作系统服务的编程接口;
2. 系统调用通常由高级语言编写(C或者C++);
3. 程序访问通常是通过高层次的API接口(比如C库)而不是直接进行系统调用。最常见的三种:1. Windows下的Win32 API;2. POSIX-based系统(包括UNIX、LINUX、Mac OS等)下的 POSIX API;3. JAVA虚拟机中的Java API。
实现:
每个系统调用对应一个系统调用编号。当使用系统调用时,系统调用首先通过软中断的方式进入到内核的中断向量表产生中断,发现是系统调用软中断后转移到系统调用表,系统调用表中记录系统调用编号与具体实现之间的映射关系,根据系统调用编号选取不同的系统调用实现,得到结果之后返回。通过这种方式,用户不需要知道系统调用内部是如何实现的,而只需要设置调用参数和湖区返回结果即可,并且系统调用接口的细节大部分都隐藏函数库后面,通过调用库函数实现。
函数调用与系统调用的不同:
使用的指令不同。系统调用使用INT和IRET,函数调用使用CALL和RET,他们的指令级是完全不同的。具体还有哪些不同呢?我们知道在调用一个函数的时候需要把参数压到堆栈中去,然后转到相应函数去执行,执行的时候从堆栈获取我的参数信息执行,然后返回结果。而对于系统调用来讲,内核是受保护的,为了保护内核,内核与应用程序之间使用不同的堆栈,因此系统调用时会有一个堆栈和特权级的切换,首先切换到内核态,此时可以使用特权指令,并拥有自己的堆栈,执行完后再切换回用户态。而常规调用是没有堆栈切换的。
开销不同。系统调用比函数调用更安全,但是开销更大。主要原因就是有一个用户态的切换。具体有以下操作导致开销更大:1. 引导机制,需要引导用户态到内核态的切换;2. 建立内核堆栈,第一次调用时需要创建新的内核堆栈;3. 验证参数,需要对传入的参数的有效性合法性进行验证;4. 在内核态中需要使用到用户态中的一些信息,此时需要建立内核态到用户态地址空间的映射关系;5. 建立用户态内核态地址空间映射时会导致缓存的变化,TLB中有些内容会失效。
示例:文件复制
一个文件复制过程可以拆分如下图所示,我们可以先看下整个过程中哪些过程会使用到系统调用:1.键盘输入;2. 屏幕显示;3. 读取文件;4. 创建文件 5. 写入文件。而在操作系统内部,键盘、屏幕与文件都视为文件系统里的,只是键盘、屏幕作为特殊文件来使用。因此用到了右图中标红的系统调用(还有一个creat)。
首先是一个read()函数如下图,我们要知道参数意义和返回值。int fd是要读的文件句柄,void *buf是缓冲区头指针, int length是缓冲区的最大长度,返回值为读出的数据长度int return_value。
那么内部是如何实现的呢?首先准备参数,如下图中上面的汇编代码,前6行都是压栈,压栈完最后一行是一个函数调用,这个函数调用还不是系统调用。因为所有系统调用都是通过一个宏展开成相应的函数。函数内的int就是进入内核态的指令,i就是系统调用的中断向量编号,T_SYSCALL代表该软中断是系统调用,a(num)是系统调用编号,后面是相应的参数。
进入到内核态之后的过程如下图:系统调用进入内核态实际是一个软中断,所有这些软中断会到最开始的一段汇编程序叫做alltraps(),在这里面获取中断的相关信息组成的数据结构,也就是TF数据结构。接下来来到trap(),判断tf中有一个成员trapno(中断向量)表明了该软中断是系统调用,则进入syscall(),发现系统调用编号代表的是sys_read()函数,接下来执行sys_read()函数,该函数读取堆栈中的参数信息,之后进入sysfile_read()函数,该函数则是直接操作底层的驱动程序进行读取,最后返回时调用trapret()函数返回给用户态
补充:
大多数计算机系统将CPU执行状态分为管态和目态。管态又称为特权状态、系统态或核心态。通常,操作系统在管态下运行。目态又叫做常态或用户态,用户程序只能在目态下运行,如果用户程序在目态下执行特权指令,硬件将发生中断,由操作系统获得控制,特权指令执行被禁止,这样可以防止用户程序有意或无意的破坏系统。从目态转换为管态的唯一途径是中断。
系统调用与库函数的区别
- 系统调用:运行在用户空间的应用程序向操作系统内核请求某些服务的调用过程。是内核提供给应用程序的接口函数,属于系统的一部分。是为了方便应用使用操作系统的接口
- 函数库调用是语言或应用程序的一部分。是为了方便人们编写应用程序而引出的,比如你自己编写一个函数其实也可以说就是一个库函数。
- write/read就是系统调用,而printf/fread就是C标准库函数.