抄写Linux源码(Day12:从 MBR 到 C main 函数 (1:研究 bootsect.s) )

回忆我们需要做的事情:
为了支持 shell 程序的执行,我们需要提供:
1.缺页中断(不理解为什么要这个东西,只是闪客说需要,后边再说)
2.硬盘驱动、文件系统 (shell程序一开始是存放在磁盘里的,所以需要这两个东西)
3.fork,execve, wait 这三个系统调用,也可以说是 进程调度 (否则无法 halt shell 程序并且启动另外的程序)
4.键盘驱动、VGA/console/uart 驱动、中断处理 (支持键盘输入和屏幕显示)
5.内存管理 (shell 启动其它进程时,不能共用内存,而是切换其它进程的页表)
6.为了写代码方便,我们需要从 MBR 进入到 main 函数,这也是从 汇编 切换到 C 语言
7.应用程序申请内存的接口

经历了 Day11,看到了 Linux 0.11 中的内存管理代码后。

显然,这样复杂的功能用汇编写会很花时间,所以我们得想办法从汇编转为 C 代码,随后进入一个 C main 函数

直接看闪客文章第十回:https://mp.weixin.qq.com/s?__biz=Mzk0MjE3NDE0Ng==&mid=2247499838&idx=1&sn=6e7335be30fb24b03c54ccaaf135f236&chksm=c2c5ba93f5b23385a21191ea0ba60431340b195a3d63ae4d0e737f8274920d4c372940e4094c&scene=178&cur_album_id=2123743679373688834#rd

在这里插入图片描述
从上图可以看到,进入 C main 之间还要做许多工作,代码为 bootsect.s, setup.s 以及 head.s

为什么要做这么多工作?为什么不能直接 jmp main 呢?

首先 MBR 不能直接 jmp main。因为 MBR 的程序并不是一个和 C 程序链接在一起的程序,也不能是一个和 C 程序链接在一起的程序。因为 MBR 的大小仅为 512 字节,根本不可能和 C 程序链接在一起。

所以 MBR 能做的事情只有:把内核(和 C main 链接的程序) 从磁盘上加载到内存中。

那么 MBR 已经能把内核加载到内存里了,为什么还需要 setup.s 和 head.s 呢?

这个问题后边再思考吧,我们先看看 bootsect.s 是如何把 setup.s 和 内核 从磁盘加载到内存上的

继续看闪客文章第三回:https://mp.weixin.qq.com/s?__biz=Mzk0MjE3NDE0Ng==&mid=2247499307&idx=1&sn=c94575dde2b9bbdbabe2d7a832aa9ff4&chksm=c2c58486f5b20d907e314fdf88c5c25b8233b44f5a6a2864c418489e7ae993574150e92c9927&scene=21#wechat_redirect

这一节就只讲了一小段汇编代码,我都做好注释了,如下:

# .equ INITSEG, 0x9000		# cs 的值是 0x9000,但是会被左移四位,所以是跳转到 0x90000 + go
# 执行了 ljmp 指令之后,cs:ip 的值会被修改,cs就是当前的代码段,ip 则是指令地址的 offset
# 由于目前我们跳转到了 cs:ip = 0x90000,所以需要先把 ds es ss 等段寄存器设置为 0x9000
# 设置 sp 栈指针为 0xFF00 的意思是,目前栈顶地址就是 ss:sp = 0x9FF00。是的, x86 中栈指针也是自顶向下的
go:	mov	%cs, %ax		#将ds,es,ss都设置成移动后代码所在的段处(0x9000)
	mov	%ax, %ds # ds 数据段
	mov	%ax, %es # es 附加段 extra segment
# put stack at 0x9ff00.
	mov	%ax, %ss # ss 堆栈段
	mov	$0xFF00, %sp		# arbitrary value >>512   # 堆栈指针

有两张图我觉得特别好,如下
在这里插入图片描述

在这里插入图片描述

x86 架构由于历史原因,还保留着“段寄存器”的设计。每次我们访问内存和代码的时候,我们写在汇编指令里的地址,实际上都只是偏移,必须要加上段寄存器里的基址才可以正确访问内存。

所以,go 这一小段代码

go:	mov	%cs, %ax		#将ds,es,ss都设置成移动后代码所在的段处(0x9000)
	mov	%ax, %ds # ds 数据段
	mov	%ax, %es # es 附加段 extra segment
# put stack at 0x9ff00.
	mov	%ax, %ss # ss 堆栈段
	mov	$0xFF00, %sp		# arbitrary value >>512   # 堆栈指针

的真正作用实际上就是在 CPU 跳转到 0x90000 后,设置好 ds es ss sp 等各个段寄存器和栈指针

sp 要设置在 0xFF00 的原因就是栈指针离代码段离得越远越好。

当然了,还有个问题

TODO: 为什么要把 MBR 从 0x7c00 移动到 0x90000?直接在 0x7c00 搞事情不行吗?估计还得往后看看才知道

继续看闪客文章第四回

https://mp.weixin.qq.com/s?__biz=Mzk0MjE3NDE0Ng==&mid=2247499359&idx=1&sn=233812a464996b9566cdf3258132bc22&chksm=c2c584f2f5b20de40a7990c754cdbf3073b4652f318d479ac0c8ff686ca7aa74eef1ba7c6c2f&scene=178&cur_album_id=2123743679373688834#rd

关于具体的代码,建议看闪客原本的文章,以及 Linux0.11 的相应源代码注释

在对整个 Linux0.11 的编译构建阶段,会做这么一些事情:

  1. 把 bootsect.s 编译成 bootsect 放在硬盘的 1 扇区。
  2. 把 setup.s 编译成 setup 放在硬盘的 2~5 扇区。
  3. 把剩下的全部代码(head.s 作为开头)编译成 system 放在硬盘的随后 240 个扇区。

随后在启动阶段时,bootsect.s 放在第一个扇区,作为 MBR,被 BIOS 加载到内存 0x7c00 处,随后 bootsect.s 再把 setup.s 和 system 加载到对应的内存位置 (0x90200 和 0x10000)

整体流程如下图:
在这里插入图片描述
所以,总的来说,bootsect.s 存在的意义就是把 setup.s 和 内核 加载到内存。

核心代码有两段,分别是 load_setup 和 ok_loadsetup,带注释的代码如下:

# NOTE: load_setup 的作用是把 setup.s 加载到内存位置 0x90200
##ah=0x02 读磁盘扇区到内存	al=需要独出的扇区数量
##ch=磁道(柱面)号的低八位   cl=开始扇区(位0-5),磁道号高2位(位6-7)
##dh=磁头号					dl=驱动器号(硬盘则7要置位)
##es:bx ->指向数据缓冲区;如果出错则CF标志置位,ah中是出错码
load_setup:
  # 从磁盘第二个扇区开始,读取四个扇区,存放到内存地址 0x90200 处
	mov	$0x0000, %dx		# drive 0, head 0
	mov	$0x0002, %cx		# sector 2, track 0  # dx 和 cx 的作用是指定要读取的扇区起始位置
	mov	$0x0200, %bx		# address = 512, in INITSEG    # es:bx = 0x90200,是读取扇区存放的内存位置
	.equ    AX, 0x0200+SETUPLEN # SETUPLEN = 4  
	mov     $AX, %ax		# service 2, nr of sectors   # 读取磁盘内存,同时要读取 4 个扇区
	int	$0x13			# read it # 发起 0x13 号中断,中断的参数为 ax, bx, cx, dx。这个中断处理程序是 BIOS 提前给我们写好的,是读取磁盘的相关功能函数

  # "jnc"指令的作用是检查进位标志位(Carry Flag)的状态。如果进位标志位为0,即没有进位发生,那么程序将会跳转到指定的目标地址执行。
  # int 0x13 在出错的时候会设置 CF=1,如果没有出错就不会设置 CF,因而 jnc 会跳转到 ok_load_setup
	jnc	ok_load_setup		# ok - continue

  # 如果 CF=1,也就是读取磁盘失败,那么就会不断重复执行这段代码,也就是重试。
	mov	$0x0000, %dx
	mov	$0x0000, %ax		# reset the diskette
	int	$0x13 
	jmp	load_setup
ok_load_setup:

# Get disk drive parameters, specifically nr of sectors/track

  # GUESS: 获取磁盘的一些参数
	mov	$0x00, %dl
	mov	$0x0800, %ax		# AH=8 is get drive parameters
	int	$0x13
	mov	$0x00, %ch
	#seg cs
	mov	%cx, %cs:sectors+0	# %cs means sectors is in %cs
	mov	$INITSEG, %ax
	mov	%ax, %es

# Print some inane message

  # GUESS: 使用 BIOS 中断,重置光标在屏幕上的位置
	mov	$0x03, %ah		# read cursor pos
	xor	%bh, %bh
	int	$0x10
	
  # GUESS: 使用 BIOS 中断,在屏幕上打印 msg1: "IceCityOS is booting ..."
	mov	$30, %cx
	mov	$0x0007, %bx		# page 0, attribute 7 (normal)
	#lea	msg1, %bp
	mov     $msg1, %bp
	mov	$0x1301, %ax		# write string, move cursor
	int	$0x10

# ok, we've written the message, now
# we want to load the system (at 0x10000)

  # NOTE: 这是 ok_load_setup 的核心代码,作用是把从磁盘第六个扇区开始往后的 240 个扇区加载到内存 0x10000
	mov	$SYSSEG, %ax # ax=0x1000
	mov	%ax, %es		# segment of 0x010000 # es=0x1000
	call	read_it   #
  # GUESS: 关掉跟软盘驱动相关的东西
	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.

	#seg cs
	mov	%cs:root_dev+0, %ax
	cmp	$0, %ax
	jne	root_defined
	#seg cs
	mov	%cs:sectors+0, %bx
	mov	$0x0208, %ax		# /dev/ps0 - 1.2Mb
	cmp	$15, %bx
	je	root_defined
	mov	$0x021c, %ax		# /dev/PS0 - 1.44Mb
	cmp	$18, %bx
	je	root_defined
undef_root:
	jmp undef_root
root_defined:
	#seg cs
	mov	%ax, %cs:root_dev+0

# after that (everyting loaded), we jump to
# the setup-routine loaded directly after
# the bootblock:

  # NOTE: 核心代码,在操作系统内核被加载到内存后,跳转到 0x90200 处,也就是 setup.s 代码开始的地方
	ljmp	$SETUPSEG, $0

那么回到最初的问题:
为了实现复杂的内核,毫无疑问我们需要使用 C 语言,需要从汇编跳转到 C 语言。由于 MBR 只有 512 字节,所以我们没法直接把内核写在 MBR,而是需要使用 MBR 加载内核到内存上。那么问题来了,现在我们已经把内核和setup.s 加载到内存上了。为什么不直接 jmp 到内核里的 main 函数,而是要先 jmp 到 setup.s 以及 head.s(system 的 entry)。它们做了些什么?这些事情可以被省略掉吗?我们还需要继续研究。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值