系统调用机制

系统调用(System Call)是用户空间程序访问内核服务的唯一合法途径。本章深入探讨系统调用的实现机制和使用方法。

系统调用原理

用户空间与内核空间切换

用户空间 (Ring 3)
    ↓
用户程序调用库函数
    ↓
库函数封装系统调用
    ↓
执行 syscall/int 0x80 指令
    ↓
CPU 切换到内核模式 (Ring 0)
    ↓
内核空间
    ↓
系统调用分发器
    ↓
执行对应的内核函数
    ↓
返回结果
    ↓
切换回用户模式
    ↓
用户程序继续执行

x86-64 系统调用实现

调用约定

// 系统调用号存放在 rax 寄存器
// 参数依次存放在:rdi, rsi, rdx, r10, r8, r9
// 返回值存放在 rax

// 示例:sys_write(fd, buf, count)
mov rax, 1        ; 系统调用号 1 (write)
mov rdi, 1        ; 参数1: fd = 1 (stdout)
mov rsi, buffer   ; 参数2: buf 地址
mov rdx, length   ; 参数3: count
syscall           ; 执行系统调用

内核入口点

// arch/x86/entry/entry_64.S
ENTRY(entry_SYSCALL_64)
    // 保存用户态寄存器
    swapgs
    movq %rsp, PER_CPU_VAR(rsp_scratch)
    movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
    
    // 保存所有寄存器到栈
    pushq %rax
    pushq %rdi
    pushq %rsi
    // ...
    
    // 调用 C 函数
    call do_syscall_64
    
    // 恢复寄存器
    popq %rsi
    popq %rdi
    popq %rax
    // ...
    
    // 返回用户空间
    swapgs
    sysretq
END(entry_SYSCALL_64)

系统调用表

// arch/x86/entry/syscalls/syscall_64.tbl
0   common  read            sys_read
1   common  write           sys_write
2   common  open            sys_open
3   common  close           sys_close
4   common  stat            sys_newstat
5   common  fstat           sys_newfstat
...

查看系统调用号

# 查看系统调用定义
cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep __NR

# 或使用 ausyscall
ausyscall --dump

常用系统调用

文件操作

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    // open - 打开文件
    int fd = open("/tmp/test.txt", O_RDWR | O_CREAT, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    // write - 写入数据
    const char *data = "Hello, Linux!\n";
    ssize_t written = write(fd, data, 14);
    printf("Written %zd bytes\n", written);
    
    // lseek - 移动文件指针
    off_t pos = lseek(fd, 0, SEEK_SET);
    
    // read - 读取数据
    char buffer[100];
    ssize_t nread = read(fd, buffer, sizeof(buffer));
    buffer[nread] = '\0';
    printf("Read: %s", buffer);
    
    // close - 关闭文件
    close(fd);
    
    return 0;
}

进程管理

#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>

int main() {
    pid_t pid = fork();  // 创建子进程
    
    if (pid == -1) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("Child PID: %d\n", getpid());
        
        // execve - 执行新程序
        char *args[] = {"/bin/ls", "-l", NULL};
        execve("/bin/ls", args, NULL);
        
        // 如果 execve 失败才会执行到这里
        perror("execve");
        return 1;
    } else {
        // 父进程
        printf("Parent PID: %d, Child PID: %d\n", getpid(), pid);
        
        // wait - 等待子进程结束
        int status;
        waitpid(pid, &status, 0);
        
        if (WIFEXITED(status)) {
            printf("Child exited with status %d\n", WEXITSTATUS(status));
        }
    }
    
    return 0;
}

内存管理

#include <sys/mman.h>
#include <stdio.h>
#include <string.h>

int main() {
    // mmap - 内存映射
    size_t size = 4096;
    void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
                     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    
    if (addr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }
    
    // 使用映射的内存
    strcpy((char *)addr, "Memory mapped data");
    printf("Data: %s\n", (char *)addr);
    
    // munmap - 解除映射
    munmap(addr, size);
    
    return 0;
}

直接系统调用

使用 syscall() 函数

#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>

int main() {
    // 方式1:使用库函数
    pid_t pid1 = getpid();
    
    // 方式2:直接系统调用
    pid_t pid2 = syscall(SYS_getpid);
    
    printf("PID (library): %d\n", pid1);
    printf("PID (syscall): %d\n", pid2);
    
    // 自定义系统调用
    long ret = syscall(SYS_write, 1, "Hello\n", 6);
    
    return 0;
}

内联汇编调用

#include <stdio.h>

ssize_t my_write(int fd, const void *buf, size_t count) {
    ssize_t ret;
    
    __asm__ volatile (
        "syscall"
        : "=a" (ret)                           // 输出:rax
        : "0" (1),                             // rax = 1 (sys_write)
          "D" (fd),                            // rdi = fd
          "S" (buf),                           // rsi = buf
          "d" (count)                          // rdx = count
        : "rcx", "r11", "memory"               // 破坏的寄存器
    );
    
    return ret;
}

int main() {
    my_write(1, "Direct syscall!\n", 16);
    return 0;
}

系统调用性能

性能开销

开销来源

  1. 上下文切换(用户态 → 内核态)
  2. 寄存器保存和恢复
  3. 参数验证和复制
  4. TLB 刷新

测量性能

#include <time.h>
#include <unistd.h>
#include <stdio.h>

#define ITERATIONS 1000000

int main() {
    struct timespec start, end;
    
    // 测量 getpid() 性能
    clock_gettime(CLOCK_MONOTONIC, &start);
    for (int i = 0; i < ITERATIONS; i++) {
        getpid();
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    long ns = (end.tv_sec - start.tv_sec) * 1000000000L +
              (end.tv_nsec - start.tv_nsec);
    
    printf("Average syscall time: %ld ns\n", ns / ITERATIONS);
    
    return 0;
}

vDSO(Virtual Dynamic Shared Object)

优化机制

  • 某些系统调用在用户空间执行
  • 避免内核态切换
  • 提高性能

vDSO 提供的函数

// 无需陷入内核的系统调用
gettimeofday()
time()
clock_gettime()
getcpu()

查看 vDSO

# 查看进程的 vDSO 映射
cat /proc/self/maps | grep vdso

# 示例输出
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0  [vdso]

跟踪系统调用

strace 工具

# 跟踪程序的所有系统调用
strace ls

# 只显示特定系统调用
strace -e open,read,write ls

# 统计系统调用
strace -c ls

# 跟踪正在运行的进程
strace -p <PID>

# 跟踪子进程
strace -f ./program

# 保存到文件
strace -o trace.log ls

输出示例

execve("/bin/ls", ["ls"], 0x7ffd...) = 0
brk(NULL)                               = 0x55f0...
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=83090, ...}) = 0

ltrace 工具

# 跟踪库函数调用
ltrace ls

# 只跟踪特定函数
ltrace -e malloc,free ls

实践:自定义系统调用监控

// syscall_monitor.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>

// 保存原始 write 函数指针
static ssize_t (*original_write)(int, const void *, size_t) = NULL;

// 初始化函数
__attribute__((constructor))
static void init(void) {
    original_write = dlsym(RTLD_NEXT, "write");
}

// 拦截 write 系统调用
ssize_t write(int fd, const void *buf, size_t count) {
    fprintf(stderr, "[MONITOR] write(%d, %p, %zu)\n", fd, buf, count);
    return original_write(fd, buf, count);
}

编译和使用

# 编译为共享库
gcc -shared -fPIC syscall_monitor.c -o libmonitor.so -ldl

# 使用 LD_PRELOAD 加载
LD_PRELOAD=./libmonitor.so ls

系统调用安全

seccomp(Secure Computing Mode)

限制系统调用

#include <seccomp.h>
#include <stdio.h>

int main() {
    // 创建过滤器
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
    
    // 允许特定系统调用
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    
    // 加载过滤器
    seccomp_load(ctx);
    
    // 现在只能使用允许的系统调用
    printf("This will work\n");
    
    // 尝试使用被禁止的系统调用(会被杀死)
    // getpid();  // 这会导致进程终止
    
    return 0;
}

编译

gcc -o seccomp_demo seccomp_demo.c -lseccomp

下一步:学习 内核空间与用户空间 章节。