Linux 0.11 内核栈进程切换实战:5步手写switch_to汇编与fork改造
2026/7/6 11:57:51 网站建设 项目流程

Linux 0.11 内核栈进程切换实战:从理论到落地的五步拆解

1. 环境准备与核心概念重塑

在开始动手修改Linux 0.11内核代码前,我们需要先搭建好实验环境并理解几个关键概念。不同于现代Linux发行版,这个经典版本需要特定的工具链:

# 实验环境配置(Ubuntu示例) sudo apt install gcc-3.4 bin86 libc6-dev-i386 wget https://oldlinux.org/Linux.old/bochs/linux-0.11.tar.gz tar zxvf linux-0.11.tar.gz

内核栈与进程控制块(PCB)的独特关系是这个实验的基石。在Linux 0.11中,每个进程的PCB和内核栈共享同一个4KB内存页:

+---------------------+ 0x0000 | PCB | | (task_struct) | +---------------------+ | | | 内核栈空间 | | (向下增长) | +---------------------+ 0x1000

这种设计带来三个重要特性:

  1. 通过current指针可直接定位当前进程的内核栈
  2. 内核栈指针可通过PAGE_SIZE + (long)p计算获得(p为PCB指针)
  3. 切换进程时只需保存/恢复ESP寄存器值

2. switch_to汇编重写详解

我们需要在kernel/system_call.s中实现新的switch_to函数,以下是关键步骤的逐行解析:

.align 2 switch_to: pushl %ebp # 保存当前栈帧 movl %esp, %ebp # 建立新栈帧 pushl %ecx # 保存寄存器 pushl %ebx pushl %eax movl 8(%ebp), %ebx # 获取参数pnext(目标PCB) cmpl %ebx, current # 判断是否为目标进程 je 1f # 如果是则跳转到结尾 # PCB切换(原子操作) movl %ebx, %eax xchgl %eax, current # 更新TSS中的ESP0(仅0号进程的TSS) movl tss, %ecx addl $4096, %ebx # 计算目标内核栈底 movl %ebx, 4(%ecx) # ESP0偏移量为4 # 内核栈切换 movl %esp, 12(%eax) # 保存当前ESP到PCB movl 8(%ebp), %ebx # 重新加载目标PCB movl 12(%ebx), %esp # 加载目标ESP # LDT切换 movl 16(%ebp), %ecx # 获取LDT选择子 lldt %cx # 加载LDTR movl $0x17, %ecx # 更新FS段寄存器 mov %cx, %fs 1: popl %eax # 恢复寄存器 popl %ebx popl %ecx popl %ebp ret

关键修改点对比表

原TSS切换方式内核栈切换方式
通过ljmp指令触发CPU自动保存状态手动保存关键寄存器
TR寄存器指向各进程的TSS所有进程共享0号进程TSS
切换开销约200+时钟周期切换开销大幅降低
依赖硬件机制纯软件实现

3. schedule函数适配改造

kernel/sched.c中,我们需要调整schedule函数以适配新的切换方式:

// 修改后的核心逻辑 void schedule(void) { struct task_struct *pnext = &init_task; // 新增目标PCB指针 while (1) { // ...原有调度逻辑不变... if ((*p)->state == TASK_RUNNING && (*p)->counter > c) { c = (*p)->counter; next = i; pnext = *p; // 记录PCB指针 } } // 调用新switch_to(增加LDT参数) switch_to(pnext, _LDT(next)); }

PCB结构变更需要在include/linux/sched.h中添加内核栈指针:

struct task_struct { long state; long counter; long priority; long kernelstack; // 新增内核栈指针字段 // ...其他原有字段... };

对应的0号进程初始化宏也需要更新:

#define INIT_TASK \ { 0,15,15, PAGE_SIZE+(long)&init_task, \ // 初始化kernelstack /* 其余初始化保持不变 */ }

4. fork函数内核栈初始化

改造kernel/fork.c中最关键的是正确初始化子进程内核栈。以下是改造后的核心代码:

int copy_process(...) { // ...原有代码... // 获取子进程内核栈指针 long *krnstack = (long *)(PAGE_SIZE + (long)p); // 构建中断返回帧 *(--krnstack) = ss & 0xffff; *(--krnstack) = esp; *(--krnstack) = eflags; *(--krnstack) = cs & 0xffff; *(--krnstack) = eip; // 构建寄存器保存区 *(--krnstack) = ds & 0xffff; *(--krnstack) = es & 0xffff; *(--krnstack) = fs & 0xffff; *(--krnstack) = gs & 0xffff; *(--krnstack) = esi; *(--krnstack) = edi; *(--krnstack) = edx; // 设置首次切换返回点 *(--krnstack) = (long)first_return_from_kernel; *(--krnstack) = ebp; *(--krnstack) = ecx; *(--krnstack) = ebx; *(--krnstack) = 0; // eax=0表示子进程 p->kernelstack = krnstack; // 保存栈指针 // ...其余初始化代码... }

父子进程内核栈对比图

父进程内核栈布局 子进程内核栈布局 +----------------+ +----------------+ | SS | | SS | | ESP | | ESP | | EFLAGS | | EFLAGS | | CS | | CS | | EIP | | EIP | +----------------+ +----------------+ | DS | | DS | | ES | | ES | | FS | | FS | | GS | | GS | | ESI | | ESI | | EDI | | EDI | | EDX | | EDX | +----------------+ +----------------+ | 返回地址 | | first_return | | EBP | | EBP | | ECX | | ECX | | EBX | | EBX | | EAX | | 0 (EAX) | +----------------+ +----------------+

5. 首次返回汇编与调试技巧

kernel/system_call.s中添加首次返回的汇编代码:

.align 2 first_return_from_kernel: popl %edx popl %edi popl %esi pop %gs pop %fs pop %es pop %ds iret # 关键指令:返回到用户态

常见问题排查指南

  1. 内核崩溃无输出

    • 检查bochs配置文件中的magic_break设置
    • 在关键位置添加printk输出
    • 使用gdb远程调试:bochs -q -f bochsrc-gdb.txt
  2. 进程切换后寄存器值异常

    • 确认first_return_from_kernel的弹栈顺序
    • 检查fork中内核栈初始化是否漏掉某些寄存器
    • 验证LDT选择子是否正确传递
  3. 系统调用后无法返回

    • 检查ret_from_sys_call标签是否保留
    • 确认system_call中保存了所有必要寄存器
    • 验证ESP0在TSS中的偏移量是否为4

调试示例代码(可在schedule()中添加):

printk("Switching from %d to %d\n", current->pid, pnext->pid); printk("Current stack: 0x%x, Next stack: 0x%x\n", current->kernelstack, pnext->kernelstack);

实验完成后,可以通过编写用户态测试程序验证效果:

#include <unistd.h> #include <stdio.h> int main() { int pid = fork(); if (pid == 0) { printf("Child process running\n"); } else { printf("Parent process running\n"); } return 0; }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询