Chinaunix首页 | 论坛 | 博客
  • 博客访问: 458562
  • 博文数量: 85
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 32
  • 用 户 组: 普通用户
  • 注册时间: 2013-04-13 13:49
文章分类

全部博文(85)

文章存档

2018年(1)

2014年(40)

2013年(44)

分类: LINUX

2014-01-23 21:19:06

原文地址:深入理解系统功能调用 作者:Jelline

摘要:

    本文先回答了为什么需要系统调用,为什么增加系统调用,以及如何编写系统调用。紧接着给出一个简单实例,最后详尽分析系统调用整个过程。


一、基础知识

1.1 为什么需要系统调用[2]

系统调用作为沟通用户空间进程和硬件设备之间的中间层,有其必要性,主要体现在:

提高系统安全性和稳定性,内核可以基于权限和其他一些规则对访问进行裁决

使得编程更加容易,从硬件设备的低级编程解放出来

增强可移植性,只要内核所提供的一组相同的接口,皆可正确编译和执行相同的程序

总的来说,系统调用主要用途有:控制硬件、设置系统状态或者读取内核数据、进程管理[3]

1.2 系统命令、APIC库、POSIX、系统调用、内核函数

Linux的系统命令格式遵循系统V的传统,多数放在/bin/sbin下。在Shell窗口下运行命令lspwdchmod,都是系统命令,它是内部引用API的可执行程序。另,可用命令strace ls跟踪系统命令ls用到的系统调用,其他命令类似[3]

API(应用编程接口)是一些函数定义,说明了如何获得一个给定的服务。POSIX标准定义了一系列的APILinuxAPI遵循了该标准。

C库可以理解成API的具体实现,其实现了POSIX绝大部分API,并且提供了一套封装例程将系统调用在用户空间包装后供用户编程使用。

除了异常和陷入外,系统调用是用户空间访问内核的惟一手段(事实上,通过软中断int 0x80引发一个异常来促使系统切换到内核态去执行异常处理程序,即系统调用处理程序),为用户提供进入内核的一些接口,而内核函数对应于其实现(进入内核后,不同的系统调用会找到其对应的内核函数)。可以通过命令cat /proc/kallsyms查看内核公开的内核函数。

1.3 为什么增加系统调用

有些服务可以选择在内核完成,也可以选择在用户空间完成。选择在内核完成通常基于以下考虑[3]

服务必须获得内核数据,比如一些服务必须获得中断或系统时间等内核数据

安全性。在内核提供服务比用户空间更安全,防止被非法访问

性能。在内核提供服务,有效避免陷入/返回和系统调用处理程序带来的花销,提高效率(适合于一些对性能要求非常高的应用或者非常频繁使用的应用)

如果内核和用户空间都需要使用该服务,那么最好实现在内核空间,比如随机数产生

1.4 编写系统调用

(1) 内核开发不同于用户空间应用程序开发[1]

没有libc库,内核不链接使用标准C函数库,主要原因在于速度和大小(对于内核,完整C库太大了)

内核开发使用的C语言涵盖了ISO 99标准和GNU C扩展特性,比如内联函数、内联汇编、分支声明(likelyunlikely)

没有内存保护机制,此外内核中的内存都不分页

内核不能完美支持浮点操作,因为其本身不能陷入

容积小且固定的栈,新版本的内核把内核栈和中断栈分开,32位机器栈大小只有4KB

同步和并发,内核的许多特性都要求能够并发的访问共享数据

可移植性的重要性

(2) 实现系统调用

明确用途、确定接口

首先明确其确切用途,不提倡一个系统调用实现多种用途(ioctl除外),另外还需确定新的系统调用的参数(接口应该简洁,参数应尽可能少)、返回值和错误码。还应考虑系统调用的通用性和可移植性。

参数验证

系统调用必须检查所有参数的合法正确。最重要的一项检查是检查用户提供的指针是否有效(属于用户空间、指向的内存区域属于进程的地址空间、内存访问权限)。另,内核提供了两种方法copy_to_user()copy_from_user()实现用户空间与内核空间的数据拷贝。还应检查对资源访问的权限,可以调用函数capable()检查是否有权访问特定资源。

 

二、增加系统调用实例

 

假设系统调用名为foo(),取《Linux内核设计与实现》的例子。

2.1 指定新的系统调用号
    在文件linux-2.6.16/include/asm-i386/unistd.h增加系统调用号并修改系统调用总数
  1. /*在文件linux-2.6.16/include/asm-i386/unistd.h增加系统调用号*/
  2. #define __NR_foo 311
  3. /*将NR_syscalls加1*/
  4. #define NR_syscalls 312
2.2 添加新系统调用处理函数名称
    在文件linux-2.6.16/arch/i386/kernel/syscall_table.S末尾添加表项,即在函数指针表sys_call_table末端插入新系统调用处理的名称,并且要严格按照功能号顺序排列函数名。书中说的是位于entry.s,有误(该版本sys_call_table已从entry.S中提出来,单独放在了文件syscall_table.S中)。
  1. /*在文件linux-2.6.16/arch/i386/kernel/syscall_table.S末尾添加*/
  2. .long sys_foo     /*added by jelline*/
2.3 实现系统功能调用
    系统调用实现可以放在linux-2.6.16/kernel/sys.c,也可以放到与其功能联系紧密的代码中去。比如与调度相关,可以放到kernel/sched.c。
  1. /*在文件linux-2.6.16/kernel/sys.c实现系统调用*/
  2. asmlinkage long sys_foo(void) 
  3. /*asmlinkage用于通知编译器仅从栈提取该函数的参数*/
  4. {
  5.         return THREAD_SIZE;    /*返回每个进程的内核栈大小*/
  6. }
2.4 重新编译内核
    该系统调用必须编译到核心的内核映像,如何编译内核请点这里
2.5 从用户空间访问系统调用
    C库并不支持新加入的系统调用,需要封装例程。Linux提供一组_syscalln()宏(n取0~6,代表需要传递给系统调用的参数个数),用于直接对系统调用访问,其会设置好寄存器并调用陷入指令。
  1. /*filename:mysyscall.h*/
  2. #include <errno.h>
  3. #include <asm/unistd.h>

  4. #define __NR_foo 311
  5. /*第1个参数对应该系统功能调用的返回值类型,第2个参数对应其返回值类型,而后每个参数的类型和名称。共有2+2n个参数*/
  6. _syscall0(long,foo) /*仅有一条下划线,书上有误*/
值得注意的下,需要加入头文件errno.h和asm/unistd.h,否则出现如下编译出错:
  1. jelline@jelline-desktop:~/add_syscall_test$ make
  2. gcc -o add_syscall_test mysyscall.h add_syscall_test.c
  3. mysyscall.h:5: error: syntax error before ‘foo’
  4. In file included from add_syscall_test.c:2:
  5. mysyscall.h:5: error: syntax error before ‘foo’
  6. add_syscall_test.c: In function ‘_syscall0’:
  7. add_syscall_test.c:5: error: syntax error before ‘{’ token
  8. make: *** [add_syscall_test] Error 1
2.6 测试用例
编写测试用例add_syscall_test.c,源码如下:
  1. /*filename:add_syscall_test.c*/
  2. #include <stdio.h>
  3. #include "mysyscall.h"

  4. int main()
  5. {
  6.         long stack_size;
  7.         stack_size = foo();
  8.         printf("The kernel stack size is %ld\n", stack_size);
  9.         return 0;
  10. }
测试结果如下:
  1. jelline@jelline-desktop:~/add_syscall_test$ ./add_syscall_test
  2. The kernel stack size is 8192

    在没有实现该系统功能调用的内核版本运行该测试用例将得到-1。

 

 

三、系统调用整个过程

通过实例对系统调用有了感性认识,现在来深入分析整个系统调用过程。

这个得过段时间再整理了,我的思路是用GDB调试上述的例子,一路跟踪下去,阅读源代码。等搞清楚了再整理。由此给您带不便,敬请谅解!欢迎交流。

 

 

参考文献:

[1]Robert Love.陈莉君译.Linux内核设计与实现(第二版)[M].北京:机械工业出版社.2006.1

[2]DANIEL P.BOVET & MARCO CESATI.陈莉君译.深入理解LINUX内核(第三版)[M].北京:中国电力出版社.2007.9

[3]博文《Linux系统调用-Printf从函数库到OS跟踪流

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