Linux内核完全注释之引导启动程序(boot)

本章包含了三个汇编文件,本部分主要介绍bootsect.s.

 

目录

 

3.1 概述

3.2 总体功能

3.3 bootsect.s 程序

3.3.1 功能描述

3.3.2 代码片段分析

3.3.3 整个代码流程

3.3.4 附相关源代码中文注释版


 

 

3.1 概述

       本章主要描述 boot/目录中的三个汇编代码文件,见列表 3-1 所示。正如在前一章中提到的,这三个文件虽然都是汇编程序,但却使用了两种语法格式。bootsect.s 和 setup.s 采用近似于Intel 的汇编语言语法,需要使用Intel 8086 汇编编译器和连接器 as86 和 ld86,而 head.s 则使用 GNU 的汇编程序格式,并且运行在保护模式下,需要用GNU 的 as 进行编译。这是一种 AT&T 语法的汇编语言程序。使用两种编译器的主要原因是由于对于Intel x86处理器系列来讲,GNU的编译器仅支持i386及以后出的CPU。不支持生成运行在实模式下的程序。

        阅读这些代码除了你需要知道一些一般8086汇编语言的知识以外,还要对采用 Intel 80X86 微处理器的PC机的体系结构以及 80386 32位保护模式下的编程原理有些了解。

3.2 总体功能

       这里先总的说明一下 Linux 操作系统启动部分的主要执行流程。当 PC 的电源打开后,80x86 结构的 CPU 将自动进入实模式,并从地址 0xFFFF0 开始自动执行程序代码,这个地址通常是ROM-BIOS 中的地址。PC 机的 BIOS 将执行某些系统的检测,并在物理地址 0 处开始初始化中断向量。此后,它将可启动设备的第一个扇区(磁盘引导扇区,512 字节)读入内存绝对地址 0x7C00 处,并跳转到这个地方。启动设备通常是软驱或是硬盘。这里的叙述是非常简单的,但这已经足够理解内核初始化的工作过程了。 

       Linux 的前面部分是8086 汇编语言编写的(boot/bootsect.s),它将由BIOS读入到内存绝对地址0x7C00(31KB)处,当它被执行时就会把自己移到绝对地址 0x90000(576KB)处,并把启动设备中后2kB 字节代码(boot/setup.s)读入到内存 0x90200 处,而内核的其它部分(system 模块)则被读入到从地址0x10000开始处,因为当时 system模块的长度不会超过0x80000字节大小(即 512KB),所以它不会覆盖在0x90000处开始的bootsect 和setup 模块。后面 setup 程序将会把 system 模块移动到内存起始处,这样system 模块中代码的地址也即等于实际的物理地址,便于对内核代码和数据的操作。图 3-1 清晰地显示出Linux系统启动时这几个程序或模块在内存中的动态位置。其中,每一竖条框代表某一时刻内存中各程序的映像位置图。在系统加载期间将显示信息"Loading..."。然后控制权将传递给 boot/setup.s中的代码,这是另一个实模式汇编语言程序。

         启动部分识别主机的某些特性以及vga卡的类型。如果需要,它会要求用户为控制台选择显示模式。然后将整个系统从地址 0x10000 移至 0x0000 处,进入保护模式并跳转至系统的余下部分(在 0x0000 处)。此时所有32位运行方式的设置启动被完成: IDT、GDT 以及 LDT 被加载,处理器和协处理器也已确认,分页工作也设置好了;最后调用init/main.c中的main()程序。上述操作的源代码是在 boot/head.S 中的,这可能是整个内核中有诀窍的代码了。注意如果在前述任何一步中出了错,计算机就会死锁。在操作系统还没有完全运转之前是处理不了出错的。 为什么不把系统模块直接加载到物理地址 0x0000 开始处而要在 setup 程序中再进行移动呢?这是因 为在 setup 程序代码开始部分还需要利用 ROM BIOS 中的中断调用来获取机器的一些参数(例如显示卡 模式、硬盘参数表等)。当 BIOS 初始化时会在物理内存开始处放置一个大小为0x400 字节(1Kb)的中断向量表,因此需要在使用完 BIOS 的中断调用后才能将这个区域覆盖掉。

3.3 bootsect.s 程序

3.3.1 功能描述

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

3.3.2 代码片段分析

bootsec.s代码移动到ox9000中:


mov ax,#BOOTSEG

mov ds,ax

mov ax,#INITSEG

mov es,ax

mov cx,#256

sub si,si

sub di,di

rep

movsw

关于cx的值,由于是要移动一个扇区的数据,一个扇区的大小是512B,这里的cx=256,但是movsw表示移动两个B,所以256*2=512B。

关于load_setup:


mov dx,#0x0000

mov cx,#0x0002

mov bx,#0x0200

mov ax,#0200+SETUPLEN

int ox13

仔细看int 0x13这个中断的各个参数,这里比较重要的是es:bx 将指向setup模块的在内存中的位置。在移动bootsec.s的代码最后有一个jmpi go,INITSEG这个指令,此时的cs=0x900不再是0x7c00了,es=cs,于是es:bx指向了0x900:0200处,从而实现了装载setup模块的功能。

关于ok_load_setup:


seg cs

mov sectors,cx

 这里主要是来谈谈这两条指令。从代码的第241行中可以看出sectors是个标量指向一个word长的地址。seg cs表明了sectors的段地址是cs,而不是ds。而且seg cs 的作用范围只有下一行,不会延生到其他地方。

最后介绍这篇文章中最重要的read_it,她主要的功能是读磁盘上 system 模块,用es作为输入参数:

read_it:	
        mov ax,es	
        test ax,#0x0fff
die:	
        jne die			! es must be at 64kB boundary	
        xor bx,bx		! bx is starting address within segment
rp_read:	
        mov ax,es	
        cmp ax,#ENDSEG		! have we loaded all yet?	
        jb ok1_read	
        ret
ok1_read:	
        seg cs	
        mov ax,sectors	
        sub ax,sread	
        mov cx,ax	
        shl cx,#9	
        add cx,bx	
        jnc ok2_read	
        je ok2_read	
        xor ax,ax	
        sub ax,bx	
        shr ax,#9
ok2_read:	
        call read_track	
        mov cx,ax	
        add ax,sread	
        seg cs	
        cmp ax,sectors	
        jne ok3_read	
        mov ax,#1	
        sub ax,head	
        jne ok4_read	
        inc track
ok4_read:	
        mov head,ax	
        xor ax,ax
ok3_read:	
        mov sread,ax	
        shl cx,#9	
        add bx,cx	
        jnc rp_read	
        mov ax,es	
        add ax,#0x1000	
        mov es,ax	
        xor bx,bx	
        jmp rp_read 
read_track:	
        push ax	
        push bx	
        push cx	
        push dx	
        mov dx,track	
        mov cx,sread	
        inc cx	
        mov ch,dl	
        mov dx,head	
        mov dh,dl	
        mov dl,#0	
        and dx,#0x0100	
        mov ah,#2	
        int 0x13	
        jc bad_rt	
        pop dx	
        pop cx	
        pop bx	
        pop ax	
        ret
bad_rt:	
        mov ax,#0	
        mov dx,#0	
        int 0x13	
        pop dx	
        pop cx	
        pop bx	
        pop ax	
        jmp read_track

1.ok1_read:

主要的功能是确定了ax的值,其实严格的说是确定了al的值,因为al的值是代表了要读取的扇区的值,关于最后的几行代码 


xor ax,ax

sub ax,bx

shr ax,#9

这几行代码是以后执行的,到时后bx的值不再是0,而是代表了一个段内读取了的数据,sub是不带进位的0-bx正好是64k-段内已读的数据(不得不佩服linus的基本功),从而得到了段内剩余的空间,从而可以求出还可以读取最大的扇区数。

2.ok2_read:

调用了read_track(),read_track()的功能其实可以看成read_track(ax),根据ax,主要是al来确定一个磁道内从哪个扇区开始读数据,从而读取一个磁道的数据;然后ax=read_track(ax)(伪代码而已),ax表示读取的扇区的值,然后再加上已读的扇区数后比较是否等于每个磁道的扇区数,如果不等,则调用ok3_read(),否则读取下一个磁头1,(软盘有两个磁头,0和1,这和硬盘不一样,硬盘磁头比较多)

3.ok4_read:

ok_read4的主要的功能是重新赋值磁头和ax即扇区的起始地址。

4.ok3_read:

主要是确定了bx的值和es的值。


mov sread,ax

shl cx,#9

add bx,cx

jnc rp_read

mov ax,es

add ax,#0x1000

mov es,ax

xor bx,bx

jmp rp_read

这里add bx,cx 是确定了bx的值,即段内已经读取的数据大小,如果bx没有溢出即超过0xffff,则跳转到开始的循环,否则段基址es+=0x1000,再重新循环。

3.3.3 整个代码流程

由于软盘只有两个磁头0和1,且只有18个扇区,每个扇区的大小是512B从而代码开始处的流程应该是如下的:

1.在ok1_read中从0磁头读取的数目最多(18-6)*512B,不会溢出,则会进入到ok2_read()中;如果bx不等于0,且数值比较接近0xffff时会溢出,则确定该段内剩余的地址可以读取最大的扇区数;

2.到ok2_read中,从0磁头读取从能够ax开始的整个磁道剩余的扇区数,cx=读取的扇区数,在加上已读的扇区数确定是否全部读完,若是则下一步,否则到4;

3.到ok4_read,磁头变成了1,(只有两个磁头,如果原磁头是1,则增加磁道track),ax=0。

4.到ok3_read中,此时ok3_read的操作是针对下一次循环的,根据这一次的循环设置下一次循环的一些变量,bx=段内已经读取的数据的大小,如果超出64k则增加段基址,由于add是无进位的(这里又体现了linus的高明了);

5.重新返回到1,执行循环,知道es的值>ENDSEG;

源代码中先是ok4_read了,而后是ok3_read,这是因为如果在源代码中把ok3_read放在ok4_read之前那么代码会多加几个jmp命令。

3.3.4 附相关源代码中文注释版

链接:https://pan.baidu.com/s/1P-rM8bDpnSmGN4yUF7vZXA 
提取码:ecv6 
 

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值