当前位置: 首页 > article >正文

【Linux0.11代码分析】03 之 setup.s 启动流程

【Linux0.11代码分析】03 之 setup.s 启动流程

  • 一、boot\setup.s


系列文章如下:

系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》
.
1.《【Linux0.11代码分析】01 之 代码目录分析》
2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》
3.《【Linux0.11代码分析】03 之 setup.s 启动流程》
4.《【Linux0.11代码分析】04 之 head.s 启动流程》



在前文中,我们知道,bootsect.ssetup.ssystem 加载到内存后,
通过 jmpi 0,SETUPSEG 指令跳转到 0x90200 开始执行 setup.s程序,把CPU交到 setup.s 手中。

setup.s 主要功能如下:

  1. ROM BIOS 中获取内存、磁盘等系统数据,并将这些数据保存到0x90000-0x901FF,它会覆盖掉 bootsect.s 程序所在的地方。
  2. system模块从0x10000 - 0x8FFFF 整块向下移动到内存绝对地址 0x00000
  3. 加载中断描述符表寄存器(idtr)和全局描述符表寄存器(gdtr
  4. 开启A20地址线,重新设置两个中断控制芯片8259A,将硬件中断号重新设置为0x20 - 0x2F
  5. 设置CPU的控制寄存器CRO(机器状态字),从而进入32位保护模式运行
  6. 跳转到位于system模块最前面部分的head.s程序运行

所取得的参数如下:

内存地址长度(字节)名称描述
0x900002光标位置列号(0x00-最左端),行号(0x00-最顶端)
0x900022扩展内存数系统从 1M 开始的扩展内存数值(KB)
0x900042显示页面当前显示页面
0x900061显示模式
0x900071字符列数
0x900082
0x9000A1显示内存显示内存(0x00-64k,0x01-128k,0x02-192k,0x03=256k)
0x9000B1显示状态0x00-彩色,I/O=0x3dX;0x11-单色,I/O=0x3bX
0x9000C2特性参数显示卡特性参数
0x9008016硬盘参数表第 1 个硬盘的参数表
0x9009016硬盘参数表第 2 个硬盘的参数表(如果没有,则清零)
0x901FC2根设备号根文件系统所在的设备号(bootsec.s 中设置)

一、boot\setup.s

详细工作如下:

  1. 配置数据段寄存器 ds = ax = 0x9000
  2. 读取当前光标位置, BIOS中断0x10, 功能号ah=0x03, 然后将当前光标位置保存在 0x90000 第一个字节处
  3. 获取扩展内存的大小值, BIOS中断0x15, 功能号是ah=0x88
  4. 获取显示当前的显示模式,调用BIOS中断0x10,功能号ah=0x0f
  5. 检查显示方式(EGA/VGA)并取参数, 调用BIOS中断0x10,附加功能选择-取方式信息, 功能号:ah=0x12,bl=0x10
  6. 获取第一个硬盘的硬盘参数表,第1个硬盘参数表的首地址是中断向量0x41的向量值,保存在0x90080
  7. 获取第二个硬盘的硬盘参数表,第2个硬盘参数表的首地址是中断向量0x46的向量值,保存在0x90090
  8. 检查系统是否存在第2个硬盘,如不存在,则将 0x90090地址的第二个表清零
  9. 禁止中断
  10. system0x10000-0x8FFFF 移动到 0x00000-0x7FFFF (512k)
  11. 加载中断描述符表(idt)寄存器和 全局描述符表(gdt)寄存器
  12. 使能 A20 地址线
  13. 对中断进行重新编程,将它们保存在 intel保留的硬件中断后面,位于0x20-0x2F
  14. 配置进入32位保护模式运行,跳转到0x00000地址,开始运行system模块的head.s

# linux-0.11\boot\setup.s

INITSEG  = 0x9000	// 原 bootsect.s 所处的段 0x90000 (576kb)    	! we move boot here - out of the way
SYSSEG   = 0x1000	// 当前system 模块所处的段 0x10000 (64kb)   	! system loaded at 0x10000 (65536).
SETUPSEG = 0x9020	// 当前 setup.s 程序所处的段 0x90200 (576.5kb)	! this is the current segment

.globl begtext, begdata, begbss, endtext, enddata, endbss	// 定义全局标志符
.text			// 文本段
begtext:
.data			// 数据段
begdata:
.bss			// 堆栈段
begbss:
.text			// 文本段

entry start		// 程序入口函数 start
start:
	// 1. 配置数据段寄存器  ds = ax = 0x9000
	mov	ax,#INITSEG				! this is done in bootsect already, but...
	mov	ds,ax
	// 2. 读取当前光标位置, BIOS中断0x10, 功能号ah=0x03, 然后将当前光标位置保存在 0x90000 第一个字节处
	// 返回:ch=扫描开始线,cl=扫描结束线, dh=行号(0x00 是顶端),dl=列号(0x00 是左边)。 
	mov	ah,#0x03	! read cursor pos
	xor	bh,bh
	int	0x10		! save it in known place, con_init fetches
	mov	[0],dx		// 0x90000: 列号(0x00 是左边),   0x90001:行号(0x00 是顶端)

	// 3. 获取扩展内存的大小值, BIOS中断0x15, 功能号是ah=0x88
	// 返回ax = 从 0x100000(1M)处开始的扩展内存大小(KB), 若出错则 CF 置位,ax = 出错码
	mov	ah,#0x88
	int	0x15
	mov	[2],ax		// 0x90002(1字)存放从 0x100000(1M)处开始的扩展内存大小(KB)

	// 4. 获取显示当前的显示模式,调用BIOS中断0x10,功能号ah=0x0f
	// 返回:ah=字符列数,al=显示模式,bh=当前显示页
	mov	ah,#0x0f
	int	0x10
	mov	[4],bx		// 0x90004(1字)存放当前页  ! bh = display page
	mov	[6],ax		// 0x90006显示模式, 0x90007 字符列数   ! al = video mode, ah = window width

	// 5. 检查显示方式(EGA/VGA)并取参数, 调用BIOS中断0x10,附加功能选择-取方式信息, 功能号:ah=0x12,bl=0x10
	// 返回:bh = 显示状态 (0x00 - 彩色模式,I/O 端口=0x3dX)  (0x01 - 单色模式,I/O 端口=0x3bX) 
	//  	 bl = 安装的显示内存 (0x00 - 64k, 0x01 - 128k, 0x02 - 192k, 0x03 = 256k)
	// 		 cx = 显示卡特性参数
	mov	ah,#0x12
	mov	bl,#0x10
	int	0x10
	mov	[8],ax			// 0x90008 
	mov	[10],bx			// 0x9000A 安装的显示内存,  0x9000B 显示状态
	mov	[12],cx			// 0x9000C 显示卡特性参数

	// 6. 获取第一个硬盘的硬盘参数表,第1个硬盘参数表的首地址是中断向量0x41的向量值,保存在0x90080处
	mov	ax,#0x0000
	mov	ds,ax			// 配置 数据段寄存器 ds = ax = 0x0000
	lds	si,[4*0x41]		// 取中断向量 0x41的值 ,即hd0 参数表的地址 ds:si
	mov	ax,#INITSEG		// 配置 段基址为 ex = ax = 0x9000
	mov	es,ax
	mov	di,#0x0080		// 传输的目的地址: 0x9000:0x0080    es:di
	mov	cx,#0x10		// 共转输 0x10 字节
	rep
	movsb

	// 7. 获取第二个硬盘的硬盘参数表,第2个硬盘参数表的首地址是中断向量0x46的向量值,保存在0x90090处
	mov	ax,#0x0000
	mov	ds,ax			// 配置 数据段寄存器 ds = ax = 0x0000
	lds	si,[4*0x46]		// 取中断向量 0x416的值 ,即hd1 参数表的地址 ds:si
	mov	ax,#INITSEG		// 配置 段基址为 ex = ax = 0x9000
	mov	es,ax
	mov	di,#0x0090		// 传输的目的地址: 0x9000:0x0090    es:di
	mov	cx,#0x10		// 共转输 0x10 字节
	rep
	movsb

	// 8. 检查系统是否存在第2个硬盘,如不存在,则将 0x90090地址的第二个表清零
	// 利用 BIOS 中断调用 0x13 的取盘类型功能, 功能号 ah = 0x15
	// 输入:dl = 驱动器号(0x8X 是硬盘:0x80 指第 1 个硬盘,0x81 第 2 个硬盘)
// 输出:ah=类型码;00没有这个盘,CF置位;01是软驱,没有change-line支持;02是软驱(或其它可移动设备),有change-line支持;03是硬盘
	mov	ax,#0x01500
	mov	dl,#0x81
	int	0x13
	jc	no_disk1
	cmp	ah,#3			// 判断 ah==3 ,判断disk1是否存在
	je	is_disk1
no_disk1:
	mov	ax,#INITSEG		// 第2个硬盘不存在,则对第2个硬盘表清零。
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	mov	ax,#0x00
	rep
	stosb
is_disk1:

! now we want to move to protected mode ...
	// 9. 禁止中断
	cli			! no interrupts allowed !

	// 10. 将system从0x10000-0x8ffff 移动到 0x00000-0x7ffff(512k)
	mov	ax,#0x0000
	cld				// 将标志寄存器Flag的方向标志位DF清零 ! 'direction'=0, movs moves forward
do_move:
	mov	es,ax		// 目的地址 es:di = 0x0000:0x0 
	add	ax,#0x1000	// ax = ax + 0x1000
	cmp	ax,#0x9000	// 判断是否等于 0x9000, 如果相等则说明移动完毕
	jz	end_move
	mov	ds,ax		// 源地址 ds:si = 0x1000:0x0
	sub	di,di		
	sub	si,si
	mov 	cx,#0x8000		// 移动0x8000字(64byte)
	rep
	movsw
	jmp	do_move

	// 11. 加载中断描述符表(idt)寄存器和 全局描述符表(gdt)寄存器
end_move:
	// 将数据段寄存器 ds = ax = 0x9020
	mov	ax,#SETUPSEG			! right, forgot this at first. didn't work :-)
	mov	ds,ax
	// 加载中断描述符表(idt)寄存器,
	// 它的操作数是6个字节,0-1 字节是描述符表的长度值(字节);2-5 字节是描述符表的 32 位线性基地址(首地址)
	// 中断描述符表中的每一个表项(8 字节)指发生中断时需要调用的代码的信息,与中断向量有些相似,但要包含更多的信息
	lidt	idt_48				! load idt with 0,0
	---------------->
	+	idt_48:
	+		.word	0			! idt limit=0
	+		.word	0,0			! idt base=0L
	<----------------	
	
	// 加载全局描述符表(gdt)寄存器
	// 全局描述符表中的每个描述符项(8字节)描述了保护模式下数据和代码段(块)的信息
	// 包括段的最大长度限制(16位)、段的线性基址(32位)、段的特权级、段是否在内存、读写许可以及其它一些保护模式运行的标志。
	lgdt	gdt_48				! load gdt with whatever appropriate
	---------------->
	+	gdt:
	+		.word	0,0,0,0		! dummy
	+	
	+		.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	+		.word	0x0000		! base address=0
	+		.word	0x9A00		! code read/exec
	+		.word	0x00C0		! granularity=4096, 386
	+	
	+		.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	+		.word	0x0000		! base address=0
	+		.word	0x9200		! data read/write
	+		.word	0x00C0		! granularity=4096, 386
	+
	+	gdt_48:
	+		.word	0x800		! gdt limit=2048, 256 GDT entries
	+		.word	512+gdt,0x9	! gdt base = 0X9xxxx
	<----------------	

! that was painless, now we enable A20
	// 12. 使能 A20 地址线
	call	empty_8042			// 等待输入缓冲器为空
	--------------->
	+	! 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.
	+	empty_8042:
	+		.word	0x00eb,0x00eb
	+		in	al,#0x64		! 8042 status port
	+		test	al,#2		! is input buffer full?
	+		jnz	empty_8042		! yes - loop
	+		ret
	<---------------
	// 0xD1 命令码-表示要写数据到8042的P2端口, P2端口的位1用于A20线的选通,数据要写到0x60口
	mov	al,#0xD1				! command write
	out	#0x64,al
	call	empty_8042			// 等待输入缓冲器为空
	mov	al,#0xDF				// 选通 A20 地址线的参数  	! A20 on
	out	#0x60,al
	call	empty_8042			// 等待输入缓冲器为空

	// 13. 对中断进行重新编程,将它们保存在 intel保留的硬件中断后面,位于0x20-0x2F
	// 0x11 表示初始化命令开始, 是 ICW1 命令字,表示边沿触发、多片 8259级连、最后要发送 ICW4 命令字。		
	mov	al,#0x11		! initialization sequence
	// 发送到 8259A 主芯片
	out	#0x20,al		! send it to 8259A-1	
	
	.word	0x00eb,0x00eb		// 延时	! jmp $+2, jmp $+2
	// 再发送到 8259A 从芯片
	out	#0xA0,al		! and to 8259A-2
	.word	0x00eb,0x00eb		// 延时
	
	// 送主芯片 ICW2 命令字,起始中断号,要送奇地址
	mov	al,#0x20		! start of hardware int's (0x20)
	out	#0x21,al
	.word	0x00eb,0x00eb		// 延时
	
	// 送从芯片 ICW2 命令字,起始中断号
	mov	al,#0x28		! start of hardware int's 2 (0x28)
	out	#0xA1,al
	.word	0x00eb,0x00eb		// 延时
	
	//  送主芯片 ICW3 命令字,主芯片的 IR2 连从芯片 INT
	mov	al,#0x04		! 8259-1 is master
	out	#0x21,al
	.word	0x00eb,0x00eb		// 延时
	
	// 送从芯片 ICW3 命令字,表示从芯片的 INT 连到主芯片的 IR2 引脚上。 
	mov	al,#0x02		! 8259-2 is slave
	out	#0xA1,al
	.word	0x00eb,0x00eb		// 延时
	
	// 送两个芯片的 ICW4 命令字。8086 模式;普通 EOI方式,需发送指令来复位。初始化结束,芯片就绪
	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

	// 14. 配置进入32位保护模式运行,跳转到0x00000地址,开始运行system模块的head.s
	mov	ax,#0x0001				! protected mode (PE) bit
	// 加载机器状态字(lmsw - Load Machine Status Word), 也称控制寄存器 CR0,其bit0 置1将导致CPU工作在保护模式
	lmsw	ax					! This is it!
	// 配置为特权级为系统级、使用全局描述符,索引项为0, 偏移为0, 也就是跳转到 0x00000的地址开始运行
	jmpi	0,8					! jmp offset 0 of segment 8 (cs)
	
	// 段选择符长度为 16 位(2 字节);
	// 位 0-1 表示请求的特权级 0-3, linux 操作系统只用到两级:0 级(系统级)和 3 级(用户级);
	// 位 2 用于选择全局描述符表(0)还是局部描述符表(1)
	// 位 3-15 是描述符表项的索引,指出选择第几项描述符
	// 所以段选择符8(0b0000,0000,0000,1000)表示请求特权级 0、使用全局描述符表中的第 1 项


http://www.kler.cn/news/17392.html

相关文章:

  • C++——类和对象(3)
  • 初识 OPC
  • 05_Uboot源码目录分析
  • Java 版 spring cloud 工程系统管理 工程项目管理系统源码 工程项目各模块及其功能点清单
  • 2.压力测试+优化(Jmeter)
  • ChatGPT提示词工程(四):Inferring推断
  • MySQL基础
  • vim编辑器
  • Build生成器模式
  • (二)【平衡小车制作】电机驱动(超详解)
  • 内存越界是否一定会导致程序崩溃吗?详解内存越界
  • CUDA下载,以及下载GPU版本的pytorch
  • JAVA基于Springboot框架的停车场管理系统开发实践和实现【附源码】
  • DP动态规划
  • 1.rabbitMQ介绍
  • JavaScript闭包的基本原理和应用场景
  • 人的全面发展评价指标体系—基于相关-主成分分析构建
  • 2000-2019年30省研发资本存量(含计算过程和原始数据)
  • 大数据Doris(八):Broker部署和集群启停脚本
  • 高效学习方法和工具推荐,让你事半功倍!
  • clickhouse里的数组数据类型与相关使用介绍
  • 【C++复习1】程序结构和C++的工作原理
  • Java程序设计入门教程--数组
  • 小球下落(dropping balls)uva679
  • go 打包文件夹成zip文件
  • Envoy控制面实践
  • 漫画 | Linux之父:财务自由以后,我失眠了!
  • 华为OD机试 - 整理扑克牌(Python)
  • [计算机图形学]光场,颜色与感知(前瞻预习/复习回顾)
  • 对比 LVS 负载均衡群集的 NAT 模式和 DR 模式,基于 CentOS 7 构建 LVS-DR 群集