Linux系统调用的最终指南

1.概述

这篇博客解释了Linux程序如何调用Linux内核中的函数。

它将概述几种进行系统调用的不同方法,如何手工制作自己的程序集来进行系统调用(包括示例),系统调用的内核入口点,系统调用的内核出口点,glibc包装器,bug等等。

2.什么是系统调用

当您运行一个调用了open、fork、read、write(以及许多其他的)的程序时,您正在进行系统调用。

系统调用是程序进入内核执行某些任务的方式。程序使用系统调用来执行各种操作,例如:创建进程、执行网络和文件IO等等。

通过检索syscalls(2)的手册页,可以找到系统调用的列表。

用户程序有几种不同的方式进行系统调用,而进行系统调用的低级指令在不同的CPU架构中有所不同。

作为一名应用程序开发人员,您通常不需要考虑如何准确地进行系统调用。您只需包含适当的头文件,然后就可以像调用普通函数一样调用它。

glibc提供了包装代码,它将您从安排传递的参数并进入内核的底层代码中抽象出来。
在深入研究如何进行系统调用之前,我们需要定义一些术语,并检查稍后将出现的一些核心思想。

3.先前准备

3.1 硬件和软件

本文假设:

  • 您使用的是32位或64位Intel或AMD CPU。关于这些方法的讨论可能对使用其他系统的人有用,但是下面的代码示例包含特定于cpu的代码。
  • 您对Linux内核3.13.0版本感兴趣。其他内核版本将是类似的,但是具体的行号、代码组织和文件路径将有所不同。GitHub上有3.13.0内核源代码树。
  • 您对glibc或glibc派生出的libc实现(例如,eglibc)感兴趣。

在这篇博文中,x86-64术语表示的是基于x86架构的64位Intel和AMD CPU。

3.2 用户程序、内核和CPU特权级别

用户程序(如您的编辑器、终端、ssh守护进程等)需要与Linux内核交互,以便内核能够代表您的用户程序执行一组它们所不能自己执行的操作。

例如,如果用户程序需要执行某种IO(open、read、write等)或修改其地址空间(mmap、sbrk等),它必须触发内核运行,以代表它完成这些操作。

是什么阻止用户程序自己执行这些操作?

事实证明,x86-64 cpu有一个称为特权级别1的概念。特权级别【Privilege levels】是一个复杂的主题,比较适合使用单独的博客来解释它。就本文而言,我们可以(大大)简化特权级别的概念:

  1. 特权级别【Privilege levels】是一种访问控制手段。当前的特权级别决定可以执行哪些CPU指令和IO。
  2. 内核运行在最高特权级别,称为“Ring 0”。用户程序在较低的级别上运行,通常是“Ring 3”。

为了让用户程序执行某些特权操作,它必须引起特权级别的更改(从“Ring 3”更改为“Ring 0”),这样内核才可以执行。

有多种方法可以引起特权级别的更改,并触发内核执行某些操作。

让我们从一种导致内核执行的常见方法开始:中断。

3.3 中断

您可以将中断看作是由硬件或软件生成(或“引发”)的事件。

硬件设备引发硬件中断,通知内核发生了特定事件。这种类型的中断的一个常见例子是当NIC【Network Interface Card】(即网卡、网络适配器)接收到一个数据包时产生的中断。

软件中断是通过执行一段代码引发的。在x86-64系统上,可以通过执行int指令引发软件中断。

每种中断通常有一个分配给它们的数字。有些中断的数字有特殊的含义。

您可以想象有那么一个驻留在CPU内存中的数组。这个数组中的每个条目映射到一个中断号。每个条目包含一个指向某个函数的地址和一些选项(比如中断处理函数应该在哪个特权级别执行),当接收到这个中断时,CPU将开始执行这个函数。
这是一张来自Intel CPU手册的图片,展示了这个数组中的一个条目的布局:
在这里插入图片描述

如果仔细查看图表,可以看到一个标记为DPL(描述符特权级别)的2-bit大小的属性字段。该属性字段中的值决定了当处理器【handler】函数执行时,CPU所处的最小特权级别。

这就是在接收到特定类型的事件时,CPU如何知道应该执行哪个地址,以及该事件的处理器【handler】程序应该在哪个特权级别执行。

实际上,在x86-64系统上有许多不同的方法来处理中断。如果你有兴趣学习更多的知识,那么可以阅读8259 Programmable Interrupt Controller, Advanced Interrupt Controllers, 以及IO Advanced Interrupt Controllers.

在处理硬件和软件中断时还涉及到其他一些复杂的问题,例如中断号码冲突和重新映射。

本文的重点是讨论系统调用,故不展开详述。

3.4 Model Specific Registers (MSRs)

模型特定寄存器(也称为MSR)是控制寄存器【control registers】,具有控制CPU某些功能的特定目的。 CPU文档列出了每个MSR的地址。

你可以使用rdmsr 和 wrmsr分别读取和写到MSRs。

还有一些命令行工具允许您读取和写入MSRs,但是不建议这样做,因为更改这些值(尤其是在系统运行时)是危险的,除非您非常小心。

如果您不介意可能会破坏您的系统或不可逆转地损坏您的数据,您可以通过安装msr-tools并加载msr内核模块来读写MSR:

% sudo apt-get install msr-tools
% sudo modprobe msr
% sudo rdmsr

我们稍后会看到的一些系统调用方法会使用MSR,我们很快就会看到。

3.5 用汇编调用系统调用是个坏主意

通过编写自己的汇编代码来调用系统调用并不是一个好主意。

其中一个重要原因是某些系统调用具有在系统调用运行之前或之后在glibc中运行的其他代码。

在下面的示例中,我们将使用exit系统调用。 事实证明,您可以通过使用atexit来注册当exist系统调用被程序调用时需要运行的函数。

那些函数是从glibc中调用的,而不是从内核中调用的。 所以,如果你编写自己的汇编程序来调用exit,如下所示,你所注册的处理器【handler】函数将不会被执行,因为你绕过了glibc。

然而,使用汇编手动进行系统调用是一种很好的学习体验。

4.遗留系统调用

根据我们已有的先决知识,我们知道两件事:

  • 我们知道我们可以通过生成软件中断来触发内核执行。
  • 我们可以使用int汇编指令生成软件中断。

结合这两个概念将我们引入Linux上的遗留系统调用接口。

Linux内核预留了一个特定的软件中断号,用户空间程序可以使用它来进入内核并执行系统调用。
LInux讲一个名为ia32_syscall的终端处理程序注册给该终端号:128(0x80)。 我们来看看实际执行此操作的代码。

来自于内核3.13.0源码中arch/x86/kernel/traps.c文件中的trap_init函数:

void __init trap_init(void)
{
        /* ..... other code ... */

        set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);

其中IA32_SYSCALL_VECTOR在arch/x86/include/asm/irq_vectors.h文件中定义为0x80。

但是,即使内核保留了一个软件中断,用户级程序可以用于触发内核,那么内核如何知道它应该执行这许多系统调用中的哪一个?

用户级程序应该将系统调用号放在eax寄存器中。 系统调用本身的参数将放在剩余的通用寄存器中。

在arch/x86/ia32/ia32entry.S中有一个地方记录了这一点:

 * Emulated IA32 system calls via int 0x80.
 *
 * Arguments:
 * %eax System call number.
 * %ebx Arg1
 * %ecx Arg2
 * %edx Arg3
 * %esi Arg4
 * %edi Arg5
 * %ebp Arg6    [note: not saved in the stack frame, should not be touched]
 *

既然我们知道了如何进行系统调用以及参数应该位于何处,那么让我们通过编写一些内联汇编程序来尝试进行调用。


  1. x86指令集中的特权级别控制当前在处理器上运行的程序对资源(如内存区域、I/O端口和特殊指令)的访问。有4个特权级别,从0到3。大多数现代操作系统对内核/执行程序使用0级,对应用程序使用3级。级别n可用的任何资源对级别0到n也是可用的,所以特权级别是环。当较弱的特权进程试图访问较高的特权进程时,操作系统会报告一般的保护错误异常。
    没有必要使用所有四个特权级别。目前市场份额较大的操作系统,包括Microsoft Windows、macOS、Linux、iOS、Android等,大多采用单比特的分页机制,指定权限级别为管理员或用户(U/S比特)。Windows NT使用两级系统。在8086中的实际模式程序在第0级(最高特权级)执行,而在8086中的虚拟模式在第3级执行所有程序。
    x86 ISA家族支持的多个特权级别的潜在未来用途包括容器化和虚拟机。主机操作系统内核可以使用具有完全特权访问的指令(内核模式),而在虚拟机或容器中的来宾操作系统上运行的应用程序可以在用户模式中使用最低级别的特权。虚拟机和客户操作系统内核本身可以使用中级指令特权来调用和虚拟化内核模式操作,例如从客户操作系统的角度进行系统调用 ↩︎

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值