欢迎光临
我们一直在努力

linux内核体系结构009:内核深处:堆栈的华丽跃迁与城池的宏大蓝图

内核深处:堆栈的华丽跃迁与城池的宏大蓝图

在上一段旅程中,我们深入探索了 Linux 0.11 如何设计那“四个堆栈”,并了解了任务 0 如何用一套精彩绝伦的 move_to_user_mode 魔法完成自我降级。当时我们提到了一个惊心动魄的瞬间——CPU 的“特权级切换”动作。

今天提供的这几张书页,恰好填补了这一瞬间的“动作特写”:当用户程序发起系统调用时,内核态与用户态堆栈是如何在硬件层面实现无缝切换的? 紧接着,作者非常贴心地为我们献上了 “Linux 0.11 源代码的完整地图”。这就像是一本 1000 页的巨著,终于给我们发了“目录册”。我们将从微观(堆栈)走向宏观(代码架构),把之前阅读的碎片化知识,瞬间拼接成一幅壮丽的城池设计图。


第一章:一场“潜伏”与“回归”的华丽戏法 —— 堆栈的绝对切换(2.6.3)

您的书页中,图 2-14 用一张极其简练的图,揭示了系统调用中最核心的硬件微操。书中明确写道:

“当任务调用系统调用时就会进入内核,执行内核代码。此时内核代码就会使用该任务的内核态堆栈进行操作。当进入内核程序时,由于优先级别发生了改变(从用户态转到内核态),用户态堆栈的堆栈指针以及 eflags 会被保存在任务的内核态堆栈中。而在执行 iret 退出内核程序,回到用户程序时,将恢复用户态的堆栈和 eflags。”

用大白话讲,这就像是特工潜入敌方司令部。

  • 潜入过程(INT 指令出发):你的普通用户程序(在用户态 3 级)正准备读取硬盘上的一个文件。它没有权限,只能发出一个“系统调用”的暗号(int 0x80)。CPU 接收到暗号后,立刻触发机制。它不能让特工(当前进程)带着自己平常的用户记事本(用户栈)进入司令部。于是,CPU 自动把特工当前的“原始私人物品”(用户栈指针 ESP、状态寄存器 EFLAGS 等)打包存放到司令部提供的“专用保险柜”(内核态堆栈)中,并立刻把指针切换到保险柜的顶部。
  • 执行任务(内核态执行):现在,进程开始利用内核栈执行内部的系统调用代码(如 sys_read)。
  • 安全撤离(IRET 指令返回):任务执行完毕,CPU 执行 iret 指令。这时候,CPU 会从“专用保险柜”里准确取出特工原本的私人物品(恢复用户栈指针和 EFLAGS),指针切回用户栈,无缝回到了用户代码执行的下一条指令!
  • 🖼️ 实时交互解析:Mermaid 图解 INT 与 IRET 的堆栈流转

    为了让你直观体验到硬件层面发生了什么,我重绘了书中图 2-14:

    #mermaid-svg-uwdLqoBApU71vUIM{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uwdLqoBApU71vUIM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uwdLqoBApU71vUIM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uwdLqoBApU71vUIM .error-icon{fill:#552222;}#mermaid-svg-uwdLqoBApU71vUIM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uwdLqoBApU71vUIM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uwdLqoBApU71vUIM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uwdLqoBApU71vUIM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uwdLqoBApU71vUIM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uwdLqoBApU71vUIM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uwdLqoBApU71vUIM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uwdLqoBApU71vUIM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uwdLqoBApU71vUIM .marker.cross{stroke:#333333;}#mermaid-svg-uwdLqoBApU71vUIM svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uwdLqoBApU71vUIM p{margin:0;}#mermaid-svg-uwdLqoBApU71vUIM .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-uwdLqoBApU71vUIM .cluster-label text{fill:#333;}#mermaid-svg-uwdLqoBApU71vUIM .cluster-label span{color:#333;}#mermaid-svg-uwdLqoBApU71vUIM .cluster-label span p{background-color:transparent;}#mermaid-svg-uwdLqoBApU71vUIM .label text,#mermaid-svg-uwdLqoBApU71vUIM span{fill:#333;color:#333;}#mermaid-svg-uwdLqoBApU71vUIM .node rect,#mermaid-svg-uwdLqoBApU71vUIM .node circle,#mermaid-svg-uwdLqoBApU71vUIM .node ellipse,#mermaid-svg-uwdLqoBApU71vUIM .node polygon,#mermaid-svg-uwdLqoBApU71vUIM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uwdLqoBApU71vUIM .rough-node .label text,#mermaid-svg-uwdLqoBApU71vUIM .node .label text,#mermaid-svg-uwdLqoBApU71vUIM .image-shape .label,#mermaid-svg-uwdLqoBApU71vUIM .icon-shape .label{text-anchor:middle;}#mermaid-svg-uwdLqoBApU71vUIM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uwdLqoBApU71vUIM .rough-node .label,#mermaid-svg-uwdLqoBApU71vUIM .node .label,#mermaid-svg-uwdLqoBApU71vUIM .image-shape .label,#mermaid-svg-uwdLqoBApU71vUIM .icon-shape .label{text-align:center;}#mermaid-svg-uwdLqoBApU71vUIM .node.clickable{cursor:pointer;}#mermaid-svg-uwdLqoBApU71vUIM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uwdLqoBApU71vUIM .arrowheadPath{fill:#333333;}#mermaid-svg-uwdLqoBApU71vUIM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uwdLqoBApU71vUIM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uwdLqoBApU71vUIM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uwdLqoBApU71vUIM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uwdLqoBApU71vUIM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uwdLqoBApU71vUIM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uwdLqoBApU71vUIM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uwdLqoBApU71vUIM .cluster text{fill:#333;}#mermaid-svg-uwdLqoBApU71vUIM .cluster span{color:#333;}#mermaid-svg-uwdLqoBApU71vUIM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uwdLqoBApU71vUIM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uwdLqoBApU71vUIM rect.text{fill:none;stroke-width:0;}#mermaid-svg-uwdLqoBApU71vUIM .icon-shape,#mermaid-svg-uwdLqoBApU71vUIM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uwdLqoBApU71vUIM .icon-shape p,#mermaid-svg-uwdLqoBApU71vUIM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uwdLqoBApU71vUIM .icon-shape .label rect,#mermaid-svg-uwdLqoBApU71vUIM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uwdLqoBApU71vUIM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uwdLqoBApU71vUIM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uwdLqoBApU71vUIM :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    IRET 返回

    CPU 执行 IRET 指令

    从内核栈弹出 EIP, CS, EFLAGS, SS, ESP

    恢复用户态 ESP 和 EFLAGS

    回到用户态代码继续执行

    内核执行系统调用

    使用内核栈执行内核代码

    系统调用处理结束

    INT 0x80 中断

    CPU 检测到特权级变化 3 -> 0

    任务的内核栈顶部被激活

    立即将 当前 ESP, EFLAGS, SS, CS, EIP 压入内核栈

    用户程序执行时

    此时 ESP 指针指向栈顶

    用户态堆栈区

    用户态代码执行

    核心机制:硬件原生支持两层堆栈的快速切换,内核态的开关就是由 TSS 中的 ss0 和 esp0 激活。

    这一节有一个特别值得回味的细节(对应第三张图的顶部文字):
    书中提到:“任务 0 的内核态堆栈是在其人工设置的初始化任务数据结构中指定的,而它的用户态堆栈就是前面进入保护模式后所使用的堆栈,即 sched.c 的 user_stack 数组的位置……” 并且特别指出了 esp 位置和 ss 被设置成 0x17。这再次印证了我们上一章的结论:任务 0 的“降级”是用一种堆栈构造的骗局,让 CPU 以为自己正在进行一次系统调用的返回。这种“用硬件机制欺骗硬件”的手法,堪称神来之笔。


    第二章:核弹级的内核“目录册” —— 代码城池建筑蓝图(2.7)

    在咀嚼了微观的堆栈切换后,书本第 21 页话锋一转,开始大谈宏观的代码目录结构。这并非是多余的,因为:

    “由于 Linux 内核是一种单内核模式系统,因此内核中所有程序几乎都有紧密的联系,它们之间的调用关系非常密切。所以在阅读一个源代码文件时往往需要参阅其他相关的文件。”

    想象一下,如果你要把 Linux 0.11 那 102 个 C 文件和 14 个目录全部背下来是不可能的。因此,必须先拿一张“代码城池的地图”。

    🏛️ 全景地图:Linux 0.11 源代码目录结构全景图(图 2-15 复现)

    书中给出了一个极简但清晰的树状图,我们直接用 Mermaid 还原它,形成一张“全景天书”:

    #mermaid-svg-fzp1lnuvUynYJFgK{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fzp1lnuvUynYJFgK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fzp1lnuvUynYJFgK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fzp1lnuvUynYJFgK .error-icon{fill:#552222;}#mermaid-svg-fzp1lnuvUynYJFgK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fzp1lnuvUynYJFgK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fzp1lnuvUynYJFgK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fzp1lnuvUynYJFgK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fzp1lnuvUynYJFgK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fzp1lnuvUynYJFgK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fzp1lnuvUynYJFgK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fzp1lnuvUynYJFgK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fzp1lnuvUynYJFgK .marker.cross{stroke:#333333;}#mermaid-svg-fzp1lnuvUynYJFgK svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fzp1lnuvUynYJFgK p{margin:0;}#mermaid-svg-fzp1lnuvUynYJFgK .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-fzp1lnuvUynYJFgK .cluster-label text{fill:#333;}#mermaid-svg-fzp1lnuvUynYJFgK .cluster-label span{color:#333;}#mermaid-svg-fzp1lnuvUynYJFgK .cluster-label span p{background-color:transparent;}#mermaid-svg-fzp1lnuvUynYJFgK .label text,#mermaid-svg-fzp1lnuvUynYJFgK span{fill:#333;color:#333;}#mermaid-svg-fzp1lnuvUynYJFgK .node rect,#mermaid-svg-fzp1lnuvUynYJFgK .node circle,#mermaid-svg-fzp1lnuvUynYJFgK .node ellipse,#mermaid-svg-fzp1lnuvUynYJFgK .node polygon,#mermaid-svg-fzp1lnuvUynYJFgK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fzp1lnuvUynYJFgK .rough-node .label text,#mermaid-svg-fzp1lnuvUynYJFgK .node .label text,#mermaid-svg-fzp1lnuvUynYJFgK .image-shape .label,#mermaid-svg-fzp1lnuvUynYJFgK .icon-shape .label{text-anchor:middle;}#mermaid-svg-fzp1lnuvUynYJFgK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fzp1lnuvUynYJFgK .rough-node .label,#mermaid-svg-fzp1lnuvUynYJFgK .node .label,#mermaid-svg-fzp1lnuvUynYJFgK .image-shape .label,#mermaid-svg-fzp1lnuvUynYJFgK .icon-shape .label{text-align:center;}#mermaid-svg-fzp1lnuvUynYJFgK .node.clickable{cursor:pointer;}#mermaid-svg-fzp1lnuvUynYJFgK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fzp1lnuvUynYJFgK .arrowheadPath{fill:#333333;}#mermaid-svg-fzp1lnuvUynYJFgK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fzp1lnuvUynYJFgK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fzp1lnuvUynYJFgK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fzp1lnuvUynYJFgK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fzp1lnuvUynYJFgK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fzp1lnuvUynYJFgK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fzp1lnuvUynYJFgK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fzp1lnuvUynYJFgK .cluster text{fill:#333;}#mermaid-svg-fzp1lnuvUynYJFgK .cluster span{color:#333;}#mermaid-svg-fzp1lnuvUynYJFgK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fzp1lnuvUynYJFgK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fzp1lnuvUynYJFgK rect.text{fill:none;stroke-width:0;}#mermaid-svg-fzp1lnuvUynYJFgK .icon-shape,#mermaid-svg-fzp1lnuvUynYJFgK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fzp1lnuvUynYJFgK .icon-shape p,#mermaid-svg-fzp1lnuvUynYJFgK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fzp1lnuvUynYJFgK .icon-shape .label rect,#mermaid-svg-fzp1lnuvUynYJFgK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fzp1lnuvUynYJFgK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fzp1lnuvUynYJFgK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fzp1lnuvUynYJFgK :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    14 个子目录

    含3个汇编文件

    含17个C程序

    linux 根目录

    boot 引导启动目录

    fs 文件系统目录

    include 头文件目录

    init 内核初始化目录

    kernel 内核进程调度目录

    mm 内存管理目录

    lib 内核函数库

    tools 生成内核镜像的工具

    asm, linux, sys, etc 等

    bootsect.s, setup.s, head.s

    file_table.c, exec.c, fcntl.c 等

    这个根目录的 Makefile 简直是'一键造城'的神器!输入 make 就能递归编译所有子目录,生成最终的内核镜像。

    大家通过这张图可以一眼看穿这座城池的各个“行政区”:

    • boot/:负责从无到有的系统第一口气(引导)。
    • fs/:掌管所有文件、文件夹、数据的读写(文件系统)。
    • kernel/:这座城池的主脑,负责进程调度、信号处理。
    • mm/:分配土地和资源的部门(内存管理)。
    • include/:全城的“通用字典”,各种宏定义、数据结构原型都在这儿。

    而这里最强大的一点是根目录下的那个 Makefile。它通过递归调用的方式,将 14 个子目录中的编译工作串在了一起。无论你修改了哪个目录下的底层代码,只要在根目录敲下 make 指令,系统就会智能地识别出变化,重新编译,并链接成一个完整的操作系统内核。


    第三章:“攻城第一梯队” —— 引导启动程序目录 boot(2.7.1)

    很多初学者一打开源码,首先去看的就是 kernel/ 下的代码,这是不对的。计算机启动必须从 boot 目录开始,这也是书中第一个详细讲解的子目录。

    书中记录得非常清晰:

    “boot 目录中含有 3 个汇编语言文件,是内核源代码文件中最先被编译的程序… bootsect.s 和 setup.s 程序需要使用 as86 软件来编译,使用的是 as86 的汇编语言格式… 而 head.s 需要用 GNU as 来编译,使用的是 AT&T 格式的汇编语言。”

    “三兄弟”各司其职:

  • bootsect.s(老大,Boot Sector):当你开机,BIOS 跑到软盘/硬盘的 0 磁道 0 磁头 1 扇区(第一扇区),把这里仅有的 512 字节代码读入物理内存 0x7C00 处。这就是 bootsect.s!它像个瘦弱的搬运工,它的任务不是启动系统,而是把磁盘后面更大的 setup 和 system 模块搬运到内存安全区域(0x90000 处)。
  • setup.s(老二,配置工兵):当搬运工完成工作,接力棒交给了老二。它负责读取机器的硬件配置参数(如内存大小、显卡类型),并且最重要的一步——将 CPU 从“16 位实模式”切换到“32 位保护模式”。同时把内核系统模块从临时位置移动到最终位置。
  • head.s(老三,先锋官):这是最早在 32 位保护模式下执行的代码。它负责设置页表(让虚拟内存生效)、初始化 GDT 和 IDT,然后直接跳转到 init/main.c 里的 main() 函数,彻底告别汇编时代,进入 C 语言统领的时代!
  • 你知道为什么用两种不同的汇编器吗?
    因为 bootsect.s 和 setup.s 还在“实模式”下,它们使用的是 16 位指令集(Intel 格式)。而 head.s 已经进入了 32 位“保护模式”,且是与 C 语言配合最紧密的 AT&T 格式。这种“跨语言、跨指令集、跨阶段”的接力,是计算机启动时最硬核的浪漫。


    第四章:“核心大管家” —— 文件系统目录 fs(2.7.2)

    在头文件目录之后,本书花了极大的篇幅去解析文件系统目录 fs。为什么是它?因为文件系统是操作系统与用户的“磁盘”交互的桥梁,也是内核代码中最复杂的一个子系统。

    文中写道:“fs 目录是文件系统实现程序的目录,共包含 17 个 C 语言程序。” 书中按照它们的功能,巧妙地把这 17 个文件分成 四个层次:

  • 高速缓冲区管理层:即 buffer.c。
  • 底层文件操作层:即 namei.c、inode.c、bitmap.c、super.c、truncate.c 等。
  • 文件数据访问层:即 char_dev.c、pipe.c、block_dev.c、file_dev.c。
  • 高层函数层:即 open.c、read_write.c、stat.c、exec.c、fcntl.c 等。
  • 🧩 函数引用关系解析(还原书中图 2-16)

    这 17 个文件并不是孤立的。书中的图 2-16 绘制了一幅“文件系统城内各职能部门的互相拉帮结派图”。为了让你更直观地看懂这种引用关系,我绘制了下面的 Mermaid 职能关系图:

    #mermaid-svg-6LeHzc71wnmBhrL5{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6LeHzc71wnmBhrL5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6LeHzc71wnmBhrL5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6LeHzc71wnmBhrL5 .error-icon{fill:#552222;}#mermaid-svg-6LeHzc71wnmBhrL5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6LeHzc71wnmBhrL5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6LeHzc71wnmBhrL5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6LeHzc71wnmBhrL5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6LeHzc71wnmBhrL5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6LeHzc71wnmBhrL5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6LeHzc71wnmBhrL5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6LeHzc71wnmBhrL5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6LeHzc71wnmBhrL5 .marker.cross{stroke:#333333;}#mermaid-svg-6LeHzc71wnmBhrL5 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6LeHzc71wnmBhrL5 p{margin:0;}#mermaid-svg-6LeHzc71wnmBhrL5 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-6LeHzc71wnmBhrL5 .cluster-label text{fill:#333;}#mermaid-svg-6LeHzc71wnmBhrL5 .cluster-label span{color:#333;}#mermaid-svg-6LeHzc71wnmBhrL5 .cluster-label span p{background-color:transparent;}#mermaid-svg-6LeHzc71wnmBhrL5 .label text,#mermaid-svg-6LeHzc71wnmBhrL5 span{fill:#333;color:#333;}#mermaid-svg-6LeHzc71wnmBhrL5 .node rect,#mermaid-svg-6LeHzc71wnmBhrL5 .node circle,#mermaid-svg-6LeHzc71wnmBhrL5 .node ellipse,#mermaid-svg-6LeHzc71wnmBhrL5 .node polygon,#mermaid-svg-6LeHzc71wnmBhrL5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6LeHzc71wnmBhrL5 .rough-node .label text,#mermaid-svg-6LeHzc71wnmBhrL5 .node .label text,#mermaid-svg-6LeHzc71wnmBhrL5 .image-shape .label,#mermaid-svg-6LeHzc71wnmBhrL5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-6LeHzc71wnmBhrL5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6LeHzc71wnmBhrL5 .rough-node .label,#mermaid-svg-6LeHzc71wnmBhrL5 .node .label,#mermaid-svg-6LeHzc71wnmBhrL5 .image-shape .label,#mermaid-svg-6LeHzc71wnmBhrL5 .icon-shape .label{text-align:center;}#mermaid-svg-6LeHzc71wnmBhrL5 .node.clickable{cursor:pointer;}#mermaid-svg-6LeHzc71wnmBhrL5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6LeHzc71wnmBhrL5 .arrowheadPath{fill:#333333;}#mermaid-svg-6LeHzc71wnmBhrL5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6LeHzc71wnmBhrL5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6LeHzc71wnmBhrL5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6LeHzc71wnmBhrL5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6LeHzc71wnmBhrL5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6LeHzc71wnmBhrL5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6LeHzc71wnmBhrL5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6LeHzc71wnmBhrL5 .cluster text{fill:#333;}#mermaid-svg-6LeHzc71wnmBhrL5 .cluster span{color:#333;}#mermaid-svg-6LeHzc71wnmBhrL5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6LeHzc71wnmBhrL5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6LeHzc71wnmBhrL5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-6LeHzc71wnmBhrL5 .icon-shape,#mermaid-svg-6LeHzc71wnmBhrL5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6LeHzc71wnmBhrL5 .icon-shape p,#mermaid-svg-6LeHzc71wnmBhrL5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6LeHzc71wnmBhrL5 .icon-shape .label rect,#mermaid-svg-6LeHzc71wnmBhrL5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6LeHzc71wnmBhrL5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6LeHzc71wnmBhrL5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6LeHzc71wnmBhrL5 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    Bottom Hardware

    ll_rw_block.c 底层块读写驱动(位于 kernel/blk_drv)

    Buffer Cache

    buffer.c 内存高速缓冲

    Low-level FS Ops

    namei.c 目录名/文件名查找

    inode.c i节点操作

    truncate.c 释放文件数据

    bitmap.c i节点/逻辑块位图

    super.c 超级块操作

    Data Access

    char_dev.c 字符设备

    pipe.c 管道

    block_dev.c 块设备

    file_dev.c 普通文件

    User Access / High Level

    exec.c

    fcntl.c

    ioctl.c

    open.c

    stat.c

    read_write.c

    核心解读:高层函数(如open)依赖于底层操作无论是读取文件还是目录,最终都汇总到 buffer.cbuffer.c 再通过与 ll_rw_block 交互直接操作磁盘。

    那些不起眼却又至关重要的 .c 文件究竟在干嘛?

    • buffer.c:所有文件读写的“中央枢纽”。它维护着一块内存缓冲池,你读的磁盘文件不会立刻到内存吗?对,是先存这里。
    • namei.c:专门负责处理“路径名”和“i 节点”之间的翻译。比如你想打开 /etc/passwd,它就得把 etc 和 passwd 的 i 节点找出来。
    • bitmap.c:掌管整块硬盘的“地图”,记录哪些逻辑块被占用,哪些是空的。
    • inode.c:管理文件的“身份证”。包括文件的权限、大小、修改时间等。
    • file_dev.c & block_dev.c:一个管常规文件读写,一个管块设备数据的读写。它们最终都指向 buffer.c。

    第五章:亲手手搓一个“双栈切换”与“文件系统”模拟器

    理论拆解得再深,不如动手跑一行代码。结合本章的两大核心内容(堆栈切换 + 源代码目录精神),我为你专门写了一个 高度仿真的微内核模拟程序。

    这个程序模拟了:

  • 一个用户态堆栈和一个内核态堆栈的分配。
  • 模拟 int 0x80 触发系统调用时,堆栈指针的“乾坤大挪移”。
  • 模拟上层文件系统调用(如 open),如何层层传递到底层 buffer 和 ll_rw_block。
  • 请按照以下步骤把代码跑起来,你就能在终端里亲眼看到书中的架构是如何在逻辑上运行的。

    🧑‍💻 完整模拟代码 kernel_sim.c

    /**
    * @file kernel_sim.c
    * @brief 极简微内核模拟器:展示 Linux 0.11 堆栈切换与 FS 调用层级。
    *
    * 本程序在用户态空间模拟了:
    * 1. 进程的 task_struct 与双栈(用户栈、内核栈)的创建。
    * 2. 模拟 INT 0x80 时,硬件层级自动切换内核栈的场景。
    * 3. 模拟 `open()` 文件打开请求在内核中自上而下(open -> namei -> inode -> buffer -> ll_rw_block)的流转。
    *
    * @version 1.0
    * @author 内核模拟器实验室
    */

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    // ———————————————————
    // 1. 系统配置与内存模拟
    // ———————————————————
    #define USER_STACK_SIZE 4096 /**< 用户态堆栈大小 */
    #define KERNEL_STACK_SIZE 4096 /**< 内核态堆栈大小 */

    char g_user_stack[USER_STACK_SIZE]; /**< 物理模拟用户栈 */
    char g_kernel_stack[KERNEL_STACK_SIZE]; /**< 物理模拟内核栈 */

    // 定义当前执行模式
    typedef enum {
    KERNEL_MODE = 0,
    USER_MODE = 1
    } cpu_mode_t;

    cpu_mode_t g_current_mode = USER_MODE; /**< 当前运行模式 */
    void *g_current_esp = NULL; /**< 当前 CPU 的堆栈指针 */

    // ———————————————————
    // 2. 文件系统模拟结构(模拟书中 fs 目录的数据)
    // ———————————————————
    typedef struct inode_s {
    int i_num; /**< i节点号 */
    char name[32]; /**< 文件名 */
    } inode_t;

    // 模拟一个已打开的文件句柄
    typedef struct file_s {
    inode_t *inode;
    int mode;
    } file_t;

    // ———————————————————
    // 3. 进程控制块 (PCB) 模拟
    // ———————————————————
    typedef struct task_struct_s {
    int pid;
    char name[20];
    void *user_esp; /**< 当前进程的用户堆栈指针 (原 ESP) */
    void *kernel_esp; /**< 进程的 TSS.esp0 内核堆栈指针 */
    } task_struct_t;

    task_struct_t g_current_task = {0};

    /**
    * @brief 模拟 `sched_init()` 和 `init_task` 的创建过程
    */

    void init_tasks() {
    g_current_task.pid = 0;
    strcpy(g_current_task.name, "init_task");
    // 初始化栈指针,指向数组末端(高地址)
    g_current_task.user_esp = (void *)(g_user_stack + USER_STACK_SIZE – 1);
    g_current_task.kernel_esp = (void *)(g_kernel_stack + KERNEL_STACK_SIZE – 1);
    g_current_esp = g_current_task.user_esp;
    printf("[系统初始化] 任务 %s (PID: %d) 创建成功。\\n", g_current_task.name, g_current_task.pid);
    printf(" 用户栈顶地址: %p\\n", g_current_task.user_esp);
    printf(" 内核栈顶地址: %p\\n", g_current_task.kernel_esp);
    }

    // ———————————————————
    // 4. 模拟硬件中断与堆栈切换
    // ———————————————————

    /**
    * @brief 模拟触发 INT 0x80 系统调用。
    *
    * 严格按照书上 2.6.3 描述:
    * 1. 保存当前用户栈指针以及 eflags 到内核态堆栈中。
    * 2. 将 CPU 的堆栈指针切换到内核栈。
    */

    void simulate_int_0x80() {
    printf("\\n[💥 硬件触发] 任务发起 INT 0x80 系统调用,特权级 3 -> 0 发生改变。\\n");
    // 1. 模拟将当前用户态的上下页压入内核栈 (在真实系统中由 CPU 原生支持)
    unsigned long *kstack_top = (unsigned long *)g_kernel_stack;
    *kstack_top = (unsigned long)g_current_esp; // 保存当前用户栈指针
    kstack_top—;
    *kstack_top = 0x00000202; // 模拟保存 EFLAGS 标志位

    // 2. 将当前栈指针硬切换到内核栈顶端
    g_current_esp = (void *)(g_kernel_stack + KERNEL_STACK_SIZE – 1);
    g_current_mode = KERNEL_MODE;

    printf(" [硬件现场] 用户态 ESP 已保存到内核栈。\\n");
    printf(" [硬件现场] CPU 堆栈指针 (ESP) 已切换至内核栈区域。\\n");
    }

    /**
    * @brief 模拟执行 IRET 指令(系统调用返回)。
    *
    * 1. 从内核栈中弹出之前保存的 eflags 和用户 ESP。
    * 2. 恢复 CPU 的堆栈指针为原来的用户栈。
    */

    void simulate_iret() {
    printf("\\n[↩️ 硬件返回] 执行 IRET 指令,特权级 0 -> 3 恢复。\\n");
    unsigned long *kstack_top = (unsigned long *)(g_kernel_stack + KERNEL_STACK_SIZE – 1);
    // 恢复 esp
    kstack_top—;
    kstack_top—; // 跳过 EFLAGS
    g_current_esp = (void *)*kstack_top; // 恢复用户栈指针

    g_current_mode = USER_MODE;
    printf(" [硬件现场] CPU 堆栈指针恢复至用户栈区域。\\n");
    }

    // ———————————————————
    // 5. 模拟文件系统各层次调用 (基于图 2-16)
    // ———————————————————

    /**
    * @brief 底层硬件调用 (模拟 kernel/blk_drv/ll_rw_block.c)
    */

    void ll_rw_block(int rw, int block_num) {
    printf(" ➡️ [底层驱动] ll_rw_block() 正在向磁盘块 %d 发送 %s 指令…\\n",
    block_num, (rw == 0) ? "READ" : "WRITE");
    printf(" ✅ [底层驱动] 磁盘 I/O 执行完成。\\n");
    }

    /**
    * @brief 高速缓冲区管理器 (模拟 fs/buffer.c)
    */

    void buffer_request(int block_num) {
    printf(" ➡️ [缓冲区] buffer_request() 申请读取磁盘块 %d\\n", block_num);
    // 模拟直接从缓冲区查找,若找不到则掉用底层 ll_rw_block
    ll_rw_block(0, block_num);
    }

    /**
    * @brief 节点与文件操作 (模拟 fs/inode.c 与 fs/namei.c)
    */

    inode_t *namei_interface(const char *pathname) {
    printf(" ➡️ [路径解析] namei() 正在解析路径: %s\\n", pathname);
    // 模拟找到一个文件的 i 节点
    inode_t *new_inode = malloc(sizeof(inode_t));
    new_inode->i_num = 105;
    strcpy(new_inode->name, "test_file");
    return new_inode;
    }

    void inode_operation(inode_t *inode) {
    printf(" ➡️ [i节点操作] inode_read() 正在读取 i节点号: %d\\n", inode->i_num);
    // 获得逻辑块号,去申请缓冲
    buffer_request(inode->i_num);
    }

    /**
    * @brief 高层系统调用实现 (模拟 sys_open)
    */

    void sys_open(const char *pathname) {
    printf("\\n[内核执行] sys_open 系统调用开始…\\n");
    inode_t *inode = namei_interface(pathname);
    if (inode) {
    inode_operation(inode);
    printf("[内核执行] sys_open 执行完毕,返回文件描述符 (fd=3)。\\n");
    free(inode);
    } else {
    printf("[内核执行] 文件不存在!\\n");
    }
    }

    /**
    * @brief 中断处理程序的入口,用来分发系统调用
    */

    void do_interrupt_handler() {
    printf("[内核执行] 成功进入内核态!正在分发系统调用…\\n");
    sys_open("/etc/passwd");
    }

    // ———————————————————
    // 6. 主函数
    // ———————————————————
    int main() {
    printf("========== 深度模拟 Linux 0.11 章节 2.6.3 & 2.7.2 ==========\\n\\n");

    // 1. 初始化任务
    init_tasks();

    // 2. 模拟用户态执行
    printf("\\n— [用户态运行] —\\n");
    printf("任务 %s 正在执行用户代码,正在准备发起 open() 系统调用。\\n", g_current_task.name);
    printf("当前 ESP 位于用户栈: %p\\n", g_current_esp);

    // 3. 触发系统调用 (执行 INT 0x80)
    simulate_int_0x80();

    // 4. 执行内核处理程序 (堆栈已切换到内核栈)
    printf("\\n— [内核态运行] —\\n");
    printf("当前 ESP 位于内核栈: %p\\n", g_current_esp);
    do_interrupt_handler();

    // 5. 返回用户态 (执行 IRET)
    simulate_iret();

    // 6. 恢复用户态执行
    printf("\\n— [用户态恢复] —\\n");
    printf("当前 ESP 恢复至用户栈: %p\\n", g_current_esp);
    printf("任务 %s 继续在用户态执行后续逻辑。\\n", g_current_task.name);
    printf("\\n========== 模拟结束 ==========\\n");

    return 0;
    }

    📁 配套 Makefile

    # 编译器和编译参数
    CC = gcc
    CFLAGS = -Wall -g -O0
    TARGET = kernel_sim

    # 默认编译目标
    all: $(TARGET)

    # 链接
    $(TARGET): kernel_sim.c
    $(CC) $(CFLAGS) -o $(TARGET) kernel_sim.c

    # 清理编译生成文件
    clean:
    rm -f $(TARGET)

    # 方便一键执行
    run: $(TARGET)
    ./$(TARGET)

    .PHONY: all clean run

    🚀 动手实操与深度解读

  • 编译与运行:将 kernel_sim.c 和 Makefile 放入同一文件夹。在命令行执行 make clean && make,接着运行 ./kernel_sim。
  • 解读终端输出结果:
    • 初始化阶段:你会看到创建了两个“栈区”并记录了内存地址。
    • 事件触发:当程序执行到“系统调用”时,它开始模拟硬件层级的动作。
    • 核心异动:注意观察 [硬件现场] CPU 堆栈指针 (ESP) 已切换至内核栈区域。 这一行。这说明我们的模拟逻辑准确地捕捉到了 Linux 0.11 双栈切换的原理。
    • 内核处理:do_interrupt_handler 被唤醒,然后内核执行文件系统调用的层级传递:sys_open -> namei -> inode -> buffer -> ll_rw_block。你甚至可以看到逻辑层如何一步一步探底到磁盘的。
    • 返回:simulate_iret() 模拟了返回的过程,堆栈指针又变回了用户栈,程序继续在用户态“跑”下一步。
  • 这个简单的模拟代码,就是原书图 2-16 那道“函数引用关系”在真实逻辑世界里的惊鸿一瞥。


    终章:从原子到宇宙的狂想

    回顾这三页知识,我们走完了一个从“原子级”到“星系级”的跨越。
    最开始,我们盯着堆栈里那一个 esp 指针的搬迁,看它是如何在硬件最底层实现特权级的跨越(2.6.3)。
    接着,我们被一把拉到高空,俯瞰整个 Linux 0.11 的宏大源码城池(2.7),看到了三兄弟如何吹响系统启动的号角(2.7.1)。
    最后,我们一头扎进了文件系统的 17 个 C 文件(2.7.2),弄懂了高层调用到底是如何层层扒掉外衣,直到触碰到冰冷的磁盘。

    这种“由点及面”、“由硬件到软件”的交叉理解方式,正是读懂操作系统内核的不二法门。当你把这几个大模块吃透,再回看整本《Linux 内核完全剖析》时,你内心的底色就不会再是恐惧,而是一种“这就是我走过的疆土”的巨大成就感。

    赞(0)
    未经允许不得转载:171主机测评 » linux内核体系结构009:内核深处:堆栈的华丽跃迁与城池的宏大蓝图
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址