系统调用就是上层应用调用操作系统提供的接口(一些函数)。POSIX就是一种接口标准。中断(系统调用使用int 0x80)是硬件提供的用户态进入内核态的唯一方式。
系统调用详细过程
参看上图系统调用过程,要从库函数write说起,write库函数其实就是调用了linux/include/unistd.h中的_syscall3宏。
linux/include/unistd.h中的_syscall3如下。
经write库函数来到_syscall3宏,"_syscall3"中的3代表3个参数,
type=int, name=write, atype=int, btype=const char *,
b=buf, ctype=off_t, c=count
int write(int fd, const char * buf, off_t count){
long __res;
// 内嵌汇编翻译,int 0x80中断,返回结果放在eax,最终给__res(mov %eax,__res)
mov __NR_write,%eax
mov fd,%ebx
mov buf,%ecx
mov count,%edx
...
}
注意上述代码是把系统调用号__NR_write写入eax寄存器,并在ebx,
ecx,edx寄存器中传入参数,调用0x80号中断进入内核完成系统调用。
__NR_write也是在linux/include/unistd.h中定义的,如下图。
int 0x80
需要查询中断表IDT,找到对应的中断门。而内核初始化的时候,恰好设置了0x80号中断对应的中断门。在文件linux/init/main.c中132行调用了sched_init()函数,而sched_init()函数在linux/kernel/sched.c中,sched_init()函数最后一行代码就是设置0x80号对应的中断门描述符,如下图。
set_system_gate在linux/include/asm/system.h中,如下,就是设置中断门描述符的,段选择子设为0x8,段内偏移设置为&system_call(中断处理函数地址),DPL设为了3。而用户态CPL=3,正好可使用该中断门,而该中断门里面的段选择子是0x8,0x8对应的二进制是b0000_0000_0000_1_0_00,对应的CPL=0,于是乎进入内核态。在中断程序执行完之后,CPL会设置为3,回到用户态。
中断处理程序就是linux/kernel/system_call.s,如下。
在linux/include/linux/sys.h中,对应了系统调用的所有函数列表,如下。
到此,就要开始真正去调用sys_write了,而sys_write内部是怎么样的呢?后面课程会讲。
系统调用大致过程
参考资料
[1] 中国大学MOOC《操作系统》李治军 哈尔滨工业大学
[2] 《Linux内核完全注释》赵炯
[3] BIOS中断大全
[4] 汇编语言最全指令表
[5] 《x86汇编语言:从实模式到保护模式》李忠 著
[6] https://blog.csdn.net/ccnuacmhdu/article/details/104946213