Linux系统调用目前了解到的情况如下
一、基础概念
1. 中断向量表 IDT(Interrupt Descriptor Table)
- 这个是CPU提供了一系列的中断类型,用一个整型的值表示,对于x86架构的CPU而言,其中断取值类型有256个。可以为每个中断类型注册一个中断发生后的回调处理地址,一旦对应的中断发生,CPU中断当前正在处理的任务,直接跳转到对应的注册地址进行中断处理。
- 注册的这256个中断及其回调处理地址(函数的地址)构成了一个IDT(中断描述符表),每个中断条目长4个字节,总共1024个字节,偏移量为0-255。应该注意的是,IDT包含中断处理程序和异常处理程序的向量,因此“IDT”有点用词不当,但这就是事实。
- 备注:对于X86的CPU而言,系统调用的中断类型是0x80(下同:仅限于x86),属于软中断。
2. 系统调用表(sys_call_table)
- 系统调用表是所有系统调用函数组成的一个数组,系统为每个系统调用分配了一个ID,表示在系统调用表的偏移量,根据ID可以在系统调用表中快速定位到具体的系统调用。
二、系统调用流程
- 操作系统启动时,已经初始化好中断向量表和系统调用表
- 向CPU发起0x80中断,x86对应的中断汇编指令为int 0x80,发起中断时需要指定对应的系统调用的ID,这就是传说中的“陷入内核”
- CPU收到中断请求后,保存和修改必要的寄存器信息,然后将IP寄存器指向中断处理函数的地址,这里的中断处理函数为系统调用:system_call
- 在系统调用中,根据发起中断时指定的系统调用ID,在系统调用表中找到对应的系统调用,然后执行系统调用:call *sys_call_table(,%eax,4)
- 系统调用执行完毕后,恢复中断前的执行状态
三、如何发起中断
- glibc库:glibc库几乎为所有的系统调用都做了一层封装,使得系统调用可以像普通函数一样进行调用,不用显示发起软中断。Linux自带的系统调用的函数名都是sys_xxx(或sysxxx,与版本有关)形式的,使用glibc后可以直接去掉对应的sys前缀了。
- syscall 直接调用
- 通过 int 指令陷入
四、注意事项
- 由于执行系统调用时发生了中断,且这个正在执行的中断不能被打断,所以如果在系统调用的回调函数中发生了死循环,那么将导致处理该中断的线程直接被卡死
- 进程收到某个信号时,若该进程有多个线程,则每个线程都有同等的机会去处理信号回调函数