Chinaunix首页 | 论坛 | 博客
  • 博客访问: 230900
  • 博文数量: 131
  • 博客积分: 259
  • 博客等级: 二等列兵
  • 技术积分: 705
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-21 21:15
文章分类

全部博文(131)

文章存档

2013年(3)

2011年(128)

分类:

2011-04-29 15:25:10

原文地址:Linux系统调用FAQ 作者:zyd_cu

1. Linux系统调用的作用?

系统调用是操作系统为用户态运行的进程与系统内核、硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口,在应用程序和硬件之间设置一个额外层的优点包括:

1.         用户编程更加简单,不必学习硬件设备的低级编程特性;

2.         提高了系统的安全性,内核在试图满足某个请求前在接口级可以检查请求正确性。

3.         这组接口使得程序具有可移植性,只要内核所提供的接口相同,使用这些接口的        程序即可正确的编译和执行。

2. POSIX API与系统调用的关系?

1.         API是函数定义(如libc库),而系统调用是通过软中断想内核态发出的请求;

2.         一个API没有必要对应一个特定的系统调用。API可能直接提供用户态服务,如数学函数;有些API函数可能调用几个系统调用;几个API函数可能调用同一系统调用(如malloccallocfree均使用brk系统调用实现)。

3.         POSIX标准针对API而不是针对系统调用,判断一个系统是否与POSIX兼容,要看它是否提供了一组合适的API

4.         从应用程序设计者的角度看,两者的差别仅在于名字、参数、返回值的不同;从内核设计者的角度看,系统调用属于内核,而API不属于内核。

 

3. 内核如何处理系统调用?

1.         每一个系统调用拥有一个系统调用号的标识,当用户态请求系统调用时,需传递系统调用号及其它信息作为参数,eax寄存器用于传递系统调用号(同时也用于传递返回值,系统调用号与返回值类型相同)。内核从寄存器读到系统调用的参数,并执行对应的系统调用服务例程。

2.         下图显示了应用程序、相应的封装例程、系统调用处理程序、系统调用服务例程之间的关系。xyz()是应用程序的调用接口,xyzlibc中的调用实现通过调用SYSCALL汇编指令实现,该指令使得CPU切换到内核态(SYSEXIT反之);内核态通过sys_call系统调用处理程序来最终调用xyz的服务例程sys_xyz()

   

3.         为了将系统调用号与相应的服务例程关联起来,内核利用一个系统调用分派表,该表存放在sys_call_table数组中,有NR_syscalls个表项,第n个表项包含系统调用号为n的服务例程的地址。

 

 

4. 如何进入和退出系统调用?

本地应用可以通过两种方式调用系统调用:

1.         执行int $0x80汇编语言指令(老版本linux中从用户态切换到内核态的唯一方式)

2.         执行sysenter汇编语言指令,Intel Pentium II微处理器引入该指令,linux2.6内核支持这条指令。

同样,内核可通过两种方式从系统调用退出,使CPU切换回到用户态:

1.         执行iret汇编语言指令

2.         执行sysexit汇编语言指令

对以上两种方式涉及到汇编代码,我也解释不太清楚,更详细的了解,请参考《深入理解linux内核》。

 

5. 系统调用参数如何传递?

1.         普通C函数的参数传递是通过把参数值写入到活动的程序栈(用户态或内核态堆栈)实现的,而系统调用同时跨越内核态和用户态,同时操作两个栈是很复杂的,而采用下面的方式(寄存器传递参数)将使得系统调用处理程序与其它异常处理程序的结构类似。

2.         在发出系统调用之前,系统调用的参数被写入到CPU的寄存器,然后在调用系统服务例程之前,内核再把存放在CPU中的参数拷贝到内核态堆栈(系统调用服务例程是普通的C函数)。

3.         使用寄存器传递参数必须满足:每个参数的长度不能超过寄存器的长度(容易解决,长度不够,可以通过传递参数的地址解决),并且参数的个数不能超过6个(系统调用号除外),因为80x86处理器的寄存器的数量是有限的(用于存放调用号和调用参数的寄存器是eaxebxecxedxesiediebp)。

 

6. 如何保证系统调用的安全性?

内核在满足用户的系统调用请求前,必须仔细的检查所有的系统调用参数,如write系统调用的参数fd,内核需检查fd是否对应于一个打开的文件,该文件是否允许写操作等,这些检查操作依赖于系统调用本身,也依赖于特定的参数。但有一种检查对所有的系统调用是通用的,只要参数指定的是地址,内核必须检查它是否属于用户地址空间,可通过检查地址所在的线性区,或直接与PAGE_OFFSET对比等两种方式实现(linux2.2开始实现第二种方式)。

 

7. 内核如何访问用户态数据?

系统调用服务例程需要非常频繁的读写进程地址空间的数据,Linux提供一组宏来实现这个目的(__开头的宏不包含对线性地址的有效性检查)。

get_user  __get_user   从用户空间读一个整数(124个字节)

put_user  __put_user   向用户空间写一个整数(124个字节)

copy_from_user __copy_from_user  从用户空间复制任意大小的块

copy_to_user  __copy_to_user  把任意大小的块复制到用户空间

strncpy_from_user  __strncpy_from_user  从用户空间复制一个以null结束的字符串

strlen_user  strnlen_user  返回用户空间以null结束的字符串的长度

clear_user __clear_user 0填充用户空间的一个内存区域

 

8. 在内核态能否调用系统调用?

尽管系统调用主要由内核态进程使用,但也可以被内核线程调用,内核线程不能使用库函数。linux定义了7个从_syscall0_syscall6的一组宏来封装相应例程,宏名中的数字0-6对应着系统调用所用的参数个数(系统调用号除外)。每个宏需要2 + 2 * n个参数(n为系统调用参数的个数),前两个参数是系统调用号返回值类型和名字;每一对附加参数指明相应的系统调用参数的类型和名字。

阅读(553) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~