原文:Anatomy of a system call, part 1
翻译:RobotCode俱乐部

系统调用是用户空间程序与Linux内核交互的主要机制。考虑到它们的重要性,不难发现内核包含了各种各样的机制,以确保系统调用能够跨体系结构通用地实现,并且能够以一种高效且一致的方式提供给用户空间。

我一直致力于将FreeBSD的Capsicum安全框架移植到Linux上,由于这涉及到添加几个新的系统调用(包括稍微不同寻常的execveat()系统调用),我发现自己正在研究它们实现的细节。因此,本文是探索内核实现系统调用(或系统调用)的详细信息的两篇文章中的第一篇。在本文中,我们将重点讨论主流情况:普通syscall (read())的机制,以及允许x86_64用户程序调用它的机制。第二篇文章将脱离主流案例,介绍更多不常见的系统调用和其他系统调用机制。

系统调用不同于常规函数调用,因为被调用的代码在内核中。需要特殊的指令使处理器执行到ring 0(特权模式)的转换。此外,被调用的内核代码由一个syscall编号标识,而不是由一个函数地址标识。

使用SYSCALL_DEFINEn()定义一个syscall

read()系统调用为研究内核的syscall机制提供了一个很好的初始示例。它是在fs/read_write.c中实现的。,作为一个短函数,它将大部分工作传递给vfs_read()。从调用的角度来看,这段代码最有趣的方面是使用SYSCALL_DEFINE3()宏定义函数的方式。实际上,从代码中,甚至不能立即清楚地知道函数的调用。

    SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count){struct fd f = fdget_pos(fd);ssize_t ret = -EBADF;/* ... */

这些SYSCALL_DEFINEn()宏是内核代码定义系统调用的标准方法,其中n后缀表示参数计数。这些宏的定义(在include/linux/syscalls.h中)为每个系统调用提供两个不同的输出。

    SYSCALL_METADATA(_read, 3, unsigned int, fd, char __user *, buf, size_t, count)__SYSCALL_DEFINEx(3, _read, unsigned int, fd, char __user *, buf, size_t, count){struct fd f = fdget_pos(fd);ssize_t ret = -EBADF;/* ... */

第一个是SYSCALL_METADATA(),它构建了一个关于系统调用的元数据集合,用于跟踪。只有在为内核构建定义CONFIG_FTRACE_SYSCALLS时,它才会展开,它的展开给出了描述syscall及其参数的数据的样板定义。(另一页更详细地描述了这些定义。)

__SYSCALL_DEFINEx()部分更有趣,因为它包含系统调用实现。一旦扩展了宏和GCC类型扩展的各个层,得到的代码包括一些有趣的特性:

    asmlinkage long sys_read(unsigned int fd, char __user * buf, size_t count)__attribute__((alias(__stringify(SyS_read))));static inline long SYSC_read(unsigned int fd, char __user * buf, size_t count);asmlinkage long SyS_read(long int fd, long int buf, long int count);asmlinkage long SyS_read(long int fd, long int buf, long int count){long ret = SYSC_read((unsigned int) fd, (char __user *) buf, (size_t) count);asmlinkage_protect(3, ret, fd, buf, count);return ret;}static inline long SYSC_read(unsigned int fd, char __user * buf, size_t count){struct fd f = fdget_pos(fd);ssize_t ret = -EBADF;/* ... */

首先,我们注意到系统调用实现实际上名为SYSC_read(),但它是静态的,因此在此模块之外是不可访问的。相反,一个名为SyS_read()并别名为SyS_read()的包装器函数在外部是可见的。仔细观察这些别名,我们注意到它们的参数类型的不同——sys_read()期望显式声明的类型(例如第二个参数是char __user *),而sys_read()只期望一组(长)整数。深入研究这个问题的历史,可以发现长版本可以确保32位值对某些64位内核平台进行正确的符号扩展,从而防止出现历史上的漏洞。

使用SyS_read()包装器,我们注意到的最后一点是asmlinkage指令和asmlinkage_protect()调用。内核新手FAQ很好地解释了asmlinkage意味着函数期望它的参数在堆栈中而不是寄存器中,并且asmlinkage_protect()的通用定义解释了它的作用是防止编译器假设它可以安全地重用堆栈的这些区域。

在定义sys_read()(具有精确类型的变体)的同时,还在include/linux/syscalls.h中声明。这允许其他内核代码直接调用系统调用实现(在6个地方发生)。直接从内核的其他地方调用系统调用通常是不鼓励的,而且也不常见。

系统调用表条目

寻找sys_read()的调用者还指出了用户空间如何到达这个函数。对于不提供自己覆盖的“通用”架构,可以使用include/uapi/asm-generic/unistd.h文件包括一个引用sys_read的条目:

    #define __NR_read 63__SYSCALL(__NR_read, sys_read)

它为read()定义了通用的syscall编号__NR_read(63),并使用__SYSCALL()宏以特定于体系结构的方式将该编号与sys_read()关联起来。例如,arm64使用asm-generic/unistd.h头文件,以填充将syscall编号映射到实现函数指针的表。

但是,我们将集中讨论x86_64体系结构,它不使用这个通用表。相反,x86_64在arch/x86/syscalls/syscall_64.tbl中定义了自己的映射,其中有一个sys_read()条目:

 0   common  read            sys_read

这表明x86_64上的read()具有syscall号0(不是63),并且对于x86_64的两个ABIs都有一个公共实现,即sys_read()。(本系列的第二部分将讨论不同的ABIs。)syscalltbl.sh脚本生成来自syscall_64.tbl表的arch/x86/include/generated/asm/syscalls_64.h,特别是为sys_read()生成__SYSCALL_COMMON()宏的调用。这个头文件依次用于填充syscall表sys_call_table, sys_call_table是将syscall编号映射到sys_name()函数的关键数据结构。

x86_64调用系统调用

现在我们来看看用户空间程序如何调用系统调用。这本质上是特定于体系结构的,因此在本文的其余部分中,我们将集中讨论x86_64体系结构(本系列的第二篇文章将研究其他x86体系结构)。调用过程还涉及几个步骤,因此下图可能有助于导航。(建议访问原文,这里图中的链接没有保留

在上一节中,我们发现了一个系统调用函数指针表;x86_64的表如下所示(使用GCC扩展进行数组初始化,以确保任何缺少的条目都指向sys_ni_syscall()):

  asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {[0 ... __NR_syscall_max] = &sys_ni_syscall,[0] = sys_read,[1] = sys_write,/*... */};

对于64位代码,可以从arch/x86/kernel/entry_64.S访问该表。,从system_call程序集入口点;它使用RAX寄存器在数组中选择相关的条目,然后调用它。在函数的前面,SAVE_ARGS宏将各种寄存器压入堆栈,以匹配前面看到的asmlinkage指令。

往外看,system_call入口点本身在syscall_init()中引用,这是一个在内核启动早期调用的函数:

    void syscall_init(void){/** LSTAR and STAR live in a bit strange symbiosis.* They both write to the same internal register. STAR allows to* set CS/DS but only a 32bit target. LSTAR sets the 64bit rip.*/wrmsrl(MSR_STAR,  ((u64)__USER32_CS)<<48  | ((u64)__KERNEL_CS)<<32);wrmsrl(MSR_LSTAR, system_call);wrmsrl(MSR_CSTAR, ignore_sysret);/* ... */

wrmsrl指令将一个值写入特定于模型的寄存器;在本例中,通用system_call 处理函数的地址被写入寄存器MSR_LSTAR (0xc0000082),这是处理syscall指令的特定于x86_64模型的寄存器。

这就为我们提供了从用户空间连接到内核代码所需的一切。x86_64用户程序如何调用系统调用的标准ABI是将系统调用号(0表示读取)放入RAX寄存器,将其他参数放入特定寄存器(前3个参数为RDI、RSI、RDX),然后发出SYSCALL指令。该指令导致处理器转换到ring 0并调用MSR_LSTAR寄存器(即system_call)引用的代码。system_call代码将寄存器压入内核堆栈,并调用sys_call_table表中RAX条目上的函数指针——即sys_read(),它是SYSC_read()中实际实现的asmlinkage 包装器。

既然我们已经看到了在最常见平台上的系统调用的标准实现,我们就能够更好地理解其他体系结构和不太常见的情况。这将是本系列第二篇文章的主题。

--未完待续

ps:这篇翻译的实在粗糙,将就看看吧

由于本人水平有限,翻译必然有很多不妥的地方,欢迎指正。
同时,欢迎关注下方微信公众号,一起交流学习:)

syscall 系统调用陷入_系统调用深度剖析(上)相关推荐

  1. syscall 系统调用陷入_trusty系统调用

    trusty中,可以通过系统调用陷入kernel,获取kernel服务. 这里记录一下trusty的系统调用框架结构,代码基于google trusty源码 1.应用程序接口 在文件lib/inclu ...

  2. syscall 系统调用陷入_linux 系统调用open 篇一

    内核源码:linux-4.4 目标平台:ARM体系结构 源码工具:source insight 4 说明: 文中由于 md 语法问题,无法在代码高亮的同时而忽略由于 __ 或者 * 造成斜体的 问题, ...

  3. 编写python扩展模块_《深度剖析CPython解释器》27. 使用Python/C API编写扩展模块:编写扩展模块的整体流程...

    楔子 到目前为止,我们已经介绍了很多关于解释器方面的内容,本来接下来应该要说内存管理的,但是个人觉得应该对前面的系列做一个总结.而最好的方式,就是使用Python/C API编写扩展模块,个人是这么认 ...

  4. python深度讲解_《深度剖析CPython解释器》21. Python类机制的深度解析(第五部分): 全方位介绍Python中的魔法方法,一网打尽...

    楔子 下面我们来看一下Python中的魔法方法,我们知道Python将操作符都抽象成了一个魔法方法(magic method),实例对象进行操作时,实际上会调用魔法方法.也正因为如此,numpy才得以 ...

  5. 云原生钻石课程 | 第5课:Kubernetes存储架构原理深度剖析(下)

    点击上方"程序猿技术大咖",关注并选择"设为星标" 回复"加群"获取入群讨论资格! 本篇文章来自<华为云云原生王者之路训练营>钻 ...

  6. Golang标准库-syscall(什么是系统调用/Go 语言中的系统调用)

    文章目录 一.什么是系统调用 二.Golang标准库-syscall 1. syscall无处不在 2. syscall demo举例: go版本的strace Strace go版本的strace ...

  7. 深度剖析E680G开发三.移植OPIE操作系统(上)

    深度剖析E680G开发三.移植OPIE操作系统(上) 草木瓜 20060918 一.前言         凡事我图写的文章,往往比较内容纵深,范围却很窄,注重实际操作.原因 很简单,水平有限,还达不到 ...

  8. ecshop 属性自动组合_【深度文章】结构设计中的荷载组合剖析(下)

    ▲ 点击上方蓝字,关注PKPM官方公众号! [深度文章]结构设计中的荷载组合剖析(上) [深度文章]结构设计中的荷载组合剖析(中) 续接前文 (本篇共1410字,阅读时长大约5分钟) 06 梁.板挠度 ...

  9. 单片机c语言必背代码_【典藏】深度剖析单片机程序的运行(C程序版)

    1.日常聊一聊 今天为大家带来一篇对于单片机学习的小伙伴非常重量级的一篇文章<深度剖析单片机程序的运行(C语言版本)>,该文章会比较全面的为大家解析我们的用C语言编译出来的程序是如何在单片 ...

最新文章

  1. react-native 安卓支持 gif动态图
  2. Caffe官方教程翻译(4):CIFAR-10 turorial
  3. Python 深度学习,你的 Keras 准备好了吗?
  4. android2.2桌面,手机桌面课表软件
  5. 数据结构与索引-- mysql InnoDB存储引擎索引
  6. 【zookeeper】zookeeper shell 删除路径 卡死
  7. 【POJ-2452】Sticks Problem【二分右端点+线段树】
  8. centos离线安装谷歌浏览器flash-player
  9. Matlab2019b中配置最小均方误差滤波器(dsp.LMSFilter)详细设置
  10. 2020年wordpress主题开发视频教程、WP主题WP模板开发视频教程
  11. unity3d 取锚点位置_《王者荣耀》破晓之心碎片在哪 破晓之心碎片位置介绍
  12. 微信小程序——订阅消息与微信公众号模板消息
  13. 目前计算机无法显示的四叠字,四叠字列表(共19个),还能用的四叠字大全,带拼音,部分注释!...
  14. Jquery给HTML元素绑定按键事件-回车事件
  15. Computer:计算机测试理论(开发/测试/上线)之DEV、SIT、UAT、PRD四套环境(测试环境/开发环境/生产环境)详细介绍之详细攻略
  16. 输入关键字的爬虫方法(运行环境python3)
  17. ClickHouse 极简教程
  18. 阿里云FinalShell连接
  19. java判断是否是数组_java判断对象是否是数组
  20. 【优化求解】基于matlab遗传算法求解仓库货位优化问题【含Matlab源码 022期】

热门文章

  1. 二级c语言笔试需要带笔吗,考计算机二级需要准备什么
  2. 1小时搞懂设计模式之工厂模式(方法工厂)
  3. spring mvc 原理及应用
  4. 【31】将文件间的编译依存关系降至最低
  5. (@WhiteTaken)设计模式学习——组合模式
  6. bzoj2752 高速公路
  7. 20155310 《Java程序设计》实验三(敏捷开发与XP实践)实验报告
  8. error_reporting()函数
  9. spring 使用小记
  10. java 获取自绘窗口_iPhone中自绘实现步骤