系统调用与系统启动

系统调用是用户程序进入操作系统内核的唯一合法通道:创建进程、读写文件、申请内存、收发网络包,都必须请内核代办。理解系统调用,才能回答"printf 为什么能打印、系统调用为什么慢、Linux 是怎么启动的"这类高频问题。

用户态与内核态

CPU 用特权级隔离代码:用户态不能直接操作硬件与关键数据结构,内核态才可以。二者通过系统调用切换:

用户程序(用户态) → syscall 指令陷入内核(切换特权级) → 内核处理 → 返回用户态

这种隔离带来安全:普通程序无法越权读写其他进程的内存,也无法直接操控磁盘,所有危险操作都要经过内核检查。代价是每次陷入都要保存/恢复现场,因此系统调用比普通函数调用慢一个数量级。

常见系统调用分组

分类典型系统调用作用
进程控制fork / exec / exit创建进程、替换进程映像、退出
文件操作open / read / write / close打开、读写、关闭文件
内存管理mmap / brk映射文件/内存、调整堆
网络通信socket / bind / connect / send建立与收发网络数据
其他getpid / sleep / kill查进程号、延时、发信号
#include <fcntl.h>
#include <unistd.h>
int main(void) {
    int fd = open("hi.txt", O_CREAT | O_WRONLY, 0644); // 系统调用
    write(fd, "hello\n", 6);                            // 系统调用
    close(fd);                                          // 系统调用
    return 0;
}

应用层很少直接使用系统调用,而是通过标准库与运行时封装;语言可以不同,最底层都要陷入同一个内核。

库函数与系统调用的区别

库函数(printf、fread)运行在用户态,不一定触发系统调用;只有真正需要内核服务时才向下调用 write。printf 会先把内容写入用户态缓冲区,攒满或遇到换行才真正 write 一次——这种"缓冲"设计大幅减少了系统调用次数,也是 printf 与 write 行为不一致的原因。

strace -c ls 2>&1 | head -20
# 输出片段:# calls 表示各类系统调用次数,如 openat、read、write 等

strace 可跟踪程序发起的全部系统调用,是排查"程序到底干了什么"的利器。

Linux 系统启动流程

通电 → BIOS/UEFI 自检 → 引导程序 GRUB 加载内核 vmlinuz
     → 内核初始化硬件、挂载根文件系统 → 启动 1 号进程(init/systemd)
     → systemd 并行拉起 sshd、nginx 等服务 → 出现登录界面

1 号进程是现代 Linux 用户态的第一个进程(systemd),负责启动和管理其他所有服务;孤儿进程会被它收养。面试常问的"进程 0 与 1":0 号是内核态的空闲进程,1 号是用户态鼻祖。

为什么系统调用要"少而批量"

每次系统调用都涉及用户态↔内核态切换与寄存器保存恢复,频繁调用会显著拖慢程序。工程上的对策都是减少进出内核的次数:用户态缓冲(stdio)、批量读写(readv/writev)、mmap 共享映射、零拷贝(sendfile)等,理解它们先从"系统调用有开销"出发。

小结

系统调用是用户态到内核态的受控入口,标准库是对它的封装,strace 可以观察它;系统启动则是"固件 → 引导 → 内核 → 1 号进程 → 服务"的接力。抓住这两条主线,相关面试题就都有了骨架。

笔记加载中…