用户态:
首先先找到系统调用号,我们来看unistd.h头文件中这样一段代码:
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
......
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
__syscall_return(type,__res); \
}
在这个头文件中,我们定义了一系列的宏,这些宏就是系统调用对用的系统调用号,比如for,它通过组装将name替换成fork之后进行二次展开就成了_NR_fork,因此在前面定义_NR_fork 就是2,因此fork对应的系统调用号就是2,而系统调用就是通过0x80号中断号找到对应的系统调用服务程序,而真正执行系统调用服务程序时就已经陷入内核态,在此之前我们需要将系统调用的调用号,调用参数进行传递,在陷入内核态前有这样一段汇编代码:(示例代码的系统调用有两个参数)
0: 89 da mov %ebx,%edx
2: 8b 4c 24 08 mov 0x8(%esp,1),%ecx
6: 8b 5c 24 04 mov 0x4(%esp,1),%ebx
a: b8 4a 00 00 00 mov $0x4a,%eax
f: cd 80 int $0x80
它首先把两个参数传入寄存器,然后将0x4a号(示例系统调用函数的系统调用号,不同的系统调用会不同,会根据前面得到的系统调用号传入eax)系统调用传入eax寄存器中,最后就调用int陷入内核态。
内核态:
系统调用服务程序:
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_THREAD_INFO(%ebp)
# system call tracing in operation
testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry
cmpl $(nr_syscalls), %eax
jae syscall_badsys
syscall_call:
call *sys_call_table(,%eax,4)
它首先将用户态的一些寄存器信息保存在自己的堆栈上(内核堆栈),save_all就是一个宏,他将依次压入:
%es
%ds
%eax
%ebp
%edi
%esi
%edx
%ecx
%ebx,而es,ds,eax,ebp均有各自的用处,所以允许传递的最大参数的个数为后面5 个,如果更多就传递指针,通过copy_from_user函数在指针处获取参数 ,用户态信息保存完了,参数也保存了就call具体的系统函数,函数调用跳转表就是保存的系统函数的函数指针,通过系统调用号找到具体的系统函数就开始执行。
系统函数跳转表:
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
.long sys_exit
.long sys_fork
.long sys_read
.long sys_write
.long sys_open /* 5 */
.long sys_close
.long sys_waitpid
.long sys_creat
.long sys_link
.long sys_unlink /* 10 */
.long sys_execve