|
源代码网推荐
注:由于code是BBCode的关键字,在某些地方将程序中的变量code改写为_code
系统调用开始于用户程序,接着到达libc进行参数的包装,然后调用内核提供的机制进入内核。
内核提供的系统调用进入内核的方式有几种,包括lcall $X, y方式和 int 0x80方式。其实现都在sys/i386/i386/exception.s中。
我们看最常见的int 0x80入口。
1,int 0x80中断向量的初始化。 ------------------
在i386CPU的初始化过程中,会调用函数init386() /*XXX*/ 其中有: 代码: (sys/i386/i386/machdep.c) ----------------------------------- setidt(IDT_SYSCALL, &IDTVEC(int0x80_syscall), SDT_SYS386TGT, SEL_UPL, GSEL(GCODE_SEL, SEL_KPL)); -----------------------------------
在这里设置好int80的中断向量表。
代码: (sys/i386/include/segments.h) --------------------------------- #define IDT_SYSCALL 0x80 /* System Call Interrupt Vector */
#define SDT_SYS386TGT 15 /* system 386 trap gate */
#define SEL_UPL 3 /* user priority level */
#define GSEL(s,r) (((s)<<3) | r) /* a global selector */
#define GCODE_SEL 1 /* Kernel Code Descriptor */
#define SEL_KPL 0 /* kernel priority level */ ----------------------------------
代码: (sys/i386/i386/machdep.c) ----------------------------------- void setidt(idx, func, typ, dpl, selec) int idx; inthand_t *func; int typ; int dpl; int selec; { struct gate_descriptor *ip;
ip = idt + idx; ip->gd_looffset = (int)func; ip->gd_selector = selec; ip->gd_stkcpy = 0; ip->gd_xx = 0; ip->gd_type = typ; ip->gd_dpl = dpl; ip->gd_p = 1; ip->gd_hioffset = ((int)func)>>16 ; } ------------------------------------
2,int0x80_syscall ------------------
系统调用的入口是int0x80_syscall,在sys/i386/i386/exception.s中。 它其实是一个包装函数,用汇编写成,其目的是为调用C函数syscall()做准备。 代码: void syscall(frame) struct trapframe frame;
由于系统调用最终是要调用syscall()这个函数, 因此需要为它准备一个调用栈,包括参数frame,其类型为struct trapframe 代码: /* * Exception/Trap Stack Frame */
struct trapframe { int tf_fs; int tf_es; int tf_ds; int tf_edi; int tf_esi; int tf_ebp; int tf_isp; int tf_ebx; int tf_edx; int tf_ecx; int tf_eax; int tf_trapno; /* below portion defined in 386 hardware */ int tf_err; int tf_eip; int tf_cs; int tf_eflags; /* below only when crossing rings (e.g. user to kernel) */ int tf_esp; int tf_ss; };
这个trapframe实际上就是保存在核心栈上的用户态寄存器的状态,当从 系统调用返回时,需要从这里恢复寄存器等上下文内容。同时,它又是 函数syscall()的参数,这样在syscall()函数里面就可以方便地操纵返回后 的用户进程上下文状态。
我们来看具体的int0x80_syscall。 代码: /* * Call gate entry for FreeBSD ELF and Linux/NetBSD syscall (int 0x80) * * Even though the name says "int0x80", this is actually a TGT (trap gate) * rather then an IGT (interrupt gate). Thus interrupts are enabled on * entry just as they are for a normal syscall. */ SUPERALIGN_TEXT IDTVEC(int0x80_syscall) pushl $2 /* sizeof "int 0x80" */
对照struct trapframe可知,此句赋值frame->tf_err=2,记录int 0x80指令的长度, 因为有可能系统调用需要重新执行(系统调用返回ERESTART的话内核会自动重新执行), 需要%eip的值减去int 0x80的指令长度。
代码: subl $4,%esp /* skip over tf_trapno */ pushal pushl %ds pushl %es pushl %fs
对照struct trapframe又可知,此时syscall(frame)的参数在堆栈上已经构造好。
代码: mov $KDSEL,%ax /* switch to kernel segments */ mov %ax,%ds mov %ax,%es mov $KPSEL,%ax mov %ax,%fs
切换到内核数据段,并将%fs设置好,%fs指向一个per cpu的段,内存CPU相关的数据, 比如当前线程的pcb和struct thread指针。
代码: FAKE_MCOUNT(13*4(%esp)) call syscall MEX99vCOUNT jmp doreti
调用syscall()函数。syscall()返回后, 将转到doreti(也在sys/i386/i386/exception.s中),判断是否可以执行AST, 最后结束整个系统调用。
3,syscall()函数 ---------------
我们接着看syscall()函数 代码: /* * syscall - system call request C handler * * A system call is essentially treated as a trap. */ void syscall(frame) struct trapframe frame; { caddr_t params; struct sysent *callp; struct thread *td = curthread; struct proc *p = td->td_proc; register_t orig_tf_eflags; u_int sticks; int error; int narg; int args[8]; u_int code;
/* * note: PCPU_LAZY_INC() can only be used if we can afford * occassional inaccuracy in the count. */ PCPU_LAZY_INC(cnt.v_syscall);
#ifdef DIAGNOSTIC if (ISPL(frame.tf_cs) != SEL_UPL) { mtx_lock(&Giant); /* try to stabilize the system XXX */ panic("syscall"); /* NOT REACHED */ mtx_unlock(&Giant); } #endif
sticks = td->td_sticks; td->td_frame = &frame; if (td->td_ucred != p->p_ucred) cred_update_thread(td);
如果进程的user credential发生了改变,更新线程的相应指针。
代码: if (p->p_flag & P_SA) thread_user_enter(p, td);
如果进程的线程模型采用scheduler activation,则需要通知用户态的线程manager (FIXME)
代码: (sys/sys/proc.h) #define P_SA 0x08000 /* Using scheduler activa
源代码网供稿. |