哈工大操作系统课程实验记录。

主要内容为

编号 实验项目 实验内容 实验目的
实验一 系统引导 编写bootsect.s和setup.s,实现一个简单的系统引导。 深入认识系统引导过程,掌握操作系统开发基本过程。
实验二 系统调用 添加一个系统调用whoami。 深入认识操作系统接口的工作原理。
实验三 进程运行轨迹的跟踪 编写程序跟踪进程的运行轨迹,主要是进程的状态切换过程。同时替换现有的Linux 0.11进程调度算法,并和现有的进程调度进行对比。 深入认识进程,能通过实际试验来评价操作系统性能。
实验四 信号量实现和应用 在Linux 0.11上实现信号量,并实践Multiprogramming。 体会并发进程,掌握进程同步(互斥)技术的实现
实验五 Linux 0.11地址映射跟踪与共享 打印Linux 0.11的地址映射过程到log文件中。 实践逻辑地址、线性地址、物理地址、段页表等概念。
实验六 控制终端显示 特殊按键修改系统状态,并控制显示(如按F1后总显示**)。 深入认识设备管理,掌握终端设备驱动的实现。
实验七 proc文件系统的实现 在Linux 0.11上实现一个proc文件系统,动态察看内核。 体会虚拟文件系统概念,掌握文件系统的实现过程。
实验八 在Linux 0.11上实现交换分区和全局页面置换 在Linux 0.11上完整的虚拟内存,主要是实现全局的页面置换和交换分区设备的管理,并将这两个模块放入系统。 相比前面的实验而言,本实验较难。将全面加深进程、虚存、磁盘、中断的认识,对OS的系统性获得直接经验。

实验环境

课程提供线上环境 蓝桥-操作系统原理与实践。因为操作过程中出现了一些问题,我改用虚拟机。

  • Ubuntu 18.04
  • Bochs
  • gcc-3.4 (参考资料第一个github中可下载)
  • vim
  • hit-oslab-linux.tar.gz (参考资料第一个github中可下载)

实验的基本流程是根据实验要求编写应用程序、修改 Linux 0.11 的源代码,用 gcc 编译后,在 Bochs 的虚拟环境中运行、调试目标代码。

Bochs 是一个免费且开放源代码的 IA-32(x86)架构 PC 机模拟器。在它模拟出的环境中可以运行 Linux、DOS 和各种版本的 Windows 等多种操作系统。且Bochs 本身具有很高的移植性,可以运行在多种软硬件平台之上。

hit-oslab-linux.tar.gz 解压后有:

hit-oslab-linux.tar.gz解压后内容

  • bochs 目录

bochs 目录下是与 bochs 相关的执行文件、数据文件和配置文件。

  • run 脚本

run 是运行 bochs 的脚本命令。

运行后 bochs 会自动在它的虚拟软驱 A 和虚拟硬盘上各挂载一个镜像文件,软驱上挂载是 linux-0.11/Image,硬盘上挂载的是 hdc-0.11.img。

因为 bochs 配置文件中的设置是从软驱 A 启动,所以 Linux 0.11 会被自动加载。而 Linux 0.11 会驱动硬盘,并 mount 硬盘上的文件系统,也就是将 hdc-0.11.img 内镜像的文件系统挂载到 0.11 系统内的根目录 —— /。在 0.11 下访问文件系统,访问的就是 hdc-0.11.img 文件内虚拟的文件系统。

  • hdc-0.11.img 文件

hdc-0.11.img 文件的格式是 Minix 文件系统的镜像。

Linux 所有版本都支持这种格式的文件系统,所以可以直接在宿主 Linux 上通过 mount 命令访问此文件内的文件,达到宿主系统和 bochs 内运行的 Linux 0.11 之间交换文件的效果。

Windows 下目前没有(或者是还没发现)直接访问 Minix 文件系统的办法,所以要借助于 fdb.img,这是一个 1.44M 软盘的镜像文件,内部是 FAT12 文件系统。将它挂载到 bochs 的软驱 B,就可以在 0.11 中访问它。而通过 filedisk 或者 WinImage,可以在 Windows 下访问它内部的文件。

hdc-0.11.img 内包含有:

  • Bash shell;
  • 一些基本的 Linux 命令、工具,比如 cp、rm、mv、tar;
  • vi 编辑器;
  • gcc 1.4 编译器,可用来编译标准 C 程序;
  • as86 和 ld86;
  • Linux 0.11 的源代码,可在 0.11 下编译,然后覆盖现有的二进制内核。

除此之外,还需要安装 gcc-3.4。因为Linux-0.11不能在gcc 4.x版本编译,所以要装老一点儿的编译器。

在Ubuntu 9.04(jaunty)及之前,用下面命令安装:

1
$ sudo apt-get install gcc-3.4

在Ubuntu 9.10(karmic)及之后:

1
2
3
$ tar -zxvf gcc-3.4-ubuntu.tar.gz
$ cd gcc-3.4
$ sudo ./inst.sh XXX # i386 or amd64

解压gcc-3.4时出现错误:

1
2
3
4
gzip: stdin: unexpected end of file
tar: Unexpected EOF in archive
tar: Unexpected EOF in archive
tar: Error is not recoverable: exiting now

解决方案:

  1. 下载失败,丢失内容:重新下载解压。
    the error you’re getting is usually due to an incomplete download. The archive ends before the expected footer of the gzipped file. It will probably work fine if you just download it again.
  2. 在windows下解压好后再拖进Ubuntu。
  3. tar.gz 文件名是否含有非法字符。

参考资料

注:实验中用标记为

为过程中出现的问题。

熟悉实验环境

1. 编译内核

1
2
$ cd ./oslab/linux-0.11/
$ make all

只要最后没有出现error就说明编译成功。

linux-0.11目录下是全部的源代码,实验内容靠修改这些代码来完成。修改后需要重新编译内核,还是执行命令:

1
$ make all

如果重新编译后,你的修改貌似没有生效,可以试试先“make clean”,再“make all”。“make clean”是删除上一次编译生成的所有中间文件和目标文件,确保是在全新的状态下编译整个工程。

2. 运行与调试

在Bochs中运行最新编译好的内核很简单,在oslab目录下执行:

1
$ ./run

如果出现Bochs的窗口,里面显示linux的引导过程,最后停止在[/usr/root/]#,表示运行成功。

Bochs

执行时出现错误:

./bochs/bochs-gdb: error while loading shared libraries: libSM.so.6: cannot open shared object file: No such file or directory

解决方案:https://blog.csdn.net/qq_40758751/article/details/88707214

内核调试分为两种模式:汇编级调试和 C 语言级调试。

(1)汇编级调试

汇编级调试需要执行命令:

1
2
3
4
5
# 确认在 oslab 目录下
$ cd ~/oslab/

# 运行脚本前确定已经关闭刚刚运行的 Bochs
$ ./dbg-asm

汇编级调试的启动之后 Bochs 是黑屏,这是正常的。

可以用命令 help 来查看调试系统用的基本命令。更详细的信息请查阅 Bochs 使用手册。

图片描述

(2)C 语言级调试

C 语言级调试稍微复杂一些。首先执行如下命令:

1
2
$ cd ~/oslab
$ ./dbg-c

然后再打开一个终端窗口,执行:

1
2
$ cd ~/oslab
$ ./rungdb

注意:启动的顺序不能交换,否则 gdb 无法连接。

出现下图所示的提示,才说明连接成功:

图片描述

新终端窗口中运行的是 GDB 调试器。关于 gdb 调试器请查阅 GDB 使用手册。

3. 文件交换

开始设置文件交换之前,务必关闭所有的 Bochs 进程。

oslab 下的 hdc-0.11-new.img 是 0.11 内核启动后的根文件系统镜像文件,相当于在 bochs 虚拟机里装载的硬盘。在 Ubuntu 上访问其内容的方法是:

1
2
3
4
$ cd ~/oslab/

# 启动挂载脚本
$ sudo ./mount-hdc

之后,hdc 目录下就是和 0.11 内核一模一样的文件系统了,可以读写任何文件(可能有些文件要用 sudo 才能访问)。

1
2
3
4
5
# 进入挂载到 Ubuntu 上的目录
$ cd ~/oslab/hdc

# 查看内容
$ ls -al

读写完毕,不要忘了卸载这个文件系统:

1
2
3
4
$ cd ~/oslab/

# 卸载
$ sudo umount hdc

经过 sudo ./mount-hdc 这样处理以后,我们可以在 Ubuntu 的 hdc 目录下创建一个 xxx.c 文件,然后利用 Ubuntu 上的编辑工具(如 gedit 等)实现对 xxx.c 文件的编辑工作,在编辑保存以后。

执行 sudo umount hdc 后,再进入 Linux 0.11(即 run 启动 bochs 以后)就会看到这个 xxx.c(即如下图所示),这样就避免了在 Linux 0.11 上进行编辑 xxx.c 的麻烦,因为 Linux 0.11 作为一个很小的操作系统,其上的编辑工具只有 vi,使用起来非常不便。

用 Ubuntu 和 Linux 0.11 完成文件交换以后再启动 Linux 0.11 以后

另外在 Linux 0.11 上产生的文件,如后面实验中产生的 process.log 文件,可以按这种方式 “放到” Ubuntu 下用 python 程序进行处理,当然这个 python 程序在 Linux 0.11 上显然是不好使的,因为 Linux 0.11 上搭建不了 python 解释环境。

注意 1:不要在 0.11 内核运行的时候 mount 镜像文件,否则可能会损坏文件系统。同理,也不要在已经 mount 的时候运行 0.11 内核。

注意 2:在关闭 Bochs 之前,需要先在 0.11 的命令行运行 “sync”,确保所有缓存数据都存盘后,再关闭 Bochs。

FAQ

Q: 为何有时Bochs的光标闪动,却不响应我的输入?

A: 按一下Alt,然后再试试。 如果你习惯用Alt+Tab切换窗口,就肯定会遇到这个问题。原因是在Bochs窗口按下Alt,Bochs会接收到Alt按下的事件,然后将此事件传给Linux 0.11。待再按下Tab时,主机操作系统经判断认定这是一个切换窗口的快捷键,于是直接切换窗口,Tab和Alt抬起的事件都不会再发给Bochs。等切换会Bochs,Linux 0.11此时还处于认为Alt已按下的状态,再按任何按键都被解释成是和Alt组合的按键,所以就“好像”不响应了(按数字键可以看到它的响应)。

Q: 怎样加快make clean、make all的速度?

A: 如果只修改了kernel目录下的文件,删除kernel目录下的kernel.o,然后直接make就行了。其它目录方法类似。

Q: Bochs屏幕乱了怎么办?

A: 这是Linux的终端控制和Bochs虚拟的终端之间配合不好导致的,一般在大量输出信息后,会出现混乱,甚至很像死机。此时按ctrl+l可以缓解一下。最好是用输出重定向功能将输出都重定向到一个文件,然后用vi看。

实验一、操作系统引导

基本内容

  1. 阅读《Linux内核完全注释》的第6章,对计算机和Linux 0.11的引导过程进行初步的了解;
  2. 按照要求改写0.11的引导程序bootsect.s
    1. bootsect.s 能在屏幕上打印一段提示信息“XXX is booting…”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等(可以上论坛上秀秀谁的 OS 名字最帅,也可以显示一个特色 logo,以表示自己操作系统的与众不同。)
  3. 改写进入保护模式前的设置程序setup.s。
    1. bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行”Now we are in SETUP”。
    2. setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。
    3. setup.s 不再加载 Linux 内核,保持上述信息显示在屏幕上即可。
  4. 配套李老师课程:L2 L3

开始实验前,需要了解:

(1)相关代码文件

Linux 0.11 文件夹中的 boot/bootsect.sboot/setup.stools/build.c 是本实验会涉及到的源文件。它们的功能详见《注释》的 6.2、6.3 节和 16 章。

(2)引导程序的运行环境

引导程序由 BIOS 加载并运行。它活动时,操作系统还不存在,整台计算机的所有资源都由它掌控,而能利用的功能只有 BIOS 中断调用。

实验中主要使用 BIOS 0x10 和 0x13 中断。

讨论

有时,继承传统意味着别手蹩脚。x86 计算机为了向下兼容,导致启动过程比较复杂。请找出 x86 计算机启动过程中,被硬件强制,软件必须遵守的两个“多此一举”的步骤(多找几个也无妨),说说它们为什么多此一举,并设计更简洁的替代方案。

参考材料:wiki: x86

x86最先由实模式慢慢发展到保护模式,这中间有过很多为了向下兼容而不得不妥协的作法,比如说为了能访问更多的内存而开启了A20,为了在保护模式下可以运行实模式的程序而设计了v86(虚拟实模式) wiki: 虚拟8086模式等。

1) 计算机上电,BIOS初始化中断向量表后,会将启动设备的第一个扇区(即引导扇区)读入内存地址0x7c00(31KB)处,并跳转到此处开始执行。而为了方便加载主模块,引导程序首先会将自己移动到内存相对靠后的位置,如linux0.11的bootsect程序先将自己移动到0x90000(576KB)处。这样先移动是多此一举的。

  • 解决方案:在保证可靠性的前提下尽量扩大实地址模式下BIOS可访问的内存的范围,如引导扇区加载到0x90000等内存高地址处而不是0x7c00。

2) 计算机上电后,ROM BIOS会在物理内存0处初始化中断向量表,其中有256个中断向量,每个中断向量占用4字节,共1KB,在物理内存地址0x000 - ox3ff处,这些中断向量供BIOS中断使用。这就导致了一个问题,如果操作系统的引导程序在加载操作系统时使用了BIOS中断来获取或者显示一些信息时,这1KB地址不能被覆盖。然而操作系统的主模块为了让其中代码地址等于实际的物理地址,需要将其加载到内存0x0000处。所以操作系统在加载时需要先将主模块加载到内存中不与BIOS中断向量表冲突的地方,之后可以覆盖中断向量表时才将其移动到内存起始处,如Linux0.11的System模块就是在bootsect程序中先加载到0x10000,之后在setup程序中移到0x0000处。 这样先加载到另外地方之后再移动到内存起始位置是多此一举的。

  • 解决方案:可以将BIOS中断向量表放到实模式下能寻址内存的其他地方,操作系统引导程序直接将操作系统的主模块读到内存的起始处。

在这里插入图片描述

在这里插入图片描述

bootsect.s 屏幕输出

代码中以 ! 开头的行都是注释,实际在写代码时可以忽略。

首先来看完成屏幕显示的关键代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
! 首先读入光标位置
mov ah,#0x03
xor bh,bh
int 0x10

! 显示字符串 “Hello OS world, my name is LZJ”
! 要显示的字符串长度
mov cx,#36
mov bx,#0x0007
mov bp,#msg1
! es:bp 是显示字符串的地址
! 相比与 linux-0.11 中的代码,需要增加对 es 的处理,因为原代码中在输出之前已经处理了 es
mov ax,#0x07c0
mov es,ax
mov ax,#0x1301
int 0x10

! 设置一个无限循环
inf_loop:
jmp inf_loop

这里需要修改的是字符串长度,即用需要输出的字符串长度替换 mov cx,#24 中的 24。要注意:除了我们设置的字符串 msg1 之外,还有三个换行 + 回车,一共是 6 个字符。比如这里 Hello OS world, my name is LZJ 的长度是 30,加上 6 后是 36,所以代码应该修改为 mov cx,#36

接下来就是修改输出的字符串了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
! msg1 处放置字符串
msg1:
! 换行 + 回车
.byte 13,10
.ascii "Hello OS world, my name is LZJ"
! 两对换行 + 回车
.byte 13,10,13,10

! boot_flag 必须在最后两个字节
.org 510
! 设置引导扇区标记 0xAA55
! 必须有它,才能引导
boot_flag:
.word 0xAA55

.org 508 修改为 .org 510,是因为这里不需要 root_dev: .word ROOT_DEV,为了保证 boot_flag 一定在最后两个字节,所以要修改 .org

完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
entry _start
_start:
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#36
mov bx,#0x0007
mov bp,#msg1
mov ax,#0x07c0
mov es,ax
mov ax,#0x1301
int 0x10
inf_loop:
jmp inf_loop
msg1:
.byte 13,10
.ascii "Hello OS world, my name is LZJ"
.byte 13,10,13,10
.org 510
boot_flag:
.word 0xAA55

接着执行下面两个命令来编译和链接 bootsect.s。

1
2
$ 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 可列出下面的信息:

1
2
3
-rw--x--x    1  root  root  544  Jul  25  15:07   bootsect
-rw------ 1 root root 257 Jul 25 15:07 bootsect.o
-rw------ 1 root root 686 Jul 25 14:28 bootsect.s

需要留意的文件是 bootsect ,它的大小是 544 字节,而引导程序必须要正好占用一个磁盘扇区,即 512 个字节。造成多了 32 个字节的原因是 ld86 产生的是 Minix 可执行文件格式,这样的可执行文件处理文本段、数据段等部分以外,还包括一个 Minix 可执行文件头部,它的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
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”可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
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 8e c0 b4 03 30 ff cd 10 b9 17 00 |.........0......|
00000030 bb 07 00 bd 3f 00 b8 01 13 cd 10 b8 00 90 8e c0 |....?...........|
00000040 ba 00 00 b9 02 00 bb 00 02 b8 04 02 cd 13 73 0a |..............s.|
00000050 ba 00 00 b8 00 00 cd 13 eb e1 ea 00 00 20 90 0d |............. ..|
00000060 0a 53 75 6e 69 78 20 69 73 20 72 75 6e 6e 69 6e |.Sunix is runnin|
00000070 67 21 0d 0a 0d 0a 00 00 00 00 00 00 00 00 00 00 |g!..............|
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000210 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000220

接下来干什么呢?是的,要去掉这 32 个字节的文件头部(tools/build.c 的功能之一就是这个)!随手编个小的文件读写程序都可以去掉它。不过,懒且聪明的人会在 Ubuntu 下用命令:

1
$ dd bs=1 if=bootsect of=Image skip=32

生成的 Image 就是去掉文件头的 bootsect。

去掉这 32 个字节后,将生成的文件拷贝到 linux-0.11 目录下,并一定要命名为“Image”(注意大小写)。然后就“run”吧!

1
2
3
4
5
6
7
# 当前的工作路径为 .../oslab/linux-0.11/boot/

# 将刚刚生成的 Image 复制到 linux-0.11 目录下
$ cp ./Image ../Image

# 执行 oslab 目录中的 run 脚本
$ ../../run

bootsect 引导后的系统启动情况

run时出错:No bootable device

实验二、

未完待续。

留言

⬆︎TOP