1.概述
系统调用是内核与用户空间程序交互的接口
很多情况下,用户进程需要获得系统服务(调用系统程序),这时就必须利用系统提供给用户的“特殊接口”——系统调用了,它的特殊性主要在于规定了用户进程进入内核的具体位置;换句话说,用户访问内核的路径是事先规定好的,只能从规定位置进入内核,而不准许肆意跳入内核。有了这样的陷入内核的统一访问路径限制才能保证内核安全无虞。
我们可以形象地描述这种机制:作为一个游客,你可以买票要求进入野生动物园,但你必须老老实实地坐在观光车上,按照规定的路线观光游览。当然,不准下车,因为那样太危险,不是让你丢掉小命,就是让你吓坏了野生动物。
2.系统调用的主要用途
l 控制硬件——系统调用往往作为硬件资源和用户空间的抽象接口,比如读写文件时用到的write/read调用。
l 设置系统状态或读取内核数据——因为系统调用是用户空间和内核的唯一通讯手段[2],所以用户设置系统状态,比如开/关某项内核服务(设置某个内核变量),或读取内核数据都必须通过系统调用。比如getpgid、getpriority、setpriority、sethostname
l 进程管理——一系统调用接口是用来保证系统中进程能以多任务在虚拟内存环境下得以运行。比如 fork、clone、execve、exit等
3.什么服务应该存在于内核
l 服务必须获得内核数据,比如一些服务必须获得中断或系统时间等内核数据。
l 从安全角度考虑,在内核中提供的服务相比用户空间提供的毫无疑问更安全,很难被非法访问到。
l 从效率考虑,在内核实现服务避免了和用户空间来回传递数据以及保护现场等步骤,因此效率往往要比在用户空间实现高许多。比如,httpd等服务。
l 如果内核和用户空间都需要使用该服务,那么最好实现在内核空间,比如随机数产生。
4.系统调用具体实现
Linux中实现系统调用利用了0x86体系结构中的软件中断。是编程人员开发出的一种异常,具体的讲就是调用int$0x80汇编指令,这条汇编指令将产生向量为128的编程异常。
之所以系统调用需要借助异常来实现,是因为当用户态的进程调用一个系统调用时,CPU便被切换到内核态执行内核函数 ,进入高特权级别——必须经过系统的门机制,这里的异常实际上就是通过系统门陷入内核。
int $0x80指令的目的是产生一个编号为128的编程异常,这个编程异常对应的是中断描述符表IDT中的第128项——也就是对应的系统门描述符。门描述符中含有一个预设的内核空间地址,它指向了系统调用处理程序运行,就可以从eax中得到数据,然后再去系统调用表中寻找相应服务例程了。:system_call()
首先Linux为每个系统调用都进行了编号(0—NR_syscall),同时在内核中保存了一张系统调用表,该表中保存了系统调用编号和其对应的服务例程,因此在系统调入通过系统门陷入内核前,需要把系统调用号一并传入内核,在x86上,这个传递动作是通过在执行int0x80前把调用号装入eax寄存器实现的。这样系统调用处理程序一旦
许多系统调用还需要传递一些参数到内核,比如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宏把这些寄存器的值保存在内核态堆栈中。
有始便有终,当服务例程结束时,system_call( ) 从eax获得系统调用的返回值,并把这个返回值存放在曾保存用户态 eax寄存器栈单元的那个位置上。然后跳转到ret_from_sys_call( ),终止系统调用处理程序的执行。
当进程恢复它在用户态的执行前,RESTORE_ALL宏会恢复用户进入内核前被保留到堆栈中的寄存器值。其中eax返回时会带回系统调用的返回码。(负数说明调用错误,0或正数说明正常完成)
5.系统调用性能
系统调用需要从用户空间陷入内核空间,处理完后,又需要返回用户空间。其中除了系统调用服务例程的实际耗时外,陷入/返回过程和系统调用处理程序(查系统调用表、存储/恢复用户现场)也需要花费一些时间,这些时间加起来就是一个系统调用的响应速度。系统调用不比别的用户程序,它对性能要求很苛刻,因为它需要陷入内核执行,所以和其他内核程序一样要求代码简洁、执行迅速。幸好Linux具有令人难以置信的上下文切换速度,使得其进出内核都被优化得简洁高效;同时所有Linux系统调用处理程序和每个系统调用本身也都非常简洁。
绝大多数情况下,Linux系统调用的性能是可以接受的,但是对于一些对性能要求非常高的应用来说,它们虽然希望利用系统调用的服务,但却希望加快响应速度,避免陷入/返回和系统调用处理程序带来的花销,因此采用由内核直接调用系统调用服务例程,最好的例子就HTTPD——它为了避免上述开销,从内核调用socket等系统调用服务例程。
参考整理: