Linux内核源代码情景分析-系统调用

    一、系统调用初始化

void __init trap_init(void)
{
        ......
	set_system_gate(SYSCALL_VECTOR,&system_call);//0x80
	......
}
    对0x80中断向量,设置了系统调用的总入口system_call。

static void __init set_system_gate(unsigned int n, void *addr)
{
	_set_gate(idt_table+n,15,3,addr);
}
    

    在IDT中设置了门描述符,如下图:

    Selector为_KERNEL_CS。P为1;DPL为11;DT为0;TYPE为15,陷阱门。Offset就是异常处理函数的偏移。


    二、系统调用响应

    系统调用都是在用户态发生的。我们从用户空间对函数sethostname()的调用开始我们的情景分析。

    int sethostname(cost char *name, size_t len);

    对sethostname.o反汇编得到如下结果:

  
    41行,由于ebx的值一会改变,我们先把它暂存在edx中;

    42行,把参数len存入寄存器ecx。

    43行,把参数name存入寄存器ebx。

    44行,把系统调用号存入寄存器eax。

    45行,系统调用陷入内核。

    46行,把暂存在edx中的值归还给寄存器ebx。

    47行,比较返回值eax,看是否系统调用出错。

    48行,如果出错,跳到出错处。

    49行,执行完sethostname,返回。


    1、执行系统调用处理函数之前

    系统调用一定发生在用户态,int0x80后,形成如下图:



    但要注意此时ORIG_EAX不在是中断请求号,而是系统调用号。


    (1)、CPU根据具体的中断向量(本例中为0x80),从中断向量表IDT中找到相应的表项,而该表项应该是一个陷阱门。 首先把用户态堆栈的SS,用户堆栈的ESP,EFLAGS,用户空间的CS,EIP存入到系统堆栈中(从TSS中获取)。


    (2)、CPU根据陷阱门的设置到达了系统调用的总入口system_call

ENTRY(system_call)
	pushl %eax			//系统调用号压入堆栈
	SAVE_ALL                        
	GET_CURRENT(%ebx)              //将指向当前进程的task_struct结构的指针置入寄存器EBX
	cmpl $(NR_syscalls),%eax
	jae badsys
	testb $0x02,tsk_ptrace(%ebx)	# PT_TRACESYS
	jne tracesys
	call *SYMBOL_NAME(sys_call_table)(,%eax,4) //在数组sys_call_table[]找到sethostname,并执行
	movl %eax,EAX(%esp)		//系统调用的返回值会存在eax中,堆栈中eax原来是系统调用号,现在该为系统调用返回值
ENTRY(ret_from_sys_call)
#ifdef CONFIG_SMP
	movl processor(%ebx),%eax
	shll $CONFIG_X86_L1_CACHE_SHIFT,%eax
	movl SYMBOL_NAME(irq_stat)(,%eax),%ecx		# softirq_active
	testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx	# softirq_mask
#else
	movl SYMBOL_NAME(irq_stat),%ecx		# softirq_active
	testl SYMBOL_NAME(irq_stat)+4,%ecx	# softirq_mask
#endif
	jne   handle_softirq
	
ret_with_reschedule:
	cmpl $0,need_resched(%ebx)
	jne reschedule
	cmpl $0,sigpending(%ebx)
	jne signal_return
restore_all:
	RESTORE_ALL

    SAVE_ALL如下:

#define SAVE_ALL \
	cld; \
	pushl %es; \
	pushl %ds; \
	pushl %eax; \
	pushl %ebp; \
	pushl %edi; \
	pushl %esi; \
	pushl %edx; \
	pushl %ecx; \
	pushl %ebx; \
	movl $(__KERNEL_DS),%edx; \
	movl %edx,%ds; \
	movl %edx,%es;
    sethostname,需要传递的参数是两个,分别是在%ebx和%eax中。在SAVE_ALL中%ebx是最后压入堆栈的,%eax次之。 所以堆栈中%ebx的内容就成为参数1,而%ecx的内容就是参数2,传递给sethostname。回到SAVE_ALL去看一下,可以看到被压入堆栈的寄存器依次为:%es、%ds、%eax、%ebp、%edi、%esi、%edx、%ecx、%ebx。这里的%eax持有系统调用号,显然不能再用来传递参数;而%ebp是用作子程序调用过程中“帧”指针,也不能用来传递参数,这样,实际上就只有最后5个寄存器可以用来传递参数,所以,在系统调用中独立传递的参数不能超过5个。


    2、执行系统调用处理函数

asmlinkage long sys_sethostname(char *name, int len)//name就是压入堆栈的ebx,len就是压入堆栈的ecx
{
	int errno;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;
	if (len < 0 || len > __NEW_UTS_LEN)
		return -EINVAL;
	down_write(&uts_sem);
	errno = -EFAULT;
	if (!copy_from_user(system_utsname.nodename, name, len)) {
		system_utsname.nodename[len] = 0;
		errno = 0;
	}
	up_write(&uts_sem);
	return errno;
}


    3、执行系统调用处理函数之后
ENTRY(system_call)
	pushl %eax			//系统调用号压入堆栈
	SAVE_ALL                        
	GET_CURRENT(%ebx)              //将指向当前进程的task_struct结构的指针置入寄存器EBX
	cmpl $(NR_syscalls),%eax
	jae badsys
	testb $0x02,tsk_ptrace(%ebx)	# PT_TRACESYS
	jne tracesys
	call *SYMBOL_NAME(sys_call_table)(,%eax,4) //在数组sys_call_table[]找到sethostname,并执行
	movl %eax,EAX(%esp)		//系统调用的返回值会存在eax中,堆栈中eax原来是系统调用号,现在该为系统调用返回值
ENTRY(ret_from_sys_call)
#ifdef CONFIG_SMP
	movl processor(%ebx),%eax
	shll $CONFIG_X86_L1_CACHE_SHIFT,%eax
	movl SYMBOL_NAME(irq_stat)(,%eax),%ecx		# softirq_active
	testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx	# softirq_mask
#else
	movl SYMBOL_NAME(irq_stat),%ecx		# softirq_active
	testl SYMBOL_NAME(irq_stat)+4,%ecx	# softirq_mask
#endif
	jne   handle_softirq              //先处理软中断
	
ret_with_reschedule:
	cmpl $0,need_resched(%ebx)       //是否需要调度
	jne reschedule
	cmpl $0,sigpending(%ebx)        //是否有信号需要处理
	jne signal_return
restore_all:
	RESTORE_ALL
    由于系统调用发生在用户态,所以不用像中断和异常那样,检查是否发生在用户态。

你可能感兴趣的:(Linux内核源代码情景分析-系统调用)