操做系统对于每一个开发者来讲都是绕不开的门槛,不论是传统的单片机也好,仍是如今分布式系统也好,都是离不开基本是计算机模型,从图灵机到冯诺依曼,从埃尼阿克到如今太湖之光,这几十年来的计算机发展都仍是在这个模型下发展起来的,能够说在量子计算机大规模推广以前,现今的操做系统软件仍是很值得学习借鉴。俗话说,它山之石能够攻玉,那么咱们本身磨石头,或许也能够发现蕴含在石头中的璞玉,这也是一件很值得期待的事情呢,不是吗?docker
最近就想本身动手实验本身写个操做系统,本文权且做为本系列做品的开篇之做,按照我对操做系统的认知来层层推动,最终的指望固然是本身写出个性化的操做系统啦,有机会的话,再继续深刻到分布式操做系统,进而进入云操做系统,想一想也是挺刺激的,试试看呗,看看能作到多少~网络上资料这么多,牛人这么强,应该能够啦编程
写Hello OS
以前,先要搞清楚所谓的操做系统在上电以后的引导流程,总结来讲以下图所示:网络
简单来讲PC机的BIOS固件是一种已经固化在PC
机主板上的 ROM
芯片中的操做系统,即便掉电也能保存,而PC机上电后的第一条指令就是在BIOS
固件中的,它负责检测和初始化 CPU、内存及主板平台,而后加载引导设备(大几率是硬盘)中的第一个扇区数据,到0x7c00
地址开始的内存空间,再接着跳转到0x7c00
处执行指令,其实就是执行GRUB
引导程序。分布式
此次实验是用来体验一下本身编译一个.bin
文件,而后修改的Ubuntu引导程序,进而启动编译完成的这个系统文件。ide
知道PC机的上电流程以后,就能够开始进行逐步开发了,好比说利用一下汇编语言来进行引导程序的开发entry.asm
。函数
MBT_HDR_FLAGS EQU 0x00010003 MBT_HDR_MAGIC EQU 0x1BADB002 ;多引导协议头魔数 MBT_HDR2_MAGIC EQU 0xe85250d6 ;第二版多引导协议头魔数 global _start ;导出_start符号 extern main ;导入外部的main函数符号 [section .start.text] ;定义.start.text代码节 [bits 32] ;汇编成32位代码 _start: jmp _entry ALIGN 8 mbt_hdr: dd MBT_HDR_MAGIC dd MBT_HDR_FLAGS dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS) dd mbt_hdr dd _start dd 0 dd 0 dd _entry ;以上是GRUB所须要的头 ALIGN 8 mbt2_hdr: DD MBT_HDR2_MAGIC DD 0 DD mbt2_hdr_end - mbt2_hdr DD -(MBT_HDR2_MAGIC + 0 + (mbt2_hdr_end - mbt2_hdr)) DW 2, 0 DD 24 DD mbt2_hdr DD _start DD 0 DD 0 DW 3, 0 DD 12 DD _entry DD 0 DW 0, 0 DD 8 mbt2_hdr_end: ;以上是GRUB2所须要的头 ;包含两个头是为了同时兼容GRUB、GRUB2 ALIGN 8 _entry: ;关中断 cli ;关不可屏蔽中断 in al, 0x70 or al, 0x80 out 0x70,al ;从新加载GDT lgdt [GDT_PTR] jmp dword 0x8 :_32bits_mode _32bits_mode: ;下面初始化C语言可能会用到的寄存器 mov ax, 0x10 mov ds, ax mov ss, ax mov es, ax mov fs, ax mov gs, ax xor eax,eax xor ebx,ebx xor ecx,ecx xor edx,edx xor edi,edi xor esi,esi xor ebp,ebp xor esp,esp ;初始化栈,C语言须要栈才能工做 mov esp,0x9000 ;调用C语言函数main call main ;让CPU中止执行指令 halt_step: halt jmp halt_step GDT_START: knull_dsc: dq 0 kcode_dsc: dq 0x00cf9e000000ffff kdata_dsc: dq 0x00cf92000000ffff k16cd_dsc: dq 0x00009e000000ffff k16da_dsc: dq 0x000092000000ffff GDT_END: GDT_PTR: GDTLEN dw GDT_END-GDT_START-1 GDTBASE dd GDT_START
以上的汇编代码分为 4 个部分:工具
GRUB
的多引导协议头,其实就是必定格式的数据,Hello OS 是用 GRUB 引导的,固然要遵循GRUB
的多引导协议标准,让 GRUB 能识别的 Hello OS。而之因此有两个引导头,是为了兼容 GRUB1 和 GRUB2。GDT_START
开始的,是 CPU 工做模式所须要的数据上面的汇编代码调用了main
函数,而在其代码中并无看到其函数体,而是从外部引入了一个符号。那是由于这个函数是用 C 语言写的在main.c
中,最终它们分别由nasm
和GCC
编译成可连接模块,由LD
连接器连接在一块儿,造成可执行的程序文件:学习
#include "vgastr.h" void main(){ printf("Hello OS!"); }
这里用到的printf
也不是咱们熟知的那个函数,只是碰巧名字同样罢了,这个显示函数是须要咱们本身实现的。调皮一些的话,printf
还能够改为echo
,show
,kankanwo
这些,不要紧的,毕竟这个函数也是咱们本身定义的。测试
咱们之因此能够看到的屏幕显示的内容,是由于有个硬件的来支撑的,即咱们常说的显卡。若是咱们要在屏幕上显示字符,本质上就是编程操做显卡。这个并不难,作完了甚至还挺有成就感。操作系统
注意到不管咱们 PC 上是什么显卡,它们都支持一种叫VESA
的标准,这种标准下有两种工做模式:字符模式和图形模式。显卡们为了兼容这种标准,不得不本身提供一种叫VGABIOS
的固件程序。
这里须要补充一下在上古时代显卡的字符模式的工做细节。它把屏幕分红 24 行,每行 80 个字符,把这(24*80)个位置映射到以0xb8000
地址开始的内存中,每两个字节对应一个字符,其中一个字节是字符的ASCII
码,另外一个字节为字符的颜色值。以下图所示:
了解细节以后就能够对显示程序vgastr.c
进行开发
void _strwrite(char* string) { char* p_strdst = (char*)(0xb8000); while (*string) { *p_strdst = *string++; p_strdst += 2; } return; } void printf(char* fmt, ...) { _strwrite(fmt); return; }
代码很简单,printf
函数直接调用了_strwrite
函数,而_strwrite
函数正是将字符串里每一个字符依次找到以0xb8000
地址开始的显存中,而p_strdst
每次加2,则是为了跳过字符的颜色信息的空间。
Hello OS 的代码都已经写好,这时就要进入安装测试环节了。不过在安装以前,还要进行系统编译,即把每一个代码模块编译最后连接成可执行的二进制文件。
make 历史悠久,小巧方便,也是不少成熟操做系统编译所使用的构建工具。
咱们在软件开发中,make
是一个工具程序,它读取一个叫makefile
的文件,也是一种文本文件,这个文件中写好了构建软件的规则,它能根据这些规则自动化构建软件,就相似咱们用docker
打包的时候,须要写dockerfile
同样。
任何一个 Linux 发行版中都默认自带这个 make 程序,因此不须要额外的安装工做,咱们直接使用便可。
下面咱们用一张图来描述咱们 Hello OS 的编译过程,以下所示
通过上述流程,能够获得Hello OS.bin
文件,可是还要让GRUB
可以找到它,才能在计算机启动时加载它。这个过程称为安装,不过这里没有写安装程序,得咱们手动来作。经研究发现,GRUB 在启动时会加载一个grub.cfg
的文本文件,根据其中的内容执行相应的操做,其中一部份内容就是启动项。GRUB 首先会显示启动项到屏幕,而后让咱们选择启动项,最后 GRUB 根据启动项对应的信息,加载 OS 文件到内存。
menuentry 'HelloOS' { insmod part_msdos #GRUB加载分区模块识别分区 insmod ext2 #GRUB加载ext文件系统模块识别ext文件系统 set root='hd0,msdos1' #注意boot目录挂载的分区,这是我机器上的状况 multiboot2 /boot/HelloOS.bin #GRUB以multiboot2协议加载HelloOS.bin boot #GRUB启动HelloOS.bin }
关于root
的配置状况,若是不知道本身boot
分区的挂载点,能够在grub的引导程序上面按C
进入GRUB的命令行,而后查看提供的挂载分区,这回没有巧办法了,只能一步步调试了,改挂载的分区点,最重要的是,记得把make以后生成的HelloOS.bin
文件拷贝到\boot
目录下
以后就正常启动吧:)完成本次实验
此次实验先从按下 PC 机电源开关开始,窥探了PC 机的引导过程。它从 CPU 上电,到加载 BIOS 固件,再由 BIOS 固件对计算机进行自检和默认的初始化,并加载 GRUB 引导程序,最后由 GRUB 加载具体的操做系统。其次,用汇编语言和 C 语言实现 Hello OS。第一步,用汇编程序初始化 CPU 的寄存器、设置 CPU 的工做模式和栈,最重要的是加入了 GRUB 引导协议头;第二步,切换到 C 语言,用 C 语言写好了主函数和控制显卡输出的函数,这个时候还须要了解显卡的一些工做细节。最后,就是编译和安装 Hello OS 了。我用了 make 工具编译整个代码,其实 make 会根据一些规则调用具体的 nasm、gcc、ld 等编译器,而后造成 Hello OS.bin 文件,最后把这个文件写复制到 boot 分区,写好 GRUB 启动项,这样就行了。
嗯,期待下次更深刻的探索O(∩_∩)O