ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入Linux 0.11引导启动:从实模式到保护模式的内核加载全解析

深入Linux 0.11引导启动:从实模式到保护模式的内核加载全解析 1. 项目概述从零启动一个操作系统内核如果你对计算机底层感兴趣想亲手触摸到操作系统最原始的脉搏那么从零开始剖析一个经典内核的引导启动过程无疑是最好的一课。Linux 0.11这个由Linus Torvalds在1991年发布的早期版本代码量适中结构清晰是无数人学习操作系统原理的“启蒙老师”。而“引导启动程序boot”正是这个庞大系统被唤醒的第一个动作它决定了计算机从按下电源键到屏幕出现字符之间究竟发生了什么。这个项目或者说这个实验常被称为HITOSLAB实验一核心目标就是深入Linux 0.11内核的引导扇区代码理解计算机加电后CPU如何从实模式切换到保护模式如何将内核从磁盘加载到内存并最终将控制权交给内核的main函数。这不仅仅是读几行汇编代码更是理解现代计算机体系结构、x86 CPU工作模式以及操作系统自举原理的绝佳实践。无论你是计算机专业的学生还是对底层技术充满好奇的开发者通过这个实验你都能获得对“程序如何运行在裸机之上”这个问题最直观、最深刻的答案。2. 核心需求与实验目标拆解这个实验看似只是分析一段512字节的引导程序但其背后串联的知识体系非常庞大。我们不能仅仅满足于“代码能跑通”更要理解每一个字节、每一条指令背后的设计意图和硬件原理。2.1 理解计算机加电后的初始状态在深入代码之前我们必须搞清楚我们的“舞台”是什么。当x86 PC加电或复位后CPU处于一种被称为**实模式Real Mode**的古老状态。在这个状态下CPU只能访问最低端的1MB物理内存地址范围0x00000到0xFFFFF。没有虚拟内存、没有内存保护、没有特权级概念。CPU从物理地址0xFFFF0CS:IP 0xF000:0xFFF0开始执行指令这个位置通常映射到主板ROM中的BIOSBasic Input/Output System程序。BIOS会进行一系列硬件自检POST然后按照预设的启动顺序如软盘、硬盘、光盘、网络寻找可启动设备。对于硬盘BIOS会读取其第一个扇区512字节即0柱面0磁头1扇区这个扇区被称为主引导记录MBR。BIOS将这512字节内容加载到物理内存地址0x7C00处然后跳转到那里开始执行。这512字节就是我们的引导程序bootsect.s。理解这个“约定俗成”的地址0x7C00是分析所有引导代码的起点。2.2 引导程序的三大核心任务基于上述硬件约定我们的引导程序bootsect.s需要完成三个核心任务这也是本实验要逐行分析的将自己从0x7C00搬运到0x90000这是为了给后续加载的操作系统内核腾出低端内存空间。0x7C00到0x90000之间有大约500KB的空隙足够存放后续的setup模块和内核。加载操作系统的第二个部分setup模块到内存setup模块setup.s负责获取机器参数如内存大小、显示模式并完成从实模式到保护模式的切换。bootsect需要从磁盘上将其读入内存的指定位置通常是0x90200。加载操作系统内核system模块到内存这是最主要的部分。内核映像由head.s和主要C代码编译链接而成被加载到内存地址0x10000处。在Linux 0.11中这里有一个精巧的设计它先加载到0x10000然后在适当的时候再移动到0x0000。2.3 实验的延伸目标从理解到修改对于HITOSLAB这类实验分析理解原始代码是第一步。更深入的一步是进行修改和验证例如修改bootsect.s中打印的启动提示信息。调整内核加载的内存位置。甚至尝试支持从不同的设备如虚拟软盘镜像启动。 通过动手修改、重新编译、运行测试你才能确信自己真正理解了每一行代码的作用而不是停留在“好像懂了”的层面。3. 环境搭建与工具链准备工欲善其事必先利其器。分析一个三十年前为特定硬件编写的汇编代码我们需要一个能够准确模拟当时环境的工具链。3.1 选择模拟器Bochs vs QEMU我们需要一个x86硬件模拟器来运行我们的内核。两个最流行的选择是Bochs和QEMU。Bochs这是一个高度可配置、专注于精确模拟x86硬件包括芯片组、BIOS的模拟器。它的调试功能极其强大可以单步执行汇编指令、查看任意内存地址和寄存器状态。对于操作系统内核学习尤其是需要深入跟踪硬件状态变化的场景Bochs几乎是首选。它的缺点是速度相对较慢。QEMU这是一个快速的处理器模拟器支持多种架构。它通常使用动态翻译来获得接近原生的速度非常适合快速启动和测试。其调试功能通过GDB stub也很强大但在某些硬件状态细节的呈现上不如Bochs直观。对于Linux 0.11的学习我强烈推荐使用Bochs。因为它能提供一个更“纯粹”、更符合当年硬件环境的模拟并且其内置的调试器是理解引导过程的神器。你可以在Ubuntu/Debian上通过sudo apt-get install bochs bochs-sdl bochsbios vgabios来安装。3.2 获取并准备Linux 0.11源码Linux 0.11的源码很容易在网上找到。一个经典的版本是linux-0.11.tar.gz。解压后你主要需要关注以下几个目录和文件boot/引导程序目录包含bootsect.s和setup.s。kernel/内核核心代码。tools/包含构建工具如build.c用于生成最终的磁盘镜像。Makefile项目的编译构建脚本。在开始之前你可能需要根据现代编译环境做一些微小调整比如将汇编器as和链接器ld的旧版本参数进行更新。不过网上已有大量适配好的版本例如“Linux 0.11实验环境”包它通常已经配置好了编译脚本和Bochs配置文件开箱即用。3.3 配置Bochs运行环境Bochs需要一个配置文件通常命名为bochsrc或bochsrc.bxrc来定义虚拟机的硬件参数。一个针对Linux 0.11的最小化配置关键部分如下# bochsrc for Linux 0.11 megs: 16 # 模拟16MB内存对0.11来说绰绰有余 romimage: file/usr/share/bochs/BIOS-bochs-latest vgaromimage: file/usr/share/bochs/VGABIOS-lgpl-latest floppya: 1_44Image, statusinserted # 使用编译生成的Image文件作为软盘A boot: a # 从软盘A启动 log: bochsout.txt panic: actionask error: actionreport info: actionreport debug: actionignore这里的关键是floppya它指定了我们的启动镜像文件。Image文件是由源码编译后通过tools/build.c程序将bootsect、setup和system三个模块依次写入一个软盘镜像文件的前部生成的。Bochs会把这个文件当作一张虚拟软盘来读取。注意确保Image文件的路径在配置文件中正确。第一次运行时Bochs可能会提示找不到键盘映射表等根据提示安装相应包或调整配置即可。4. 引导启动程序bootsect.s逐行精析现在让我们穿上“显微镜”深入到bootsect.s这个仅有512字节的汇编文件中。我将结合代码、注释和硬件原理进行讲解。4.1 第一阶段自举与重定位! bootsect.s (Linux 0.11) .globl begtext, begdata, begbss, endtext, enddata, endbss .text begtext: .data begdata: .bss begbss: .text SETUPLEN 4 ! setup.s占用的扇区数nr of setup-sectors BOOTSEG 0x07c0 ! bootsect的原始地址BIOS加载地址 INITSEG 0x9000 ! 我们将把bootsect移到这里 SETUPSEG 0x9020 ! setup.s将被加载到这里 SYSSEG 0x1000 ! system模块加载到0x1000064KB处 ENDSEG SYSSEG SYSSIZE ! 停止加载的段地址 entry start start: mov ax,#BOOTSEG mov ds,ax ! ds 0x07c0 mov ax,#INITSEG mov es,ax ! es 0x9000 mov cx,#256 ! 计数器移动256个字512字节 sub si,si ! 源偏移 si 0 sub di,di ! 目的偏移 di 0 rep movw ! 重复执行movw直到cx0 jmpi go,INITSEG ! 段间跳转CSINITSEG, IPgo代码解读与原理定义常量SETUPLEN、BOOTSEG等定义了内存布局的关键地址。注意在实模式下段寄存器如CS、DS的值需要左移4位再加上偏移地址才能得到物理地址。所以BOOTSEG0x07c0对应的物理地址是0x07c0 4 0x7c00。搬运自身rep movw是核心。ds:si指向源0x7c00es:di指向目标0x90000。cx256表示移动256次每次移动一个字2字节总计512字节。这是一次内存到内存的块复制。跳转到新位置jmpi go, INITSEG。这条指令执行后CS被设置为INITSEG0x9000IP被设置为标号go的偏移地址。CPU接下来将从CS:IP即0x9000:go处取指执行也就是我们刚刚搬运过去的代码的go标签处。至此bootsect成功将自己从0x7c00搬运到了0x90000并继续执行。实操心得为什么是jmpi而不是jmp因为jmp是段内跳转只改变IP。而我们需要同时改变CS和IP切换到新的代码段所以必须用jmpi跳转至中断这里指段间跳转。这是实模式编程的一个关键点。4.2 第二阶段加载setup模块执行流跳转到go标签后代码继续go: mov ax,cs ! 此时cs0x9000 mov ds,ax ! 让ds, es, ss都指向我们所在的段0x9000 mov es,ax ! 设置栈指针sp。栈向低地址增长我们将其设在0x9ff00处离代码有足够空间。 mov ss,ax mov sp,#0xFF00 ! 任意一个足够大的值如0x9FF00 ! 接下来使用BIOS中断0x13来加载setup.s load_setup: mov dx,#0x0000 ! 驱动器0DL磁头0DH mov cx,#0x0002 ! 柱面0CH扇区2CL注意扇区号从1开始 mov bx,#0x0200 ! 缓冲区ES:BX 0x9000:0x0200 - 物理地址0x90200 mov ax,#0x0200SETUPLEN ! AH0x02读扇区AL扇区数SETUPLEN4 int 0x13 ! 调用BIOS磁盘服务 jnc ok_load_setup ! 如果成功CF0则跳转 mov dx,#0x0000 ! 如果失败复位磁盘驱动器 mov ax,#0x0000 int 0x13 j load_setup ! 重试代码解读与原理初始化段寄存器与栈将DS、ES、SS都设置为CS的值0x9000这样数据段、附加段和栈段都与代码段重合简化了地址计算。栈指针SP被设置为0xFF00这意味着栈顶在0x9FF00栈向下低地址增长。BIOS磁盘读取这是引导程序与硬件交互的关键。通过int 0x13调用BIOS的磁盘服务。AH0x02功能号表示“读扇区”。AL要读取的扇区数量这里是SETUPLEN4。CH柱面号0。CL扇区号2。注意BIOS的扇区编号从1开始所以2表示磁盘上的第2个扇区紧挨着引导扇区。DH磁头号0。DL驱动器号0表示第一个软盘驱动器。ES:BX数据缓冲区地址。ES0x9000BX0x0200所以物理地址是0x90200。这正是为setup模块预留的位置。错误处理读取成功后进位标志CF会被清零jnc跳转。如果失败CF1则先尝试复位磁盘驱动器AH0x00然后重试。这种简单的重试机制在早期引导程序中很常见。4.3 第三阶段加载system模块与获取磁盘参数成功加载setup后代码继续加载内核的system模块并获取磁盘参数以备后用。ok_load_setup: ! 获取磁盘驱动器参数特别是每个磁道的扇区数后续加载system会用到。 mov dl,#0x00 ! 驱动器0 mov ax,#0x0800 ! AH8获取驱动器参数 int 0x13 mov ch,#0x00 ! 清空CX的高位柱面数 and cl,#0x3f ! 取CL的低6位即每磁道扇区数 mov sectors,cx ! 保存每磁道扇区数到变量sectors mov ax,#INITSEG mov es,ax ! 恢复ES为0x9000 ! 在屏幕上打印信息“Loading system ...” mov ah,#0x03 ! 功能3读取光标位置 xor bh,bh ! 页号0 int 0x10 mov cx,#24 ! 字符串长度 mov bx,#0x0007 ! 页号0属性7浅灰色 mov bp,#msg1 ! 字符串地址 mov ax,#0x1301 ! 功能13写字符串并移动光标 int 0x10 ! 现在开始加载system模块 mov ax,#SYSSEG ! SYSSEG 0x1000 mov es,ax ! ES 0x1000目标段 call read_it ! 调用子程序read_it call kill_motor ! 关闭软驱马达 jmpi 0,SETUPSEG ! 跳转到setup.s开始执行0x9020:0代码解读与原理获取磁盘参数int 0x13, AH0x08。返回的参数中CL寄存器的低6位包含了每磁道的扇区数。这个值被保存到变量sectors中因为后续的read_it子程序需要知道这个参数来计算读取位置避免跨磁道时出错。打印信息通过int 0x10BIOS视频服务在屏幕上显示“Loading system ...”。这提供了基本的用户反馈是操作系统启动过程中我们看到的第一行信息。加载system模块这是最复杂的部分被封装在read_it子程序中。它需要将内核的system模块从磁盘连续读取到内存0x10000ES:0开始的位置。由于内核可能很大超过64KBread_it需要处理多次磁盘读取并妥善管理ES段寄存器每次加载64KB后ES需要增加0x1000。收尾与跳转加载完成后kill_motor子程序发送命令关闭软驱马达省电并减少噪音。最后jmpi 0, SETUPSEG这条指令进行了一次关键的跳转CS被设为SETUPSEG0x9020IP被设为0。这意味着CPU将跳转到物理地址0x90200处执行——这正是我们刚刚加载的setup.s代码的起始位置。至此bootsect.s的使命全部完成控制权移交给了setup.s。4.4read_it子程序内核加载的核心read_it是bootsect.s中最长的子程序其逻辑是循环读取磁盘直到将整个system模块加载完毕。其核心伪代码如下read_it: mov ax, es ! 检查当前ES是否已超过ENDSEG加载结束边界 cmp ax, #ENDSEG jb ok1_read ret ! 如果已加载完毕则返回 ok1_read: ! 根据当前已加载的段地址(ES)计算对应的磁头、柱面、起始扇区 ! 这个计算依赖于之前保存的每磁道扇区数(sectors) ! 计算公式是线性的逻辑扇区号 (ES - SYSSEG) * 32 因为每个段64KB/512字节128扇区这里需要根据代码具体计算 ! 实际上Linux 0.11的build工具在生成Image时已经将system模块放在连续的扇区中。 ! 计算本次读取的扇区数不能跨磁道读取。 ! 如果本次请求的起始扇区欲读扇区数 每磁道扇区数则只能读到当前磁道结束。 mov ax, #sectors sub ax, [当前起始扇区] mov [本次读取扇区数], ax ! 调用read_track子程序读取一个磁道的数据 call read_track ! 读取完成后调整ES目标内存地址和起始扇区号 ! 如果起始扇区号归零说明进入下一个磁头或柱面 ! 循环直到全部加载完成。read_track子程序则封装了具体的int 0x13, AH0x02调用。这里有一个关键细节错误处理。在读取磁盘时可能会遇到可恢复的错误如磁头未就位。read_track中通常包含一个重试循环例如尝试5次如果都失败才报错。注意事项理解read_it的关键在于理解实模式下的“段:偏移”地址与磁盘“柱面/磁头/扇区”CHS寻址之间的换算关系。虽然现代操作系统使用LBA逻辑块地址但BIOS时代必须使用CHS。bootsect.s中的换算逻辑是早期操作系统与硬件直接对话的经典范例。5. setup.s承上启下的桥梁当bootsect跳转到0x90200后setup.s开始执行。它的工作比bootsect更复杂承担着从实模式到保护模式切换的重任。5.1 获取并保存硬件参数setup.s做的第一件事是利用BIOS中断获取机器系统数据并保存在内存0x90000-0x901FF这块原本由bootsect使用的区域bootsect已搬走可复用。这些参数包括光标位置、扩展内存大小1MB的部分、显示页面等。硬盘参数表这对后续内核访问硬盘至关重要。这些参数通过int 0x15、int 0x10、int 0x11、int 0x12、int 0x16等BIOS调用获得并保存在内存的固定位置以便内核的C代码在初始化时读取。5.2 将system模块移动到0x0000这是一个关键操作。在bootsect中我们将system模块加载到了0x1000064KB处。现在setup.s需要将它移动到物理内存最低端0x0000。为什么历史兼容性实模式下中断向量表IVT位于0x0000到0x03FF。在进入保护模式后我们会使用全新的中断描述符表IDT这个区域就可以被覆盖用于存放内核代码。简化寻址内核代码和数据从物理地址0开始使得线性地址到物理地址的映射在初期非常简单直接。 移动是通过rep movsw指令块完成的目标地址是0x0000源地址是0x10000。5.3 切换到保护模式这是setup.s最核心、最精彩的部分。保护模式开启了现代操作系统内存管理、特权级保护的大门。切换步骤是标准化的关闭中断cli。在切换关键数据结构如GDT时不能被中断打断。加载全局描述符表寄存器GDTRlidt idt_48 ! 先加载一个空的IDT因为目前还没启用中断 lgdt gdt_48 ! 加载GDTgdt_48是一个6字节的数据结构包含GDT的线性基地址32位和界限16位。GDT全局描述符表定义了内存段的属性是保护模式的基石。Linux 0.11的初始GDT非常简单只包含几个条目空描述符、内核代码段、内核数据段。打开A20地址线这是一个历史遗留问题。早期8086/8088 CPU只有20根地址线可寻址1MB。为了兼容第21根地址线A20默认被关闭。在保护模式下我们需要访问超过1MB的内存必须打开它。方法是通过向键盘控制器8042发送命令。call empty_8042 mov al,#0xD1 out #0x64,al call empty_8042 mov al,#0xDF out #0x60,al call empty_8042设置保护模式标志将控制寄存器CR0的第0位PE位设置为1。mov ax,#0x0001 lmsw ax ! 加载机器状态字MSW实际上就是设置CR0的低16位执行lmsw指令后CPU正式进入保护模式。但此时CS的值还是实模式下的段选择子0x9020左移4位是基地址我们需要一个段间跳转来让CPU加载新的段描述符到CS。跳转到保护模式下的代码jmpi 0,8 ! 跳转到段选择子8偏移0处这里的8是段选择子。在GDT中索引为1的条目因为索引从0开始8 1*8 0其中RPL0对应的是内核代码段描述符。这个跳转指令会让CPU用选择子8去GDT中查找对应的描述符加载到CS寄存器并跳转到描述符定义的基地址在Linux 0.11的初始GDT中内核代码段基地址是0加上偏移0处也就是物理地址0x0000。而那里正是我们刚刚移动过去的system模块的头部也就是head.s的起点。6. head.s保护模式下的初始化head.s是用ATT语法编写的汇编代码运行在32位保护模式下。它主要完成以下工作重新设置GDT和IDTsetup中设置的GDT/IDT是临时的。head.s会设置更完整的GDT并初始化IDT虽然中断处理函数暂时都是空的。设置页目录和页表开启分页机制这是现代操作系统虚拟内存的核心。head.s会在物理内存中创建页目录表和4个页表将线性地址0-16MB映射到相同的物理地址0-16MB恒等映射。然后设置CR3寄存器指向页目录并设置CR0的PG位开启分页。设置栈指针为内核代码的执行设置一个栈。调用C语言主函数在完成所有底层初始化后head.s使用call main指令跳转到内核的C语言入口函数main()在init/main.c中。至此汇编语言的舞台彻底落幕操作系统内核的C代码时代正式开始。7. 实验操作、调试与问题排查理解了原理动手实验才能巩固知识。以下是在HITOSLAB实验一中可能遇到的操作和问题。7.1 编译与运行步骤假设你已有一个配置好的Linux 0.11实验环境例如赵炯博士的《Linux内核完全注释》附带环境解压与进入目录tar zxf linux-0.11.tar.gz cd linux-0.11编译在源码根目录执行make。这个命令会编译bootsect.s、setup.s为二进制文件。编译head.s和所有内核C代码链接成system模块。使用tools/build.c工具将bootsect、setup、system依次写入Image文件并确保bootsect正好512字节且末尾两个字节是启动标志0xAA55。运行确保Bochs配置文件bochsrc中的floppya指向正确的Image文件路径然后运行bochs -f bochsrc。如果一切正常Bochs窗口会显示“Loading system ...”然后出现一堆内核初始化信息。7.2 使用Bochs进行调试Bochs的强大之处在于其调试功能。你可以在配置文件中启用调试器或者运行时按CtrlC中断。查看寄存器在Bochs调试命令行输入r或reg。查看内存x /nuf addr。例如x /40bx 0x7c00查看0x7c00处开始的40个字节以十六进制显示。反汇编u start end或u /num。例如u 0x7c00从0x7c00开始反汇编。单步执行s或step。n或next跳过子程序调用。设置断点vb seg:off虚拟地址断点或lb addr线性地址断点。例如vb 0x7c00在bootsect开始处断点。连续执行c。一个经典的调试流程在bochsrc中设置magic_break: enabled1。在bootsect.s的start:标签后添加一行汇编指令xchg %bx, %bx。在Bochs中这条指令是“魔术断点”执行到此会自动进入调试器。重新编译运行Bochs会在最开始暂停此时你可以单步跟踪每一条指令观察寄存器和内存的变化直观地理解引导过程。7.3 常见问题与解决方案实录在实验过程中你几乎一定会遇到以下问题问题现象可能原因排查思路与解决方案make编译失败提示as86或ld86找不到缺少古老的8086汇编器/链接器安装bin86包sudo apt-get install bin86。或者使用实验环境提供的已编译好的工具链。make时build工具编译/执行出错build.c与现代GCC不兼容检查build.c源码通常需要将void main()改为int main()并在文件开头添加#include unistd.h。网上有大量已修复的版本。Bochs启动后黑屏无任何输出1.Image文件未正确生成。2. Bochs配置文件中磁盘镜像路径错误。3. 模拟的磁盘类型不对应为1.44MB软盘。1. 检查make输出是否有错误确认Image文件大小合理100KB。2. 使用绝对路径指定floppya。3. 确保配置中是1_44Image。屏幕显示“Loading system ...”后卡死或乱码1.system模块编译有问题。2.read_it子程序加载内核时磁盘读取错误。3. 内存移动到0x0000出错。1. 使用Bochs调试器在read_it和移动内存的代码处设断点检查读取的扇区数、目标地址是否正确。2. 检查tools/build.c看它是否正确计算了system模块的大小并写入Image。进入保护模式后立即崩溃Triple fault1. GDT设置错误基地址、界限、属性。2. 进入保护模式后的跳转指令jmpi 0,8中的选择子错误。3. A20地址线未成功开启。1. 在setup.s的lgdt gdt_48后暂停使用Bochs命令info gdt检查GDT内容是否正确。2. 单步执行到lmsw ax之后检查CR0的PE位是否为1。3. 检查A20开启代码确保empty_8042子程序能正确等待键盘控制器空闲。修改bootsect.s中的提示信息后编译运行无变化修改后未重新编译或未正确生成Image1. 执行make clean然后make彻底重新编译。2. 确认修改的字符串长度未超过原字符串否则需要调整打印代码mov cx,#长度。实操心得调试汇编的思维调试引导程序不同于调试高级语言。你需要密切关注1) **段寄存器(CS, DS, ES, SS)和指令指针(IP/EIP)**的变化2) **标志寄存器(EFLAGS)**的状态特别是进位CF、零标志ZF3)关键内存区域如0x7c00, 0x90000, 0x90200, 0x10000的内容。在Bochs中善用x命令查看内存用u命令反汇编当前指令是定位问题的关键。8. 实验的深度扩展思考完成基础实验后你可以尝试以下扩展这将极大深化你的理解修改启动信息这是最简单的扩展。找到bootsect.s中msg1处的字符串“Loading system ...”将其改为你自己的信息如“Hello, OS World!”。注意字符串长度和mov cx,#24这行代码中的长度值需要匹配。研究tools/build.c这个工具是连接编译和启动的桥梁。它决定了bootsect、setup、system在Image文件中的布局。阅读其源码理解它如何计算system模块的大小并将其写入bootsect.s中的一个变量在编译时替换这对于理解整个构建流程至关重要。尝试从硬盘启动Linux 0.11默认配置为从软盘启动。研究如何修改代码和Bochs配置使其从一个硬盘镜像如c.img启动。这涉及到引导扇区代码识别硬盘分区表MBR结构并加载活动分区引导记录等更复杂的过程。探究更现代的实现对比现代GRUB等引导加载器理解它们如何支持多重引导规范、文件系统识别、图形界面等复杂功能。你会发现其核心原理——加载自身、读取磁盘、切换保护模式——与这个简单的bootsect.s一脉相承。通过这个实验你收获的不仅仅是一段汇编代码的分析能力更是对计算机从加电到操作系统接管整个硬件的完整链路的深刻洞察。这种从“裸机”角度理解系统的能力是区分普通应用开发者和资深系统开发者的重要标志。当你下次看到“Loading system...”时你脑海中浮现的将是CPU寄存器、内存地址、磁盘扇区与机器指令交织而成的壮丽图景。
返回列表