前置知识
1.1 基础概念
入理论课程的学习。 如果网易云上的课程无法查看,也可以看 Bilibili 上的 操作系统哈尔滨工业大学李治军老师。
- L2 开始揭开钢琴的盖子
- L3 操作系统启动
同济大学赵炯博士的《Linux 内核 0.11 完全注释(修正版 V3.0)》(以后简称《注释》)的第 6 章是非常有帮助的参考,实验中可能遇到的各种问题,几乎都能找到答案。谢煜波撰写的《操作系统引导探究》也是一份很好的参考。
1.2 概念的代码实现
Linux 0.11 文件夹中的 boot/bootsect.s、boot/setup.s 和 tools/build.c 是本实验会涉及到的源文件。
它们的功能详见《注释》的 6.2、6.3 节和 16 章。
2. 实验内容
阅读《Linux 内核完全注释》的第 6 章,对计算机和 Linux 0.11 的引导过程进行初步的了解;
- 按照下面的要求改写 0.11 的引导程序 bootsect.s
- 有兴趣同学可以做做进入保护模式前的设置程序 setup.s。
- 改写 bootsect.s 主要完成如下功能:
- bootsect.s 能在屏幕上打印一段提示信息“XXX is booting…”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等(可以上论坛上秀秀谁的 OS 名字最帅,也可以显示一个特色 logo,以表示自己操作系统的与众不同。)
改写 setup.s 主要完成如下功能:
- bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行"Now we are in SETUP"。
- setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。
- setup.s 不再加载 Linux 内核,保持上述信息显示在屏幕上即可。
2.1 实验提示
操作系统的 boot 代码有很多,并且大部分是相似的。本实验仿照 Linux-0.11/boot 目录下的 bootsect.s 和 setup.s,以剪裁它们为主线。当然,如果能完全从头编写,并实现实验所要求的功能,是再好不过了。
需要注意的是,oslab 中的汇编代码使用 as86 编译。
下面将给出一些更具体的 “提示”。这些提示并不是实验的一步一步的指导,而是罗列了一些实验中可能遇到的困难,并给予相关提示。它们肯定不会涵盖所有问题,也不保证其中的每个字都对完成实验有帮助。所以,它们更适合在你遇到问题时查阅,而不是当作指南一样地亦步亦趋。本课程所有实验的提示都是秉承这个思想编写的。
2.2 实验环境
(1)相关代码文件
Linux 0.11 文件夹中的 boot/bootsect.s、boot/setup.s 和 tools/build.c 是本实验会涉及到的源文件。它们的功能详见《注释》的 6.2、6.3 节和 16 章。
(2)引导程序的运行环境
引导程序bootsect.s
由BIOS 加载并运行, 引导程序在执行的过程中, 操作系统还不存在, 此时整台计算机的所有资源由引导程序掌控, 能使用的功能只有BIOS 中断调用,
在此过程中, 主要使用了 0X10, 0X13 这两个中断。
3. bootsect.s
3.1 修改 bootsect.s
-
改动, 这里需要修改的是字符串长度,即用需要输出的字符串长度替换 mov cx,#24 中的 24。要注意:除了我们设置的字符串 msg1 之外,还有三个换行 + 回车,一共是 6 个字符。比如这里 Hello OS world, my name is Chu Yun 的长度是 34,加上 6 后是 40,所以代码应该修改为 mov cx,#40。
-
修改启动时的字符串,将 .org 508 修改为 .org 510,是因为这里不需要 root_dev: .word ROOT_DEV,为了保证 boot_flag 一定在最后两个字节,所以要修改 .org。
完整的代码如下:
entry _start
_start:
! Print some inane message
! 读入光标所在位置
mov ah,#0x03 ! read cursor pos
xor bh,bh
int 0x10
! 显示启动过程中的字符串; “Hello OS world, my name is Chu Yun”
!mov cx,#24
mov cx,#40
mov bx,#0x0007 ! page 0, attribute 7 (normal)
mov bp,#msg1
!mov ax,#0x1301 ! write string, move cursor
!int 0x10
! es:bp 是显示字符串的地址;
! 相比与原始的代码linux-0.11(原始代码中在输出之前处理了es), 这里增加对es 的处理,
mov ax,#0x07c0
mov es,ax
mov ax,#0x1301
int 0x10
! 设置一个无限循环
inf_loop:
jmp inf_loop
! 在msg1处放置字符串
msg1:
! 一对回车+ 换行
.byte 13,10
.ascii "Hello OS world, my name is Chu Yun"
! 两对回车 + 换行
.byte 13,10,13,10
.org 510
! 设置引导扇区标记, 0xAA55, 必须有该引导扇区的标记,才能引导;
boot_flag:
.word 0xAA55
3.2 编译和运行 bootsect.s
执行下面两个命令编译和链接 bootsect.s:
$ as86 -0 -a -o bootsect.o bootsect.s
$ ld86 -0 -s -o bootsect bootsect.o
其中 -0
(注意:这是数字 0,不是字母 O)表示生成 8086 的 16 位目标程序,-a
表示生成与 GNU as 和 ld 部分兼容的代码,-s
告诉链接器 ld86 去除最后生成的可执行文件中的符号信息。
如果这两个命令没有任何输出,说明编译与链接都通过了。
Ubuntu 下用 ls -l 可列出下面的信息:
~/os/oslab/linux-0.11/boot$ ls -l
总用量 72
-rwxrwxr-x 1 shiyanlou shiyanlou 544 11月 29 15:54 bootsect
-rw-rw-r-- 1 shiyanlou shiyanlou 924 11月 29 15:53 bootsect.o
-rw-r--r-- 1 shiyanlou shiyanlou 5059 8月 28 2008 bootsect_ori.s
-rw-r--r-- 1 shiyanlou shiyanlou 1799 11月 29 15:55 bootsect.s
其中 bootsect.o
是中间文件。bootsect
是编译、链接后的目标文件。
注意到; bootsect
的文件大小是 544 字节,而引导程序必须要正好占用一个磁盘扇区,即 512 个字节。
造成多了 32 个字节的原因是 ld86 产生的是 Minix 可执行文件格式,这样的可执行文件除了文本段、数据段等部分以外,还包括一个 Minix 可执行文件头部,它的结构如下:
struct exec {
unsigned char a_magic[2]; //执行文件魔数
unsigned char a_flags;
unsigned char a_cpu; //CPU标识号
unsigned char a_hdrlen; //头部长度,32字节或48字节
unsigned char a_unused;
unsigned short a_version;
long a_text; long a_data; long a_bss; //代码段长度、数据段长度、堆长度
long a_entry; //执行入口地址
long a_total; //分配的内存总量
long a_syms; //符号表大小
};
算一算:6 char(6 字节)+ 1 short(2 字节) + 6 long(24 字节)= 32,正好是 32 个字节,去掉这 32 个字节后就可以放入引导扇区了(这是 tools/build.c 的用途之一)。
对于上面的 Minix 可执行文件,其 a_magic[0]=0x01,a_magic[1]=0x03,a_flags=0x10(可执行文件),a_cpu=0x04(表示 Intel i8086/8088,如果是 0x17 则表示 Sun 公司的 SPARC),所以 bootsect 文件的头几个字节应该是 01 03 10 04。
为了验证一下,Ubuntu 下用命令hexdump -C bootsect
可以看到:
hexdump -C bootsect
00000000 01 03 10 04 20 00 00 00 00 02 00 00 00 00 00 00 |.... ...........|
00000010 00 00 00 00 00 00 00 00 00 82 00 00 00 00 00 00 |................|
00000020 b8 c0 07 8e d8 b8 00 90 8e c0 b9 00 01 29 f6 29 |.............).)|
3.3 修改 bootsect 文件大小
接下来干什么呢?
是的,要去掉这 32 个字节的文件头部(tools/build.c 的功能之一就是这个)!随手编个小的文件读写程序都可以去掉它。不过,
在 Ubuntu 下可以用命令:
3.4 ubuntu 中读入文件指定大小字节的数据
dd bs=1 if=bootsect of=Image skip=32
记录了512+0 的读入
记录了512+0 的写出
512字节已复制,0.00892562 s,57.4 kB/s
shiyanlou@respectinglife-Inspiron-24-5459:~/os/oslab/linux-0.11/boot$ ls
bootsect bootsect.o bootsect_ori.s bootsect.s head.o head.s Image setup setup.o setup.s
3.5 运行引导文件 bootsect
去掉这 32 个字节后,将生成的文件拷贝到 linux-0.11 目录下,
并一定要命名为Image
(注意大小写)。
~/os/oslab/linux-0.11/boot$ cp ./Image ../Image
然后就“run”吧, 注意到这里是在当前路径下, 回退的方式运行的;
~/os/oslab/linux-0.11/boot$ ../../run