Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🐧 Linux Kernel Development
    • Chapter 1 - Linux 内核简介
    • Chapter 2 - 从内核出发
    • Chapter 3 - 进程管理
    • Chapter 4 - 进程调度
    • Chapter 5 - 系统调用
    • Chapter 7 - 中断和中断处理
    • Chapter 9 - 内核同步介绍
    • Chapter 10 - 内核同步方法
    • Chapter 11 - 定时器和时间管理
    • Chapter 13 - 虚拟文件系统
    • Chapter 14 - 块 I/O 层
    • Chapter 16 - 页高速缓存和页回写

Chapter 5 - 系统调用

Created by : Mr Dk.

2019 / 10 / 18 08:56

Nanjing, Jiangsu, China


5.1 与内核通信

在 用户空间进程 和 硬件设备 之间添加了一个中间层,为用户空间提供了硬件的抽象接口,保证了系统的稳定性和安全。是用户空间访问内核的唯一合法入口。

5.2 API、POSIX 和 C 库

应用程序通过在用户空间实现的 API 而不是直接通过系统调用来编程:

  • API 实际上并不需要和内核提供的系统调用对应
  • API 可以在各种 OS 上实现,给应用程序提供完全相同的接口

在 Unix 世界中,最流行的 API 是基于 POSIX 标准的,Linux 尽力与 POSIX 和 SUSv3 兼容。Linux 的系统调用像大多数 Unix 系统一样,作为 C 库的一部分提供。

  • Mechanism, not policy. (提供机制而不是策略) 是 Unix 文化贯穿的一条设计主线
  • What capabilities are to be provided (the mechanism)
  • How those capabilities can be used (the policy)

Unix 的系统调用仅抽象用于完成某种确定目的的函数,函数怎么用完全不需要内核关心。

5.3 系统调用

访问系统调用,通常使用 C 库中定义的函数来进行。

  • 可能需要 0/1/2 甚至几个参数
  • 可能产生一些副作用 (使系统的状态发生改变)
  • 通过一个 long 类型的返回值来表示成功或者错误
SYSCALL_DEFINE0(getpid)
{
    return task_tgid_vnr(current);
}

SYSCALL_DEFINE0 是一个宏,定义了无参数的系统调用。宏展开后:

asmlinkage long sys_getpid(void)
  • asmlinkage 是一个编译执行,通知编译器仅从栈中提取函数参数
  • sys_xxx() 是 Linux 中所有系统调用都应该遵守的命名规则

5.3.1 系统调用号

Linux 中每个系统调用都被赋予了一个系统调用号。系统调用号很重要,一旦分配就不能再有变更,不然编译好的程序就会崩溃。系统调用被删除后,调用号也不能被回收,不然编译好的程序可能会调用另一个系统调用。Linux 中有一个 sys_ni_syscall(),除了返回 -ENOSYS 不做任何其它工作。当系统调用被删除,这个函数负责填补空缺。内核在 sys_call_table 中记录了所有已注册的系统调用。

5.3.2 系统调用的性能

Linux 系统调用比许多其它 OS 执行得快,较短的上下文切换时间是一个重要原因:进出内核都被优化得简洁高效。

5.4 系统调用处理程序

用户空间程序无法直接执行内核代码

  • 不能直接调用内核空间中的函数
  • 内核驻留在受保护的地址空间上
  • 如果进程可以直接读写内核,系统的安全性将不复存在

应用程序应该以某种方式通知系统,促使系统切换到内核态去执行异常处理程序。通知内核是靠软中断实现的:

  • 引发一个异常,促使 CPU 进入内核态
  • 此时异常处理程序实际上就是系统调用处理程序

x86 系统上系统调用的中断号为 128,通过 INT $0x80 触发中断,导致系统切换到内核态,并执行第 128 号异常处理程序。处理程序名为 system_call(),与硬件体系结构紧密相关。最近,x86 处理器增加了一条 sysenter 指令,提供更快、更专业的陷入内核执行系统调用的方式。

5.4.1 指定恰当的系统调用

仅陷入内核空间是不够的,需要把系统调用号一并传给内核。在 x86 中,通过 eax 进行传递:在陷入内核空间前,用户空间把相应系统调用的调用号放入 eax 中。system_call() 函数通过将给定的调用号与 NR_syscalls 作比较,检查有效性。由于系统调用表中的表项以 8B 存放,因此:

call *sys_call_table(, %rax, 8)

5.4.2 参数传递

在 x86-32 系统上,ebx、ecx、edx、esi、edi 按顺序存放前五个参数。需要 6 个或以上参数时,应当指定一个寄存器,存放指向所有参数的用户空间地址。给用户空间的返回值也通过寄存器传递(x86 eax)。

5.5 系统调用的实现

5.5.1 实现系统调用

确定每个系统调用的用途。Linux 不提倡多用途的系统调用——ioctl()。很多系统调用提供了 标志参数 以确保向前兼容,目的并不是让单个系统调用有多个不同行为,而是为了增加新的功能和选项。设计接口尽量多为将来做考虑。

5.5.2 参数验证

系统调用必须仔细检查它们所有的参数是否合法有效。内核需要保证用户空间提供的指针:

  • 指向的内存区域属于用户空间
  • 指向的内存区域在进程的地址空间内
  • 不能绕过内存访问限制

使用 copy_from_user() 和 copy_to_user() 来进行数据传递

5.6 系统调用上下文

内核在执行系统调用的时候处于 进程上下文:

  • 内核可以休眠
  • 内核可以被抢占

需要保证系统调用是可重入的。系统调用返回时,控制权仍在 system_call(),由该函数负责切换到用户空间。

5.6.1 绑定一个系统调用的最后步骤

  1. 在系统调用表的最后加入一项
  2. 对于所支持的各种体系结构,将系统调用号进行定义
  3. 系统调用被实现,并被编译进内核映像 (不能被编译为模块)

5.6.2 从用户空间访问系统调用

Linux 提供了一组宏,用于直接对系统调用访问——_syscalln() - n 从 0 到 6。对于每个宏,都有 2 + 2 × n 个参数:

  • 系统调用的返回值类型
  • 系统调用名称
  • n 个参数的类型和名称

宏会被展开为内嵌汇编的 C 函数。

5.6.3 为什么不通过系统调用的方式实现

建立新的系统调用非常容易,但绝不提倡这么做。

Edit this page on GitHub
Prev
Chapter 4 - 进程调度
Next
Chapter 7 - 中断和中断处理