一、系统调用初始化
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; }
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由于系统调用发生在用户态,所以不用像中断和异常那样,检查是否发生在用户态。