Chinaunix首页 | 论坛 | 博客
  • 博客访问: 84158
  • 博文数量: 12
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 331
  • 用 户 组: 普通用户
  • 注册时间: 2013-05-17 19:34
文章分类

全部博文(12)

文章存档

2014年(1)

2013年(11)

我的朋友

分类: LINUX

2013-05-17 22:06:35

一、概述
     系统调用是操作系统为在用户态运行的进程与硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口。系统调用作为一种接口,通过系统调用,应用程序能够进入操作系统内核,从而使用内核提供的各种资源,比如操作硬件(如磁盘等)。
二、系统调用与API的区别
   
(1)API只是一个函数的定义,说明了如何获取一个给定的服务,只是一个提供给应用程序的接口,一组函数,是与程序员进行直接              交互的。而系统调用不是与程序 员进行交互,而是根据API函数,通过一个软中断机制向内核提交请求,以获取               内核              服务的一个接口。

 (2)API不一定是与系统调用一一对应的,有可能一个API对应多个系统调用,但是也   有可能API不与任何系统调用对应(这个时候  的API将不会完成内核服务,只提供   用户态的服务)。即:要完成内核服务则必须要经过系统调用。

    (3)系统调用属于内核,而用户态的API则不属于内核。
三、系统调用从用户态进入内核态和系统调用退出内核态的方法
     (1)进入内核态的方法:a、执行int $ox80汇编指令(这种方式在内核的入口是system_call()函数),在老的linux内核中,这是从用户态切换到内核态的唯一方法。b、执行sysenter汇编指令(这种方式在内核中的入口是sysenter_entry()函数)。也就是说:system_call()函数和sysenter_entry()函数是linux中所有系统调用的公共入口点。
     (2)退出内核态的方法:a、执行iret汇编语言指令。b、执行sysexit汇编指令。
四、系统调用的过程
   以下以read系统调用为例来进行说明。
   在用户空间,read()函数的声明位于#include,原型为:ssize_t read(int fd, void *buf, size_t count)。下面是read()函数在用户空间的定义的伪代码:
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 anyway  
3  pushl_cfi %eax            # save orig_eax  
4  SAVE_ALL  
5  GET_THREAD_INFO(%ebp)  
6  # system call tracing in operation / emulation  
7  testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)  
8  jnz syscall_trace_entry  
9  cmpl $(nr_syscalls), %eax 
10  jae syscall_badsys 
11 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_ALL  
2 cld  
3  PUSH_GS  
4  pushl_cfi %fs  
5 /*CFI_REL_OFFSET fs, 0;*/  
6  pushl_cfi %es  
7 /*CFI_REL_OFFSET es, 0;*/  
8  pushl_cfi %ds  
9 /*CFI_REL_OFFSET ds, 0;*/ 
10  pushl_cfi %eax 
11 CFI_REL_OFFSET eax, 0 
12  pushl_cfi %ebp 
13 CFI_REL_OFFSET ebp, 0 
14  pushl_cfi %edi 
15 CFI_REL_OFFSET edi, 0 
16  pushl_cfi %esi 
17 CFI_REL_OFFSET esi, 0 
18  pushl_cfi %edx 
19 CFI_REL_OFFSET edx, 0 
20  pushl_cfi %ecx 
21 CFI_REL_OFFSET ecx, 0 
22  pushl_cfi %ebx 
23 CFI_REL_OFFSET ebx, 0 
24  movl $(__USER_DS), %edx 
25  movl %edx, %ds 
26  movl %edx, %es 
27  movl $(__KERNEL_PERCPU), %edx 
28  movl %edx, %fs 
29  SET_KERNEL_GS %edx 
30 .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_exit 
4  .long ptregs_fork 
5  .long sys_read 
6  .long sys_write 
7 .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;  
6  
7 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这个博客。这个博客对系统调用进行了很详细的讲解。
接下来的工作就是学习怎么实现自己的系统调用。

阅读(1279) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:文件系统中的dentry结构的详细介绍

给主人留下些什么吧!~~