操作系统加载---setup

1.简介

作用:setup.s是操作系统加载程序,它的作用是利用ROM BIOS中断读取系统数据,并将这些数据保存到0x90000开始的位置处(覆盖了原来bootsect程序所在的地方)

读取到数据保存的位置:
在这里插入图片描述
将读取到的数据保存后,setup将system模块从 0x10000-0x8ffff整块移动到内存绝对地址0x00000处。
然后加载中断描述符表寄存器(idir)和全局描述符表寄存器(gdtr),开启A20地址线,重新设置两个中断控制芯片8259A,将硬件中断号重新设置为0x20-0x2f。再设置cpu的控制寄存器CR0(机器状态字),从而进入32位保护模式,并跳到system模块最前面部分的hean.s程序继续运行。

为了能让head.s在32位保护模式下运行,在程序中临时设置中断描述符(IDT)和全局描述符表(GDT),并在GDT中设置当前内核代码段的描述符和数据段的描述符。

GDT
段描述符存放在描述符表中。描述符表其实就是内存中描述符项的一个阵列。

描述符表有两类:全局描述符表(G60aldescriptor table-GDT)和局部描述符表(Local descri ptan⁡table−LDT)。

处理器是通过使用GDTR和LDTR寄存器来定位GDT表和当前的LDT表。
这两个寄存器以线性地址的方式保存了描述符表的基地址和表的长度。

指令Igd和sgd用于访问GDTR寄存器; 指令Hdt和slut用于访问LDTR寄存器。 lgd使用内存中一个6字节操作数来加载GDTR寄存器。头两个字节代表描述符表的长度,后4个字节是描述符表的基地址。但,访问LDTR寄存器的指令lut所使用的操作数却是一个2字节的操作数,表示全局描述符表GDT中一个描述符项的选择符。该选择符所对应的GDT表中的描述符项应该对应一个局部描述符表。
在这里插入图片描述
setup设置的GDT描述符项,代码段描述符的值是0x00C09A0000007FF,
表示代码段的限长是 8MB(=(0x7F+1)∗4KB, 这里加1是因为限长值是从0开始算起的,段在线性地址空间中的基址是0,段类型值009A表示该段存在于内存中、段的特权级别为0、段类型是可读可执行的代码段,段代码是32位的并且段的颗粒度是4KB。

数据段描述符的值是0x00C0920000007FF,表示数据段的限长是8MB…段在线性地址空间中的基址是0。段类型值0x92表示该段存在于内存中、段的特权级别为0、段类型是可读可写的数据段、段代码是32位的并且段的颗粒度是4KB。
逻辑地址的选择符部分用于指定一描述符,它是通过指定一描述符表并且索引其中的一个描述符项完成的。

段选择符格式:
在这里插入图片描述
其中索引值用于指定描述符表中8192(2**13)个描述符中的一个。
处理器将该索引值乘上8,并加上描述符表的基地址即可访问表中指定的段描述符。
表指示器(Table Indicator - TD)用于指定选择符所引用的描述符表。值为0表示指定GDT表,值为1表示指定当前的LDT表。请求者特权级(R capaestor’sPrivalege Level-RPL)用于保护机制。

由于GDT表的第一项(索引值为0)没有被使用,因此一个具有索引值0和表指示器值也为0的选择符(也即指向GDT的第一项的选择符)可以用作为一个空(null)选择符。当一个段寄存器(不能是 CS或SS)加载了一个空选择符时,处理器并不会产生一个异常。但是若使用这个段寄存器访问内存时就会产生一个异常。对于初始化还未使用的段寄存器以陷入意外的引用来说,这个特性是很有用的。

在进入保护模式之前,我们必须首先设置好将要用到的段描述符表,例如全局描述符表GDT。然后使用指令lgdt把描述符表的基地址告知CPU(GDT表的基地址存入g知寄存器)。再将机器状态字的保护模式标志置位即可进入32位保护运行模式。

Linux 0.11硬盘设备号
在Linux中,硬盘的主区号是3,其他设备的主设备号分别为:

1–内存
2–磁盘
3–硬盘
4–ttyx
5–tty
6–并行口
7–非命名管道

一个硬盘可以有1-4个分区,可以依据分区的不同用次设备号进行指定分区,所以:设备号=主设备号*256+次设备号
在这里插入图片描述

磁盘
一个磁盘由多个盘片(如下图中的 0 号盘片)叠加而成。盘片的表面涂有磁性物质,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面
在这里插入图片描述

每个盘片被划分为一个个磁道,每个磁道又划分为一个个扇区
在这里插入图片描述
柱面
每个盘面对应一个磁头。所有的磁头都是连在同一个磁臂上的,因此所有磁头只能“共进退”。所有盘面中相对位置相同的磁道组成柱面
在这里插入图片描述
磁盘的物理地址
可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”

可根据该地址读取一个“块”,操作如下:

① 根据“柱面号”移动磁臂,让磁头指向指定柱面;

② 激活指定盘面对应的磁头;

③ 磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读/写

2.源码分析

1.setup完成OS前的初始化和设置

1)保存光标的位置
2)得到扩展内存的大小
3)得到显示卡当前的显示模式
4)检测显示方式
5)读取硬盘参数表信息

硬盘基本参数表(INT 0x41)
在中断向量表中,int 0x41的中断向量位置(4*0x41=0x0000:0x0140)存放的不是中断程序的地址入口,而是第一个硬盘参数表的信息,0x46存放第二个硬盘参数表

硬盘参数表信息:
在这里插入图片描述

2.将整个system模块移动到0x00000处

; bootsect引导程序将system模块移动到(0x10000)处,
; 并把自己移动到(0x90000)处,把setup加载在它后面
; 下面这段程序将整个system模块移动到0x00000处,
; 即把从0x100000x8ffff的内存数据块整块的向内存地址低端移动了0x10000的位置

	mov	ax,#0x0000
	cld			! 'direction'=0, movs moves forward
do_move:
	mov	es,ax		! destination segment
	add	ax,#0x1000
	cmp	ax,#0x9000  ! 判断代码是否移动完成
	jz	end_move    ! 移动完成则跳转
	mov	ds,ax		! source segment
	sub	di,di
	sub	si,si
	mov 	cx,#0x8000   ! 循环移动,循环次数,每次循环完次数减 移动0x8000字
	rep				! 用于把内容从ds:si 复制es:di  以字节单位
	movsw           ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中
	jmp	do_move

移动后内存存放数据:
在这里插入图片描述

3.跳转到绝对地址0x00000处
在跳转之前还要进行相应的设置

1)加载段描述符,设置全局描述符表和中断描述表
2)开启A20地址线,为了能够访问和使用1MB以上的物理内存
3)重新对中断进行编程

进入保护模式:jmpi 0,8

;进入保护模式,只是跳转到绝对地址0x00000; 加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式
	mov	ax,#0x0001	! protected mode (PE) bit 保护模式比特位(PE)
	lmsw	ax		! This is it!             加载状态寄存器
	;段选择符8表示请求特权0级,使用GDT第二个段描述符
	jmpi	0,8		! jmp offset 0 of segment 8 (cs)  跳转至cs段偏移地址位0(system已经移动到0x00000)

setup程序完整代码

!
!	setup.s		(C) 1991 Linus Torvalds
!
! setup.s is responsible for getting the system data from the BIOS,
! and putting them into the appropriate places in system memory.
! both setup.s and system has been loaded by the bootblock.
!
! This code asks the bios for memory/disk/other parameters, and
! puts them in a "safe" place: 0x90000-0x901FF, ie where the
! boot-block used to be. It is then up to the protected mode
! system to read them from there before the area is overwritten
! for buffer-blocks.
!
; setup从BIOS中获取数据,并将这些数据保存到0x90000开始的位置处(0x90000-0x901FF覆盖了原来bootsect程序所在的地方)
; 此时setup和system已经由bootsect引导块加载到内存中
; 
! NOTE! These had better be the same as in bootsect.s!

INITSEG  = 0x9000	! we move boot here - out of the way 原来bootsect所在段
SYSSEG   = 0x1000	! system loaded at 0x10000 (65536).   system所在0x10000处
SETUPSEG = 0x9020	! this is the current segment          本程序所在段地址

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

entry start
start:

! ok, the read went well so we get current cursor position and save it for
! posterity.
; 保存光标的位置
; 使用BIOS中断取屏幕当前光标的位置(列,行),保存到内存(0x90000),2个byte
; 控制台初始化程序会到此处读取该值
; BISO 中断0x10 功能号 ah = 0x30 ,读光标的位置
; 输入:bh=页号
; 返回:返回:ch = 扫描开始线,cl = 结束开始线,dh = 行号(0x00顶端),dl=列号(0x00最左边)
	mov	ax,#INITSEG ! this is done in bootsect already, but...
	mov	ds,ax
	mov	ah,#0x03	! read cursor pos 功能号 ah = 0x30 ,读光标的位置
	xor	bh,bh
	int	0x10		! save it in known place, con_init fetches
	mov	[0],dx		! it from 0x90000.  将ds设置成0x90000(INITSEG)

! Get memory size (extended mem, kB)
; 得到扩展内存的大小
; 利用BIOS中断0x15 功能号 ah= 0x88取系统所含扩展内存大小并保存到0x90002; 返回: ax= 0x10000(1M)处开始的扩展内存大小,若出错CF置位,ax=出错码
	mov	ah,#0x88
	int	0x15
	mov	[2],ax      !扩展内存的大小保存到0x90002! Get video-card data:
; 得到显示卡当前的显示模式
; 调用BIOS中断0x10,功能号 ah = 0x0f
; 返回:ah=字符列数,al=显示模式,bh=显示当前页数

	mov	ah,#0x0f
	int	0x10
	mov	[4],bx		! bh = display page
	mov	[6],ax		! al = video mode, ah = window width

! check for EGA/VGA and some config parameters
; 检测显示方式
; 调用BIOS中断0x10, 功能号 ah=0x12,bl=0x10
	mov	ah,#0x12
	mov	bl,#0x10
	int	0x10
	mov	[8],ax      ! 0x90008 =ax
	mov	[10],bx     ! 0x9000A = 安装的显示内存,0x9000B = 显示状态
	mov	[12],cx		!0X9000C = 显卡特性参数

! Get hd0 data
; 取第一个硬盘信息
; 第一个硬盘参数表的首地址是中断向量0x41的向量值
; 第二个紧跟着对应着中断向量0x46
; 下面两个程序分别复制BIOS有关硬盘参数表,
; 第一个硬盘存放在0x90080,第二个硬盘存放在0x90090
	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x41]         !取中断向量0x41对应的地址 ,hd0参数表的地址--> ds:si
	mov	ax,#INITSEG         
	mov	es,ax
	mov	di,#0x0080          !传输的目的地址(0x9000:0x0080) -->es:di
	mov	cx,#0x10            ! 循环次数,每次循环完次数减一,共传输16个字节
	rep						! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中
	movsb                   ! 用于把内容从ds:si 复制es:di  以字节单位

! Get hd1 data

	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x46]         !取中断向量0x41对应的地址 ,hd0参数表的地址--> ds:si
	mov	ax,#INITSEG 
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	rep
	movsb


! Check that there IS a hd1 :-)
; 检测是否有第二个硬盘,如果没有则把第2个清零
; 利用BIOS中断调用0x13的取盘的类型,功能号 ah =0x15
	mov	ax,#0x01500
	mov	dl,#0x81         ! dl = 驱动器号(0x8X是硬盘,0x81是第一个硬盘,0x82是第二个硬盘)
	int	0x13
	jc	no_disk1         ! 第二个不存在
	cmp	ah,#3			 ! ah =类型码 指硬盘
	je	is_disk1         ! 存在

; 第二个硬盘不存在,对第二个硬盘表清零
no_disk1:
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	mov	ax,#0x00
	rep
	stosb
; 第二个硬盘存在,进入保护模式,从此开始不允许中段
is_disk1:

! now we want to move to protected mode ...

	cli			! no interrupts allowed !

! first we move the system to it's rightful place
; bootsect引导程序将system模块移动到(0x10000)处,
; 并把自己移动到(0x90000)处,把setup加载在它后面
; 下面这段程序将整个system模块移动到0x00000处,
; 即把从0x100000x8ffff的内存数据块整块的向内存地址低端移动了0x10000的位置

	mov	ax,#0x0000
	cld			! 'direction'=0, movs moves forward
do_move:
	mov	es,ax		! destination segment
	add	ax,#0x1000
	cmp	ax,#0x9000  ! 判断代码是否移动完成
	jz	end_move    ! 移动完成则跳转
	mov	ds,ax		! source segment
	sub	di,di
	sub	si,si
	mov 	cx,#0x8000   ! 循环移动,循环次数,每次循环完次数减 移动0x8000字
	rep				! 用于把内容从ds:si 复制es:di  以字节单位
	movsw           ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中
	jmp	do_move

! then we load the segment descriptors
; 加载段描述符,设置全局描述符表和中断描述表

end_move:
	mov	ax,#SETUPSEG	! right, forgot this at first. didn't work :-)
	mov	ds,ax
; lidt指令用于加载中断描述符表(IDT)寄存器
; 中断描述符表中每一个8个字节对应每个中断发生时所需要的中断程序地址入口
	lidt	idt_48		! load idt with 0,0   
; lgdt指令用于加载全局描述符表(GDT)寄存器
; 全局描述符表中每个描述符项(8字节)描述了保护模式下数据段和代码段的信息
	lgdt	gdt_48		! load gdt with whatever appropriate

! that was painless, now we enable A20
; 开启A20地址线,为了能够访问和使用1MB以上的物理内存
	call	empty_8042    ! 测试8042状态寄存器,等待输入缓冲器空,
	mov	al,#0xD1		  ! command write 0xD1命令码表示写数据到8042的P2端口
	out	#0x64,al

	call	empty_8042    !等待输入缓冲器空,看命令是否被接受
	mov	al,#0xDF		  ! A20 on
	out	#0x60,al
	call	empty_8042    !若此时输入缓冲器为空,则表示A20线也选通 

! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.

; 重新对中断进行编程

	mov	al,#0x11		! initialization sequence
	out	#0x20,al		! send it to 8259A-1  发送到8259A主芯片
;   0x00eb直接使用机器码表示两条相对跳转指令,起延时作用
	.word	0x00eb,0x00eb		! jmp $+2, jmp $+2
	out	#0xA0,al		! and to 8259A-2   再发送到8259A从芯片
	.word	0x00eb,0x00eb
;   系统硬件中断号被设置成0x20开始
	mov	al,#0x20		! start of hardware int's (0x20)
	out	#0x21,al		!送主芯片ICW2命令字,设置起始中断,要送奇端口
	.word	0x00eb,0x00eb

	mov	al,#0x28		! start of hardware int's 2 (0x28)
	out	#0xA1,al        !送主芯片ICW2命令字,从芯片的起始中断号

	.word	0x00eb,0x00eb
	mov	al,#0x04		! 8259-1 is master
	out	#0x21,al        !ICW3
	.word	0x00eb,0x00eb
	mov	al,#0x02		! 8259-2 is slave
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0x01		! 8086 mode for both
	out	#0x21,al
	.word	0x00eb,0x00eb
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0xFF		! mask off all interrupts for now
	out	#0x21,al
	.word	0x00eb,0x00eb
	out	#0xA1,al

! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it's less
! "interesting" anyway. This is how REAL programmers do it.
!
! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in 32-bit protected mode.

; 进入保护模式,只是跳转到绝对地址0x00000; 加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式
	mov	ax,#0x0001	! protected mode (PE) bit 保护模式比特位(PE)
	lmsw	ax		! This is it!             加载状态寄存器
	;段选择符8表示请求特权0级,使用GDT第二个段描述符
	jmpi	0,8		! jmp offset 0 of segment 8 (cs)  跳转至cs段偏移地址位0(system已经移动到0x00000)

! This routine checks that the keyboard command queue is empty
! No timeout is used - if this hangs there is something wrong with
! the machine, and we probably couldn't proceed anyway.

; 检差键盘命令队列是否为空
; 只有当输入缓冲器为空(键盘控制器状态寄存器位1 = 0)才可以进行写命令
empty_8042:
	.word	0x00eb,0x00eb      !延时作用
	in	al,#0x64	! 8042 status port
	test	al,#2		! is input buffer full?
	jnz	empty_8042	! yes - loop
	ret

; GDT全局描述符表开始处,描述符表由多个8字节长的描述符项组成,
; 3个描述符项
; 第一项没有作用,但是必须存在
; 第二项是系统代码段描述符
; 第三项是系统数据段描述符
gdt:
	.word	0,0,0,0		! dummy  第一个描述符 不用

; 在GDT表这里的偏移量是0x80,它是内核代码段选择符的值
	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		! base address=0
	.word	0x9A00		! code read/exec
	.word	0x00C0		! granularity=4096, 386

; 在GDT表这里的偏移量是0x10,它是内核数据段选择符的值
	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		! base address=0
	.word	0x9200		! data read/write
	.word	0x00C0		! granularity=4096, 386

; 加载中断描述符表寄存器(idtr)
; 这里设置一个长度为0的空表
idt_48:
	.word	0			! idt limit=0
	.word	0,0			! idt base=0L

; 加载全局描述符表寄存器(gdtr)
; GDT表长度为2kb
gdt_48:
	.word	0x800		! gdt limit=2048, 256 GDT entries
	.word	512+gdt,0x9	! gdt base = 0X9xxxx
	
.text
endtext:
.data
enddata:
.bss
endbss:

猜你喜欢

转载自blog.csdn.net/qq_53144843/article/details/120374983