钟晶晶 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
工作过程
以41号进程dup为例,说明在应用程序如何使用Linux的系统调用。dup()复制一个打开的文件描述符,并返回一个新描述符,二者都指向同一个打开的文件句柄。系统会保证新描述符一定是编号低最低的未使用文件描述符。
使用库函数API调用dup()的程序:
使用C语言嵌入汇编代码来实现调用dup():
第一条汇编语句将立即数复制到寄存器edi,用作dup()的参数,表示要复制的描述符;
第二条汇编语句将立即数41复制到寄存器eax,41是dup()在内核中的系统调用号;
第三条汇编语句执行中断指令int,从用户态切换到内核态,发起系统调用;
第四条汇编语句将寄存器eax中保存的系统调用返回值复制fd,表示复制的新描述符。
可以看出上面两个小程序的功能是等价的,复制得到的新描述符值都是3,因为文件描述符0、1和2分别用作标准输入,标准输出和标准输出。
系统调用的三层皮:
API、中断向量对应的system_call、中断服务程序sys_xyz
当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数,在Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常。(Intel Pentium II中引入了sysenter指令(快速系统调用),2.6已经支持)内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数,使用eax寄存器(系统调用号将xyz()和sys_xyz()关联起来了)
系统调用的参数传递方法
system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号
一个应用程序调用fork()封装例程,那么在执行int $0x80之前就把eax寄存器的值置为2(即__NR_fork)
这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号
进入sys_call之后,立即将eax的值压入内核堆栈
寄存器传递参数具有如下限制:
1)每个参数的长度不能超过寄存器的长度,即32位
2)在系统调用号(eax)之外,参数的个数不能超过6个(ebx, ecx,edx,esi,edi,ebp)
超过6个怎么办?
如果超过6个,就把某一个寄存器作为一个指针,指向一块内存,进入到内核态之后可以访问到所有的地址空间,通过内存来传递参数