目录
一、操作系统引导流程
BIOS被固化在主板上的ROM中,PC加电后,第一条指令由从BIOS中执行,它的作用大致是:检查初始化CPU、内存、主板平台,加载磁盘第一个扇区的内容到内存的0x7c00起始地址处,并跳转到0x7c00处继续执行,次此实验就是gruby引导程序。
二、准备运行环境
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部分:
1.代码 1~40 行,用汇编定义的 GRUB 的多引导协议头,其实就是一定格式的数据,我们的 Hello OS 是用 GRUB 引导的,当然要遵循 GRUB 的多引导协议标准,让 GRUB 能识别我们的 Hello OS。
2.代码 44~52 行,关掉中断,设定 CPU 的工作模式。
3.代码 54~73 行,初始化 CPU 的寄存器和 C 语言的运行环境。
4.代码 78~87 行,GDT_START 开始的,是 CPU 工作模式所需要的数据,同样,后面讲 CPU 时会专门介绍。
三、HelloOS主函数
void printf(char *fmt,...);
int main(void)
{
printf("hello world");
return 0;
}
HelloOS的主要功能就是向屏幕上打印HelloOS,功能跟学习C语言打印“HelloWorld”一样,但此时的HelloOS 没有标准库的支持,printf需要自己实现。
四、printf 函数简易实现
其实无论我们 PC 上是什么显卡,它们都支持一种叫 VESA 的标准,这种标准下有两种工作模式:字符模式和图形模式。显卡们为了兼容这种标准,不得不自己提供一种叫 VGABIOS 的固件程序。
在字符模式下,它把屏幕分成 24 行,每行 80 个字符,把这(24*80)个位置映射到以 0xb8000 地址开始的内存中,每两个字节对应一个字符,其中一个字节是字符的 ASCII 码,另一个字节为字符的颜色值。如下图所示:
另外:C语言字符串是UTF-8编码,当字符是英文字符时,它的UTF-8编码与ASCII编码是相等的。所以要想在屏幕上显示字符,只需从地址0xb8000开始每间隔一个字节存放一个字符即可。
另外对于字符的颜色,可以参考这幅图:
要想改变字符的颜色,只需关注表示颜色字节的后4位,如 0x02代表绿,0x0a代表浅绿。
那么实现printf的代码如下:
void _strwrite(const char *str)
{
char* start = (char*)(0xb8000);
while(*str != '\0')
{
*start++ = *str++;
*start++ = 0x02; //字符颜色为绿色
}
}
void printf(char *fmt,...)
{
_strwrite(fmt);
}
五、HelloOS编译
1.工程树:
2.编译过程
3.具体代码与编译操作:代码地址
六、项目部署
0、实验环境:vmware + ubuntu 16.04
1、将编译得到的HelloOS.bin拷贝到/boot下
2、在/boot/grub/grub.cfg 文件中追加
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
}
其中msdos1 根据/boot 处于磁盘的哪个分区而定,如果是第四分区,则msdos4,可以使用 df /boot查看。
3.重启:reboot -f
4.结果: