Linux 0.11 源码解读:从 5 个关键函数看进程创建与切换的实现

发布时间:2026/7/8 21:34:03
Linux 0.11 源码解读:从 5 个关键函数看进程创建与切换的实现 Linux 0.11 源码解读从 5 个关键函数看进程创建与切换的实现在操作系统的核心机制中进程管理无疑是最为精妙的设计之一。当我们翻开 Linux 0.11 的源码这个发布于1991年的经典版本就像打开了一本关于计算机科学的活教材。今天我们将深入五个关键函数——fork()、copy_process、schedule、switch_to和sys_pause通过代码级的分析揭示进程创建与切换的实现奥秘。1. fork()用户视角的进程创建入口fork()是 Unix/Linux 系统中最著名的系统调用之一它创造了分身术般的编程体验。在用户空间我们看到的只是一个简单的函数调用pid_t pid fork(); if (pid 0) { // 子进程代码 } else { // 父进程代码 }但在内核层面这个简单的调用背后隐藏着复杂的机制。Linux 0.11 中fork()的实现始于sys_fork函数在kernel/system_call.s中_sys_fork: call _find_empty_process testl %eax,%eax js 1f push %gs pushl %esi pushl %edi pushl %ebp pushl %eax call _copy_process addl $20,%esp 1: ret这段汇编代码揭示了fork()的核心流程调用find_empty_process寻找空闲的进程槽检查返回值进程号是否有效保存关键寄存器状态调用copy_process完成实际的进程复制清理栈空间并返回提示Linux 0.11 中进程控制块PCB使用固定大小的数组NR_TASKS默认为64而非现代Linux中的动态分配方式。2. copy_process进程复制的核心实现copy_process函数位于kernel/fork.c是进程创建的核心它完成了以下关键操作int copy_process(int nr, long ebp, long edi, long esi, long gs, long none, long ebx, long ecx, long edx, long fs, long es, long ds, long eip, long cs, long eflags, long esp, long ss) { struct task_struct *p; // 分配任务结构体内存 p (struct task_struct *) get_free_page(); // 复制父进程任务结构 *p *current; // 设置子进程特定属性 p-state TASK_UNINTERRUPTIBLE; p-pid last_pid; p-counter p-priority; // 复制页表 copy_mem(nr, p); // 设置内核栈 set_tss_desc(nr, (p-tss)); set_ldt_desc(nr, (p-ldt)); // 修改子进程返回值为0区分父子进程 p-tss.eax 0; // 设置就绪状态 p-state TASK_RUNNING; return last_pid; }这个函数中几个关键点值得注意内存分配使用get_free_page获取一页内存4KB作为子进程的 task_struct结构复制直接通过*p *current复制父进程的整个任务结构返回值处理通过修改子进程的tss.eax实现父子进程返回不同值内存管理copy_mem负责复制页表实现写时复制COW机制注意Linux 0.11 还没有实现现代Linux中的写时复制优化这里的页表复制是立即完成的。3. schedule进程调度的决策中心进程切换的核心是调度器Linux 0.11 的schedule函数位于kernel/sched.c实现了一个简单但高效的时间片轮转调度算法void schedule(void) { int i, next, c; struct task_struct ** p; // 遍历所有任务寻找就绪任务中counter最大的 while (1) { c -1; next 0; for (p LAST_TASK; p FIRST_TASK; --p) { if (!*p || (*p)-state ! TASK_RUNNING) continue; if ((*p)-counter c) { c (*p)-counter; next i; } } // 找到可运行任务则跳出循环 if (c) break; // 所有任务时间片用完重新分配时间片 for (p LAST_TASK; p FIRST_TASK; --p) if (*p) (*p)-counter ((*p)-counter 1) (*p)-priority; } // 切换到选中的任务 switch_to(next); }调度算法的核心逻辑可以总结为遍历任务数组寻找counter值最大的就绪任务如果所有就绪任务的counter都为0则重新计算时间片新时间片 原时间片/2 priority调用switch_to执行任务切换这种设计实现了优先级调度高优先级任务获得更多CPU时间时间片轮转保证公平性防止任务饥饿动态调整长时间运行的任务优先级会逐渐降低4. switch_to上下文切换的底层实现实际的进程上下文切换发生在switch_to宏中定义于include/linux/sched.h#define switch_to(n) {\ struct {long a,b;} __tmp; \ __asm__(cmpl %%ecx,_current\n\t \ je 1f\n\t \ xchgl %%ecx,_current\n\t \ movw %%dx,%1\n\t \ ljmp %0\n\t \ cmpl %%ecx,_last_task_used_math\n\t \ jne 1f\n\t \ clts\n \ 1: \ ::m (*__tmp.a),m (*__tmp.b), \ d (_TSS(n)),c ((long) task[n])); \ }这段内联汇编完成了以下关键操作检查目标进程是否是当前进程如果是则无需切换更新current指针指向新进程使用ljmp指令跳转到新进程的TSS描述符处理浮点运算状态如果上一个使用协处理器的是目标进程上下文切换的核心是CPU状态的保存与恢复这包括通用寄存器通过任务状态段TSS自动保存/恢复段寄存器包括CS、DS等程序计数器EIP栈指针ESP提示现代Linux已不再使用硬件任务切换TSS而是采用软件上下文切换以提升性能。5. sys_pause主动让出CPU的系统调用sys_pause是进程主动让出CPU的系统调用实现int sys_pause(void) { current-state TASK_INTERRUPTIBLE; schedule(); return 0; }这个简单函数展示了Linux调度的一个重要特性——协作式调度。进程可以通过将自己状态设置为非运行状态这里是可中断等待并调用调度器来主动放弃CPU。这种机制被用于等待信号signal实现sleep类函数等待资源可用进程状态转换的全景图结合这五个关键函数我们可以描绘出Linux 0.11中进程状态转换的全景新建 (fork) → 就绪 (TASK_RUNNING) ↑ ↓ | 被调度选中 (schedule) | ↓ | 运行中 (switch_to) | ↓ | 主动放弃CPU (sys_pause) | ↓ └─── 等待 (TASK_INTERRUPTIBLE/UNINTERRUPTIBLE)这个简单的状态机模型支撑起了整个Linux的进程管理机制。与现代Linux相比0.11版本缺少实时进程支持多核调度CFS完全公平调度器更丰富的进程状态如停止状态但正是这种简洁性使得Linux 0.11成为学习操作系统原理的绝佳教材。通过这五个函数的分析我们不仅理解了进程创建与切换的机制也看到了操作系统设计中的一些基本思想资源复用通过fork实现进程复制时间片轮转公平分配CPU时间优先级调度重要任务获得更多资源状态隔离通过上下文切换实现进程保护在实际项目开发中理解这些底层机制有助于我们更合理地设计多进程/多线程应用优化进程创建开销如使用进程池理解各种调度策略的影响调试复杂的并发问题