Linux 0.11 setup.s 代码全注释

本文详细介绍了Linux内核启动时的初始化过程,包括从BIOS获取系统参数,移动内存映像,设置段描述符,启用保护模式,处理中断向量,以及检查和配置硬件。这一系列步骤确保了内核能够正确地接管系统的控制权,并为后续的系统运行打下基础。
摘要由CSDN通过智能技术生成

基础概念讲解

当前内存映像
  • 系统模块 system 被移动到物理地址 0x0000 开始处
  • 0x90000 处存放了内核将会使用的一些系统基本参数
  • gdt 中有三个描述符
    第一个是(NULL)不用
    另外两个分别是代码段描述符和数据段描述符。他们都指向系统模块的起始处(0x0000)
    当执行最后一条指令 jmp 0, 8时,就会跳到head.s程序开始继续执行。8是段选择符(在gdt表中的位置,一个gdt表项占用8个字节),0是在代码段中的偏移地址
    在这里插入图片描述
BIOS 视频中断 0x10

在这里插入图片描述

硬盘基本参数表(INT 0x41)

在这里插入图片描述

内存管理寄存器

在这里插入图片描述

控制寄存器

在这里插入图片描述

代码

!
!	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.s负责从BIOS中获取系统数据,并将这些数据放到系统内存的适当地方。
! 此时setup.s和system已经由bootsect引导块加载到内存中。
!
! 这段代码询问bios有关内存/磁盘/其它参数,并将这些参数放到一个“安全的”地
! 方:0x90000 - 0x901FF,也即原来bootsect代码块曾经在的地方,然后在被缓冲
! 块覆盖掉之前由保护模式的system读取。

! NOTE! These had better be the same as in bootsect.s!
! 以下这些参数最好和bootsect.s中的相同!

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

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

entry start
start:
	! ———————————————————————— 将光标位置放在 0x90000 处 高位行号 低位列号 ————————————————————————
	! ok,整个读磁盘过程都正常,现在将光标位置保存以备今后使用。
	mov	ax,#INITSEG			! this is done in bootsect already, but...
							! 将ds 置成#INITSEG(0x9000)。这已经在bootsect 程序中
							! 设置过,但是现在是setup 程序,Linus 觉得需要再重新
							! 设置一下。
	mov	ds,ax
	mov	ah,#0x03			! read cursor pos
							! BIOS 中断0x10 的读光标功能号 ah = 0x03
							! 输入:bh = 页号
							! 返回:ch = 扫描开始线,cl = 扫描结束线,
							! dh = 行号(0x00 是顶端),dl = 列号(0x00 是左边)。
	xor	bh,bh
	int	0x10				! save it in known place, con_init fetches
	mov	[0],dx				! it from 0x90000.
							! 上两句是说将光标位置信息存放在0x90000 处,控制台
							! 初始化时会来取。


	! ———————————————————————— 将从 0x100000 处开始的扩展内存放在 0x90002 处 ————————————————————————
	! Get memory size (extended mem, kB)	! 下面3 句取扩展内存的大小值(KB)。
											! 是调用中断0x15,功能号ah = 0x88
											! 返回:ax = 从0x100000(1M)处开始的扩展内存大小(KB)! 若出错则CF 置位,ax = 出错码。
	mov	ah,#0x88
	int	0x15
	mov	[2],ax				! 将扩展内存数值存在0x90002 处(1 个字)。


	! ———————————————————————— 0x90004(1)存放当前页,0x90006 显示模式,0x90007 字符列数 ————————————————————————
	! Get video-card data:	! 下面这段用于取显示卡当前显示模式。
							! 调用BIOS 中断0x10,功能号 ah = 0x0f
							! 返回:ah = 字符列数,al = 显示模式,bh = 当前显示页。
							! 0x90004(1)存放当前页,0x90006 显示模式,0x90007 字符列数。
	mov	ah,#0x0f
	int	0x10
	mov	[4],bx				! bh = display page
	mov	[6],ax				! al = video mode, ah = window width


	! ———————————————————————— 0x90008 , 0x9000A 显示状态, 0x9000B 安装的显示内存, 0x9000C 显示卡特性参数 ————————————————————————
	! check for EGA/VGA and some config parameters	! 检查显示方式(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
	mov	[10],bx
	mov	[12],cx

	! ———————————————————————— 0x9000:0x0080 ~ 0x9000:0x0090 第一个硬盘(hd0)的信息 ————————————————————————
	! ———————————————————————— 0x9000:0x0090 ~ 0x9000:0x00A0 第二个硬盘(hd1)的信息 ————————————————————————
	! Get hd0 data			! 取第一个硬盘的信息(复制硬盘参数表)。
							!1 个硬盘参数表的首地址竟然是中断向量 0x41 的向量值!而第 2 个硬盘
							! 参数表紧接第 1 个表的后面,中断向量 0x46 的向量值也指向这第 2 个硬盘
							! 的参数表首址。表的长度是 16 个字节(0x10)! 下面两段程序分别复制 BIOS 有关两个硬盘的参数表,0x90080 处存放第1 个
							! 硬盘的表,0x90090 处存放第 2 个硬盘的表。
	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			! 共传输 0x10 字节。
	rep
	movsb


	! Get hd1 data
	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x46]			! 取中断向量0x46 的值,也即hd1 参数表的地址??ds:si
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0090			! 传输的目的地址: 0x9000:0x0090 ?? es:di
	mov	cx,#0x10
	rep
	movsb


	! ———————————————————————— 检查系统中是否存在第2个硬盘,如果不存在,将 0x9000:0x0090 ~ 0x9000:0x00A0 清零————————————————————————
	! Check that there IS a hd1 :-)		! 检查系统是否存在第2 个硬盘,如果不存在则第2 个表清零。
										! 利用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				! 是硬盘吗?(类型 = 3 ?)。
	je	is_disk1
no_disk1:
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	mov	ax,#0x00
	rep
	stosb

	! ———————————————————————— ! 从这里开始我们要保护模式方面的工作了 ————————————————————————
is_disk1:
	cli						! cli 禁止终端的发生

	! ———————————————————————— 将system模块从 0x10000~0x8ffff 移动到 0x00000~0x7ffff————————————————————————
	mov	ax,#0x0000
	cld						! 'direction'= 0, movs moves forward, CLD使DF复位
do_move:
	mov	es,ax				! es:di -> 目的地址(初始为 0x0000:0x0)
	add	ax,#0x1000	
	cmp	ax,#0x9000			! 让 ax 和源地址末尾比较
	jz	end_move
	mov	ds,ax				! ds:si -> 源地址(初始为 0x1000:0x0)
	sub	di,di
	sub	si,si
	mov cx,#0x8000			! 移动0x8000 字(64k 字节)。
	rep
	movsw
	jmp	do_move


! then we load the segment descriptors
! 此后,我们加载段描述符。
! 从这里开始会遇到32 位保护模式的操作,
!
! lidt 指令用于加载中断描述符表(idt)寄存器,它的操作数是6 个字节,0-1 字节是描述符表的
! 长度值(字节);2-5 字节是描述符表的32 位线性基地址(首地址),其形式参见下面
! 219-220 行和223-224 行的说明。中断描述符表中的每一个表项(8 字节)指出发生中断时
! 需要调用的代码的信息,与中断向量有些相似,但要包含更多的信息。
!
! lgdt 指令用于加载全局描述符表(gdt)寄存器,其操作数格式与lidt 指令的相同。全局描述符
! 表中的每个描述符项(8 字节)描述了保护模式下数据和代码段(块)的信息。其中包括段的
! 最大长度限制(16)、段的线性基址(32 位)、段的特权级、段是否在内存、读写许可以及
! 其它一些保护模式运行的标志。参见后面205-216 行。
end_move:
	! ———————————————————————— 加载 ldt 和 gdt ————————————————————————
	mov	ax,#SETUPSEG		! 0x9020 setup.s
	mov	ds,ax				! ds 指向本程序(setup)段。
	lidt	idt_48			! load idt with 0,0
							! 加载中断描述符表(idt)寄存器,idt_48 是6 字节操作数的位置
							! (见218 行)。前2 字节表示idt 表的限长,后4 字节表示idt 表
							! 所处的基地址。
	lgdt	gdt_48			! load gdt with whatever appropriate
							! 加载全局描述符表(gdt)寄存器,gdt_48 是6 字节操作数的位置


	! ———————————————————————— 现在我们开启 A20 地址线 ————————————————————————
	call	empty_8042		! 等待输入缓冲器空。
	! 只有当输入缓冲器为空时才可以对其进行写命令。
	mov	al,#0xD1			! command write		! 0xD1 命令码-表示要写数据到
	out	#0x64,al			! 8042 的P2 端口。P2 端口的位1 用于A20 线的选通。
	! 数据要写到0x60 口。
	call	empty_8042		! 等待输入缓冲器空,看命令是否被接受。
	mov	al,#0xDF			! A20 on	! 选通A20 地址线的参数。
	out	#0x60,al
	call	empty_8042		! 输入缓冲器为空,则表示A20 线已经选通。


	! ———————————————————————— 将中断地址从 0x08 ~ 0x0f 改为 0x20-0x2F ————————————————————————
	!! 我们将它们放在正好处于intel 保留的硬件中断后面,在int 0x20-0x2F。
	!! 在那里它们不会引起冲突。不幸的是IBM 在原PC 机中搞糟了,以后也没有纠正过来。
	!! PC 机的bios 将中断放在了0x08-0x0f,这些中断也被用于内部硬件中断。
	!! 所以我们就必须重新对8259 中断控制器进行编程,这一点都没劲。
	mov	al,#0x11				! initialization sequence
	! 0x11 表示初始化命令开始,是ICW1 命令字,表示边
	! 沿触发、多片8259 级连、最后要发送ICW4 命令字。	
	out	#0x20,al				! send it to 8259A-1	! 发送到8259A 主芯片。
	.word	0x00eb,0x00eb		! jmp $+2, jmp $+2		! $ 表示当前指令的地址,
	! 两条跳转指令,跳到下一条指令,起延时作用。
	out	#0xA0,al				! and to 8259A-2		! 再发送到8259A 从芯片。
	.word	0x00eb,0x00eb
	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 命令字,主芯片的IR2 连从芯片INT。
	.word	0x00eb,0x00eb		!参见代码列表后的说明。
	mov	al,#0x02				! 8259-2 is slave
	out	#0xA1,al				! 送从芯片ICW3 命令字,表示从芯片的INT 连到主芯
								! 片的IR2 引脚上。
	.word	0x00eb,0x00eb
	mov	al,#0x01				! 8086 mode for both
	out	#0x21,al				! 送主芯片ICW4 命令字。8086 模式;普通EOI 方式,
								! 需发送指令来复位。初始化结束,芯片就绪。
	.word	0x00eb,0x00eb
	out	#0xA1,al				!送从芯片ICW4 命令字,内容同上。
	.word	0x00eb,0x00eb
	mov	al,#0xFF				! mask off all interrupts for now
	out	#0x21,al				! 屏蔽主芯片所有中断请求。
	.word	0x00eb,0x00eb
	out	#0xA1,al				!屏蔽从芯片所有中断请求。


	! 这里设置进入32 位保护模式运行。首先加载机器状态字(lmsw - Load Machine Status Word),也称
	! 控制寄存器CR0,其比特位0 置1 将导致CPU 工作在保护模式。
	mov	ax,#0x0001				! protected mode (PE) bit			! 保护模式比特位(PE)。
	lmsw	ax					! This is it!						! 就这样加载机器状态字!
	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 中的代码。
								! 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.


! ———————————————————————— 检查键盘命令队列是否为空 ————————————————————————
! 下面这个子程序检查键盘命令队列是否为空。这里不使用超时方法 - 如果这里死机,
! 则说明PC 机有问题,我们就没有办法再处理下去了。
! 只有当输入缓冲器为空时(状态寄存器位2 = 0)才可以对其进行写命令。
empty_8042:
	.word	0x00eb,0x00eb		! 这是两个跳转指令的机器码(跳转到下一句),相当于延时空操作。
	in	al,#0x64				! 8042 status port				! 读AT 键盘控制器状态寄存器。
	test	al,#2				! is input buffer full?			! 测试位2,输入缓冲器满?
	jnz	empty_8042				! yes - loop
	ret


! ———————————————————————— 全局描述符表 ————————————————————————
! 一个段描述符8个字节
! 这里给出了3 个描述符项。
! 第1 项无用(206 行),但须存在
! 第2 项是系统代码段描述符(208-211 行)
! 第3 项是系统数据段描述符 (213-216 行)
gdt:
	.word	0,0,0,0				! dummy ! 第1 个描述符,不用。
	! 这里在gdt 表中的偏移量为0x08  系统代码段  ds[段描述符] 就是使用此值
	.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  系统数据段	ds[段描述符] 就是使用此值
	.word	0x07FF				! 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000				! base address=0
	.word	0x9200				! data read/write
	.word	0x00C0				! granularity=4096, 386


! ———————————————————————— 前 2 字节表示 idt 表的限长,后 4 字节表示 idt 表所处的基地址 ————————————————————————
idt_48:
	.word	0					! idt limit=0
	.word	0,0					! idt base=0L


! ———————————————————————— 前 2 字节表示 gdt 表的限长,后 4 字节表示 idt 表所处的基地址 ————————————————————————
gdt_48:
	.word	0x800				! gdt limit=2048, 256 GDT entries
								! 全局表长度为2k 字节,因为每8 字节组成一个段描述符项
								! 所以表中共可有256 项。
	.word	512+gdt,0x9			! gdt base = 0X9xxxx [0x0009<<16 + 0x0200+gdt] [0x90200(setup的地址) + gdt(即在本程序段中的偏移地址,205 行)]
								
.text
endtext:
.data
enddata:
.bss
endbss:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值