You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

293 lines
16 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 10 | 设置工作模式与环境(上):建立计算机
你好我是LMOS。
经过前面那么多课程的准备,现在我们距离把我们自己操作系统跑起来,已经是一步之遥了。现在,你是不是很兴奋,很激动?有这些情绪说明你是喜欢这门课程的。
接下来的三节课我们会一起完成一个壮举从GRUB老大哥手中接过权柄让计算机回归到我们的革命路线上来为我们之后的开发自己的操作系统做好准备。
具体我是这样来安排的今天这节课我们先来搭好操作系统的测试环境。第二节课我们一起实现一个初始化环境的组件——二级引导器让它真正继承GRUB权力。第三节课我们正式攻下初始化的第一个山头对硬件抽象层进行初始化。
好,让我们正式开始今天的学习。首先我们来解决内核文件封装的问题,然后动手一步步建好虚拟机和生产虚拟硬盘。课程配套代码你可以在[这里](https://gitee.com/lmos/cosmos/tree/master/lesson10~11)下载。
## 从内核映像格式说起
我们都知道一个内核工程肯定有多个文件组成为了不让GRUB老哥加载多个文件因疲劳过度而产生问题我们决定让GRUB只加载一个文件。
但是要把多个文件变成一个文件就需要封装,即把多个文件组装在一起形成一个文件。这个文件我们称为**内核映像文件**其中包含二级引导器的模块内核模块图片和字库文件。为了这映像文件能被GRUB加载并让它自身能够解析其中的内容我们就要定义好具体的格式。如下图所示。
![](https://static001.geekbang.org/resource/image/e4/14/e4000be6a176d1fd09b99bf6df02f914.jpg?wh=2045*2460 "内核映像文件格式")
上图中的GRUB头有4KB大小GRUB正是通过这一小段代码来识别映像文件的。另外根据映像文件头描述符和文件头描述符里的信息这一小段代码还可以解析映像文件中的其它文件。
映像文件头描述符和文件描述符是两个C语言结构体如下所示。
```
//映像文件头描述符
typedef struct s_mlosrddsc
{
u64_t mdc_mgic; //映像文件标识
u64_t mdc_sfsum;//未使用
u64_t mdc_sfsoff;//未使用
u64_t mdc_sfeoff;//未使用
u64_t mdc_sfrlsz;//未使用
u64_t mdc_ldrbk_s;//映像文件中二级引导器的开始偏移
u64_t mdc_ldrbk_e;//映像文件中二级引导器的结束偏移
u64_t mdc_ldrbk_rsz;//映像文件中二级引导器的实际大小
u64_t mdc_ldrbk_sum;//映像文件中二级引导器的校验和
u64_t mdc_fhdbk_s;//映像文件中文件头描述的开始偏移
u64_t mdc_fhdbk_e;//映像文件中文件头描述的结束偏移
u64_t mdc_fhdbk_rsz;//映像文件中文件头描述的实际大小
u64_t mdc_fhdbk_sum;//映像文件中文件头描述的校验和
u64_t mdc_filbk_s;//映像文件中文件数据的开始偏移
u64_t mdc_filbk_e;//映像文件中文件数据的结束偏移
u64_t mdc_filbk_rsz;//映像文件中文件数据的实际大小
u64_t mdc_filbk_sum;//映像文件中文件数据的校验和
u64_t mdc_ldrcodenr;//映像文件中二级引导器的文件头描述符的索引号
u64_t mdc_fhdnr;//映像文件中文件头描述符有多少个
u64_t mdc_filnr;//映像文件中文件头有多少个
u64_t mdc_endgic;//映像文件结束标识
u64_t mdc_rv;//映像文件版本
}mlosrddsc_t;
#define FHDSC_NMAX 192 //文件名长度
//文件头描述符
typedef struct s_fhdsc
{
u64_t fhd_type;//文件类型
u64_t fhd_subtype;//文件子类型
u64_t fhd_stuts;//文件状态
u64_t fhd_id;//文件id
u64_t fhd_intsfsoff;//文件在映像文件位置开始偏移
u64_t fhd_intsfend;//文件在映像文件的结束偏移
u64_t fhd_frealsz;//文件实际大小
u64_t fhd_fsum;//文件校验和
char fhd_name[FHDSC_NMAX];//文件名
}fhdsc_t;
```
有了映像文件格式我们还要有个打包映像的工具我给你提供了一个Linux命令行下的工具在Gitee代码仓库中通过[这个链接](https://gitee.com/lmos/cosmos/tree/master/tools/lmoskrlimg)获取),你只要明白使用方法就可以,如下所示。
```
lmoskrlimg -m k -lhf GRUB头文件 -o 映像文件 -f 输入的文件列表
-m 表示模式 只能是k内核模式
-lhf 表示后面跟上GRUB头文件
-o 表示输出的映像文件名
-f 表示输入文件列表
例如lmoskrlimg -m k -lhf grubhead.bin -o kernel.img -f file1.bin file2.bin file3.bin file4.bin
```
## 准备虚拟机
打包好了映像文件,我们还有很重要的一步配置——准备虚拟机。这里你不妨先想一想,开发应用跟开发操作系统有什么不同呢?
在你开发应用程序时可以在IDE中随时编译运行应用程序然后观察结果状态是否正确中间可能还要百度一下查找相关资料不要笑这是大多数人的开发日常。但是你开发操作系统时不可能写5行代码之后就安装在计算机上重启计算机去观察运行结果这非常繁琐也很浪费时间。
好在我们有虚拟机这个好帮手。虚拟机用软件的方式实现了真实计算机的全部功能特性它在我们所使用的Linux下其实就是个应用程序。
使用虚拟机软件我们就可以在现有的Linux系统之上开发、编译、运行我们的操作系统了省时且方便。节约的时间我们可以喝茶、听听音乐、享受美好生活。
### 安装虚拟机
这里我们一致约定**使用甲骨文公司的**VirtualBox**虚拟机**。经过测试我发现VirtualBox虚拟机有很多优点它的功能相对完善、性能强、BUG少而且比较稳定。
在现代Linux系统上安装VirtualBox虚拟机是非常简单的你只要在Linux发行版中找到其应用商店在其中搜索VirtualBox就行了。我们作为专业人士一条命令可以解决的事情为什么要用鼠标点来点去呢多浪费时间。
所以你只要在终端中输入如下命令就行了我假定你安装了Ubuntu系的Linux发行版这里Ubuntu的版本不做规定。
```
sudo apt-get install virtualbox-6.1
```
运行Virtualbox后如果出现如下界面就说明安装VirtualBox成功了。
![](https://static001.geekbang.org/resource/image/8f/a9/8f8f2c86722cf19e8301c6c72426f9a9.jpg?wh=980*600 "安装VirtualBox")
### 建立虚拟电脑
前面我们只是安好了虚拟机管理软件,我们还要新建虚拟机才可以。点击上图中的新建,然后选择专家模式,就可以进入专家模式配置我们的电脑了。
尽管它是虚拟的我们还是可以选择CPU类型、内存大小、硬盘大小、网络等配置为了一致性请你按照如下截图来配置。
![](https://static001.geekbang.org/resource/image/f1/e3/f1ed52ae172ea8bc1710a3ef9296bbe3.jpg?wh=902*634 "新建虚拟机")
![](https://static001.geekbang.org/resource/image/b0/b1/b05365a0a6fb4477042a43326e43f5b1.jpg?wh=1000*820 "新建虚拟机")
可以看到我们选择了64位的架构1024MB内存但是不要添加硬盘后面自有妙用。显卡是VBoxVGA还有硬件加速这会让虚拟机调用我们机器上真实的CPU来运行我们的操作系统。
## 手工生产硬盘
上面的虚拟机中还没有硬盘,没有硬盘虚拟机就没地方加载数据,我们当然不是要买一块硬盘挂上去,而是要去手工生产一块硬盘。你马上就会发现,从零开始生产一块虚拟硬盘,这比从零开始写一个操作系统简单得多。
至于为什么手工生产硬盘,我先卖个关子,你看完这部分内容就能找到答案。
其实大多数虚拟机都是用文件来模拟硬盘的即主机系统HOST OS 即你使用的物理机系统 )下特定格式的文件,虚拟机中操作系统的数据只是写入了这个文件中。
### 生产虚拟硬盘
其实虚拟机只是用特定格式的文件来模拟硬盘所以生产虚拟硬盘就变成了生成对应格式的文件这就容易多了。我们要建立100MB的硬盘这意味着要生成100MB的大文件。
下面我们用Linux下的**dd命令**用指定大小的块拷贝一个文件并在拷贝的同时进行指定的转换生成100MB的纯二进制的文件就是1100M字节的文件里面填充为0 ),如下所示。
```
dd bs=512 if=/dev/zero of=hd.img count=204800
;bs:表示块大小这里是512字节
;if表示输入文件/dev/zero就是Linux下专门返回0数据的设备文件读取它就返回0
;of表示输出文件即我们的硬盘文件。
;count表示输出多少块
```
执行以上命令就可以生成100MB的文件。文件数据为全0。由于我们不用转换数据就是需要全0的文件所以dd命令只需要这几个参数就行。
### 格式化虚拟硬盘
虚拟硬盘也需要格式化才能使用,所谓格式化就是在硬盘上建立文件系统。只有建立了文件系统,现有的成熟操作系统才能在其中存放数据。
可是问题来了。虚拟硬盘毕竟是个文件如何让Linux在一个文件上建立文件系统呢这个问题我们要分成三步来解决。
第一步把虚拟硬盘文件变成Linux下的回环设备让Linux以为这是个设备。其实在Linux下文件可以是设备设备可以是文件。下面我们用losetup命令将hd.img变成Linux的回环设备代码如下。
```
sudo losetup /dev/loop0 hd.img
```
第二步将losetup命令用于设置回环设备。回环设备可以把文件虚拟成Linux块设备用来模拟整个文件系统让用户可以将其看作硬盘、光驱或软驱等设备并且可用mount命令挂载当作目录来使用。
我们可以用Linux下的mkfs.ext4命令格式化这个/dev/loop0回环块设备在里面建立EXT4文件系统。
```
sudo mkfs.ext4 -q /dev/loop0
```
第三步我们用Linux下的mount命令将hd.img文件当作块设备把它挂载到事先建立的hdisk目录下并在其中建立一个boot这也是后面安装GRUB需要的。如果能建立成功就说明前面的工作都正确完成了。
说到这里,也许你已经想到了我们要手工生成硬盘的原因。**这是因为mount命令只能识别在纯二进制文件上建立的文件系统如果使用虚拟机自己生成的硬盘文件mount就无法识别我们的文件系统了。**
```
sudo mount -o loop ./hd.img ./hdisk/ ;挂载硬盘文件
sudo mkdir ./hdisk/boot/ ;建立boot目录
```
进行到这里我们会发现hdisk目录下多了一个boot目录这说明我们挂载成功了。
### 安装GRUB
正常安装系统的情况下Linux会把GRUB安装在我们的物理硬盘上可是我们现在要把GRUB安装在我们的虚拟硬盘上而且我们的操作系统还没有安装程序。所以我们得利用一下手上LinuxHOST OS通过GRUB的安装程序把GRUB安装到指定的设备上虚拟硬盘
想要安装GRUB也不难具体分为两步如下所示。
```
第一步挂载虚拟硬盘文件为loop0回环设备
sudo losetup /dev/loop0 hd.img
sudo mount -o loop ./hd.img ./hdisk/ ;挂载硬盘文件
第二步安装GRUB
sudo grub-install --boot-directory=./hdisk/boot/ --force --allow-floppy /dev/loop0
--boot-directory 指向先前我们在虚拟硬盘中建立的boot目录。
--force --allow-floppy :指向我们的虚拟硬盘设备文件/dev/loop0
```
可以看到,现在/hdisk/boot/目录下多了一个grub目录表示我们的GRUB安装成功。请注意这里还要在/hdisk/boot/grub/目录下建立一个**grub.cfg文本文件**GRUB正是通过这个文件内容查找到我们的操作系统映像文件的。
我们需要在这个文件里写入如下内容。
```
menuentry 'HelloOS' {
insmod part_msdos
insmod ext2
set root='hd0,msdos1' #我们的硬盘只有一个分区所以是'hd0,msdos1'
multiboot2 /boot/HelloOS.eki #加载boot目录下的HelloOS.eki文件
boot #引导启动
}
set timeout_style=menu
if [ "${timeout}" = 0 ]; then
set timeout=10 #等待10秒钟自动启动
fi
```
### 转换虚拟硬盘格式
你可能会好奇我们前面好不容易生产了mount命令能识别的虚拟硬盘这里为什么又要转换虚拟硬盘的格式呢
这是因为这个纯二进制格式只能被我们使用的Linux系统识别但不能被虚拟机本身识别但是我们最终目的却是让这个虚拟机加载这个虚拟硬盘从而启动其中的由我们开发的操作系统。
好在虚拟机提供了专用的转换格式的工具,我们只要输入一行命令即可。
```
VBoxManage convertfromraw ./hd.img --format VDI ./hd.vdi
;convertfromraw 指向原始格式文件
--format VDI 表示转换成虚拟需要的VDI格式
```
### 安装虚拟硬盘
好了到这里我们已经生成了VDI格式的虚拟硬盘这正是我们虚拟机所需要的。然而虚拟硬盘必须要安装虚拟机才可以运行也就是这个hd.vdi文件要和虚拟机软件联系起来。
因为我们之前在建立虚拟机时并没有配置硬盘相关的信息,所以这里需要我们进行手工配置。
配置虚拟硬盘分两步第一步配置硬盘控制器我们使用SATA的硬盘其控制器是intelAHCI第二步挂载虚拟硬盘文件。
具体操作如下所示。
```
#第一步 SATA的硬盘其控制器是intelAHCI
VBoxManage storagectl HelloOS --name "SATA" --add sata --controller IntelAhci --portcount 1
#第二步
VBoxManage closemedium disk ./hd.vdi #删除虚拟硬盘UUID并重新分配
#将虚拟硬盘挂到虚拟机的硬盘控制器
VBoxManage storageattach HelloOS --storagectl "SATA" --port 1 --device 0 --type hdd --medium ./hd.vdi
```
因为VirtualBox虚拟机用UUID管理硬盘所以每次挂载硬盘时都需要删除虚拟硬盘的UUID并重新分配。
### 最成功的失败
现在硬盘也安装好了下面终于可以启动我们的虚拟电脑了我们依然通过命令启动在Linux终端中输入如下命令就可以了。
```
VBoxManage startvm HelloOS #启动虚拟机
```
输入以上命令就会出现以下界面出现GRUB引导菜单。
![](https://static001.geekbang.org/resource/image/f7/8d/f7a091c67a53582255117f3790904a8d.jpg?wh=740*553 "虚拟机启动")
直接按下回车键就能选择我们的HelloOSGRUB就会加载我们的HelloOS但是会出现如下错误。
![](https://static001.geekbang.org/resource/image/80/ee/809e93a2e3bdce9f5d3e0ac7d699feee.jpg?wh=740*553 "虚拟机GRUB未找到文件")
上面的错误显示GRUB没有找到HelloOS.eki文件这是因为我们从来没有向虚拟硬盘中放入HelloOS.eki文件所以才会失败。
**但这是我们最成功的失败因为我们配置好了虚拟机手动建造了硬盘并在其上安装了GRUB到这里我们运行测试环境已经准备好了。**
其实你不必太过担心,等我们完成了二级引导器的时候,这个问题会迎刃而解。
## 重点回顾
希望今天这节课给你带来成就感虽然我们才走出了万里长征的第一步。为了这一步我们准备了很多。但是我们始终没忘记这一课程的目的即我们要从GRUB老大哥手里接过权柄控制计算机王国为此我们完成了后面这三个工作。
1. 我们了解了内核映像格式,以便我们对编译产生的内核程序文件进行封装打包。
2. 为了方便测试我们的操作系统,我们了解并安装了虚拟机。
3. 手动建立了虚拟硬盘对其格式化在其中手动安装了GRUB引导器并且启动了虚拟电脑。
虽然我们启动虚拟电脑失败了,但是对我们而言却是巨大的成功,因为它标志着我们测试运行内核的环境已经成功建立,下一课我们将继续实现二级引导器。
## 思考题
请问我们为什么要把虚拟硬盘格式化成ext4文件系统格式呢
欢迎你在留言区跟我交流探讨,如果你身边有对写操作系统感兴趣的朋友,也欢迎把这节课分享给他,一起学习。
我是LMOS我们下节课见