3、引导启动程序

引导启动程序主要是指/boot目录中的三个汇编文件:

  • bootsect.s
  • head.s
  • setup.s
  • bootsect.s和setup.s采用近似于Intel的汇编语言语法,需要使用Intel 8086汇编编译器和连接器as86和ld86
  • head.s使用GUN的汇编程序格式(AT&T语法),需要用GNU的as进行编译。
  • 这里使用两种编译器的主要原因是由于对于Intel x86处理器系列来讲,GNU的编译器仅支持i386及以后出的CPU,不支持生成运行在实模式下的程序。

Linux启动流程:

  1. PC电源打开后,80x86结构的CPU将自动进入实模式,从地址0xFFFF0开始自动执行程序代码,这个地址通常是ROM-BIOS中的地址;
  2. PC机的BIOS将执行某些系统的检测,并在物理地址0处开始初始化中断向量;
  3. 将可启动设备的第一个扇区(磁盘引导扇区,512字节,对应的是boot/bootsect.s)读入内存绝对地址0x7C00(31KB)处,并跳转到这个地方。
  4. boot/bootsect.s被执行时,会把自己移到绝对地址0x90000(576KB)处,并把启动设备中后2KB字节代码(boot/setup.s)读入到内存0x90200处,
  5. 内核的其他部分(system模块)则被读入到从地址0x10000开始处,当时system模块的长度不会超过0x80000字节大小(512KB),所以它不会覆盖在0x90000处开始的bootsect和setup模块
  6. setup程序将会把system模块移动到内存起始(0x00000)处,这杨system模块中代码的地址也即等于实际的物理地址,便于对内核代码和数据的操作。
  7. 进入保护模式并跳转至系统的余下部分(0x00000),此时所有32位运行方式的设置启动被完成:IDT、GDT以及LDT被加载,处理器和协处理器也已确认,分页工作也设置好了
  8. 调用init/main.c中的main()程序
    在这里插入图片描述

bootsect.s

在as86汇编语言程序中,凡是以感叹号’!’或分号’;’开始的语句其后面均为注释文字。注释语句可以放在任何语句的后面,也可以从一个新行开始。

!
! SYS_SIZE is the number of clicks (16 bytes) to be loaded.
! 0x3000 is 0x30000 bytes = 196kB, more than enough for current
! versions of linux
! SYS_SIZE 是要加载的节数(16字节为1节)。0x3000共为x30000字节=196kB
! 若以1024字节为1KB计,则应该是192KB
SYSSIZE = 0x3000   !指编译连接后system模块的大小,这里给出一个最大的默认值
!
!	bootsect.s		(C) 1991 Linus Torvalds
!
! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
! iself out of the way to address 0x90000, and jumps there.
! bootsect.s被bios-启动子程序加载至0x7c00(31k)处,并将自己移到了地址
! 0x90000(576k)处,并跳转到那里
! It then loads 'setup' directly after itself (0x90200), and the system
! at 0x10000, using BIOS interrupts. 
!  然后使用BIOS中断将‘setup’加载到0x90200,system 加载到地址0x10000处
! NOTE! currently system is at most 8*65536 bytes long. This should be no
! problem, even in the future. I want to keep it simple. This 512 kB
! kernel size should be enough, especially as this doesn't contain the
! buffer cache as in minix
! 注意!目前 system最大长度限制为512KB,即使实在将来这也应该没有问题。我想让他
! 简单明了。这样512KB的内核长度应该足够了,尤其是在这里没有像minix中一样包含缓冲区
! 高速缓冲
! The loader has been made as simple as possible, and continuos
! read errors will result in a unbreakable loop. Reboot by hand. It
! loads pretty fast by getting whole sectors at a time whenever possible.
! 加载程序已经做的够简单了,所以持续的读出错误将导致死循环,只能手工重启
!只要可能,通过一次读取所有的扇区,加载过程可以做的很快
!‘.global‘是汇编指示符(或称为汇编伪指令、伪操作符),用于定义随后的标号标识符
! 是外部的或全局的,并且即使不使用也强制引入。
!汇编指示符均以一个字符’.’开始,并且不会在编译时产生任何代码。
! 汇编指示符由1个伪操作码,后跟0个或多个操作数组成。
! ’global’是一个伪操作码,而其后面的标号’begtext, begdata, begbss’等就是它的操作数。
! 标号是后面带冒号的标识符,例如’begtext:’。但在引用一个标号时无须带冒号。
!’.text’、’.data’、’.bss’。它们分别对应汇编程序编译产生目标文件中的3个段,即正文段、数据段和未初始化数据段。
! ’.text’用于标识正文段的开始位置,并切换到text段;
! ’.data’用于标识数据段的开始位置,并把当前段切换到数据段;
! ’.bss’用于标识未初始化数据段的开始,并把当前段改变成bss段。
!  这里在每个段中定义一个标号,最后再切换到text段开始编写随后的代码。
! 把三个段都定义在同一重叠地址范围中,因此实际上不分段。
.globl begtext, begdata, begbss, endtext, enddata, endbss ! 定义了6个全局标识符
.text         ! 文本段
begtext:
.data         ! 数据段
begdata:
.bss          ! 未初始化数据段(Block Started by Symbol)
begbss:
.text         ! 文本段
!  等号’=’(或符号’EQU’)用于定义标识符BOOTSEG所代表的值,因此这个标识符可称为符号常量。这个值与C语言中的写法一样,可以使用十进制、八进制和十六进制。
SETUPLEN = 4				! nr of setup-sectors setup程序的扇区数
BOOTSEG  = 0x07c0			! original address of boot-sector bootsect的原始地址(段地址)
INITSEG  = 0x9000			! we move boot here - out of the way 将bootsect移到这里
SETUPSEG = 0x9020			! setup starts here 
SYSSEG   = 0x1000			! system loaded at 0x10000 (65536).
ENDSEG   = SYSSEG + SYSSIZE		! where to stop loading 停止加载的段地址

! ROOT_DEV:	0x000 - same type of floppy as boot.
!     根文件系统设备使用与引导时同样的软驱设备
!		0x301 - first partition on first drive etc
!               根文件系统设备在第一个硬盘的第一个分区上
ROOT_DEV = 0x306 ! 指定根文件系统设备是第2个硬盘的第1个分区。这是Linux老式的硬盘命名方式,具体值的含义如下:
                 ! 设备号 = 主设备号*256 + 次设备号 (也即 dev_no = (major<<8) + minor)
                 ! (主设备号 : 1 -内存,2 - 磁盘,3 - 硬盘,4-ttyx,5-tty ,6-并行口,7-非命名管道)
                 ! 0x300 - /dev/hd0 第1个硬盘
                 ! 0x301 - /dev/hd1 第1个硬盘的第1个分区
                 ! ...
                 ! 0x304 - /dev/hd4 第1个盘的第4个分区
                 ! 0x305 - /dev/hd5 第二个硬盘
                 ! 0x306 - /dev/hd6 第二个硬盘的第1个分区
                 ! ...
                 ! 0x309 - /dev/hd9 第2个盘的第4个分区
! 标识符’entry’是保留关键字,用于迫使链接器ld86在生成的可执行文件中包括进其后指定的标号’start’。
entry start ! 告知连接程序,程序从start标号开始执行,指定程序的入口地址
start:
! 注意:程序运行在实模式下面,物理地址=代码段cs*16+偏移,要想在0x7c00处开始执行,要设BOOTSEG=0x07c0,而不是0x7c00。
	mov	ax,#BOOTSEG ! 将ds段寄存器置为 0x7c00
	mov	ds,ax
	mov	ax,#INITSEG ! 将es寄存器置为 0x9000
	mov	es,ax
	mov	cx,#256  ! 移动计数值 = 256 字
	sub	si,si  ! 源地址 ds:si = 0x7c0:0x0000
	sub	di,di  ! 目的地址 es:di = 0x9000:0x0000
	rep        ! 重复操作指令前缀,循环执行movw,且cx递减1,直到cx为0为止
	movw       ! 移动一个字,将DS:SI的16位bit数据复制送至ES:DI
	jmpi	go,INITSEG ! jmpi为段间跳转指令,执行这条指令之后,CS = INITSEG,IP = go
	                   ! 从下面开始,CPU执行已移动到0x9000段处的代码
go:	mov	ax,cs      ! 将ds、es、ss都设置成移动到0x9000段处的代码
	mov	ds,ax      ! 由于程序中有堆栈操作,因此必须设置堆栈
	mov	es,ax
! put stack at 0x9ff00. ! 将堆栈指针sp指向0x9ff00(即0x9000:0xff00处)
	mov	ss,ax
	mov	sp,#0xFF00		! arbitrary value >>512
                        ! 由于代码段移动过了,所以要重新设置堆栈段的位置。
                        ! sp只要执行远大于512偏移(即地址0x90200)处都可以。因为
                        ! 从0x90200地址开始处还要设置setup程序,而此时setup程序大约为4个扇区
                        ! 因此sp要指向大于(0x200 + 0x200*4 + 堆栈大小)处
! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.
! 在bootsect程序块后紧跟着加载setup模块的代码数据
! 注意es已经设置好了 0x9000、

load_setup:
    ! 利用BIOS中断INT 0x13将setup模块从磁盘第二个扇区开始读到0x90200处,共读4个扇区。如果读出错
    ! 则复位驱动器,并重试,没有退路。INT 0x13的使用方法如下:
    ! 读扇区:
    ! ah = 0x02 - 读磁盘扇区到内存;al = 需要读出的扇区数量
    ! ch = 磁道(柱面)号的低8位; cl = 开始扇区(0-5位),磁道号高2位(6-7)
    ! dh = 磁头号;   dl = 驱动器号
    ! es:bx -> 指向数据缓冲区;如果出错则CF标志置位
    ! 汇编中的CF是什么意思?
    ! 进位标志CF(Carry Flag)。进位标志CF主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位,那么,其值为1,否则其值为0。涉及到它的指令有两个:即JC在CF=1时跳转,JNC在CF=0时跳转。
	mov	dx,#0x0000		! drive(驱动器号) = 0, head(磁头号) = 0
	mov	cx,#0x0002		! sector(柱面号) = 2, track(开始扇区) = 0
	mov	bx,#0x0200		! address(偏移地址) = 512, in INITSEG
	mov	ax,#0x0200+SETUPLEN	! service 2, nr of sectors
	int	0x13			! read it 进入读取磁盘的中断接口(0x13为指定读取磁盘中断号)
	jnc	ok_load_setup		! ok - continue 条件跳转指令,如果CF等于0(运算没有产生进位或借位),则跳转到ok_load_setup标签地址处执行代码.
	mov	dx,#0x0000
	mov	ax,#0x0000		! reset the diskette
	int	0x13
	j	load_setup

ok_load_setup:

! Get disk drive parameters, specifically nr of sectors/track
! 取磁盘驱动器的参数,特别是每道的扇区数量
! 取磁盘驱动器参数INT 0x13调用格式和返回信息如下:
! ah = 0x80 dl = 驱动器号(如果是硬盘则要置位7为1)
! 返回信息:
! 如果出错则CF置位,并且ah=状态码
! ah = 0 , al = 0, bl = 驱动器类型(AT/PS2)
! ch = 最大磁道号的低8位, cl = 每磁道最大扇区数(位0-5),最大磁道号高位
! dh = 最大磁头数 , dl = 驱动器数量
! es:di -> 软驱磁盘参数表
	mov	dl,#0x00
	mov	ax,#0x0800		! AH=8 is get drive parameters
	int	0x13
	mov	ch,#0x00 
	seg cs     ! 表示下一条语句的操作数在cs段寄存器所指的段中
	mov	sectors,cx  ! 保存每磁道扇区数
	mov	ax,#INITSEG 
	mov	es,ax  # 因为上面取磁盘参数中断改掉了es的值,这里重新改回

! Print some inane message ! 显示一些信息
! INT 10H 中断读取光标,入参: AH =3 ,BH=页号
! 输出 : CH = 光标开始行 CL = 光标结束行 DH = 行 DL = 列
	mov	ah,#0x03		! read cursor pos
	xor	bh,bh           ! 读光标位置
	int	0x10
	
	mov	cx,#24          ! 共24个字符
	mov	bx,#0x0007		! page 0, attribute 7 (normal)
	mov	bp,#msg1        ! 指向要显示的字符串
	mov	ax,#0x1301		! write string, move cursor
	int	0x10            ! 写字符串并移动光标

! ok, we've written the message, now
! we want to load the system (at 0x10000)  ! 现在开始将system模块加载到0x10000(64k)处

	mov	ax,#SYSSEG
	mov	es,ax		! segment of 0x010000 ! es = 存放system的段地址
	call	read_it ! 读磁盘上system模块,es为输入参数
	call	kill_motor ! 关闭驱动器马达,这样就可以知道驱动器的状态了

! After that we check which root-device to use. If the device is
! defined (!= 0), nothing is done and the given device is used.
! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
! on the number of sectors that the BIOS reports currently.
! 此后,我们检查要使用哪个根文件系统设备(简称根设备)。如果已经指定了设备(!=0)
! 就直接使用给定的设备。否则就需要根据BIOS报告的每磁道扇区来确定到底使用/dev/PS0(2,28)还是/dev/at0(2,8)
    ! 在Linux中软驱的主设备号是2,次设备号 = type*4 +nr,其中nr为0-3分别对应软驱A、B、C、D;type是软驱类型
    ! 2->1.2M 7->1.44M 因为7*4+0 =28,所以/dev/PS0(2,28)指的是1.44M A 驱动器,其设备号是0x021c,/dev/at0(2,8)
    ! 指的是1.2M A驱动器,其设备号是0x0208
	seg cs
	mov	ax,root_dev
	cmp	ax,#0
	jne	root_defined
	seg cs
	mov	bx,sectors      ! 取保存的每磁道扇区数,如果sectors =15则说明是1.2Mb的驱动器;
	                    ! 如果sectors = 18 则说明是1.44Mb软驱,因为是可引导的驱动器,所以肯定是A驱
	mov	ax,#0x0208		! /dev/ps0 - 1.2Mb
	cmp	bx,#15          ! 判断每磁道扇区是否=15
	je	root_defined    ! 如果等于,则ax中就是引导驱动器的设备号
	mov	ax,#0x021c		! /dev/PS0 - 1.44Mb
	cmp	bx,#18
	je	root_defined
undef_root:              ! 如果都不一样,则死循环(死机)
	jmp undef_root
root_defined:
	seg cs
	mov	root_dev,ax      ! 将检查过的设备号保存起来

! after that (everyting loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:
! 到此,所有程序都加载完毕,我们就跳转到被加载在bootsect后面的setup程序去

	jmpi	0,SETUPSEG  ! 跳转到0x9020:0000(setup.s程序的开始处)
	                    ! !!!本程序到此就结束了!!!!!

! 下面是两个子程序

! This routine loads the system at address 0x10000, making sure
! no 64kB boundaries are crossed. We try to load it as fast as
! possible, loading whole tracks whenever we can.
! 该子系统将系统模块记载到内存地址0x10000处,并确定没有跨越64KB的
! 内存边界,我们试图尽快地进行加载,只要可能,就每次加载整条磁道的数据
! in:	es - starting address segment (normally 0x1000)
! 输入:es - 开始内存地址段值(通常是0x1000)
sread:	.word 1+SETUPLEN	! sectors read of current track
                            ! 当前磁道中已读的扇区数。开始时已经读进1扇区的引导扇区
head:	.word 0			! current head 当前磁头号
track:	.word 0			! current track 当前磁道号

read_it:
! 测试输入的段值。从盘上读入的数据必须存放在位于内存地址64KB的边界开始处,否则进入死循环。
! 清bx寄存器,用于表示当前内存放数据的开始位置。
	mov ax,es
	test ax,#0x0fff     ! 将两个操作数进行逻辑与运算,,运算结果为0时置1,否则置0. 
die:	jne die			! es must be at 64kB boundary  当零标志 Z=0 则跳转; 否则 零标志 Z=1 则顺序执行下一条指令。
	xor bx,bx		! bx is starting address within segment 异或
rp_read:
! 判断是否已经读入全部数据。比较当前所读段是否就是系统数据末端所处的段(#ENDSEG),
! 如果不是就跳转值下面ok1_read标号处继续读数据。否则退出子程序返回
	mov ax,es
	cmp ax,#ENDSEG		! have we loaded all yet? 是否已经加载了全部数据 实际上求(ax-ENDSEG)
	jb ok1_read         ! 当CF=1(ax<ENDSEG, 有借位)时跳转到ok1_read
	ret                 ! 当ax>=ENDSEG时返回(我认为不会出现大于的情况)
ok1_read:
! 计算和验证当前磁道需要读取的扇区数,放在ax寄存器中。
! 根据当前磁道还未读取的扇区数以及段内数据字节开始偏移位置,计算如果全部
! 读取这些未读扇区,所读总字节数是否超过64KB段长度的限制,若会超过,则根据此次
! 最多能读入的字节数(64KB-段偏移位置),反算出此次需要读取的扇区数。
	seg cs
	mov ax,sectors    !  这两句相当于 mov ax, cs:[sectors]; 取每磁道扇区数
	sub ax,sread      ! ax = ax - sread,减去当前磁道已读扇区数
	mov cx,ax         ! 当前磁道未读扇区数
	shl cx,#9         ! cx=cx*512 字节
	add cx,bx         ! 以上3行相当于 cx = ax * 512 + bx
	                  !   假设再读ax个扇区,cx就是段内共读入的字节数
	jnc ok2_read      ! 若cx < 0x10000(CF=0,没有进位)则跳转到ok2_read,若没有超过64KB字节,则跳转至ok2_read处执行
	je ok2_read       ! 若cx = 0(ZF=1),说明刚好读入64KB,则跳转到ok2_read
	xor ax,ax         ! ax = 0x0000
	sub ax,bx         ! 求bx对0x10000的补数,结果在ax中
	shr ax,#9         ! 除以512,得到扇区数,AL作为参数,传给read_track
ok2_read:
	call read_track
	mov cx,ax         ! cx = 该次操作已读取的扇区数
	add ax,sread      ! 当前磁道上已读取的扇区数
	seg cs
	cmp ax,sectors    ! 如果当前磁道上的还有扇区未读,则跳转到ok3_read
	jne ok3_read
! 读该磁道的下一磁头面(1号磁头)上的数据。如果已经完成,则去读下一磁道
	mov ax,#1
	sub ax,head       ! 判断当前磁头号
	jne ok4_read      ! 如果是0磁头,则再去读1磁头面上的扇区数据
	inc track         ! 否则去读下一磁道
ok4_read:
	mov head,ax       ! 保存当前磁头号
	xor ax,ax         ! 清当前磁道已读扇区数
ok3_read:
	mov sread,ax      ! 保存当前磁道已读扇区数
	shl cx,#9         ! 上次已读扇区数*512字节
	add bx,cx         ! 调整当前段内数据开始位置
	jnc rp_read       ! 若小于64KB边界值,则跳转到rp_read处,继续读数据
	                  ! 否则调整当前段,为读下一段数据作准备
	mov ax,es     
	add ax,#0x1000    !将段基址调整为指向下一个64KB内存开始处
	mov es,ax
	xor bx,bx         ! 清段内数据开始偏移值
	jmp rp_read       ! 跳转值rp_read,继续读取数据
! 读当前磁道上指定开始扇区和需读扇区数的数据到es:bx开始处
! al - 需读扇区数;ex:bx 缓冲区开始位置
!
read_track:
	push ax
	push bx
	push cx
	push dx
	mov dx,track     ! 取当前磁道号
	mov cx,sread     ! 取当前磁道上已读扇区数
	inc cx           ! cl = 开始读扇区
	mov ch,dl        ! ch = 当前磁道号
	mov dx,head      ! 取当前磁头号
	mov dh,dl        ! dh = 磁头号
	mov dl,#0        ! dl = 驱动器号(0表示当前A驱动器)
	and dx,#0x0100   ! 磁头号不大于1
	mov ah,#2        ! ah = 2,读磁盘扇区功能号
	int 0x13
	jc bad_rt        ! 若出错,则跳转至bad_rt
	pop dx
	pop cx
	pop bx
	pop ax
	ret
! 执行驱动器复位操作(磁盘中断功能号0),再跳转到read_track处重试	
bad_rt:	mov ax,#0
	mov dx,#0
	int 0x13
	pop dx
	pop cx
	pop bx
	pop ax
	jmp read_track

/*
 * This procedure turns off the floppy drive motor, so
 * that we enter the kernel in a known state, and
 * don't have to worry about it later.
 */
! 这个子程序用于关闭软驱的马达,这样我们进入内核后它处于已知状态,以后也就无须担心它了
kill_motor:
	push dx
	mov dx,#0x3f2    ! 软驱动控制卡的驱动端口,只写
	mov al,#0        ! A驱动器,关闭FDC,禁止DMA和中断请求,关闭马达
	outb             ! 将al中的内容输出到dx指定的端口去
	pop dx
	ret

sectors:
	.word 0         ! 存放当前软盘每磁道的扇区数

msg1:
	.byte 13,10     ! 回车、换行的ASII码
	.ascii "Loading system ..."
	.byte 13,10,13,10    ! 共24ge ASCII码字符

.org 508             ! 表示下面语句从地址508(0x1FC)开始,所以root_dev
                     ! 在启动扇区的第508开始的2个字节
root_dev:
	.word ROOT_DEV   ! 这里存放根文件系统所在的设备号(init/main.c会使用)
boot_flag:
	.word 0xAA55     ! 硬盘有效标识

.text
endtext:
.data
enddata:
.bss
endbss:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值