注:由于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
MEXITCOUNT
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 activations. */
代码:
params = (caddr_t)frame.tf_esp + sizeof(int);
code = frame.tf_eax;
params指向用户传递的系统调用参数。code指示是何种系统调用,后面还有描述。
代码:
orig_tf_eflags = frame.tf_eflags;
if (p->p_sysent->sv_prepsyscall) {
/*
* The prep code is MP aware.
*/
(*p->p_sysent->sv_prepsyscall)(&frame, args, &code, ¶ms);
如果该进程有自己的系统调用准备函数,则调用之。事实上,所谓的系统调用准备函数,
其作用应该就是对用户传进来的参数进行解释。如果没有准备函数,则内核做缺省处理,如下:
代码:
} else {
/*
* Need to check if this is a 32 bit or 64 bit syscall.
* fuword is MP aware.
*/
if (code == SYS_syscall) {
/*
* Code is first argument, followed by actual args.
*/
code = fuword(params);
params += sizeof(int);
} else if (code == SYS___syscall) {
/*
* Like syscall, but code is a quad, so as to maintain
* quad alignment for the rest of the arguments.
*/
code = fuword(params);
params += sizeof(quad_t);
}
}
如果该进程没有自己的系统调用准备函数,即缺省情况,则根据系统调用是32位还是64位,
得到相应的具体系统号,并相应调整指向用户参数的指针。
SYS_syscall对应32位方式,
SYS___syscall对应64位方式。
函数fuword()意为fetch user word,即从用户空间拷贝一个word到内核空间来。其定义在
sys/i386/i386/support.s中,其实现与copyin()类似,我们略过。
此时,具体的系统调用号已经在变量code中了。
代码:
if (p->p_sysent->sv_mask)
code &= p->p_sysent->sv_mask;
对系统调用号做一些调整和限制。
代码:
if ( code >= p->p_sysent->sv_size)
callp = &p->p_sysent->sv_table[0];
else
callp = &p->p_sysent->sv_table[_code];
得到系统调用的函数入口。
代码:
narg = callp->sy_narg & SYF_ARGMASK;
得到该系统调用的参数个数。
代码:
/*
* copyin and the ktrsyscall()/ktrsysret() code is MP-aware
*/
if (params != NULL && narg != 0)
error = copyin(params, (caddr_t)args,
(u_int)(narg * sizeof(int)));
else
error = 0;
将参数从用户态拷贝到内核态的args中。
代码:
#ifdef KTRACE
if (KTRPOINT(td, KTR_SYSCALL))
ktrsyscall(code, narg, args);
#endif
/*
* Try to run the syscall without Giant if the syscall
* is MP safe.
*/
if ((callp->sy_narg & SYF_MPSAFE) == 0)
mtx_lock(&Giant);
如果该系统调用不是MP安全的,则获取全局锁。
代码:
if (error == 0) {
td->td_retval[0] = 0;
td->td_retval[1] = frame.tf_edx;
STOPEVENT(p, S_SCE, narg);
PTRACESTOP_SC(p, td, S_PT_SCE);
error = (*callp->sy_call)(td, args);
}
调用具体的系统调用。
这里,之所以要间接地使用一个系统调用函数表,是因为模拟其他操作系统的
需要。同一个系统调用在不同的操作系统里"系统调用号"是不同的,当运行其他
操作系统的应用程序时,因为其编译结果是用其他操作系统的"系统调用号",
此时需要转换到相应的FreeBSD的"系统调用号"上来,使用系统调用函数表就可以
方便地作到这一点。
代码:
switch (error) {
case 0:
frame.tf_eax = td->td_retval[0];
frame.tf