linxu-kernel-syscall
参考文章
系统调用的基本概念
在linux中,操作系统负责硬件资源的封装,任务的创建、调度、读写磁盘
- μc/os中,采用OSinit-OSTaskCreate-创建一个任务
- Linux中,拥有权限管理来保证安全,划分内核态和用户态。通过系统调用,让APP某些运行陷入到内核态,以此来访问硬件设备
软中断
权限管理
- 程序的用户态、内核态
- 操作系统+ CPU软中断:swi/svc
- CPU的运行级别:普通权限(普通运行)、特权(陷入到内核态)
- ARM32:
- 普通模式:User
- 特权模式:FIQ、IRQ、SVC、ABT、UND
- ARM64:EL0、EL1、EL2、EL3
- X86:ring0 ~ ring
- ARM32:
系统调用号
- ARM:swi、svc
- ARM : swi、svc
- 系统调用接口:read、write、printf
- 内核中的实现:sys_read、sys_write
- 系统调用号:
- 32位ARM:3、4
- 64位ARM:0、1
数据传递
- 软中断指令:– X86 int 80H – ARM swisvc
- 用户函数的参数传递
- ARM:R0、R1、R2、R3、R4、R5、R6
- ARM64:X1、X2、X3、X4、X5
- 系统调用号– ARM:R7 – ARM64:X8
- 内核函数的返回值– ARM:R0– ARM64:X
Arch/ABI Instruction System Ret Ret Error Notes |
ARM汇编系统调用:.text
.global _start
_start:
mov r0, #1 /* stdout*/
add r1, pc, #16 /* address of the string*/
mov r2, #12 /* string length*/
mov r7, #4 /*syscall for 'write'*/
swi #0 /* software interrupt*/ 软中断调用sys_write来实现字符串的打印
_exit:
mov r7, #1 /* syscall for 'exit'*/
swi #0 /* software interrupt*/
_string:
.asciz "Hello world\n" @ our string, NULL terminated
系统调用接口的封装
写这些汇编非常麻烦,好在:
- C标准库包含一系列系统调用接口的封装– read、write、fork、open…
syscall系统调用接口的封装
对于在C标准库中没有封装的系统调用
- syscall是一个库函数:
long syscall(long number, ...);,如果想使用syscall来实现write系统调用,直接syscall(1, 1, "helloworld\n", 12)就行 - 封装了系统调用的汇编接口– 系统调用前保存CPU寄存器– 从系统调用返回后,恢复寄存器
syscall的汇编实现:000dad70 <syscall@@GLIBC_2.4>:
dad70: e1a0c00d mov ip, sp
dad74: e92d00f0 push {r4, r5, r6, r7}
dad78: e1a07000 mov r7, r0
dad7c: e1a00001 mov r0, r1
dad80: e1a01002 mov r1, r2
dad84: e1a02003 mov r2, r3
dad88: e89c0078 ldm ip, {r3, r4, r5, r6}
dad8c: ef000000 svc 0x00000000
dad90: e8bd00f0 pop {r4, r5, r6, r7}
dad94: e3700a01 cmn r0, #4096 ; 0x1000
dad98: 312fff1e bxcc lr
dad9c: eafcf2c3 b 178b0 <__libc_start_main@@GLIBC_2.4+0x278>
系统调用流程分析
以kill这个系统调用来分析:
- 接口封装: /usr/arm-linux-gnueabi/lib/libc.a
- 系统调用号: arch/arm/include/generated/calls-eabi.S 这里定义了一个系统调用表,将系统调用号和指针对应起来,用户使用系统调用就能根据此表找到函数指针,从而跳转过去运行
- 内核实现: kernel/signal.c 不同系统调用实现分布在不同的内核部分
- 中断处理: arch/arm/kernel/entry-common.S 软中断
实现过程就是根据系统调用号,从系统调用表中找到对应的入口函数指针,跳转执行,中间包含各种软中断的管理操作
系统调用号:
arch/arm/include/generated/uapi/asm/unistd-common.h |
x系统调用实现:
kernel/signal.c : |
系统调用函数实现:
include/linux/syscalls.h |
获取系统调用号
arch//kernel/-.S : 保护现场,获取系统调用号 |
系统调用表
arch///generated/calls-eabi.S : |
添加一个系统调用
增加内核对应的实现函数
diff --git a/arch/arm/kernel/signal.c b/arch/arm/kernel/signal.c |
在系统调用表中增加一个系统调用号及入口
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h |
系统调用的开销
主要的开销
- 中断,软中断也是中断,变态是有代价的,每次切换需刷新 CPU 流水线、TLB 和缓存,现代 CPU 需约 100-1000 时钟周期。
- 上下文保存与恢复,CPU 需保存用户态寄存器状态(如 PC、SP、EFLAGS)到内核栈,返回时再恢复。容易造成额外的内存访问
- 抢占系统、任务调度
- 同步,内核全局资源(如文件系统)可能需加锁,引发争用。
- IO等待,频繁拷贝(尤其是大块数据)会显著降低性能。
解决思路
快速系统调用指令:
x86 的 syscall(比 int 0x80 快 2-3 倍)。虚拟系统调用:
- vdso (Virtual Dynamic Shared Object):
将部分调用(如 gettimeofday())映射到用户态,无需切换。 - vsyscall(Virtual System Call)
内核将部分系统调用的代码映射到固定的用户空间地址(如 0xffffffffff600000)。用户程序直接跳转到该地址执行,无需切换特权级。
- vdso (Virtual Dynamic Shared Object):
vsyscall
$ cat /proc/self/maps |
可以看到程序段映射中有vsyscall段,这里保存了一些固定的系统调用。
尽管 vsyscall 机制已被弃用,但 Linux 内核仍然在内存映射中保留
ffffffffff600000-ffffffffff601000 这个区域(标记为 [vsyscall])
VDSO
3. 对比 vsyscall 和 vDSO
| 特性 | vsyscall |
vDSO |
|---|---|---|
| 地址分配 | 固定 (0xffffffffff600000) |
动态加载(ASLR 支持) |
| 安全性 | 低(固定地址易受攻击) | 高(随机化地址) |
| 内核支持 | 旧版机制,已废弃 | 现代默认机制 |
| 性能 | 模拟执行(较慢) | 直接用户态执行(最快) |
| 调用方式 | 硬编码地址 | 通过 glibc 或 dlopen 调用 |
在内存映射中,可以看道vdso这个段,这个地址是随机的
源码在内核中实现
- arch/arm/kernel/vdso.c
- 关键函数:vdso_mremap、install_vvar
- 速度最快
- 开销最小,基本等价于函数调用开销
一切皆文件的哲学
“一切皆文件”是 Linux 对系统资源的高度抽象,通过文件接口屏蔽底层差异,提供了简洁、一致的操作方式。、
这种设计降低了系统复杂性,使得工具、脚本和应用程序能够以统一模式处理多样化资源,是 Linux 强大灵活性的重要基石。
简单来说,在Linux操作系统中,所有的资源(包括普通文件(文本、二进制文件等)、目录、设备(如磁盘、键盘)、进程信息、网络套接字、管道等)都被抽象为了文件。
在用户层面上,我们可以通过对对应的文件进行操作,进而完成对这些资源的操作。
这样做最明显的好处是,开发者仅需要使用一套 API 和开发工具,即可调取 Linux 系统中绝大部分的资源。
举个简单的例子,Linux 中几乎所有读(读文件,读系统状态,读PIPE)的操作都可以用read 函数来进行;几乎所有更改(更改文件,更改系统参数,写 PIPE)的操作都可以用 write 函数来进行。
每一种设备都有用于描述自身的读写方法与属性等(在对应的数据结构中),将这些方法的地址赋值给对应的函数,将属性抽象成文件的内容,就可以用访问文件的方式来访问这些资源。
虽然在访问这些设备时所调用的函数都是文件的 read 和 write 等,但实际上调用的却是对应设备的读写函数。
按照面向对象语言的视角来理解就是:struct file 是一个抽象类,而各种设备继承自 struct file 并各自实现了读写等方法。在较高的层次就可以将这些设备都看作文件来处理。

硬件识别机制
在Linux当中的/dev目录下可以看到存在许多文件;$ ls /dev
AliSecGuard initctl
autofs input
block kmsg
btrfs-control log
bus loop-control
char mapper
console mcelog
而这些文件被称为设备文件;
同时这些设备文件代表着系统当中的各种硬件设备,它们将为用户的程序提供一个接口;
用户可以通过这些口从而间接的调用硬件;
驱动程序等后续会说到
- 驱动程序负责管理和控制硬件,而驱动程序本身也是被OS进行管理的;
- 设备文件本身也是为用户提供一个与用户与驱动交互的接口;
- 而驱动程序则为为设备文件提供的一个与硬件交互的一个接口;
- 这些抽象的接口本身对于OS来说是不知情的;
- OS只知道在调用对应的硬件时只需要去调用对应的设备文件即可;这样就把硬件设备抽象成执行文件了







