全部博文(2759)
分类: LINUX
2013-08-17 02:49:57
原文地址:Linux内核设计与实现(6)---系统调用 作者:leon_yu
现代操作系统中,内核提供了用户进程和内核进程交互的一组接口,让app可以受限的访问硬件资源,提供进程间通信机制,实际上主要是为了保证系统稳定可靠,避免应用程序do whatever they want.
1.与内核通信
系统调用在用户空间进程和硬件设备之间添加了一个中间层,主要作用:
①为用户空间提供了一种硬件的抽象接口;
②保证了系统的稳定和安全,可以给予权限,用户对访问进行裁决;
③每个进程都运行在虚拟系统中;
在Linux中,系统调用是用户空间访问内核的唯一手段;除异常和陷入外,是内核唯一的合法入口;实际上像设备文件和/proc之类的方式,也是通过系统调用进行访问的。
2 API、POSIX和C库
应用程序通过在用户空间实现的应用编程接口(API),而不是直接通过系统调用来编程。
因为API实际上不需要和系统调用对应,一个API可以实现成一个系统调用,也可以通过调用多个系统调用来实现,也可以完全不用。POSIX、API、C库及系统调用关系如下
程序员只跟API打交道,内核只跟系统调用打交道;即内核提供机制,API提供策略。
C库实现了大部分的POSIX标准API.
3.系统调用
系统调用一般用返回0来表示成功,返回负数表明错误,错误码写入errno全局变量,用peeror()库函数可以把错误码转变成错误字符串.
举一例,获取进程ID号的系统调用getpid()
点击(此处)折叠或打开
①asmlinkage限定词是编译器指令,告知编译器仅从堆栈中提取函数的参数;
②内核返回long,用户空间返回int,是为了保证32位/64位系统兼容;
③get_pid在内核被定义为sys_getpid(),内核对系统调用都是如此定义的;
(1)系统调用号
Linux中,每个系统调用号被赋予一个唯一的系统调用号,进程不会提及系统调用名称,而是用系统调用号来关联具体的系统调用。
一个系统调用号一旦被分配,不能随意变更;用sys_ni_syscall()来补缺已经删除的调用号;
系统调用号保持在unsigned long sys_call_table[NR_syscalls];
(2)系统调用的性能
Linux上下文切换时间很短,进出内核都被优化的简洁高效;系统调用处理程序和每个系统调用本身都非常简洁,所以Linux系统调用比许多其他操作系统都执行的快。
(3)系统调用处理程序
通过软中断引发一个异常,促使系统切换到内核态,执行异常处理程序代码;这个异常处理程序就是系统调用处理程序system_call()。
①找到指定的系统调用
X86上是通过eax把系统调用号传给内核,system_call()通过查找sys_call_table[]找到对应的系统调用
②参数传递
Ebx,ecx,edx,esi和edi依次存放前五个参数,若需要六个以上参数,用单独寄存器指向这些参数在用户空间地址的指针。通过eax存放返回值。
4.系统调用的实现
(1)决定用途,每个系统调用功能应该单一明确,不提倡多用途系统调用。系统调用参数,返回值和错误码都要明确,不要对机器字节长度和字节序做假设。
(2)参数验证:内核必须保证
①指向用户空间内存的指针,内核不能直接访问;
②指针指向的内存在用户进程空间里,内核不能读其他进程空间;
③内存不能绕过访问限制:可读内存标记为可读,可写标记为可写,可执行标记为可执行
内核用copy_to_user()和copy_from_user()来从用户空间读写数据,都是把第二个参数指定位置数据传送到第一个参数指定位置,长度由第三个参数决定。执行失败,返回未传送字节,成功返回0。copy_to_user()和copy_from_user()都可能引起休眠。
④检查权能,针对合法权限,比如if (!capable(CAP_SYS_BOOT)) return –EPERM;
(3)内核执行系统调用时处于进程上下文,current指针指向引发系统调用的那个进程。能够休眠,所以系统调用必须是可重入的。
(4)往系统添加一个系统调用的一个简单实例
①添加系统调用名字函数名字sys_mytest,一般在calls.S或者entry.S
/* 320 */ .long sys_get_mempolicy
.long sys_set_mempolicy
.long sys_mytest
②在unistd.h添加系统调用号,322
#define __NR_get_mempolicy (__NR_SYSCALL_BASE+320)
#define __NR_set_mempolicy (__NR_SYSCALL_BASE+321)
#define __NR_mytest (__NR_SYSCALL_BASE+322)
③实现系统调用函数
点击(此处)折叠或打开
④在syscalls.h做系统调用函数声明
点击(此处)折叠或打开
⑤app测试
点击(此处)折叠或打开
5.添加系统调用
优点有:
①系统调用创建容易,且使用方便;
②Linux系统调用高性能显而易见
缺点是:
①需要一个系统调用号,这个需要官方分配
②系统调用被加入稳定内核固化后,接口不能改变;
③需要将系统调用分别分配到各种体系结构去(与硬件相关)
④在脚本中不容易调用系统调用,也不能从文件系统直接访问系统调用
⑤在主内核树之外很难维护
⑥如果只进行简单信息交换,系统调用大材小用了。所以尽管建立一个系统调用非常容易,但是不建议这么做,替代方法:
①实现一个设备节点,并对此实现read()和write(),ioctl()来进行操作
②像信号量这样的某些接口,可以用文件描述符来表示
③把增加的信息作为一个文件放在sysfs的合适位置
Linux尽量使系统调用简洁,事实上Linux已经是一个相对稳定并且功能已经较为完善的操作系统。