一行代码的千里之行
当你写下 write(1, "Hello, World!\n", 14) 这行代码时,你实际上正在发起一场跨越两个世界的旅行。这行代码将穿越CPU的特权级边界,从你熟悉的用户空间进入神秘的内核空间,完成一次完整的系统调用。
系统调用是操作系统最基础的概念之一,但它的实现细节却鲜为人知。为什么一个简单的函数调用需要几百纳秒?为什么KPTI补丁能让某些工作负载性能下降800%?vDSO是如何做到"不进入内核的系统调用"的?这篇文章将带你深入理解系统调用从用户态到内核态的完整旅程。
特权级的物理边界
理解系统调用,首先要理解现代CPU的特权级机制。x86架构定义了四个特权级别(Ring 0-3),但Linux只使用其中的两个:Ring 0用于内核,Ring 3用于用户程序。
这种分离不是软件层面的抽象,而是硬件强制执行的物理边界。当Ring 3的代码试图执行特权指令(如修改页表、访问I/O端口)时,CPU会触发异常。系统调用正是这种边界跨越的合法通道。
硬件层面的特权级检查发生在多个地方:
- 页表项的U/S位:标识页面属于用户还是内核
- 段描述符的DPL字段:定义访问该段所需的最低特权级
- I/O权限位图:控制对特定I/O端口的访问权限
当系统调用发生时,CPU必须完成从Ring 3到Ring 0的转换。这个转换过程涉及栈指针切换、页表切换、以及最重要的——代码执行权限的提升。
系统调用的历史演进
系统调用的实现方式经历了三次重大演进,每一次都带来了显著的性能提升。
int 0x80:软件中断时代
最早的Linux系统调用通过软件中断实现。用户程序执行 int 0x80 指令,触发128号中断,CPU通过中断描述符表(IDT)找到内核入口点。
; 32位系统调用示例
mov eax, 4 ; sys_write的系统调用号
mov ebx, 1 ; 文件描述符 stdout
mov ecx, msg ; 缓冲区地址
mov edx, 14 ; 长度
int 0x80 ; 触发系统调用
这种方式的开销主要来自中断处理:CPU需要保存完整的上下文、查询IDT、执行权限检查。在早期处理器上,一次 int 0x80 系统调用大约需要1000-1500个时钟周期。
sysenter/sysexit:快速系统调用
Intel在Pentium Pro处理器中引入了 sysenter 和 sysexit 指令,专门用于加速系统调用。这些指令绕过了中断处理的大部分开销,直接跳转到预定义的内核入口点。
sysenter 需要内核预先设置三个MSR寄存器:
- IA32_SYSENTER_CS:内核代码段选择子
- IA32_SYSENTER_ESP:内核栈指针
- IA32_SYSENTER_EIP:内核入口点地址
syscall/sysret:现代标准
AMD在x86-64架构中引入了 syscall 和 sysret 指令,这是目前Linux x86_64系统的标准系统调用方式。syscall 指令的设计比 sysenter 更加简洁高效:
- 不需要显式加载栈指针
- 自动保存返回地址到RCX寄存器
- 自动保存RFLAGS到R11寄存器
- 直接从MSR寄存器加载目标地址
一条 syscall 指令只需要几十个时钟周期就能完成特权级转换,相比 int 0x80 有了数量级的提升。
SYSCALL指令的工作原理
syscall 指令的执行过程可以用以下伪代码描述:
RCX ← RIP ; 保存返回地址
R11 ← RFLAGS ; 保存标志寄存器
RIP ← IA32_LSTAR ; 从MSR加载内核入口点
; 从IA32_STAR加载段选择子
CS ← IA32_STAR[47:32]
SS ← IA32_STAR[47:32] + 8
; 清除IA32_FMASK中指定的标志位
RFLAGS ← RFLAGS AND (NOT IA32_FMASK)
这里有几个关键点值得注意:
首先,syscall 不保存栈指针。内核必须通过其他方式获取内核栈地址,Linux使用 swapgs 指令配合per-CPU变量来实现。
其次,段寄存器的加载有特殊规则。syscall 不从GDT/LDT加载描述符缓存,而是使用固定值。这意味着内核必须确保GDT中对应的描述符与这些固定值一致。
第三,RFLAGS的修改。syscall 会自动清除某些标志位(如中断标志IF),这是通过 IA32_FMASK MSR控制的。
MSR寄存器的完整配置
Linux内核在初始化时配置以下MSR寄存器来支持系统调用:
// arch/x86/kernel/cpu/common.c
void syscall_init(void)
{
// MSR_STAR: 存储内核和用户的CS选择子
// [63:48] = 用户CS (用于sysret)
// [47:32] = 内核CS (用于syscall)
wrmsrl(MSR_STAR, ((u64)__USER32_CS)<<48 | ((u64)__KERNEL_CS)<<32);
// MSR_LSTAR: 系统调用入口点
wrmsrl(MSR_LSTAR, entry_SYSCALL_64);
// MSR_CSTAR: 兼容模式系统调用入口
wrmsrl(MSR_CSTAR, entry_SYSCALL_compat);
// MSR_SYSCALL_MASK: 需要清除的标志位
wrmsrl(MSR_SYSCALL_MASK,
X86_EFLAGS_TF|X86_EFLAGS_DF|X86_EFLAGS_IF|
X86_EFLAGS_IOPL|X86_EFLAGS_AC|X86_EFLAGS_NT);
}
MSR_LSTAR 是最关键的寄存器,它存储了 entry_SYSCALL_64 的地址。每当用户程序执行 syscall 指令,CPU就会跳转到这个地址开始执行内核代码。
内核入口点:entry_SYSCALL_64
entry_SYSCALL_64 是x86_64 Linux内核处理系统调用的汇编入口点,定义在 arch/x86/entry/entry_64.S。它的核心工作流程如下:
flowchart TD
A[用户执行syscall指令] --> B[CPU跳转到entry_SYSCALL_64]
B --> C[swapgs: 切换GS基址]
C --> D[保存用户栈指针到rsp_scratch]
D --> E[加载内核栈指针]
E --> F[保存用户态上下文到栈]
F --> G{检查系统调用号是否合法}
G -->|合法| H[从sys_call_table查找处理函数]
G -->|非法| I[返回-ENOSYS]
H --> J[调用系统调用处理函数]
J --> K[保存返回值到栈]
K --> L[恢复用户态寄存器]
L --> M[sysretq返回用户态]
I --> L
第一阶段:切换栈和保存上下文
entry_SYSCALL_64:
swapgs ; 交换GS基址
movq %rsp, PER_CPU_VAR(rsp_scratch) ; 保存用户栈
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp ; 加载内核栈
/* 构造pt_regs结构 */
pushq $__USER_DS ; SS
pushq PER_CPU_VAR(rsp_scratch) ; RSP (用户栈)
pushq %r11 ; RFLAGS (保存在r11中)
pushq $__USER_CS ; CS
pushq %rcx ; RIP (返回地址在rcx中)
/* 保存参数寄存器 */
pushq %rax ; 系统调用号
pushq %rdi ; 参数1
pushq %rsi ; 参数2
pushq %rdx ; 参数3
pushq %r12 ; 保存被调用者保存寄存器
pushq %r8 ; 参数5
pushq %r9 ; 参数6
pushq %r10 ; 参数4 (注意顺序)
pushq %r11 ; 已保存,但为对齐再压一次
swapgs 指令在这里扮演关键角色。在用户态,GS寄存器指向用户空间的TLS(线程局部存储);swapgs 将其与内核空间的per-CPU数据区指针交换。这样内核就能通过 PER_CPU_VAR 宏访问当前CPU的专用数据。
第二阶段:调用系统调用处理函数
/* 检查系统调用号 */
cmpq $__NR_syscall_max, %rax
ja badsys ; 超出范围则跳转
/* 第四个参数从r10移到rcx */
movq %r10, %rcx
/* 调用系统调用处理函数 */
call *sys_call_table(, %rax, 8)
注意这里有一个微妙的细节:系统调用的参数传递使用RDI、RSI、RDX、R10、R8、R9,而不是标准C调用约定的RDI、RSI、RDX、RCX、R8、R9。这是因为 syscall 指令会覆盖RCX(用于保存返回地址)。内核在调用实际的C函数之前,必须把R10的内容移动到RCX。
第三阶段:返回用户空间
/* 保存返回值 */
movq %rax, RAX(%rsp)
/* 恢复寄存器 */
RESTORE_C_REGS_EXCEPT_RCX_R11
/* 恢复RCX和R11 */
movq RIP(%rsp), %rcx ; 恢复返回地址
movq EFLAGS(%rsp), %r11 ; 恢复标志寄存器
movq RSP(%rsp), %rsp ; 恢复用户栈
swapgs ; 切换回用户GS
sysretq ; 返回用户态
sysretq 指令是 syscall 的逆操作:它从RCX恢复RIP,从R11恢复RFLAGS,然后切换回Ring 3继续执行用户代码。
系统调用表与参数传递
系统调用表是内核维护的一个函数指针数组,每个系统调用号对应一个处理函数:
// arch/x86/entry/syscall_64.c
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,
[2] = sys_open,
[3] = sys_close,
// ...
};
系统调用处理函数通过 SYSCALL_DEFINE 宏定义:
// fs/read_write.c
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
size_t, count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos = file_pos_read(f.file);
ret = vfs_write(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
fdput_pos(f);
}
return ret;
}
这个宏展开后会产生多个函数,其中最重要的是安全处理CVE-2009-0029漏洞的包装函数。注意到第二个参数的类型是 const char __user *,这个 __user 注解告诉sparse工具这是一个用户空间指针,需要特殊处理。
安全边界:copy_from_user和copy_to_user
内核不能直接解引用用户空间指针。用户程序可能传递一个无效地址、已释放的地址、甚至是内核地址。直接访问可能导致:
- 内核崩溃:访问无效地址触发内核Oops
- 安全漏洞:用户程序通过精心构造的地址访问内核内存
Linux提供了 copy_from_user 和 copy_to_user 函数来安全地跨边界复制数据:
// 安全的用户空间内存访问
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
这些函数的工作原理:
flowchart TD
A[copy_from_user调用] --> B{access_ok检查}
B -->|地址合法| C[开启页面异常处理]
B -->|地址非法| D[返回未复制字节数]
C --> E[__copy_from_user复制]
E --> F{复制成功?}
F -->|成功| G[返回0]
F -->|页面异常| H[异常处理程序介入]
H --> I[返回未复制字节数]
access_ok 宏检查地址是否在用户空间范围内:
#define access_ok(addr, size) \
(__addr_ok(addr) && __addr_ok((addr) + (size)))
但真正的安全来自于异常处理机制。copy_from_user 使用 __get_user_asm 等宏,这些宏会在访问用户内存时设置异常表项。如果发生页面异常,内核会查找异常表,如果找到匹配项就返回错误值而不是崩溃。
返回值与错误处理
系统调用的错误处理有一个统一约定:
- 成功:返回非负值(通常是结果或0)
- 失败:返回负的错误码
在内核内部,系统调用返回 -EINVAL、-ENOMEM 等负值。glibc包装器会将其转换为POSIX风格的返回:
// glibc包装器的典型实现
ssize_t write(int fd, const void *buf, size_t count) {
ssize_t result = syscall(SYS_write, fd, buf, count);
if (result < 0) {
errno = -result; // 错误码取反存入errno
return -1; // 返回-1表示错误
}
return result;
}
这就是为什么用户程序检查 if (write(...) < 0) 然后查看 errno 来确定具体错误。
性能代价:一次跨越的代价
系统调用不是免费的。让我们看看具体的性能数据:
| 操作类型 | 延迟(近似) | 相对开销 |
|---|---|---|
| 普通函数调用 | ~5.5 ns | 1x |
| 系统调用(无vDSO) | ~250 ns | ~45x |
| vDSO gettimeofday | ~8 ns | ~1.5x |
这些数字来自实际的微基准测试。在现代CPU上,一次系统调用的开销大约是普通函数调用的40-50倍。
开销主要来自:
- 特权级切换:CPU流水线刷新、TLB可能的更新
- 上下文保存/恢复:保存用户寄存器、加载内核栈
- 内存屏障:确保内存操作的顺序一致性
- 安全检查:参数验证、权限检查
但这只是"直接开销"。间接开销可能更加显著。
KPTI:安全漏洞的代价
2018年初,Meltdown漏洞的披露彻底改变了系统调用的性能格局。KPTI(Kernel Page Table Isolation)补丁将内核页表与用户页表完全分离,导致每次系统调用都需要切换页表。
Brendan Gregg的测试结果揭示了惊人的性能影响:
xychart-beta
title "KPTI对系统调用性能的影响"
x-axis ["无KPTI", "KPTI(无PCID)", "KPTI+PCID", "KPTI+PCID+THP"]
y-axis "相对性能损失" 0-->20
bar [0, 8.7, 0.5, -3]
测试条件:100MB工作集,5k syscalls/sec/CPU
关键发现:
- 最坏情况:KPTI可以导致超过800%的性能下降
- PCID帮助很大:进程上下文标识符(PCID)允许在不刷新TLB的情况下切换页表
- Huge Pages更佳:使用大页面减少TLB压力,甚至可能获得性能提升
这是因为系统调用导致的TLB刷新会清除所有的地址转换缓存。如果工作集较大,重建这些缓存需要大量内存访问。
vDSO:跨越边界的免费午餐
vDSO(virtual Dynamic Shared Object)是Linux的一个巧妙优化,它将某些"只读"的系统调用实现放在用户空间,完全避免了特权级切换。
vDSO的工作原理:
flowchart LR
A[用户程序调用gettimeofday] --> B{查看vDSO映射}
B --> C[直接读取内核写入的时间数据]
C --> D[在用户态计算并返回]
D --> E[无需syscall指令]
内核将一个只读内存页映射到每个进程的地址空间,这个页面包含:
- 时间数据(由内核周期性更新)
- 快速版本的系统调用实现
$ cat /proc/self/maps
7fffcbcb7000-7fffcbcb8000 r-xp 00000000 00:00 0 [vdso]
目前vDSO支持的系统调用包括:
gettimeofdaytimeclock_gettimeclock_getresgetcpu
对于这些调用,glibc会自动使用vDSO版本。性能测试显示,vDSO版本的 gettimeofday 延迟仅为8纳秒,接近普通函数调用。
vDSO vs vsyscall
vDSO的前身是vsyscall,它有一个致命的安全缺陷:固定地址。vsyscall总是映射在 0xffffffffff600000,这使得攻击者可以利用它作为跳板执行任意系统调用。
// vsyscall的固定地址(已废弃)
#define VSYSCALL_ADDR 0xffffffffff600000
vDSO采用地址随机化(ASLR),每次加载地址都不同,从根本上解决了这个问题。现代Linux内核甚至可以用陷阱指令替换vsyscall的内容,让老程序继续工作但强制进入内核处理。
io_uring:重新思考系统调用
io_uring是Linux 5.1引入的异步I/O接口,它采用了一种全新的思路:通过共享内存队列批量提交请求,最小化系统调用次数。
flowchart TD
A[用户程序] --> B[写入提交队列SQE]
B --> C[提交队列 tail指针+1]
C --> D{需要立即处理?}
D -->|是| E[执行syscall提交]
D -->|否| F[延迟批量提交]
E --> G[内核处理SQE]
F --> G
G --> H[写入完成队列CQE]
H --> I[用户程序轮询结果]
io_uring的核心是两个环形缓冲区:
- 提交队列(SQ):用户写入I/O请求
- 完成队列(CQ):内核写入完成结果
使用io_uring,程序可以:
// 提交100个读请求,只需要一次系统调用
for (int i = 0; i < 100; i++) {
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, bufs[i], sizes[i], offsets[i]);
}
io_uring_submit(&ring); // 一次系统调用提交所有请求
// 轮询完成结果,甚至不需要系统调用
for (int i = 0; i < 100; i++) {
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe); // 可选:等待完成
// 处理结果...
}
在高I/O密集的场景下,io_uring可以将系统调用次数减少一个数量级,带来显著的性能提升。
seccomp:过滤系统调用
seccomp(Secure Computing Mode)是Linux的安全机制,允许进程限制自己能执行的系统调用。这在沙箱和容器安全中至关重要。
seccomp有两种模式:
严格模式
只允许4个系统调用:read、write、_exit、sigreturn。其他任何系统调用都会导致进程被杀死。
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
过滤模式
使用BPF(Berkeley Packet Filter)程序定义复杂的过滤规则:
struct sock_filter filter[] = {
/* 只允许read, write, exit */
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_read, 0, 1),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_write, 0, 1),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_exit, 0, 1),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
};
Docker和Kubernetes使用seccomp配置文件来限制容器的系统调用:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["read", "write", "exit"],
"action": "SCMP_ACT_ALLOW"
}
]
}
seccomp过滤在系统调用入口时执行,在内核实际处理系统调用之前。这增加了一定的开销,但对于安全性来说是值得的。
监控与调试:strace和ptrace
strace 是调试系统调用的常用工具,它使用 ptrace 系统调用实现:
sequenceDiagram
participant T as 被追踪进程
participant K as 内核
participant S as strace
T->>K: 执行syscall指令
K->>K: 检测到ptrace附加
K->>S: SIGTRAP信号
S->>K: PTRACE_GETREGS读取参数
K->>S: 返回寄存器值
S->>S: 打印系统调用信息
S->>K: PTRACE_SYSCALL继续执行
K->>T: 执行系统调用
T->>K: 系统调用返回
K->>S: SIGTRAP信号
S->>K: PTRACE_GETREGS读取返回值
K->>S: 返回寄存器值
S->>S: 打印返回值
S->>K: PTRACE_SYSCALL继续
ptrace 的工作原理:
- 父进程调用
ptrace(PTRACE_TRACEME)或被strace附加 - 每次系统调用前后,内核都会暂停进程并通知tracer
- Tracer可以读取和修改寄存器、内存
- Tracer可以决定是否允许系统调用继续
这种机制的开销是巨大的——每个系统调用都会触发多次上下文切换。这就是为什么在生产环境不应该使用strace。
/proc文件系统:实时查看
Linux提供了 /proc/[pid]/syscall 来查看进程当前正在执行的系统调用:
$ cat /proc/1/syscall
232 0x4 0x7ffdf82e11b0 0x1f 0xffffffff 0x100 0x7ffdf82e11bf 0x7ffdf82e11a0 0x7f9114681193
第一个数字是系统调用号(232 = epoll_wait),后面是参数寄存器的值。
完整的系统调用流程
让我们把所有内容整合起来,看看一个完整的系统调用旅程:
flowchart TD
subgraph 用户空间
A[应用程序调用write] --> B[glibc包装函数]
B --> C[设置RAX=系统调用号]
C --> D[设置RDI/RSI/RDX/R10/R8/R9=参数]
D --> E[执行syscall指令]
end
subgraph 硬件
E --> F[CPU保存RCX=RIP, R11=RFLAGS]
F --> G[CPU从MSR_LSTAR加载RIP]
G --> H[CPU切换到Ring 0]
end
subgraph 内核空间
H --> I[entry_SYSCALL_64]
I --> J[swapgs切换GS基址]
J --> K[保存用户寄存器到栈]
K --> L[检查系统调用号]
L --> M[从sys_call_table查找函数]
M --> N[调用sys_write]
N --> O[vfs_write处理]
O --> P[返回值存入RAX]
P --> Q[恢复用户寄存器]
Q --> R[swapgs恢复用户GS]
R --> S[sysretq返回]
end
subgraph 硬件返回
S --> T[CPU恢复RIP=RCX, RFLAGS=R11]
T --> U[CPU切换回Ring 3]
end
subgraph 用户空间返回
U --> V[glibc检查RAX]
V --> W{RAX < 0?}
W -->|是| X[设置errno, 返回-1]
W -->|否| Y[直接返回RAX]
end
总结:边界跨越的艺术
系统调用是操作系统设计中最精妙的部分之一。它不仅是一个技术机制,更是用户空间和内核空间、自由与控制、性能与安全之间平衡的体现。
从硬件角度看,系统调用依赖于CPU的特权级机制和专门的指令。从软件角度看,它是内核向用户程序提供服务的统一接口。从安全角度看,它是需要仔细过滤和监控的攻击面。从性能角度看,它是需要尽可能优化的热点路径。
理解系统调用的完整旅程,有助于我们:
- 写出更高效的程序——减少不必要的系统调用,利用vDSO和io_uring
- 更好地调试问题——知道strace能看到什么、不能看到什么
- 更深入地理解操作系统——从抽象概念到具体实现
当你下次写下 write(fd, buf, len) 时,你会知道这行代码背后发生的一切:CPU的特权级跳转、内核栈的切换、参数的安全复制、系统调用表的查找、以及最终的返回。这是一次跨越两个世界的旅程,而这一切,只需要一条指令。
参考文献
-
Intel Corporation. Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 2B. SYSCALL instruction reference.
-
AMD. AMD64 Architecture Programmer’s Manual, Volume 2: System Programming. System call mechanism details.
-
Torvalds, L. Linux kernel source code, arch/x86/entry/entry_64.S. https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64.S
-
Torvalds, L. Linux kernel source code, arch/x86/kernel/cpu/common.c. syscall_init function.
-
Gregg, B. “KPTI/KAISER Meltdown Performance Overhead.” Brendan Gregg’s Blog, February 2018. https://www.brendangregg.com/blog/2018-02-09/kpti-kaiser-meltdown-performance.html
-
“On vsyscalls and the vDSO.” LWN.net, June 2011. https://lwn.net/Articles/446528/
-
0xAX. “Introduction to system calls.” Linux Insides. https://0xax.gitbooks.io/linux-insides/content/SysCall/linux-syscall-1.html
-
0xAX. “How does the Linux kernel handle a system call.” Linux Insides. https://0xax.gitbooks.io/linux-insides/content/SysCall/linux-syscall-2.html
-
“Measurements of system call performance and overhead.” Arkanis Development, January 2017. https://arkanis.de/weblog/2017-01-05-measurements-of-system-call-performance-and-overhead/
-
“io_uring(7) - Linux manual page.” man7.org. https://man7.org/linux/man-pages/man7/io_uring.7.html
-
“Seccomp BPF (SECure COMPuting with filters).” Linux Kernel Documentation. https://www.kernel.org/doc/html/v4.16/userspace-api/seccomp_filter.html
-
“seccomp(2) - Linux manual page.” man7.org. https://man7.org/linux/man-pages/man2/seccomp.2.html
-
“strace(1) - Linux manual page.” man7.org. https://man7.org/linux/man-pages/man1/strace.1.html
-
“User space memory access from the Linux kernel.” IBM Developer. https://developer.ibm.com/articles/l-kernel-memory-access/
-
“The Definitive Guide to Linux System Calls.” Packagecloud Blog. https://blog.packagecloud.io/the-definitive-guide-to-linux-system-calls/
-
“Why do you have to use copy_to_user()/copy_from_user() to access user space from kernel?” Stack Overflow. https://stackoverflow.com/questions/12666493/
-
“Why return a negative errno?” Stack Overflow. https://stackoverflow.com/questions/1848729/
-
“x86 calling conventions.” Wikipedia. https://en.wikipedia.org/wiki/X86_calling_conventions
-
“AMD64 Linux Kernel Conventions.” UCW. https://www.ucw.cz/~hubicka/papers/abi/node33.html
-
“Adding a New System Call.” Linux Kernel Documentation. https://www.kernel.org/doc/html/v4.17/process/adding-syscalls.html
-
“Intercepting and modifying Linux system calls with ptrace.” Phil Eaton’s Blog. https://notes.eatonphil.com/2023-10-01-intercepting-and-modifying-linux-system-calls-with-ptrace.html
-
“Intercepting and Emulating Linux System Calls with Ptrace.” null program. https://nullprogram.com/blog/2018/06/23/
-
“Why you should use io_uring for network I/O.” Red Hat Developer. https://developers.redhat.com/articles/2023/04/12/why-you-should-use-iouring-network-io
-
“Configure Linux system auditing with auditd.” Red Hat. https://www.redhat.com/en/blog/configure-linux-auditing-auditd
-
“Glibc wrappers for (nearly all) Linux system calls.” LWN.net. https://lwn.net/Articles/655028/
-
“Time To Get Rid Of errno.” LWN.net. https://lwn.net/Articles/655134/
-
“Linux System Calls, Error Numbers, and In-Band Signaling.” null program. https://nullprogram.com/blog/2016/09/23/
-
“How much do function calls impact performance?” Software Engineering Stack Exchange. https://softwareengineering.stackexchange.com/questions/318055/
-
“Do system calls in x86_64 linux still generate interrupts?” Server Fault. https://serverfault.com/questions/879875/
-
“The difference between entry_SYSCALL64_slow_path and entry_SYSCALL64_fast_path.” Stack Overflow. https://stackoverflow.com/questions/61840459/
-
“System V Application Binary Interface.” AMD. https://gitlab.com/x86-psABIs/x86-64-ABI
-
“errno(3) - Linux manual page.” man7.org. https://man7.org/linux/man-pages/man3/errno.3.html
-
“audit.rules(7) - Linux manual page.” man7.org. https://man7.org/linux/man-pages/man7/audit.rules.7.html
-
“Understanding Linux Audit.” SUSE Documentation. https://documentation.suse.com/sles/12-SP5/html/SLES-all/cha-audit-comp.html
-
“Seccomp-bpf and Its Performance Impact.” Zatoichi’s Engineering Blog. https://zatoichi-engineer.github.io/2017/11/06/seccomp-bpf.html
-
“Learning About Syscall Filtering With Seccomp.” Ben Burwell. https://www.benburwell.com/posts/learning-about-syscall-filtering-with-seccomp/
-
“Glibc and System Calls Documentation.” https://media.readthedocs.org/pdf/sys/latest/sys.pdf
-
“How to wrap a system call (libc function) in Linux.” Saman Barghi. http://samanbarghi.com/post/2014-09-05-how-to-wrap-a-system-call-libc-function-in-linux/
-
“Measuring syscall overhead in Linux.” Stack Overflow. https://stackoverflow.com/questions/62884891/
-
“io_uring: Linux Performance Boost or Security Headache?” Upwind. https://www.upwind.io/feed/io_uring-linux-performance-boost-or-security-headache
-
“The Secret Weapon for Performance Boosting — Asynchronous Operations in the Kernel.” Medium. https://vicxu.medium.com/the-secret-weapon-for-performance-boosting-asynchronous-operations-in-the-kernel-io-uring-6345c81be936
-
“Linux kernel entry_64.txt documentation.” https://www.kernel.org/doc/Documentation/x86/entry_64.txt