自己动手写操作系统(一)

自己动手写操作系统(一)本系列文章将一步步实现一个简单的操作系统 实验环境是在 Linux 系统下通过 Bochs 虚拟机运行我们自己写的操作系统

本系列文章将一步步实现一个简单的操作系统。实验环境是在Linux系统下通过Bochs虚拟机运行我们自己写的操作系统。

一、实验环境搭建

1. Ubuntu的安装,Windows用户可以选择在虚拟机中安装Ubuntu,具体安装教程可自行搜索。

2. Bochs虚拟机的安装

在学习编写操作系统的过程中,我们需要一个虚拟机来模拟出一个虚拟的计算机硬件环境,比如cpu、内存、硬盘等,并且能够运行并且调试我们写的代码。Bochs很好的提供了以上所有功能,在它面前我们就像上帝一样随时可以让时间停止,”钻“到计算机内部,查看这个虚拟电脑的一切信息,这正是开发操作系统所需要的。

Ubuntu下我们可以直接运行以下命令以源码方式安装Bochs(注意我们之所以选择以源码方式安装,是因为通过apt install安装的Bochs是没有调试功能的)

$ sudo apt update $ sudo apt install build-essential libx11-dev xorg-dev libgtk2.0-dev $ wget https://sourceforge.net/projects/bochs/files/bochs/2.7/bochs-2.7.tar.gz $ tar zxvf bochs-2.7.tar.gz $ cd bochs-2.7/ $ ./configure --enable-debugger --enable-disasm --enable-debugger-gui $ make $ sudo make install 

./configure后面的参数便是打开调试功能的开关

到此实验环境搭建完毕。

二、简单的引导扇区汇编代码

先简单讲一下计算机的启动流程,详细启动过程可参考我的另一篇博客 操作系统启动过程

按下开机键后,计算机首先会运行BIOS中的代码,BIOS在进行硬件检查和初始化后,会按照设置好的启动顺序(我们在使用U盘安装系统时,经常要进入BIOS设置这个启动顺序),依次寻找启动设备(比如硬盘、U盘等)。然后将第一个可用的启动设备的第一个扇区载入内存0x7c00处,并把执行权限交给它。

启动设备的第一个扇区我们称之为引导扇区(MBR),共512个字节,必须以数值0x55及0xaa结尾。包括三部分内容:引导加载程序(Boot Loader)(前446个字节,如GRUB等)、磁盘分区表(DPT,Disk Partition Table)、分区有效性标志(55AA)。其中的引导加载程序负责加载启动硬盘分区中的操作系统。

在BIOS向引导程序移交执行权之前,BIOS会对处理器进行初始化,这其中就包括处理器的代码段寄存器CS和指令指针寄存器IP。当BIOS跳转至引导程序时,CS和IP的值分别为0x0000和0x7c00。此时的处理器处于实模式下,物理地址必须经过CS寄存器和IP寄存器转换才能得到。转换公式为:物理地址=CS<<4+IP,也就是物理地址0x7c00处。

BIOS由Bochs虚拟机提供,我们接下来写的就是这个512字节的引导扇区(MBR)的汇编代码。目前它并不用加载操作系统,我们只让它在屏幕上打印出经典的“hello world”即可。

首先看一下汇编代码:

 org 0x07c00 mov ax,cs mov ds,ax mov es,ax mov ax,Message mov bp,ax mov cx, 13 mov ax,0x1301 mov bx,0x0002 mov dh,0 mov dl,0 int 0x10 jmp $ Message: db "Hello, world!" times 510-($-$$) db 0 dw 0xaa55 

代码和数据是按汇编程序的编写顺序依次连续存放到内存的,即上面的程序在0x7c00处开始存放的是org 0x07c00的机器指令,在512字节最后放的是0xaa55数据。

BIOS程序在把引导程序加载到内存时,同时还创建了中断系统,在物理内存的前1KB空间初始化中断向量表,在物理内存最后256KB物理地址空间内保存中断处理程序。cpu运行完BIOS后,物理内存的布局如下:

自己动手写操作系统(一)

本程序就是调用0x10号中断,在屏幕上打印字符串。在调用0x10号中断处理程序往显示器的屏幕上打印字符串时,所有的参数都是通过cpu中的寄存器传递的,各参数的含义如下:

  • 寄存器ah:0x13表示向屏幕打印字符串;
  • 寄存器al:指定光标和字符的属性
    0,表示字符的属性值保存在寄存器bl中,光标停留在字符串的首字符
    1,表示字符的属性值保存在寄存器bl中,光标停留在字符串的尾字符
    2,表示字符的属性值紧跟在字符之后,光标停留在字符串的首字符
    3,表示字符的属性值紧跟在字符之后,光标停留在字符串的尾字符








  • 寄存器bl:若寄存器al的值为0或者1时,保存字符的属性值。如图所示,字符属性由一个字节大小的数据表示。
自己动手写操作系统(一)

自己动手写操作系统(一)

  • 寄存器[es:bp]:保存字符串的首字符在数据段中的逻辑地址
  • 寄存器cx:保存字符串的长度
  • 寄存器dh,dl:字符串在屏幕上的起始坐标,其中寄存器dh为行号,寄存器dl为列号。显示器的屏幕只能显示25行字符,并且每行只能显示80个字符,因此dh的取指范围为0~24,dl的取指范围为0~79。

代码解析:

第1行,告诉编译器程序加载到内存的0x7c00处。

第2~4行,统一数据段寄存器DS和附加段寄存器ES的值和代码段寄存器CS一致,即不论数据段还是代码段,段起始地址都是0x7c00。

第5~6行,将字符串”Hello, world!”的首地址传递给寄存器bp。注意任何不被方括号[ ]括起来的标签或变量名都被认为是地址,访问标签或变量中的内容必须使用[ ]

第7行,将字符串的长度传递给寄存器cx

第8~11行,字符串属性设置

第12行,调用0x10号中断处理程序,在屏幕上显示字符串

$表示当前行被汇编后的地址,$$表示程序被汇编后的开始地址,也就是0x7c00.

第15、17行,分别以字节和字的形式存放的数据

第16行,表示将0这个字节重复510-($-$$)遍,也就是在剩下的空间不停填充0,直到第510个字节为止。这样加上结束标志0xaa55占用的两个字节,恰好是512个字节。

在运行代码之前我们需要将其转换成计算机能读懂的机器指令形式,这就需要编译器。我们编译c代码使用GCC,编译汇编程序使用nasm编译器。

把上面的代码保存成boot.asm,然后使用nasm编译一下,生成二进制可执行文件boot.bin

$ nasm boot.asm -o boot.bin 

三、虚拟硬盘的制作

下面我们将制作一个虚拟硬盘并将已经生成的可执行文件boot.bin放到虚拟硬盘的第一个磁盘块(引导扇区MBR)中。Bochs虚拟机将使用这块“硬盘”引导启动。

首先选择合适的地方创建一个工程目录

$ mkdir projectest $ cd projectest 

将可执行文件boot.bin拷贝到该工程目录中

然后在本层目录中创建一个大小为1MB的硬盘镜像文件b.img的命令如下:

$ dd if=/dev/zero of=b.img bs=512 count=2048 

dd是文件拷贝命令,其中:

  • if=/dev/zero:表示拷贝的源文件的路径,”/dev/zero”是一个特殊的文件,可以提供n个0(n的值等于bs和count参数的积)
  • of=b.img:表示拷贝的目标文件路径。若不存在,则创建该文件
  • bs=512:表示块大小,单位为B
  • count=2048:表示拷贝的文件的块的数量。

由bs和count参数可知,硬盘镜像文件的大小为:2048*512B=1MB,硬盘镜像文件制作好后,将可执行文件boot.bin拷贝到硬盘镜像文件b.img(硬盘)的引导扇区的命令如下:

$ dd if=boot.bin of=b.img bs=512 seek=0 conv=notrunc 

其中:

  • seek=0:表示把可执行文件boot.bin拷贝到硬盘镜像文件b.img的引导扇区(扇区号为0)。
  • conv=notrunc:表示不改变目标文件的大小,若没有该选项,则硬盘镜像文件b.img的大小会由1MB变为可执行文件boot.bin的大小512B。

这样一个写入了引导程序的“硬盘”就制作好了。

四、Bochs的使用

1. 启动Bochs

“硬盘”制作好后,要想启动bochs还需要一个配置文件——bochsrc.bxrc。为什么需要配置文件呢?因为你需要告诉bochs你希望的虚拟机是什么样的,比如,内存多大,使用哪个硬盘启动等等。在下载bochs的源码包中有一个.bochsrc,就是官方提供的配置文件示例,我们可以根据这个更改。

下面是本实验用到的bochs配置文件代码

romimage: file=/usr/local/share/bochs/BIOS-bochs-latest vgaromimage: file=/usr/local/share/bochs/VGABIOS-lgpl-latest ata0-master: type-disk, path="b.img" megs: 16 cpu: count=1 boot: disk 

其中:

  • romimage:指定bochs运行过程中使用的ROM-BIOS的路径。
  • vgaromimage:指定bochs运行过程中使用的VGA的ROM-BIOS的路径。
  • ata0-master:指定硬盘镜像文件b.img的路径。
  • megs:指定物理内存的大小,单位为MB。
  • cpu:指定cpu的个数,1个。
  • boot:指定启动方式,从硬盘启动。

将上面的代码保存为bochsrc.bxrc,也存到工程目录下。

现在一切准备就绪,启动bochs的命令如下:

$ bochs -q -f bochsrc.bxrc 

其中:

  • -q: 跳过bochs启动后的配置界面。
  • -f : bochsrc.bxrc:指定配置文件的路径。
  • 如果不指定路径,那么Bochs将按照如下顺序在当前目录中寻找配置文件:
  1. .bochsrc
  2. bochsrc
  3. bochsrc.txt
  4. bochsrc.bxrc (windows only)
  5. /home/.bochsrc (Unix only)
  6. /etc/bochsrc(Unix only)

运行bochs后会在终端出现bochs调试命令行,等待我们输入调试命令,这里输入c继续执行
在这里插入图片描述
可以看到在虚拟机中我们的引导程序已成功运行,在屏幕上打印出了hello world。
在这里插入图片描述






2. Bochs调试

常用调试命令 作用
b 使用物理地址打断点
vb 使用逻辑地址打断点
blist 查看所有断点信息
n 单步执行(遇到函数跳过)
s 单步执行(遇到函数进入函数内部)
c 继续执行
r 查看所有通用寄存器的值
(eax、ebx、ecx、edx、esp、ebp、esi、edi、eip、eflags)

sreg 查看所有段寄存器的值
u /5 打印CPU接下来将执行的5条指令
xp 查看物理内存中指定物理地址的内容
xp /2bx 物理地址:打印两个字节,以十六进制格式输出。
xp /13c 物理地址:打印13个字节,以ASCII码对应的字符显示




watch 变量名 运行时,若某一行代码修改了变量,则中断,并打印修改前后的值。
q 退出调试继续执行
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/177246.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午7:58
下一篇 2026年3月26日 下午7:58


相关推荐

  • Matlab画图 线条的颜色、宽度等相关设置

    Matlab画图 线条的颜色、宽度等相关设置线条的属性有:Color——颜色、LineWidth——线条宽度、LineStyle——线型、LineJoin——线条边角的样式、AlignVertexCenters——锐化垂直线和水平线线条属性的默认值为([0,0,0],’-‘,’0.5′,’round’,’off’)颜色、线型、一、线条颜色二、线条宽度线条宽度的默认值为0.5,线条宽度只能指定正值。三、线条类型实例:plot(t…

    2022年5月29日
    423
  • Sybase bcp命令详解

    Sybase bcp命令详解bcp 命令详解 bcp nbsp 实用工具在 nbsp Microsoft nbsp SQL nbsp Server nbsp 2000 nbsp 实例和数据文件之间以用户指定的格式复制数据 语法 bcp nbsp database name owner table name nbsp nbsp view name nbsp nbsp query nbsp nbsp nbsp nbsp nbsp nbsp in nbsp nbsp out nbsp nbsp queryout nbsp nbsp format nbsp data file

    2026年3月18日
    2
  • Java课程设计[通俗易懂]

    Java课程设计[通俗易懂]#1.团队名称、团队成员##团队名称:秃头团队|成员|任务分配|||||林小强(组长)|dao包util包可视化||陈泽役|model包可视化|#2.前期

    2022年6月30日
    26
  • python基础(2)字符串常用方法[通俗易懂]

    python基础(2)字符串常用方法[通俗易懂]python字符串常用方法find(sub[,start[,end]])在索引start和end之间查找字符串sub​找到,则返回最左端的索引值,未找到,则返回-1​start和end都可

    2022年7月30日
    9
  • Alex 的 Hadoop 菜鸟教程: 第10课 Hive 安装和使用教程

    Alex 的 Hadoop 菜鸟教程: 第10课 Hive 安装和使用教程Hive提供了一个让大家可以使用sql去查询数据的途径。让大家可以在hadoop上写sql语句。但是最好不要拿Hive进行实时的查询。因为Hive的实现原理是把sql语句转化为多个MapReduce任务所以Hive非常慢,官方文档说Hive适用于高延时性的场景而且很费资源。

    2022年6月7日
    98
  • Mac命令行退出vim

    Mac命令行退出vim@Mac命令行退出vimMac退出vim按下ESC后输入:才能进入命令模式!进入之后输入:qw(保存退出)然后按2次大写Z退出q!(不保存退出)输入后回车退出新的改变我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:全新的界面设计,将会带来全新的写作体验;在创作中心设置你喜爱的代码高…

    2022年6月11日
    607

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号