系统调用时值就是函数调用,值时调用的函数是系统函数,处于内核态而已。用户在调用系统调用时会向内核传递一个系统调用号,然后系统调用处理程序通过此号从系统调用表中找到相应的内核函数执行(系统调用服务例程),最后返回。
1.系统调用号
linux系统有几百个系统调用号,为了唯一的表识每一个系统调用,linux为每一个系统调用定义了一个唯一的编号,此编号就是系统调用号。系统调用号的定义在 /usr/include/asm/unistd_32.h 文件中(版本不同为头文件位置不同)。
由此可见当前系统拥有358个系统调用。系统调用号的另一个目的是作为系统调用表的下标,当用户空间的进程执行一个系统调用时,这个系统调用号就被用来指明到底是要执行哪个系统调用,系统调用号相当关键,一旦分配好就不能再有任何改变,否则编译好的应用程序就会因为调用到错误的系统调用而导致程序崩溃。
2.系统调用表
为了把系统调用号与相应的服务例程关联起来,内核利用了一个系统调用表,这个系统调用表放在sys_call_table数组中,他是一个函数指针数组,每一组指针都指向其系统调用的封装例程,有NR_syscalls个表项,第n个表项包含系统调用号为n的服务例程的地址。NR_syscalls宏只是对可实现的系统调用最大个数的静态限制,并不表示实际以实现的系统调用个数。这样我们就可以利用系统调用号作为下标,找到其系统调用例程。
3.系统调用服务例程和系统调用处理函数
每一个系统调用bar()在内核都有一个对应的内核函数sys_bar(),这个内核函数就是系统调用bar()的实现,也就是说在用户态调用bar(),最终会有内核函数sys_bar()为用户服务,这里的sys_bar()就是系统调用的服务例程。
既然最终还是由内核函数完成,那么我们为什么不直接调用内核函数呢?这是因为用户空间无法直接执行内核代码,因为内核驻流在受保护的地址空间上,不允许用户进程在内核地址空间上进行读写。所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,而这种机制是通过软中断实现的,通过引发一个异常促使系统切换到内核态去执行异常处理程序。此时的异常处理程序就是所谓的系统调用处理程序。