拼一个自己的操作系统 SnailOS 0.03的实现
拼一个自己的操作系统SnailOS0.03源代码-Linux文档类资源-CSDN下载
操作系统SnailOS学习拼一个自己的操作系统-Linux文档类资源-CSDN下载
SnailOS0.00-SnailOS0.00-其它文档类资源-CSDN下载
准备一些工具软件包吧
https://pan.baidu.com/s/19tBHKyzOSKACX-mGxlvIeA?pwd=i889
相信“工欲善其事必先利其器”这句话在大家的耳朵里已经磨出了茧子。不过无论如何拼操作系统都是有点小难度的事情。我们也不能例外,要是打一场无准备之丈,失败的后果对于我这样脆弱的心灵的人恐怕是无法承受的。
啰嗦了这么多,还是让给大家展示一个我们精挑细选浓缩精华的工具清单。
Bochs-win64-2.6.11.exe
dd.exe
mingw-get-setup.exe
nasm-2.15.05-installer-x64.exe
npp.8.1.4.Installer.x64.exe
ue_chinese.exe
UQiDong_STA_bd.exe U启动U盘启动盘制作工具
VirtualBox-6.1.34-150636-Win.exe
Windows7以上自带分区工具diskpart、copy、move
Grub2.06
嗯,就是清单上的这几种工具了,这些就可能完全够用了,而且是分别针对两种开发环境的。Bochs和virtualbox是虚拟机软件,也就是在windows上模拟PC机裸机的软件。当然,这些年的这类软件层出不穷,据说还有一款国产的。本来想支持国货来的,不过真的是用惯了这两种比较早的虚拟机,特别地懒得迁移到新的产品上去。dd.exe和diskpart、copy、move软件是将编译好的内核文件拷贝到硬盘镜像文件的必备工具,它们能够很大程度地提高开发平台的自动化率(吹吹小牛了)。Mingw和nasm分别是汇编和C语言的编译器,内核的所有代码的编译都是它们的功劳。notepad++和ue是文本编辑软件,同时ue还是一款二进制查看和编辑软件,这类软件真的是数不胜数了,你甚至可以使用windows自带的记事本,当然效率上就毁誉参半了。U启动是一款用于生成winpe启动引导镜像iso文件的软件。有了它我们可以极其方便地将grub2安装到虚拟机的虚拟硬盘上。而上面提到的grub2是一款符合multiboot2标准的引导加载程序,向unix、linux、windows都可以通过它来引导启动。
当然还有必不可少的基本参考书,
30天自制操作系统.pdf
bochs中文用户手册.pdf
CLK-5.0.1-WithCover.pdf
GCC 中文手册.pdf
nasm中文手册.pdf
x86汇编语言 从实模式到保护模式完整版.pdf
[ORANGE’S:一个操作系统的实现].于渊.清晰扫描版.pdf
操作系统真象还原.pdf
汇编语言程序设计.林邦杰.陈明.扫描版.pdf
顺便说一下,这些都是之前下载得到pdf版本了,只是书不在跟前的时候方便我自己使用。网上很可能存在版权问题,真心强烈的建议大家购买正版书籍了!同时上面这些书也不是都真正的用到了,这还是要根据大家自己学习的程度了。而且仅仅这些东西,恐怕也还是不够用的,还是以后用到的时候,再一一介绍吧。
搭建一个可靠、快捷的工作环境
bochs和dd搭配组成的工作环境
Bochs就是一款模拟x86硬件的软件,它本身能够在Windows等不同的操作系统上运行,而且是一款开源的模拟器,如果你对它的实现感兴趣,可以从网络上轻易下载到源代码。不过,在这里我们更关心它在Windows平台上的安装和应用了。
在Windows平台上,为了应用我们只要直接下载它的二进制包就可以了,通常就是叫做“Bochs-win64-2.6.11.exe”的文件。双击之后,按照提示轻松的就安装完成了,完全是迅雷不及掩耳的速度。这主要是由于这款软件小巧而精致的原因了。顺便说一下,之所以说这款软件好用,主要是因为在Windows上安装完成后,虚拟机是自带调试功能的。这样一来,我们在早期开发中遇到什么棘手的问题,就可以通过出错的指令来判断问题的具体位置,所以说这一点是这款虚拟机软件贴心让人感动的原因。
nasm汇编器的安装和配置
那么下面我们就讲讲和虚拟机是如何跟编译软件以及dd软件密切配合,从而实现开发编译运行环境自动化的。
首先让我们从简单的来说,对了说到这里,差点忘了,我们的汇编和C语言的编译器还都没有安装和配置了。
用到的汇编器是Nasm,它也是一款开源软件,同样的我们也是更关心它的安装和配置。开源的部分留给喜欢编译原理的朋友好了。Windows软件安装的雷同之处就在于,双击然后一路下一步就好了,这也是我偏向于选择Windows原因了,唯一需要我们注意的一点是软件的安装路径,这个路径关系到软件原始位置的查找,也就是在我写作这本书的时候,发现居然找不到软件的安装路径(主要是嫌桌面太乱,把一些常用的快捷方式删除了),因此,在确定路径的时候,颇费周章,搞了好半天。
在我的Windows上,nasm的默认安装路径居然是C:\Users\free2\AppData\Local\bin\,这可让我废了就牛二虎之力才找到它。起因就是自己没有在安装的时候注意安装路径。得到和复制好这个路径后,就可以按照下面的步骤粘贴到系统路径中了。
第1步点击设置
第2步点击系统
第3步点击关于
第4步点击高级设置
第5步点击环境变量
第6步点击Path
通过新建一个路径的环境变量把“C:\Users\free2\AppData\Local\bin\NASM”添加到系统路径中去,这样做的好处是只要打开命令行,不管默认工作路径是什么,都可以运行我们的软件。这里为什么要这么细致的用贴图来说一下,设置路径环境变量的工作呢?还不是因为接下来的软件的设置都是依葫芦画瓢做出来的,因此,这里的费力当然是为了以后的省力,一劳永逸吗!
接下来用Windows旗标+R,并输入cmd
确定后,出现了命令行窗口,输入nasm,确认一下我么们刚才的设置。
出现了nasm提示没有输入文件信息,说明安装和设置成功。如果是“'nasm' 不是内部或外部命令,也不是可运行的程序或批处理文件。”的提示,就是刚才的安装或者设置有问题。
mingw套件的安装和配置
Mingw是一款在windows上开发软件的工具集合,也是一款开源软件。下图是双击mingw-get-setup.exe程序弹出的窗口,我们要点击install。
下面这张图,唯一需要我们注意的是Installation Directory,我们可以根据喜好安排一个自己满意的路径。
当我们按下continue后,如下图系统会自动下载mingw manager,它才是真正的安装程序,当然会是网络安装模式。
mingw manager安装完成后,continue按钮才会让我们点击,则进入下图,我们已经标记好了要安装的软件,这个当然是根据实际需要来安装了。
在installation菜单下选择apply changes,应用改变。
接下来点击apply
下图就是联网安装了。
由于网络的问题,mingw的安装还是挺漫长的,而且还会出现一些包不能下载的情况。这时候我们只要点击确定就可以了,大概率不会影响我们的使用。安装完成后,我们应该到软件的bin目录下确认一下是否有gcc.exe、as.exe、ld.exe、objcopy.exe、mingw32-make.exe。这几个软件是我们开发时必要用到的。当然其他软件是否存在依赖关系,我不能确定。所以大家还是不要将哪些认为没有的套件删除。一个值得提醒的问题是,这个软件安装一次后,如果不想升级的话,以后直接复制此目录的所有文件就行了。对于mingw32-make.exe的文件名称太长了。我干脆复制一个,并改名为make.exe,这样以来我们将来的用到make命令时就不用输入那么长的一串字符了。
同样的,安装完成后也需要在windows的环境变量中设置适当的路径。这样才能在命令行的任何路径中使用。
bochs的安装和配置
Bochs虚拟机的安装不想费那么多的笔墨了。主要是安装过程太简单了,只要一路下一步就好了。不过,我们最好把dlx linux demo选上。
这样可以免费的得到一份bochsrc.bxrc,这是bochs运行虚拟机的配置文件,我们只要稍作改动就能运行自己的虚拟机了。至于dlx linux真的没啥用,也就这样放着吧。哦,对了不要忘记设置windows的环境变量啊。
dd要放在哪里
在简单介绍工具集合时,我们已经提到了Dd。它是一款能够将文件写入磁盘或磁盘镜像文件扇区的软件。然而它小到只有一个可执行程序。因此,我们可以和上面任意一款软件的可执行文件放在一起,这样就不用单独设置环境变量了。比如,我就把他放在了nasm的目录中。
运行起第1个开发环境
上面这种基于bochs虚拟机的开发环境的所有软件就全部安装完成了,下面我们该测试一下好不好用了。这其实就是围绕命令行和bochsrc.bxrc文件来完成的。让我们从头梳理一下吧。
第1步:准备测试环境需要的所有文件,让我们做个清单吧。
顶级工作目录SnailOS_bochs
子目录bochs位于顶级目录中,其中的文件有:
a.img
b.img
BIOS-bochs-latest
bochsrc.bxrc
c.img
VGABIOS-lgpl-latest
x11-pc-us.map
子目录working位于顶级目录中,其中的文件有:
Makefile
Objcopy_move.cmd
Boot.asm
Q.cmd
为了将来的开发方便,我们新建了一个SnailOS_bochs的目录,并把它作为开发的顶级目录。
SnailOS_bochs中有两个子目录分别是bochs和working。我们的所有工作将在working中完成,而bochs是虚拟机运行起来必须的文件,这些文件我们一旦备齐,就基本上不会改动了。现在让我们一个一个的把它们找出来,从而当成我们开发环境的一部分。先说我自己杜撰的q.cmd文件吧。在working目录中新建一个q.cmd的文本文件,文件的内容如下:
%qcmd.cmd 创建者:至强 创建时间:2022年8月%
cmd.exe
这个文件简单的没法再简单了吧,就是起到在工作目录中快捷的运行命令行提示符的作用。双击我们就来到命令行,而且工作路径是当前目录。
再来看看Bochs目录中叫*.img的3个文件,它们都是由bochs工具箱中的软件bximage生成的。从名字上我们就可以知道它们分别是A盘、B盘、C盘的镜像文件。可见bximage是一款生成.img格式镜像文件的软件。至于怎么生成还是详见下图吧。
和下面的图配合起来看,大家就明明白白了吧。
值得一提的是,上面生成了a.img文件的最后几句英文。
Creating floppy image 'a.img' with 2880 sectors
The following line should appear in your bochsrc:
floppya: image="a.img", status=inserted
(The line is stored in your windows clipboard, use CTRL-V to paste)
对,它说a.img有2880个扇区,floppya......这行在bochs的配置文件中会出现,希望我们复制到剪切板,以备后用。
到此,A盘、B盘的已经生成了,下面在看看C盘的生成。
同样的有几句英文,我们只是择了中间的一句。
Creating hard disk image 'c.img' with CHS=8322/16/63 (sector size = 512)
它告诉我们C盘柱面/磁头/扇区的个数,以及每个扇区的字节数。之所以择了这句,是因为在bochs的配置文件中,要用到这些的地方。顺便说一下,我们的硬盘大小是4096M,这个应该够用了吧。
生成好的a.img、b.img、c.img都要移动到bochs目录中。
BIOS-bochs-latest、VGABIOS-lgpl-latest、x11-pc-us.map是bochs中硬件相关的文件,前两个在bochs的安装目录下,后一个在keymaps目录下,复制过来就行了。从名字上可以看出来它们分别是BIOS、VGABIOS和键盘的二进制文件。
最后我们还要把bochsrc.bxrc文件也复制到bochs目录中, 它正是我们屡次提到的bohcs配置文件,最后我们要好好的做一番修改。
我们把修改后的文件放在了下面,并且在关键的地方给了一些注释。
Bochsrc.bxrc文件全文
###############################################################
# bochsrc.txt file for DLX Linux disk image.
###############################################################
## 在bochs的配置文件中"#"是注释语句,我们的注释用"##"。
# how much memory the emulated machine will have
## 这里是虚拟机的内存容量。
megs: 512
# filename of ROM images
## 看到了吧,用到我们的从bochs目录中复制的文件。
romimage: file=../bochs/BIOS-bochs-latest
vgaromimage: file=../bochs/VGABIOS-lgpl-latest
# what disk images will be used
## A盘和B盘的路径和状态设置。
floppya: 1_44=../bochs/a.img, status=inserted
floppyb: 1_44=../bochs/floppyb.img, status=inserted
# hard disk
## PC机的ide通道有2个,每个通道可以装2块硬盘,主通道的第1块硬盘io端口的地址
## 是0x1f0,第2块硬盘的io端口的地址0x3f0,所用的中断向量号是14。
## 主通道的类型、C盘的路径、以及柱面/磁头/扇区数(这个在创建硬盘的时候我们
## 复制了,看来有备无患真的不是白说的!)。
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=disk, path="../bochs/c.img", cylinders=8322, heads=16, spt=63
# choose the boot disk.
## 默认的引导设备
boot: c ##floppy
# default config interface is textconfig.
#config_interface: textconfig
#config_interface: wx
#display_library: x
# other choices: win32 sdl wx carbon amigaos beos macintosh nogui rfb term svga
# where do we send log messages?
# log: bochsout.txt
# disable the mouse, since DLX is text only
## 鼠标是否默认可用,0为不可用,1为可用。
mouse: enabled=0
# set up IPS value and clock sync
## 下面两项让我来猜一下,应该是CPU的频率和时钟的同步,我们不用改的。
cpu: ips=15000000
clock: sync=both
# enable key mapping, using US layout as default.
#
# NOTE: In Bochs 1.4, keyboard mapping is only 100% implemented on X windows.
# However, the key mapping tables are used in the paste function, so
# in the DLX Linux example I'm enabling keyboard_mapping so that paste
# will work. Cut&Paste is currently implemented on win32 and X windows only.
## 用到了吧,这个是我们从bochs安装目录中复制的键盘相关的二进制文件。
keyboard: keymap=../bochs/x11-pc-us.map
#keyboard: keymap=../keymaps/x11-pc-fr.map
#keyboard: keymap=../keymaps/x11-pc-de.map
#keyboard: keymap=../keymaps/x11-pc-es.map
通过上面的文件我们知道,这个文件没有什么难度,只要我们把上面的关键点都改好了就大功告成了。
经过一番折腾,终于又切换到讲working目录的文件了。还是从简单的开始吧。
%objcopy_move.cmd 创建者:至强 创建时间:2022年8月%
objcopy -I pe-i386 -O elf32-i386 kernel.pe
move kernel.pe kernel.elf32
Objcopy是mingw工具集中的可执行程序,它可以将windows系统的经过gcc编译生成的可执行文件,转化为elf格式文件。为什么要转化呢?第一个原因是elf格式分析的资料比较多,这样就方便我们将来解析elf文件。二是elf文件是multiboot2规范中无条件解析的文件,能够默认的被grub作为内核引导。
Move是windows中命令行中一个常驻内存的命令,用于给文件改名或者移动文件,我们就是用了改名的功能。也是objcopy的原因,它居然在改变文件格式的情况下保留原文件名称,因此我们要改名,以区分原文件和生成后的文件。当然目前我们还没有用到这个文件。不过既然这么简单就在这里说了吧。
我们要说的第二个文件是Makefile,相信大名顶顶的make都听过过吧,还是先看文件内容。
# Makefile 创建者:至强 创建时间:2022年8月
build: boot.asm
# 用nasm把boot.asm编译成纯二进制可执行文件boot.bin。
nasm -fbin -o boot.bin boot.asm
install: build
# 把boot.bin写入到硬盘映像文件的引导扇区,也就是第
# 1个扇区,但dd从0开始计算,所以"seek=0"。
dd if=boot.bin of=..\bochs\c.img seek=0 count=1
run: install
# 用bochsrc.bxrc作为配置文件快速启动bochs
bochs -q -f ..\bochs\bochsrc.bxrc
dbg: install
# 用bochsrc.bxrc作为配置文件快速启动bochsdbg,从名称上
# 就猜到了把这是运行调试模式。
bochsdbg -q -f ..\bochs\bochsrc.bxrc
clean:
# 删除产生的中间文件和垃圾文件
del *.bin *.o *.pe *.elf32 ..\bochs\c.img.lock
为了照顾初学,文件注释是多了些。这里还是要再说一些东西。在文件中像“#”是注释。“:”之前的我称之为自定义命令,而之后的是依赖关系。自定义命令下面的语句是在命令行提示符中输入“make run”等形式后,真正执行的命令集合,如这里的bochs -q -f ..\bochs\bochsrc.bxrc。还是“make run”这条,由于它依赖于install命令,因此会先执行install下面的至下一个自定义命令开始的若干条语句(这里暂时都是1条),而“install”依赖于“build”,所以又去执行“build”下面的语句,依次递归,直到最初的没有依赖的语句。一个小的关注点是,每条语句都是以tab键开头的,只是tab键是非显示字符,这个一定要关切呀。
boot.asm对于初学算是有点长了,好在依然是注释满满。
【boot.asm】
; boot.asm 创建者:至强 创建时间:2022年8月
; 告诉编译器,此处开始的代码要汇编成实模式16位
; 的指令。
bits 16
; 指令开始的标签,这里也可以没有此标签。
start16:
; 将段寄存器的值都设置位0x7c0,也就是段基地为0x7c00。
; 将堆栈指针也设置成段的首地址。
mov ax, 0x7c0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0
; 清屏的过程调用。
call cls
; 简单打印字符串的过程调用。
push string
call put_s
add sp, 1 * 2
jmp $
; 打印一个字符的过程。
put_c: ; void put_c(char c)
push bp
mov bp, sp
push es
push di
mov ax, 0xb800
mov es, ax
mov di, [pos]
mov al, [bp + 2 * 2]
mov ah, 0xc
mov [es:di], ax
inc di
inc di
mov [pos], di
pop di
pop es
mov sp, bp
pop bp
ret
; 打印字符串的过程。
put_s: ; void put_s(char* s)
push bp
mov bp, sp
push bx
mov bx, [bp + 2 * 2]
.1:
mov ax, [bx]
or ax, ax
jz .2
push ax
call put_c
add sp, 1 * 2
inc bx
jmp .1
.2:
pop bx
mov sp, bp
pop bp
ret
; 清除屏幕的过程。
cls: ; void cls(void)
push bp
mov bp, sp
push es
push di
mov ax, 0xb800
mov es, ax
xor di, di
mov cx, 2000
mov al, ' '
mov ah, 0xf
.1:
mov [es:di], ax
inc di
inc di
loop .1
pop di
pop es
mov sp, bp
pop bp
ret
; 用于存储屏幕位置数值。
pos: dw 0
; 打印的字符串。
string: db ' Hello SnailOS...!', 0
; 告诉编译器预留出合适的空间到509字节处都写入数值0。
times 512 - 2 - ($ - $$) db 0
; 在510和511字节处分别写入魔数0x55和0xaa。地址通常是
; 从0开始计算。
magic: dw 0xaa55
8086是一个16位的处理器,它的地址总线确是20位。可访问的最大物理内存是1M。可是装载段基地的段寄存器确是16位的。那么怎么访问20位的地址呢?因此,设计者就开始套路了。它们刻意把段基地址弄成是16的整数倍,这样以来16位段基地址左移4位(等效于乘以16),就形成了一个20位的地址。所以段基地址可以从任何16的整数倍开始,而段的最大长度只能是64k。计算机开机自检后,CPU使用一条跳转指令,跳转到CMOS中的BIOS代码中去执行。BIOS也做一些重要的工作,最后将启动设备上的首扇区(被称为引导扇区)读入内存中。即是物理地址的0x7c00处,换算成段寄存器中的段基地址即是0x7c0。而且BIOS接下来就会跳转到此处继续执行。这个时候的代码段寄存器的值应该是0,也就是代码段的基地址是0。因为没有超过64k,所以这样是没有问题的。而这个引导扇区的512字节是我们自己定义的代码和数据,所以我们期待用数据段基地址来访问数据。因此,这里我们重置了ds、es、ss三个段寄存器,同时接下来我们要进行过程调用(就是C中的函数),过程调用中,必须使用堆栈,所以这里还设置了栈指针为该段的开始处。而堆栈是向下生长的,这样就不会和我们的程序相互覆盖数据。
在BIOS的诸多骚操作后,不但载入了引导扇区。还把显示模式设置的成了,80*25的字符模式,显存物理地址定义为0xb8000处。所以在显示字符和清理屏幕的函数中,我们都不得不使用段前缀,来访问这个超过64k的内存区域。关于函数调用、汇编以及C语言的问题现在讨论还为时过早,那几个函数大家也可以忽略,现在只知道功能就可以了。因此这里不说了,如果有兴趣,可以仔细的看看代码就好了。对了如果没有学过汇编语言的话,因该找一本intel汇编的书来学学,nasm正好是intel的汇编语法,啊,我们这个时代的程序员还是蛮幸运的。
最后这段程序还只是剩了一个魔数没有说了,不要在意它,这种玩意都是随意为之的,只是时间久了,大家都习惯了,也就不会改了。跟引导有关的扇区大都是这个结尾。
第一个运行环境就讲到这里了,是不是很臭很长,好啰嗦。好了,让我们看一下运行效果吧!
这里就只能是静态的图片了,其实那个光标还在那里一闪一闪的,很是动人。
以virtual box为核心的开发环境
virtual box的安装与配置
Virtual box虚拟机同样是一款开源软件,我安装的宿主系统是windows10,安装的过程非常简单,一路下一步安装完成。但是到了运行的阶段就会提示问题。我先后xp、win7上也安装过这个软件。在win7、win10上安装都不难,但运行是先后出现过很多的问题。由于问题不定,而且已经在自己的系统上运行好了,所以无法追溯到原来的问题。这里只是给大家提个具体的建议,如果出现了运行问题,建议大家把问题复制下来,在搜索引擎中直接查问题,大多数情况下都有答案,而且解决的办法大都是修改注册表。同时安装和运行后也应该设置环境变量。
下图是virtual box在windows10成功运行的情况。
安装一个U启动软件
这个软件是能够生成启动引导ISO镜像的软件,似乎与我们开发风马牛不相及了。不过大家别急,它可是发挥了至关重要的作用呀!
它的安装极其简单,我们主要是用它生成iso镜像文件。
确定好正确的路径后,点击“ISO模式”,点击下面的“生成ISO”
创建一个适合开发环境的虚拟机
新建一个虚拟机,如图
创建虚拟机需要确定名称,选择安装路径、类型以及版本,如下图是我们的选择。然后就可以下一步。
确定内存容量,这里我们要搞得大一些,否则ISO中的winpe可能无法运行。我选择了4G。
下图是询问我们是否创建虚拟硬盘。这里我们当然要创建了。
接下来,我们要选择虚拟硬盘的格式,我们果断的选择VHD格式,因为他是windows7及其以上系统默认支持的。等会儿大伙就会知道了,选择这种格式的诸多好处。
继续要做的是选择“固定大小”比较稳妥。
磁盘的容量根据需要来确定,我趋向至少是物理内存的2倍。最后点击“创建”,稍等,则虚拟机创建完成。
创建完成后,我们应该观察一下存储那块。确保我们的控制器是IDE,对了硬盘容量怎么是1G,你不是说最好是内存的2倍吗,怎么是1G,哈哈,我是在演示吗!
在虚拟机中折腾一番,完成环境创建
这次我们就一气呵成了,因为本来就不怎么复杂吗。
首先我们在存储——第二个IDE控制起主通道:点击[光驱] 没有盘片,选择盘片的ISO文件要根据自己的ISO镜像的具体路径。完成后启动虚拟机,则进入进入以下画面。
通过键盘上的方向键,进入到DiskGenius硬盘分区工具
在上图中,点击选择硬盘0-->选择新建分区,在建立新分区对话框中,分区类型为“主硬盘分区”,文件系统类型为FAT32,其他默认,然后确认。而后点击保存更改,并在接下来的“立即生效对话框”中选择“是”,在“格式化对话框”中选择“是”。则我们的第一阶段工作完成。
我们的第二阶段工作是使用diskpart(windows7以上系统自带)工具将虚拟硬盘挂载到windows宿主机上,然后将下列文件和工具集复制到虚拟硬盘上。
在系统上找到虚拟机文件的绝对路径,并复制下来。我的路径是C:\Users\free2\.VirtualBox\temp\temp.vhd
这里我们先完成挂载的过程,运行命令行,如下图。
成功挂载后我们便能够在,Windows系统的“此电脑”中看到新增的盘符。可以向其中复制文件。
我们要复制的就是前面提到的grub整个文件夹和一个gurb.cfg的配置文件。
接下来我们返回到该虚拟磁盘的上层,右键菜单中选择“弹出”,卸载此硬盘。
然后,我们要再次启动虚拟机,进入winpe系统(我选择2003经典版),稍等启动完毕后进行grub的安装和配置。
安装成功的画面入下。
在虚拟机的C盘上会多了一个boot目录,这就是我们安装的grub。
这时,我们还需要把配置文件grub.cfg复制到\boot\grub\中去。
关闭虚拟机后,移除虚拟光盘。
重新启动虚拟机后就会后进入gurb的画面了。
现在展示一下,grub.cfg文件,同时把刚在安装grub过程中,用到的最重要的命令提供给大家。
#grub.cfg 创建者:至强 创建时间:2022年8月
timeout=10
defualt=0
menuentry "SnailOS" {
set root=hd0,1
multiboot2 /kernel
}
重要命令: grub-install //./physicaldirve0
到目前为止,我们在上面的画面中按下回车键,是不能进入任何系统的,它会提示我们没有内核文件/kernel。
因此,在这一节的最后,我们仍然坚持要实现一个“内核”,并再次把“Hello World”的风采呈现给大家。不过这一次是用grub2来引导内核,完全没有个头绪吗?好在作为笔者的我早就给大家准备好了剧本,就等着接着演这出好戏了。
这个剧本就是《Multiboot2 规范版本 2.0》。什么?“来到这里就要读一本规范,操作系统的开发简直太特么难了吧?”要真的是这样,不要说是大家了,笔者也早就放弃了。其实没有那么难了。这里我们仅仅是来个“断章取义”,只要是抓住重点就完全好了。这是大家先要去下图的网址参观一下。
什么完全的E文,一脸懵逼了吧!好了不必惊慌,大多数浏览器都自带翻译软件,而且翻译的还不错哦。看到了吧,瞬间变脸那是妥妥的。
客观的说multiboot2真的像老太太裹脚布又臭又长了,能够坚持从头看到尾的人真的凤毛麟角,哈哈,“古之圣人鲜矣!”。接下来我们该做些什么呢?
我们直接看示例好了,看看能不能找到些灵感。看到了吗?“4个例子”,瞧瞧这翻译的多么的尴尬呀!不过下面几行中有个“示例操作系统代码”却是很抢眼了。还是点进去看下吧。
看到了吧,multiboot2.h、boot.S、kernel.c三个文件,和上面的4个例子,简直驴唇对了马嘴的感觉。
不过boot.S倒是挺像我们第一个开发环境的boot.asm的,让我们凭直觉果断看进去吧!
这次我们又切换回了英文,是不满心的欢喜又没了。而且感觉这个boot.S的语法怪怪的,似曾相识,又无可奈何!
是的boot.S使用的是AT&T的汇编语法,初学的同学真的是在这里又遇到了一头拦路虎呀!
好在笔者比较“勤奋”,居然把boot.s改成了intel汇编的格式。哈哈,到了这里也不买关子了。直接把实现“操作系统”的几个文件,展示个大家吧。这一波的文件比较多,但是好在都不难,只要耐心都是可以看懂的。
Boot.asm kernel.c global.h a.txt b.txt cd_.cmd Makefile mount_copy.cmd
首先让我们来看Makefile文件。
【Makefile】
#Makefile 创建者:至强 创建时间:2022年8月
#在第一个自定义命令前面的诸多等号构成的东西是称作"变量",
#还是称作"别称"好呢?总之,它在这两个方面的含义都是有的。
AS=nasm
CC=gcc
LD=ld
INCLUDE = -I ./kernel/
OBJ=./create/boot.o ./create/kernel.o
#除了多了上面的"变量"以外,根本就没有任何新意,如果不太明白,
#可以参考开发环境1的Makefile解释。
./create/kernel.elf: $(OBJ)
#这里的含义是在链接时如果遇到绝对地址的情况要加上0x00100000。
$(LD) -Ttext 0x00100000 -o $@ $(OBJ)
install: ./create/kernel.elf
mount_copy.cmd
run: install
start virtualboxvm --startvm "C:\Users\free2\.VirtualBox\temp\temp.vbox"
dbg: install
start virtualboxvm --startvm "C:\Users\free2\.VirtualBox\temp\temp.vbox" --debug-command-line
resume:
vboxmanage controlvm "C:\Users\free2\.VirtualBox\temp\temp.vbox" resume
clean:
del /q .\create\*
./create/boot.o: ./boot/boot.asm
$(AS) -felf -o $@ $^
./create/kernel.o: ./kernel/kernel.c
$(CC) $(INCLUDE) -c -o $@ $^
接下来的mount_copy.cmd文件似乎执行了一些匪夷所思的命令。
【mount_copy.cmd】
%mount_copy.cmd 创建者:至强 创建时间:2022年8月%
@echo off
copy .\create\kernel.elf .\
ping -n 1 127.0.0.1 > nul
objcopy -I pe-i386 -O elf32-i386 kernel.elf
ping -n 1 127.0.0.1 > nul
diskpart /s a.txt
ping -n 1 127.0.0.1 > nul
move kernel.elf d:\kernel
ping -n 1 127.0.0.1 > nul
diskpart /s b.txt
ping -n 1 127.0.0.1 > nul
它首先是将生成的内核文件kernel.elf复制到当前目录。我们生成的内核临时文件是放在create目录中的,这主要是防止虚拟机的根目录混乱不堪。然后我们竟然用了一个测试网络的命令,是的就是ping命令。其实这个命令可以起到延时和同步的效果。主要是为了防止复制还没有完成就运行了下面的命令。接下来的诸多此类命令,大家就应该懂了吧,objcopy将pe格式文件转换位elf32格式文件。用diskpart的自动模式挂载虚拟机硬盘,移动内核文件到D盘,更名为kernel,这与grub.cfg配置文件的内核文件相同。自动卸载虚拟机硬盘。虚拟机硬盘如果不从windows中卸载,virtual box会认为该硬盘被占用,因为没有可用引导设备,所以虚拟机就不能正常启动。
现在该轮到diskpart使用的a.txt和b.txt文件了。
【a.txt】
a.txt
select vdisk file="C:\Users\free2\.VirtualBox\temp\temp.vhd"
detach vdisk
【b.txt】
b.txt
select vdisk file="C:\Users\free2\.VirtualBox\temp\temp.vhd"
detach vdisk
a.txt就是在windows中挂载虚拟硬盘,b.txt就是卸载了。
然后我们再来看看cd_.cmd的批处理文件
【cd_.cmd】
%cd_.cmd 创建者:至强 创建时间:2022年8月%
@echo off
cd "C:\Users\free2\.VirtualBox\temp\"
在关闭了终端输出后,进入了虚拟机所在的目录。这主要是以下原因,在windows10上,由于权限的设置,如果想要自动化运行所用的命令的话,必须要全部获得管理员的权限。如果我们没有获得权限,直接运行“make run”,各个线程就难以达到同步,也就是出现还没有将内核复制到虚拟机硬盘的情况下,虚拟就开始运行的情况。
为了避免这些情况的发生,我们在运行windows10命令行的最开始,是通过点击鼠标右键,以管理员身份运行的qcmd.cmd批处理命令。如下图。而以管理员运行的qcmd.cmd,命令行窗口的默认路径是\windows\system32\,因此我们需要将cd_.cmd复制到\windows\system32\目录中,然后在进入到该目录后,运行cd_.cmd方便快捷地进入到我们的开发工作目录。
看到了吗,已经在工作目录了。
这时候我们“make run”,就会编译链接复制以及运行虚拟机的整个流程一气呵成,从而达到操作系统开发“所见即所得的”目标。
下面分别是boot.asm、global.h、kernel.c三个文件,这三个文件是真正的内核文件,在文件中已经解释的很清楚了。因此,直接列出文件的内容来。
【boot.asm】
; boot.asm 创建者:至强 创建时间:2022年8月
;编译成32位代码
bits 32
;标记下面的段位代码段
section .text
global _start, _stack
extern _kernel_main
_start:
;在代码段的开始,即跳过下面的数据定义,直接跳转到
;entry处执行。代码是一种特殊的数据,数据在符合代码
;定义的情况下就是代码。因此在汇编中,经常会出现,
;代码与数据混合存在的情况。
jmp entry
; 按照multiboot2规范的要求,此处因该是64字节边界
; 对齐,然而我们用了8字节,似乎也正常引导了。
align 8
; 这是multiboot2要求的引导头格式。
header_start:
; 下面是魔数,也就是固定值。
dd 0xe85250d6
; 体系结构,0表示32位保护模式,4为MIPS。
dd 0x0
; 引导头的长度。
dd header_end - header_start
; 校验和,校验和的值 = -(魔数 + 属性 + 引导头长度)
dd - (0xe85250d6 + 0x0 + (header_end - header_start))
; multiboot2 头的地址信息
; 根据multiboot2规范,每个标签都至少由3部分组成
; 1是标签类型,2是标签标志,3是标签长度。
; 其余标签依此类推。
add_tag_start:
dw 0x2 ; 这就是multiboot头地址的类型。
dw 0x0 ; 是头地址的标志。
dd add_tag_end - add_tag_start ; 头地址标签的长度。
dd header_start
dd _start
dd 0x0
dd 0x0
add_tag_end:
; 可执行程序的入口地址信息。
entry_add_tag_start:
dw 0x3
dw 0x1
dd entry_add_tag_end - entry_add_tag_start
dd entry
entry_add_tag_end:
align 8
; 帧缓冲的信息
framebuffer_tag_start:
; dw 0x5
; dw 0x1
; dd framebuffer_tag_end - framebuffer_tag_start
; dd 1024
; dd 768
; dd 32
framebuffer_tag_end:
align 8
; multiboot2 头的结尾信息。
dw 0x0
dw 0x0
dd 0x8
header_end:
align 8
; 这是经由grub2引导来的内核的汇编程序入口。
entry:
; 在进入c程序的内核时,是通过函数调用进入的
; 因此这里要设置栈指针,栈段(描述符)的基
; 地址由grub提前设置好,我们暂时不用管它。
mov esp, _stack
; 清空标志寄存器,以关闭所有中断。
push 0
popf
; 进入内核的函数,该函数可接收2个参数,因此
; 这里通过压栈的方式传递参数,根据规范,ebx
; 为multiboot2信息结构的地址,而eax为魔数值。
push ebx
push eax
; 调用真正的内核函数,从而进入c内核,当然如果
; 我们准备用汇编语言开发的话,在这里尽管写下
; 神奇的汇编代码就好了。
call _kernel_main
; 如果c函数没有问题的话,将永远不会返回到这里。
; 即使返回到这里,也意味着系统的宕机。
add esp, 2 * 4
jmp $
times 4096 - ($ - $$) db 0
_stack:
【global.h】
// global.h 创建者:至强创建时间:2022年8月
// 这个C语言的头文件真的没有什么好介绍的,
// 仅仅是声明了两个函数的原型。
// 下面的编译预处理的语句,几乎在每一个头文件
// 中都会出现,它的主要作用是让引用此文件的c语
// 源文件中只包含此头文件的1个副本,从而避免
// 多次重复引用。
#ifndef __KERNEL_H
#define __KERNEL_H
void put_c(char c, unsigned char colour);
void put_s(char* s, unsigned char colour);
#endif
【kerne.c】
// kernel.c 创建者:至强 创建时间:2022年8月
#include "global.h"
// 这个文件实在是没有什么好讲的,当然对于c语言的初学者
// 有些时候还是有点困惑,好在重要地方的注释已经足够了。
unsigned int pos;
void kernel_main(unsigned int magic, unsigned int addr) {
pos = 0;
put_s(" Hello SnailOS...! ", 0xc);
put_s(" Hello, World!......", 0x9);
while(1);
}
void put_c(char c, unsigned char colour) {
// 在默认文本模式下,物理地址0xb8000是显存的开始,
// 因此下面语句是把常量的物理地址赋值给变量video_base,
// 而在文本模式下低字节赋值ascii字符,高字节赋值8位
// 颜色值,在屏幕上就会显示该颜色的字符。
unsigned char* video_base = (unsigned char*)0xb8000;
video_base[pos++] = c;
video_base[pos++] = colour;
}
void put_s(char* s, unsigned char colour) {
// 字符串是以值0为结束字符的,这是约定,因此,显示
// 完整字符串就可以用循环的方式,显示在字符串结尾。
while(*s) {
put_c(*s++, colour);
}
}
还是请大家看看运行效果吧!“make run”后下图是进入了grub2。
接下来“回车”就进入了咱们自己的操作系统。
本章结束语
这一章是我们拼凑自己的操作系统至关重要的一环,编程的难度不大,却为我们准备了两种都很有战斗力的平台,选择哪一种平台作为今后继续战斗的基地,那就是大家仁者见仁智者见智的事情了。接下来的劳动才是我们真正体验快感的事情,我相信大家在今后,一定会更深刻地领会到什么是“痛并快乐着”的感觉。