搭建实验环境
# 0. 搭建实验环境
工欲善其事必先利其器
# 前言
在 B 站的评论区看到,老师的配套实验是在这里做的:操作系统原理与实践_Linux - 蓝桥云课 (opens new window)
这是一个网站,可以提供远程连接虚拟机给到用户操作。同时上面有很详细的实验手册。但有这样一个问题:如果想要保存自己的环境,是要收费的(完成学生认证后 5 折);且远程还是有一点不方便,因此我决定自己搭建一套环境。当然,在网站上做也是有好处的,可以在上面看到很多同学留下的笔记等,有问题也可以在上面提。
后来其实已经有实验手册了,发现自己白写了....:HIT-OSLAB-MANUAL (opens new window)
后续我会记录实验过程,供大家参考
# 工具介绍
Bochs:发音同"Box",是一款开源,使用 C++ 编写的,高度便携的 IA-32(X86)架构 PC 模拟器。简单来说,就是可以运行操作系统,并且方便调试
GCC:编译器,这里用来编译操作系统。Linux 最初的版本就是由 GCC 编译的
GDB:调试器,GCC 编译器的兄弟。类似汇编里的 debug
# Ubuntu 下实验环境搭建(推荐)
# 准备操作系统
由于我的电脑是 Windows,可以用 VMWare 或 VirtualBox 运行 Ubuntu。我这里用 Virtual Box,免费的。然后在 VirtualBox 下安装 Ubuntu,可以参考 VirtualBox 虚拟机安装 Ubuntu16_megfli 的博客 (opens new window) 。安装花费了 2~3 个小时
我自己遇到的问题如下:
- 安装时黑屏时间过长:参考自己解决虚拟机 Ubuntu 开机黑屏 (opens new window),我尝试重启后正常了
- 安装时 Ubuntu 显示屏幕过小:参考关于 VirtualBox 安装 Ubuntu 时界面显示不全,没有下一步选项 (opens new window)
- 安装完后,我们可以设置共享目录,方便 Windows 和虚拟机共享文件:新版本 virtualbox Ubuntu16.04 设置共享文件夹 (opens new window)
# 安装依赖
我们不必从头开始安装 Bochs,GCC 等,有很多热心的网友已经帮我们配置好了:在终端里运行以下命令即可
$ git clone https://github.com/Wangzhike/HIT-Linux-0.11.git
$ cd HIT-Linux-0.11/prepEnv/hit-oslab-qiuyu/
$ ./setup.sh
2
3
这样,就帮我们装好了很多依赖了,包括 Bochs,GCC 等
如果第一个下载不了,可以试试国内镜像地址:
git clone https://gitcode.net/mirrors/Wangzhike/HIT-Linux-0.11.git
然后我们就可以运行 Linux0.11 了:
cd ~/oslab
./run
2
命令还提供 init 脚本进行初始化:
./run init
注:这样会将 Image 也删除,所以要重新 cd linux 然后 make all
关于本项目,更多的可以参考作者的 Github 说明文档。
# 编译内核
在实验过程中,我们需修改操作系统的代码,然后重新编译一个新的操作系统镜像,并让 Bochs 运行,编译步骤如下:
$ cd ./linux-0.11/
$ make all
2
以下说明来自实验楼:
make 命令会显示很多很多的信息,你可以尽量去看懂,也可以装作没看见。只要最后几行中没有 “error” 就说明编译成功。
make 命令会自动跳过未被修改的文件,链接时直接使用上次编译生成的目标文件,从而节约编译时间。但如果重新编译后,你的修改貌似没有生效,可以试试先
make clean
,再make all
(或者一行命令:make clean && make all
。make clean
是删除上一次编译生成的所有中间文件和目标文件,确保是在全新的状态下编译整个工程。
# 调试操作系统
我们编写操作系统代码的时候,难免会遇到问题,此时调试能帮助我们分析问题:
汇编语言级别的调试:
$ cd ~/oslab/
# 运行脚本前确定已经关闭刚刚运行的 Bochs
$ ./dbg-asm
2
3
4
以下说明来自实验楼:
汇编级调试的启动之后 Bochs 是黑屏,这是正常的。
可以用命令
help
来查看调试系统用的基本命令。更详细的信息请查阅 Bochs 使用手册。
按 q 可以退出调试
C 语言级别的调试
先运行
$ cd ~/oslab
$ ./dbg-c
2
再开一个终端,运行
$ cd ~/oslab
$ ./rungdb
2
以下说明来自实验楼
注意:启动的顺序不能交换,否则 gdb 无法连接。
出现下图所示的提示,才说明连接成功
注意:可能要等一段时间后,才能连接成功。
# Windows 下实验环境搭建(待完善,不推荐)
不推荐理由:有很多工具在 Windows 下不能运行,例如调试工具等,不太推荐。具体可以参考实验手册,也有详细的说明了。
如果你是用 Windows,那么如果如果运行虚拟机,虚拟机里再运行 Bochs 的话,对电脑的配置比较高的。如果内存不够,那么运行虚拟机后就容易卡顿,并且虚拟机内存不够的话也容易卡顿。(内存高的话就不用担心卡顿,比如我是 32G 台式机,给虚拟机分配 8G 内存后,总的 CPU 利用率也才 71%,使用过程中基本没有卡顿)
如果自己电脑配置不高的同学,也可以通过云服务器的方式搭建实验环境(网上有教程)。
安装 Bochs:默认一路 Next 即可。安装完后会有两个对话框提示是否打开更新日志和介绍,关闭即可。
接下来打开复制以下文件到 Bochs 的安装目录(安装过程中有指定安装目录的 ↑, 其实就是除了文件 Bochs-2.1.1.exe、README、rootimage-0.11,其他文件都复制过去)
bochsout.txt
bochsrc-fd.bxrc
bochsrc-hd.bxrc
bootimage-0.11
bootimage-0.11-fd
bootimage-0.11-hd
bootimage-0.12-fd
bootimage-0.12-hd
diskb.img
gcclib-1.40.taz
hdc-0.11.img
2
3
4
5
6
7
8
9
10
11
接下来,直接双击 bochsrc-hd.bxrc,就可以看到如画面,操作系统就被运行起来了
# Bochs 调试技巧
调试 Bochs 的脚本,环境里已经有了:.dbg-asm 的内容:
#!/bin/sh
export OSLAB_PATH=$(dirname `which $0`)
$OSLAB_PATH/bochs/bochs-dbg -q -f $OSLAB_PATH/bochs/bochsrc.bxrc
2
3
我们先启动:
peterjxl@Ubuntu16:~/oslab$ ./dbg-asm
========================================================================
Bochs x86 Emulator 2.3.7
Build from CVS snapshot, on June 3, 2008
========================================================================
00000000000i[ ] reading configuration from ./bochs/bochsrc.bxrc
00000000000i[ ] installing x module as the Bochs GUI
00000000000i[ ] using log file ./bochsout.txt
Next at t=0
(0) [0xfffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b ; ea5be000f0
<bochs:1>
2
3
4
5
6
7
8
9
10
11
运行后,Bochs 会等待我们输入调试指令(在最后一行处光标会闪烁)
Table 1. 部分 Bochs 调试指令
行为 | 指令 | 举例 |
---|---|---|
在某物理地址设置断点 | b addr | b 0x30400 |
显示当前所有断点信息 | info break | info break |
继续执行,直到遇上断点 | c | c |
单步执行 | s | s |
单步执行(遇到函数则跳过) | n | n |
查看寄存器信息 | info cpurfpsregcreg | info cpurfpsregcreg |
查看堆栈 | print-stack | print-stack |
查看内存物理地址内容 | xp /nuf addr | xp /40bx 0x9013e |
查看线性地址内容 | x /nuf addr | x /40bx 0x13e |
反汇编一段内存 | u start end | u 0x30400 0x3040D |
反汇编执行的每一条指令 | trace-on | trace-on |
每执行一条指令就打印 CPU 信息 | trace-reg | trace-reg on |
退出 | q | q |
其中 "xp /40bx 0x9013e" 这样的格式可能显得有点复杂,读者可以用 "help x" 这一指令在 Bochs 中亲自看一下它代表的意义
我们知道 bootsect.s 默认是加载到 0x7c00,我们可以将断点打到 0x7c00,然后输入 c 指令,直接运行到断点处:
<bochs:1> b 0x7c00
<bochs:2> c
(0) Breakpoint 1, 0x00007c00 in ?? ()
Next at t=4967696
(0) [0x00007c00] 0000:7c00 (unk. ctxt): mov ax, 0x07c0 ; b8c007
<bochs:3>
2
3
4
5
6
可以看到 Bochs 提示我们,下一条指令就是 mov ax, 0x7c00,这就是 bootsect.s 的第一条执行的指令(注意,此时还没开始执行)
entry start
start:
mov ax,#BOOTSEG
mov ds,ax
2
3
4
我们输入 n 指令,让 Bochs 单步执行,可以看到下一条要执行的指令就是 bootsect.s 的第二条执行(第一条已经执行了)
<bochs:1> b 0x7c00
<bochs:2> c
(0) Breakpoint 1, 0x00007c00 in ?? ()
Next at t=4967696
(0) [0x00007c00] 0000:7c00 (unk. ctxt): mov ax, 0x07c0 ; b8c007
<bochs:3> n
Next at t=4967697
(0) [0x00007c03] 0000:7c03 (unk. ctxt): mov ds, ax ; 8ed8
<bochs:4>
2
3
4
5
6
7
8
9
(注:如果我们有执行到 call 的话,可以用 s 指令进去方法里)
如果我们什么都不输入,直接按回车,默认就是执行上一步的命令
<bochs:4>
Next at t=4967698
(0) [0x00007c05] 0000:7c05 (unk. ctxt): mov ax, 0x9000 ; b80090
<bochs:5>
Next at t=4967699
(0) [0x00007c08] 0000:7c08 (unk. ctxt): mov es, ax ; 8ec0
<bochs:6>
2
3
4
5
6
7
查看寄存器:用 r 指令
<bochs:6> r
eax: 0x00009000 36864
ecx: 0x00000000 0
edx: 0x00000000 0
ebx: 0x00000000 0
esp: 0x0000ffdc 65500
ebp: 0x00000000 0
esi: 0xffff0000 -65536
edi: 0x0008fdba 589242
eip: 0x00007c08
eflags 0x00000082
id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
2
3
4
5
6
7
8
9
10
11
12
但看段寄存器要用 sreg 命令
<bochs:7> sreg
cs:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1
ds:s=0x07c0, dl=0x7c00ffff, dh=0x00009300, valid=1
ss:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=7
es:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1
fs:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1
gs:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1
ldtr:s=0x0000, dl=0x0000ffff, dh=0x00008200, valid=1
tr:s=0x0000, dl=0x0000ffff, dh=0x00008b00, valid=1
gdtr:base=0x000faeb2, limit=0x30
idtr:base=0x00000000, limit=0x3ff
2
3
4
5
6
7
8
9
10
11
info break:查看打了多少个断点
<bochs:8> info break
Num Type Disp Enb Address
1 pbreakpoint keep y 0x00007c00
2
3
打印堆栈:print-stack
<bochs:9> print-stack
Stack address size 2
| STACK 0xffdc [0x8a60001]
| STACK 0xffde [0x8a60000]
| STACK 0xffe0 [0x8a60000]
| STACK 0xffe2 [0x8a60000]
| STACK 0xffe4 [0x8a60000]
| STACK 0xffe6 [0x8a60000]
| STACK 0xffe8 [0x8a60000]
| STACK 0xffea [0x8a60000]
| STACK 0xffec [0x8a60000]
| STACK 0xffee [0x8a67c00]
| STACK 0xfff0 [0x8a60000]
| STACK 0xfff2 [0x8a60040]
| STACK 0xfff4 [0x8a60000]
| STACK 0xfff6 [0x8a69fc0]
| STACK 0xfff8 [0x8a6fff6]
| STACK 0xfffa [0x8a6a3f9]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 文件交换
我们在实验过程中,会遇到 ubuntu 和 Linux0.11 之间的文件交换。因为在 Linux0.11 里,只有 vi 这个工具,编写代码实在是太麻烦了。我们可以在 Ubuntu 里用其他编辑器或者 IDE 编写完后,传给 Linux0.11 上运行
oslab 下的 是 0.11 内核启动后的根文件系统镜像文件,相当于在 bochs 虚拟机里装载的硬盘。在 Ubuntu 上访问其内容的方法是:
$ cd ~/oslab/
# 启动挂载脚本
$ sudo ./mount-hdc
2
3
4
之后,hdc 目录下就是和 0.11 内核一模一样的文件系统了,可以读写任何文件(可能有些文件要用 sudo 才能访问)。
# 进入挂载到 Ubuntu 上的目录
$ cd ~/oslab/hdc
# 查看内容
$ ls -al
2
3
4
5
读写完毕,不要忘了卸载这个文件系统:
$ cd ~/oslab/
# 卸载
$ sudo umount hdc
2
3
4
经这样处理以后,我们可以在 Ubuntu 的 hdc 目录下创建一个 xxx.c 文件,然后利用 Ubuntu 上的编辑工具(如 gedit 等)实现对 xxx.c 文件的编辑工作,在编辑保存以后。
注意 1:不要在 0.11 内核运行的时候 mount 镜像文件,否则可能会损坏文件系统。同理,也不要在已经 mount 的时候运行 0.11 内核。
注意 2:在关闭 Bochs 之前,需要先在 0.11 的命令行运行 “sync”,确保所有缓存数据都存盘后,再关闭 Bochs。
# 参考
mirrors / Wangzhike / HIT-Linux-0.11 · GitCode (opens new window)
操作系统原理与实践 - 熟悉实验环境 - 蓝桥云课 (opens new window)
Windows 下用 Bochs 编译运行 Linux-0.11_Demon 的黑与白的博客-CSDN 博客 (opens new window)