burningcodes  原创作品转载请注明出处《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

1.参数传递

系统调用也需要参数,但是它在内核态下的参数传递方式和用户态下不太一样,不是通过栈而是通过寄存器来存放参数(在64位系统中用户程序的参数也变为用寄存器存放),若参数个数不超过6个可以使用ebx, ecx,edx,esi,edi,ebp来存放(eax中存放系统调用号,系统调用号列表见:/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl),超过的话要用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。

2.为什么是0x80

在系统调用时总是能看到一条固定的指令:

那么为什么是0x80,系统调用时到底发生了什么,要想解开疑惑就首先得从内核初始化代码中看起:

在start_kernel中通过调用trap_init初始化了中断向量:

再来看看trap_init这个函数(arch/x86/kernel/traps.c):

可以看出,trap_init中通过set_system_trap_gate将SYSCALL_VECTOR和system_call函数绑定在了一起,那这两个东西又是什么呢?继续往下找(/arch/x86/include/asm/irq_vectors.h

原来SYSCALL_VECTOR就是0x80,而system_call是一段汇编(/linux-3.18.6/arch/x86/kernel/entry_32.S):

所以说在使用0x80时相当于调用了system_call函数,这个函数实际上主要做了2件事,先保存现场然后跳转到到真正的系统调用处理函数。继续分析,可以看到当进入system_call后会首先用SACE_ALL保存保存寄存器信息,接下来会执行到这句:

sys_call_table是一张系统调用表,这张表中保存了各个系统调用的真实地址(32位机上占4字节),所以%eax * 4  再加上系统调用表的基地址就能找到这个函数在内核中的真实地址(/linux-3.18.6/arch/sh/kernel/syscalls_32.S):

用一个实例来加深对上面内容的理解:

上述代码会输出helllo c和hello asm,这也证实了函数调用最终会通过int $0x80陷入内核, 下面这张图是execve函数执行的大致过程:

syscall1

3.总结

本文分析了系统调用的过程,在进行系统调用时内核是通过寄存器来保存参数,同时本文也讲明了为什么系统要用int $0x80来作为陷入内核的指令, 通过跟踪内核源码理清了系统调用的流程。

 

 

观看更多有关 的文章?

*

+
跳转到评论