分享
 
 
 

FreeBSD 5内核源代码分析之系统调用过程

王朝system·作者佚名  2006-11-23
窄屏简体版  字體: |||超大  

注:由于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, &params);

如果该进程有自己的系统调用准备函数,则调用之。事实上,所谓的系统调用准备函数,

其作用应该就是对用户传进来的参数进行解释。如果没有准备函数,则内核做缺省处理,如下:

代码:

} 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

[1] [2] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有