Linux内核设计与实现 第5章 系统调用

系统调用简介

用户空间进程和硬件设备之间的中间层,为用户空间提供一种硬件的抽象接口。
在linux中,系统调用是用户空间访问内核的唯一手段,除异常和陷入外,系统调用是内核的唯一合法入口。Linux提供的系统调用比大部分操作系统都少得多。

系统调用

访问系统调用,通常通过 访问C库中定义的函数 实现。系统调用通过返回long型值表示成功或者错误,系统调用出错时C库会把错误码写入全局变量errno,再通过调用perror()库函数把错误码翻译成用户可以理解的字符串。

系统调用在用户空间int和内核空间long有不同的返回值。

通过 定义系统调用。SYSCALL_DEFINEx (yyy) { },x表示参数个数,yyy表示在用户空间的函数名,再加上sys_yyy是在内核中定义的函数名(Linux所有系统调用遵守的命名规则)。

系统调用号

在Linux中每个系统调用被赋予一个独一无二的调用号。一旦分配不再变更,否则系统错乱。
有个未实现的系统调用sys_ni_syscall(),只返回-ENOSYS不做任何事,专门针对无效调用设计,罕用。

系统调用处理程序

用户空间的程序无法直接执行内核代码,不能直接调用内核空间的函数。
需要通知内核,其机制是软中断。通过引发异常使系统切换到内核态去执行异常处理程序(此时的异常处理程序就是系统调用处理程序)。
x86上预定义的软中断是中断号128。触发一个异常导致系统切换到内核态去执行128号异常处理程序,就是系统调用处理程序,system_call(),与硬件体系结构紧密相关。

指定恰当的系统调用

x86上,系统调用号通过eax寄存器传递给内核。其他体系结构实现方法类似。system_call()将根据调用号和NR_syscalls对比检查有效性,最终执行对应的系统调用。call *sys_call_table(, %rax, 8)

参数调用

x86上,参数也存放在寄存器上,返回值在eax寄存器中。

read() —> read()封装例程  |—>   system_call() —> sys_read()
应用     C库     |—>  系统调用处理程序 系统调用
    用户空间       |—>       内核空间

系统调用的实现

给Linux添加新的系统调用比较容易,而设计和实现一个系统调用才是难度所在。
确保系统调用不对系统做错误的假设,否则可能崩溃。如机器字节长度和字节序等。

参数验证

系统调用必须检查他们所有的参数是否合法有效且正确。最重要的检查就是检查用户提供的指针是否合法有效。必须保证:

  1. 指针指向的内存区域属于用户空间
  2. 指针指向的内存区域在进程的地址空间
  3. 读/写/可执行 内存限制要明确,进程绝不能绕过内存访问限制

从用户空间读取数据copy_from_user(),反之copy_to_user()。三个参数,arg2位置的数据拷贝到arg1位置,arg3是拷贝数据长度(字节数)。成功返回0,失败返回没能完成拷贝的数据的字节数。失败则 系统调用返回-EFAULT。
Notecopy_from_user()copy_to_user()都可能引起阻塞,当包含用户数据的页被换出到硬盘上而不是在物理内存上时,进程就会休眠,直到缺页处理程序将该页从硬盘重新换回物理内存。

权能机制(capabilities)

检查针对特定资源的特殊权限。通过capable()检查是否有权能对指定的资源进行操作,返回非0则有权,返回0则无权操作。如capable(CAP_SYS_NICE)capable(CAP_SYS_REBOOT)
默认情况下,属于超级用户的进程拥有所有权利。

系统调用上下文

内核在执行系统调用时处于进程上下文,current宏指向引发系统调用的进程。
在进程上下文中内核可以休眠,并可被抢占。
可以休眠:比如,在系统调用阻塞或显式调用schedule()的时候。
可以被抢占:当前进程同样可被其他进程抢占,其他进程也可使用该系统调用,因此要考虑系统调用的重入问题。

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

中断处理程序不能休眠,与系统调用不同。

绑定系统调用最后的步骤

编写完系统调用后,需要注册成正式的系统调用。

  1. 在系统调用表最后加入一个表项,对应位置就是系统调用号;
  2. 定义系统调用号于<asm/unistd.h>
  3. 系统调用必须被编译进内核镜像,不能编译成模块。放入kernel/的相关文件即可。

从用户空间访问系统调用

1. 通过C库支持

通常,系统调用靠C库支持,用户只需包含标准头文件并与C库链接就可以使用系统调用了。

2. 直接使用Linux提供的宏操作

Linux提供一组宏,也可以直接对系统调用进行访问,它会设置好寄存器并调用陷入指令,这些宏是_syscalln(),n的范围从0到6,代表需要传递给系统调用的参数个数(该宏必须了解有多少参数按照什么顺序压入寄存器)。参数个数为2+2xn

举例:
系统调用open()的定义是long open(const char *filename, int flags, int mode)
不需要任何库支持而直接使用系统调用:
#define __NR_open 5
_syscall3(long, open, const char *, filename, int, flags, int, mode),这样用户程序就可以直接使用open()了。

用系统调用实现 的优缺点

建立新的系统调用 的优点

  1. 创建容易且使用方便
  2. Linux系统调用高性能

建立新的系统调用 的问题(缺点)

  1. 需要一个系统调用号,并且内核处于开发阶段时就要分配好
  2. 系统调用被固化到内核镜像,无法改动接口
  3. 需要将系统调用分别注册到各个支持的体系中去
  4. 在脚本中不容易使用系统调用,在文件系统中也无法直接访问系统调用
  5. 由于需要调用号,在主内核树之外很难维护和使用系统调用
  6. 如果仅仅是简单的信息交换,那又大材小用了

替代方法

实现一个设备节点,并对此实现read()write()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值