硬件提供了进入内核态的方式,对于x86,使用中断指令int,将CPL置0,这也是用户程序发起的调用内核代码的唯一方式。
系统调用的核心:①用户程序中包含一段带有int指令的代码;②操作系统写中断处理,获取想要调用的程序的编号;③操作系统根据编号执行相应代码。
#define __NR_write 4 // 系统调用号,函数表索引
// "=a"(__res) 输出,"=a"表示eax,eax值会交给__res
// ""(__NR_##name) 输入,""空表示同样是eax,__NR_write值会交给eax
// 后面就是把三个参数交给ebx,ecx,edx
// int 0x80表示软中断,也就是系统调用
# define _syscall3(type name, atype a, btype b, ctype c) \
type name(atype a, btype b, ctype c) { \
long __res; \
__asm__ volatile("int 0x80":"=a"(__res):""(__NR_##name),“b”((long)(a)),"c"((long)(b)),"d"((long)(c)); \
if(__res >= 0) return (type)__res; \
errno = -__res; \
return -1; \
}
_syscall3(int, write, int, fd, const char *buf, off_t, count)
上面代码的意思就是把代表write函数的系统调用号给到eax,然后执行int 0x80指令。
int 0x80中断是如何处理的?
void sched_init(void) {
set_system_gate(0x80, &system_call);
}
# define set_system_gate(n, addr) _set_gate(&idt[n], 15, 3, addr);
# define set_gate(gate_addr, type, dpl, addr) __asm__(...)
// 上面这一行先置DPL为3,这样CPL=3可以进来,然后内部置CPL为0,进入内核态
set_system_gate设置了0x80的中断处理门,使用system_call这个函数来处理0x80中断,操作系统在IDT中找到该函数入口并执行,执行结果记录在eax并交给__res,继续回到用户程序执行。
// system_call.s
...
call _sys_call_table(,%eax,4)
// sys.h
fn_ptr sys_call_table[] = {
sys_setup, sys_exit, sys_fork, sys_read, sys_write...
};
// sched.h
typedef int (fn_ptr*)();
system_call中根据系统调用号,决定具体调用哪个函数来处理,至于sys_write是如何实现的,会在学习IO驱动之后了解到。