Linux内核设计与实现 五、系统调用

系统调用
 内核提供了用户进程与内核进行交互的一组接口。这些接口让应用程序受限地访问硬件设备,提供了创建新进程并与已有进程进行通信的机制,也提供了申请操作系统其他资源的能力。

5.1 与内核通信

系统调用在用户空间进程和硬件设备之间添加了一个中间层。

三个作用:
●为用户空间提供一种硬件的抽象接口。如读写文件时,应用程序就可以不管磁盘类型和介质,甚至不用管文件所在的文件系统到底是哪种类型。
●系统调用保证了系统的稳定和安全。内核可以基于权限、用户类型等规则对访问进行裁决。
●内核需要知道应用程序访问硬件,才能实现多任务和虚拟内存。

在Linux中,系统调用是用户空间访问内核的唯一手段,除异常和陷入外,它们是内核唯一的合法入口。

5.2 API、POSIX和C库

API和系统调用
 一般应用程序通过用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。

 程序员只需要和API打交道。内核只跟系统调用打交道。
在这里插入图片描述

POSIX标准
 在Unix世界中,最流行的应用编程接口是基于POSIX标准的。

5.3 系统调用

调用:
 系统调用通常通过C库中定义的函数调用来进行。通常通过long类型(为了32位与64位系统兼容)的返回值表示成功或者错误。

错误码

 系统调用出现错误时C库会把错误码写入errno全局变量。通过调用perror()库函数,可以把该变量翻译成用户可以理解的错误字符串。

asmlinkage限定词

 如getpid系统调用,内核中的实现非常简单,SYSCALL_DEFINE0是一个宏,定义一个无参数的系统调用(数字为0)展开后的代码为asmlinkage long sys_getpid(void),asmlinkage是一个编译指令,通知编译器仅从栈中提取该函数的参数。所有的系统调用都需要这个限定词。

SYSCALL_DEFINE0(getpid){
	return task_tgid_vnr(current);	//returns current->tgid;
}

系统调用号

概念:
 在LInux中,每个系统调用被赋予一个系统调用号。通过这个独一无二的号就可以关联系统调用。

独一无二:
 一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。

系统调用的性能

Linux系统调用比其他许多操作系统执行得要快,原因有两个
●Linux上下文切换快
●系统调用处理程序和每个系统调用本身都非常简洁

5.4系统调用处理程序

应用程序需要通过内核执行系统调用

原因
 内核驻留在受保护的地址空间上,如果进程可以直接在内核的地址空间上读写的话,系统的安全性和稳定性将不复存在。所以应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用。

通知机制
 软中断:通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序就是系统调用处理程序system_call()。
在这里插入图片描述

指定恰当的系统调用

先指定调用号,再陷入内核:
 仅仅陷入内核空间是不够的。必须把系统调用号一并传给内核。在x86上,系统调用号是通过eax寄存器传递给内核的,在陷入内核之前,用户空间就把相应系统调用所对应的号放入eax中。这样系统调用处理程序一旦运行,就可以从eax中得到数据。

参数传递
 除了系统调用号以外,大部分系统调用还需要一些外部的参数输入。最简单的办法就是像传递系统调用号一样,把这些参数存放在寄存器里。

返回值
 给用户空间的返回值也通过寄存器传递。在x86系统上,它存放在eax寄存器中。

5.5 系统调用的实现

实现

要求
 Linux中不提倡采用多用途的系统调用(传递不同参数选择完成不同的工作)
 要提供标志参数,不是用来让单个系统调用具有不同行为,而是为了即使增加新的功能和选项,也不破坏向后兼容或不需要增加新的系统调用。
 提供机制而不是策略。

参数验证

原因
 系统调用在内核空间执行,如果任由用户将不合法的输入传递给内核,那么系统的安全和稳定将受到威胁。

要求
●与文件I/O相关的系统调用必须检查文件描述符是否有效
●与进程相关的函数必须检查提供的PID是否有效
●最重要:检查用户提供的指针是否有效,保证1.指向的内存区域属于用户空间 2.内存区域在进程的地址空间 3.如果要读,内存应该被标记为可读,写标记为可写,可执行标记为可执行,进程不能绕过内存访问限制。

5.6 系统调用上下文

内核在执行系统调用的时候处于进程上下文。current指针指向当前任务,即引发系统调用的那个进程。

在进程上下文中,内核可以休眠(系统调用阻塞时)并且可以被抢占。

进程可以被抢占,新的进程可以使用相同的系统调用,所以必须保证系统调用是可重入的(可以被中断的函数)。

当系统调用返回的时候,控制权仍然在system_call()中,它最终会负责切换到用户空间,并让用户进程继承执行下去。

绑定系统调用

见书5.6.2

从用户空间访问系统调用

 仅仅写出系统调用,可能不提供支持。

 Linux本身提供了一组宏,用于直接对系统调用进行访问。会设置好寄存器并调用陷入指令。这些宏是_syscalln(),其中n的范围从0到6,代表需要传递给系统调用的参数个数。如open(),定义是

long open(const char *filename, int flags, int mode)

不靠库支持,直接调用此系统调用的宏的形式为:

#define NR_open 5
_syscall3(long, open, const char*, filename, int, flags, int ,mode)

对于每个宏来说,都有2+2*n个参数,第一个参数对应返回值类型,第二个参数是系统调用的名称。再以后是按照系统调用参数的顺序排列的每个参数的类型和名称。

尽量少通过系统调用的方式实现

系统调用的优点
●系统调用创建容易且使用方便
●Linux系统调用的高性能显而易见

原因
避免每出现一种新的抽象就简单加入一个新的系统调用,使得Linux稳定简洁且功能完善。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值