系统调用 系统调用门 由于引入了特权级机制,处于ring0的os内核代码不能被处于ring3的用户进程代码直接访问。用户进程想要获得os内核的功能支持需要通过系统调用(syscall)。应用程序可以通过调用门接口,实现在低特权级的情况下对os提供的接口函数进行调用。调用门由调用门描述符实现,其结构如下图所示: 16-31位代表被调用函数所在的代码段,0-15位代表被调用函数相对于代码段的偏移量。调用门的工作流程与权限检查可以查看intel提供的文档。
系统调用流程 下面以sleep系统调用为例来说明一下系统调用的流程。首先是传递系统调用参数的结构体
1 2 3 4 5 6 7 8 typedef struct _syscall_args_t { int id; int arg0; int arg1; int arg2; int arg3; }syscall_args_t ;
系统调用号定义在syscall.h,目前的支持如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #define SYS_msleep 0 #define SYS_getpid 1 #define SYS_fork 2 #define SYS_execve 3 #define SYS_yield 4 #define SYS_exit 5 #define SYS_wait 6 #define SYS_open 50 #define SYS_read 51 #define SYS_write 52 #define SYS_close 53 #define SYS_lseek 54 #define SYS_isatty 55 #define SYS_sbrk 56 #define SYS_fstat 57 #define SYS_dup 58 #define SYS_ioctl 59 #define SYS_opendir 60 #define SYS_readdir 61 #define SYS_closedir 62 #define SYS_unlink 63 #define SYS_printmsg 100
msleep的syscall函数实现如下
1 2 3 4 5 6 7 8 9 10 int msleep (int ms) { if (ms <= 0 ) { return 0 ; } syscall_args_t args; args.id = SYS_msleep; args.arg0 = ms; return sys_call(&args); }
首先在函数内部定义了一个系统调用参数结构体,传入id和参数之后调用sys_call这个函数,其实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static inline int sys_call (syscall_args_t * args) { const unsigned long sys_gate_addr[] = {0 , SELECTOR_SYSCALL | 0 }; int ret; __asm__ __volatile__( "push %[arg3]\n\t" "push %[arg2]\n\t" "push %[arg1]\n\t" "push %[arg0]\n\t" "push %[id]\n\t" "lcalll *(%[gate])\n\n" :"=a" (ret) :[arg3]"r" (args->arg3), [arg2]"r" (args->arg2), [arg1]"r" (args->arg1), [arg0]"r" (args->arg0), [id]"r" (args->id), [gate]"r" (sys_gate_addr)); return ret; }
首先定义了一个数组变量sys_gate_addr,传递了系统调用门描述符的地址,然后将参数、系统调用号、门描述符地址压栈后调用lcalll指令进行调用。调用lcalll后cpu会自动进行特权级检查,获取syscall代码段的代码和偏移量,即找到do_handler_syscall函数(其GDT表项在init时进行初始化和装填),实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void do_handler_syscall (syscall_frame_t * frame) { if (frame->func_id < sizeof (sys_table) / sizeof (sys_table[0 ])) { syscall_handler_t handler = sys_table[frame->func_id]; if (handler) { int ret = handler(frame->arg0, frame->arg1, frame->arg2, frame->arg3); frame->eax = ret; return ; } } task_t * task = task_current(); log_printf("task: %s, Unknown syscall: %d" , task->name, frame->func_id); frame->eax = -1 ; }
syscall_frame_t存储了系统调用需要的栈信息,结构为
1 2 3 4 5 6 7 8 typedef struct _syscall_frame_t { int eflags; int gs, fs, es, ds; int edi, esi, ebp, dummy, ebx, edx, ecx, eax; int eip, cs; int func_id, arg0, arg1, arg2, arg3; int esp, ss; }syscall_frame_t ;
这时候就可以通过系统调用号查找sys_table,sys_table相当于一个函数指针表,每一项都指向了一个syscall的处理函数。找到对应的处理函数时就可以让操作系统处理对应的功能,达到请求操作系统服务的效果。例如msleep对应的内核函数为sys_mslepp,其实现方式如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void sys_msleep (uint32_t ms) { if (ms < OS_TICK_MS) { ms = OS_TICK_MS; } irq_state_t state = irq_enter_protection(); task_set_block(task_manager.curr_task); task_set_sleep(task_manager.curr_task, (ms + (OS_TICK_MS - 1 ))/ OS_TICK_MS); task_dispatch(); irq_leave_protection(state); }
fork与exec系统调用