赵炯Linux内核完全解密读书笔记

赵炯Linux内核完全解密读书笔记,该笔记内容从BIOS开始执行到main函数执行之前的过程

Linux内核主要由5个模块构成:

进程调度模块、内存管理模块、文件系统模块、进程间通信模块和网络接口模块。

进程调度模块用来负责控制进程对CPU资源的使用。所采取的调度策略是各进程能够公平合理地访问CPU,同时保证内核能及时地执行硬件操作。内存管理模块用于确保所有进程能够安全地共享机器主内存区,同时,内存管理模块还支持虚拟内存管理方式,使得Linux支持进程使用比实际内存空间更多的内存容量。并可以利用文件系统把暂时不用的内存数据块会被交换到外部存储设备上去,当需要时再交换回来。文件系统模块用于支持对外部设备的驱动和存储。虚拟文件系统模块通过向所有的外部存储设备提供一个通用的文件接口,隐藏了各种硬件设备的不同细节。从而提供并支持与其它操作系统兼容的多种文件系统格式。进程间通信模块子系统用于支持多种进程间的信息交换方式。网络接口模块提供对多种网络通信标准的访问并支持许多网络硬件。

/:

bootsect.s

bootsect.s程序是磁盘引导块程序,编译后会驻留在磁盘的第一个扇区中(引导扇区,0磁道(柱面),0磁头,第1个扇区)。在PC机加电ROMBIOS自检后,将被BIOS加载到内存0x7C00处进行执行。

setup.s程序主要用于读取机器的硬件配置参数,并把内核模块system移动到适当的内存位置处。

head.s程序会被编译连接在system模块的最前部分,主要进行硬件设备的探测设置和内存管理页面的初始设置工作。

bootsect.s代码是磁盘引导块程序,驻留在磁盘的第一个扇区中(引导扇区,0磁道(柱面),0磁头,第1个扇区)。在PC机加电ROMBIOS自检后,引导扇区由BIOS加载到内存0x7C00处,然后将自己移动到内存0x90000处。该程序的主要作用是首先将setup模块(由setup.s编译成)从磁盘加载到内存,紧接着bootsect的后面位置(0x90200),然后利用BIOS中断0x13取磁盘参数表中当前启动引导盘的参数,接着在屏幕上显示“Loadingsystem…”字符串。再者将system模块从磁盘上加载到内存0x10000开始的地方。随后确定根文件系统的设备号,若没有指定,则根据所保存的引导盘的每磁道扇区数判别出盘的类型和种类(是1.44MA盘吗?)并保存其设备号于root_dev(引导块的0x508地址处),最后长跳转到setup程序的开始处(0x90200)执行setup程序。

bootsect.s 代码,即引导扇区程序,执行了以下步骤:

  1. 自我迁移:加电自检后,BIOS将引导扇区加载到内存0x7C00处。bootsect.s 随后将自身代码从0x7C00移动到内存的0x90000处,为操作系统加载腾出空间。

  2. 加载 setup 模块:将磁盘上的 setup 模块(由 setup.s 编译而成)加载到内存中bootsect后面的位置,即0x90200。

  3. 读取磁盘参数:利用BIOS中断0x13,读取磁盘参数表中当前启动引导盘的参数。

  4. 显示加载信息:在屏幕上显示“Loading system…”字符串,提供用户反馈,表明系统正在加载。

  5. 加载 system 模块:从磁盘上将 system 模块加载到内存的0x10000处。这是内核映像的标准加载地址。

  6. 确定根文件系统设备号

    • 如果已指定设备号(root_dev不为0),则直接使用该设备号。
    • 如果未指定,根据引导盘的每磁道扇区数确定盘的类型,并保存相应的设备号到 root_dev(位于引导块的0x508地址处)。
  7. 执行 setup 程序:最后,执行长跳转到 setup 程序的开始处(0x90200),以继续操作系统的启动过程。

通过上述步骤,bootsect.s 代码负责启动引导过程的初期阶段,为操作系统的进一步加载和启动做准备。

setup.s

setup.s程序的作用主要是利用 ROM BIOS 中断读取机器系统数据,并将这些数据保存到 0x90000 开始的位置(覆盖掉了 bootsect 程序所在的地方),所取得的参数和保留的内存位置见下表 3–1 所示。这些参数将被内核中相关程序使用,例如字符设备驱动程序集中的 ttyio.c 程序等。然后 setup 程序将 system 模块从 0x10000-0x8ffff(当时认为内核系统模块 system 的长度不会超过此 值:512KB)整块向下移动到内存绝对地址 0x00000 处。接下来加载中断描述符表寄存器(idtr)和全局描 述符表寄存器(gdtr),开启 A20 地址线,重新设置两个中断控制芯片 8259A,将硬件中断号重新设置为 0x20 - 0x2f。最后设置 CPU 的控制寄存器 CR0(也称机器状态字),从而进入 32 位保护模式运行,并跳 转到位于 system 模块最前面部分的 head.s 程序继续运行。为了能让 head.s 在 32 位保护模式下运行,在本程序中临时设置了中断描述符表(idt)和全局描述符表(gdt),并在 gdt 中设置了当前内核代码段的描述符和数据段的描述符。在下面的 head.s 程序中会 根据内核的需要重新设置这些描述符表。

setup.s 执行了以下主要任务:

  1. 读取系统数据:利用ROM BIOS中断读取计算机的硬件和配置信息,并将这些信息保存到内存地址0x90000处,覆盖了原先的 bootsect 程序所在的位置。

  2. 移动 system 模块:将系统模块(内核映像)从0x10000-0x8ffff的位置整体移动到内存的0x00000处,这是为了准备将控制权交给操作系统。

  3. 加载IDTR和GDTR:设置中断描述符表寄存器(IDTR)和全局描述符表寄存器(GDTR),为进入保护模式做准备。

  4. 开启A20地址线:为了访问超过1MB的内存,启用A20地址线是必要的步骤。

  5. 重新配置8259A中断控制器:将硬件中断号设置为0x20-0x2f,以避免与x86架构的软件中断号冲突。

  6. 进入32位保护模式:设置CPU的控制寄存器CR0,切换CPU到32位保护模式。

  7. 跳转到 head.s:在准备完毕后,跳转到 head.s 程序,这是内核执行的下一个阶段,head.s 在保护模式下运行。

  8. 设置临时的IDT和GDTsetup.s 中还包括了设置中断描述符表(IDT)和全局描述符表(GDT)的代码,其中还包括了当前内核代码段的描述符和数据段的描述符,以确保在切换到32位模式后CPU能够正确解析内核的地址空间。

当前内存映像:

在 setup.s 程序执行结束后,系统模块 system 被移动到物理地址 0x0000 开始处,而从 0x90000 处则存放了内核将会使用的一些系统基本参数,示意图如图 3-3 所示。

此时临时全局表中有三个描述符,第一个是(NULL)不用,另外两个分别是代码段描述符和数据 段描述符。它们都指向系统模块的起始处,也即物理地址 0x0000 处。这样当 setup.s 中执行最后一条指 令 'jmp 0,8 '(第 193 行)时,就会跳到 head.s 程序开始处继续执行下去。这条指令中的’8’是段选择符, 用来指定所需使用的描述符项,此处是指 gdt 中的代码段描述符。'0’是描述符项指定的代码段中的偏移值。

head.s

head.s 程序在被编译后,会被连接成 system 模块的最前面开始部分,这也就是为什么称其为头部(head) 程序的原因。从这里开始,内核完全都是在保护模式下运行了。heads.s 汇编程序与前面的语法格式不同, 它采用的是 AT&T 的汇编语言格式,并且需要使用 GNU 的 gas 和 gld2进行编译连接。因此请注意代码 中赋值的方向是从左到右。

这段程序实际上处于内存绝对地址 0 处开始的地方。这个程序的功能比较单一。首先是加载各个数 据段寄存器,重新设置中断描述符表 idt,共 256 项,并使各个表项均指向一个只报错误的哑中断程序。 然后重新设置全局描述符表 gdt。接着使用物理地址 0 与 1M 开始处的内容相比较的方法,检测 A20 地 址线是否已真的开启(如果没有开启,则在访问高于 1Mb 物理内存地址时 CPU 实际只会访问(IP MOD 1Mb)地址处的内容),如果检测下来发现没有开启,则进入死循环。然后程序测试 PC 机是否含有数学 协处理器芯片(80287、80387 或其兼容芯片),并在控制寄存器 CR0 中设置相应的标志位。接着设置管 理内存的分页处理机制,将页目录表放在绝对物理地址 0 开始处(也是本程序所处的物理内存位置,因 此这段程序将被覆盖掉),紧随后面放置共可寻址 16MB 内存的 4 个页表,并分别设置它们的表项。最 后利用返回指令将预先放置在堆栈中的/init/main.c 程序的入口地址弹出,去运行 main()程序。

head.s 程序在Linux启动过程中负责以下主要任务:

  1. 设置数据段寄存器:加载各个数据段寄存器,为后续程序的运行做准备。

  2. 初始化中断描述符表(IDT):重新设置IDT,共256项,每项都指向一个处理中断的程序。

  3. 重新设置全局描述符表(GDT):根据保护模式的要求,配置全局描述符表。

  4. 检测A20地址线:通过比较物理地址0和1MB处的内容,确保A20地址线已经开启,这是访问高于1MB内存地址的前提条件。

  5. 检测数学协处理器:检测PC机是否含有数学协处理器,并相应设置控制寄存器CR0中的标志位。

  6. 设置分页机制:配置内存分页处理机制,将页目录表放在物理地址0开始处,并设置后续的页表,使其能够寻址最多16MB的内存。

  7. 覆盖自身:由于页目录表被放置在head.s所在的内存位置,因此head.s程序会被覆盖。

  8. 跳转到main()函数:利用返回指令跳转到在/init/main.c程序中定义的main()函数,这标志着内核的高级初始化过程的开始。

head.s 程序在Linux启动过程中负责从实模式到保护模式的过渡,并进行一系列的硬件检测和内存管理设置,为操作系统内核的进一步初始化打下基础。

head.s程序执行结束后的内存映像:

head.s 程序执行结束后,已经正式完成了内存页目录和页表的设置,并重新设置了内核实际使用的中断描述符表 idt 和全局描述符表 gdt。另外还为软盘驱动程序开辟了 1KB 字节的缓冲区。此时 system 模块在内存中的详细映像见图 3-6 所示。

小结

在引导加载程序 bootsect.s 将 setup.s 代码和 system 模块加载到内存中,并且分别把自己和 setup.s 代 码移动到物理内存 0x90000 和 0x90200 处后,就把执行权交给了 setup 程序。其中 system 模块的首部包 含有 head.s 代码。

setup 程序的主要作用是利用 ROM BIOS 的中断程序获取机器的一些基本参数,并保存在 0x90000 开始的内存块中,供后面程序使用。同时把 system 模块往下移动到物理地址 0x00000 开始处,这样,system 中的 head.s 代码就处在 0x00000 开始处了。然后加载描述符表基地址到描述符表寄存器中,为进行 32 位保护模式下的运行作好准备。接下来对中断控制硬件进行重新设置,最后通过设置机器控制寄存器 CR0 并跳转到 system 模块的 head.s 代码开始处,使 CPU 进入 32 位保护模式下运行。

Head.s 代码的主要作用是初步初始化中断描述符表中的 256 项门描述符,检查 A20 地址线是否已经打开,测试系统是否含有数学协处理器。然后初始化内存页目录表,为内存的分页管理作好准备工作。 最后跳转到 system 模块中的初始化程序 init.c 中继续执行。

关于jmpi 0,8

jmpi    0,8  ! jmp offset 0 of segment 8 (cs) ! 跳转至cs段8,偏移0处。

此时我们已经将 system 模块移动到 0x00000开始的地方,所以这里的偏移地址是 0。这里的段值8是保护模式下的段选择符,用于选择描述符表和描述符表项以及所要求的特权级。 段选择符长度为16 位(2 字节);位 0-1 表示请求的特权级0-3,linux 操作系统只用到两级:0 级(系统级)和 3 级(用户级);位 2用于选择全局描述符表(0)还是局部描述符表(1);位 3-15 是描述符表项的索引,指出选择第几项描述符。所以段选择符8(0b0000,0000,0000,1000)表示请求特权级0、使用全局描述符表中的第1项,该项指出代码的基地址是0(参见 209行),因此这里的跳转指令就会去执行 system 中的代码。

  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北纬40度~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值