系统调用的详细过程

用户态:

首先先找到系统调用号,我们来看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


 

你可能感兴趣的:(Linux)