分类: LINUX
2013-05-17 22:06:35
(2)API不一定是与系统调用一一对应的,有可能一个API对应多个系统调用,但是也 有可能API不与任何系统调用对应(这个时候 的API将不会完成内核服务,只提供 用户态的服务)。即:要完成内核服务则必须要经过系统调用。
(3)系统调用属于内核,而用户态的API则不属于内核。1 ssize_t read(int fd, void *buf, size_t count)
2 {
3 long res;
4 %eax = __NR_read
5 %ebx = fd
6 %ecx = (long)buf
7 %edx= count
8 int $0x80
9 res = %eax
10 return res;
11 }
第4行,用eax寄存器保存read()的系统调用号,在在内核代码中的如下路径:/arch/x86/include/asm/unistd_32.h里定义了内核中所有系统调用号有关的宏(#define __NR_read 3);第5~7行,分别将三个参数放入三个寄存器(通过寄存器来传递参数);第8行,执行系统调用,进入内核;第9行,获取eax寄存器所保存的函数返回值。
执行第8行后已经进入了系统内核,由于这是一个中断,因此程序进入到中断向量表中记录0x80号的中断处理程序,中断向量表的初始化在内核代码中的路径:/arch/x86/kernel/traps.c中定义:
1 void __init trap_init(void)
2 {
3 ...................
4
5 #ifdef CONFIG_X86_32
6 set_system_trap_gate(SYSCALL_VECTOR, &system_call);
7 set_bit(SYSCALL_VECTOR, used_vectors);
8 #endif
9 ...................
10 }
如第6行所示。SYSCALL_VECTOR是系统调用的中断号,在/arch/x86/include/asm/irq_vectors.h中定义:
1 #ifdef CONFIG_X86_32
2 # define SYSCALL_VECTOR 0x80
3 #endif
正好是0x80。而system_call是系统调用的中断处理函数指针,用户执行int $0x80后会执行到这个函数,它在/arch/x86/kernel/entry_32.S中定义:
1 ENTRY(system_call)2 RING0_INT_FRAME # can't unwind into user space anyway3 pushl_cfi %eax # save orig_eax4 SAVE_ALL5 GET_THREAD_INFO(%ebp)6 # system call tracing in operation / emulation7 testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)8 jnz syscall_trace_entry9 cmpl $(nr_syscalls), %eax10 jae syscall_badsys11 syscall_call:12 call *sys_call_table(,%eax,4)13 movl %eax,PT_EAX(%esp) # store the return value...........
第4行,SAVE_ALL是一个宏,也在这个文件里定义:
1 .macro SAVE_ALL2 cld3 PUSH_GS4 pushl_cfi %fs5 /*CFI_REL_OFFSET fs, 0;*/6 pushl_cfi %es7 /*CFI_REL_OFFSET es, 0;*/8 pushl_cfi %ds9 /*CFI_REL_OFFSET ds, 0;*/10 pushl_cfi %eax11 CFI_REL_OFFSET eax, 012 pushl_cfi %ebp13 CFI_REL_OFFSET ebp, 014 pushl_cfi %edi15 CFI_REL_OFFSET edi, 016 pushl_cfi %esi17 CFI_REL_OFFSET esi, 018 pushl_cfi %edx19 CFI_REL_OFFSET edx, 020 pushl_cfi %ecx21 CFI_REL_OFFSET ecx, 022 pushl_cfi %ebx23 CFI_REL_OFFSET ebx, 024 movl $(__USER_DS), %edx25 movl %edx, %ds26 movl %edx, %es27 movl $(__KERNEL_PERCPU), %edx28 movl %edx, %fs29 SET_KERNEL_GS %edx30 .endm
主要作用就是将各个寄存器压入栈中。
第9行,比较eax的值是否大于等于nr_syscalls,nr_syscalls是比最大有效系统调用号大1的值,在/arch/x86/kernel/entry_32.S中定义:
1 #define nr_syscalls ((syscall_table_size)/4)
其中syscall_table_size就是系统调用表的大小(单位:字节),syscall_table_size其实是一个数组,数组里存放的是各个系统调用函数的地址,元素类型是long型,除以4刚好是系统调用函数的个数。
如果从eax寄存器传进来的系统调用号有效,那么就执行第12行,在系统调用表里找到相应的系统调用服务程序,sys_call_table在/arch/x86/kernel/syscall_table_32.S中定义:
1 ENTRY(sys_call_table)2 .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */3 .long sys_exit4 .long ptregs_fork5 .long sys_read6 .long sys_write7 .long sys_open /* 5 */8 .long sys_close 9 .................
*sys_call_table(,%eax,4)指的是sys_call_table里偏移量为%eax*4上的那个值指向的函数,这里%eax=3,那么第5行的sys_read()函数就会被调用。sys_read()在/fs/read_write.c中定义:
1 SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)2 {3 struct file *file;4 ssize_t ret = -EBADF;5 int fput_needed;67 file = fget_light(fd, &fput_needed);8 if (file) {9 loff_t pos = file_pos_read(file);10 ret = vfs_read(file, buf, count, &pos);11 file_pos_write(file, pos);12 fput_light(file, fput_needed);13 }14
15 return ret;16 }
这就是read系统调用进入内核的所有的步骤。这篇博客主要是参考了http://www.cnblogs.com/lknlfy/archive/2012/07/14/2591366.html这个博客。这个博客对系统调用进行了很详细的讲解。接下来的工作就是学习怎么实现自己的系统调用。