1.10系统调用和库函数
1.背景
本文主要是博主针对在apue中的1.10系统调用和库函数的补充,也就是通过查阅其他书籍来进一步深入搞明白linux中的系统调用,在本文最后博主将会把参阅的书籍列出,另外需要注意的是本文主要讲解的linux下的系统调用,其次如果本文中有任何错误的地方,还望提出。
2.内容
系统调用是操作系统给在用户态运行的进程与底层硬件设备进行交互所提供的接口,系统调用的存在,使得编程不再需要学习底层硬件相关的编程,提高了对底层硬件设备访问的安全性,因为系统调用总是会检查参数,其次,系统调用提高了程序的可移植性,设想下,如果所有的内核使用了相同的一组接口,而程序使用的是该接口,则该程序可在这些内核中进行编译运行。
系统调用是通过一系列步骤来实现的,考虑read的调用。在调用read库函数时,调用程序将参数压栈,当调用系统调用sys_read时,必须先通过执行int $0x80指令产生异常,陷入内核态,调用异常处理函数,实际上这个函数就是系统调用函数,另外在陷入时,还会将所谓的系统调用号传递给内核,在x86中系统调用号使用eax寄存器传递给内核,在陷入内核之前,用户空间就把相应的系统调用所对应的调用号放入eax中。系统调用号存储在所谓的系统调用表中,这个表中存储了所有已注册过的系统调用,每一个系统调用在这个表中都只有唯一的系统调用号。除了系统调用号之外,系统调用可能还需要其他参数,存放系统调用参数所用的寄存器有6个即eax,ebx,ecx,edx,esi,edi,系统调用的入口点system_call()函数使用SAVE_ALL宏将这些寄存器的值存放在内核态堆栈中,system_call中会检查系统调用号的有效性,如果有效即会利用系统调用号对应系统调用表中的系统调用,而在实际的服务例程返回后,system_call将从eax中获得返回值,并把返回值存放在用户态中的eax中,然后跳转到ret_from_sys_call终止系统调用处理程序的执行。注意上面提到了系统调用总会检查参数,例如sys_read,必定会检查文件描述符对应的文件是否被允许让这个进程读取数据,参数如果是一个地址,那么必须该地址属于用户空间或者进程的地址空间,也就是说不能是内核空间的地址,也不能是其他进程空间的地址。
3.总结
现在简单描述下系统调用的过程,首先是调用system_call函数入口点,传递各种参数,system_call检查系统调用号,接着在系统调用表中调用该系统调用号对应的系统调用,系统调用将检查其他参数,检查成功后完成其功能,然后设置返回码,system_call从eax中得到这个返回码并将返回码放入用户态中的eax中。
4.参考的书籍
因为上面的内容都是一些总结性的,很多详细的内容需要在书中看到,在此我给出这些我参考的书籍。
现代操作系统第四版,主要参考了1.6节,主要介绍的是一个read的调用过程。
linux内核设计与实现第三版,主要参考了第5章,这里我主要关注的是其中介绍的系统调用的介绍,另外该书介绍的内容稍微好理解一些。
深入理解linux内核第三版,主要参考了第8章,详细介绍了调用系统调用时的过程,如果需要详细了解系统调用的内容可以参考这本书,另外需要了解本书的第4章。