1 操作过程
我的环境:
在virtualBox上面先安装好虚拟机Ubuntu 16.04
源码:
https://gitee.com/hnuwjw/os-hello-os
1.1 设置grub进入引导菜单
安装nasm,才能执行make
先安装nasm:
sudo apt-get install -y nasm
然后在HelloOS目录下执行:
make -f Makefile
就可以得到HelloOS.bin文件了
配置下启动项,不然无法选择HelloOS菜单
先修改文件:
/etc/default/grub
如果修改的时候报错“readonly option is set XXXXX”,则使用超级管理员角色编辑文件:
sudo vim grub
然后执行:
sudo update-grub
1.2 增加HelloOS启动选项
修改/boot/grub/grub.cfg,增加HelloOS启动项:
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 }
文件系统 1K-块 已用 可用 已用% 挂载点 /dev/sda4 48752308 8087584 38158536 18% /
其中的“sda1”就是硬盘的第四个分区,但是GRUB的menuentry中不能写sda4,而是要写“hd0,msdos1”,这是GRUB的命名方式,hd0表示第一块硬盘。
然后把HelloOS.bin文件复制到/boot/目录下,最后重启计算机
只要我们的PC机上安装了Ubuntu Linux操作系统,GRUB就已经存在了,就不用我们从引导程序开始写了。
2 原理
2.1 Hello OS的引导流程
PC机BIOS固件是固化在PC机主板上的ROM芯片中的,掉电也能保存,PC机上电后的第一条指令就是BIOS固件中的,它负责检测和初始化CPU、内存及主板平台,然后加载硬盘中的第一个扇区数据,到0x7c00地址开始的内存空间,再接着跳转到0x7c00处执行指令,这里就是GRUB引导程序。
2.2 Hello OS引导汇编代码
对应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
代码1~40行,用汇编定义的GRUB的多引导协议头,之所以有两个引导头,是为了兼容GRUB1和GRUB2。
代码44~52行,关掉中断,设定CPU的工作模式。
代码54~73行,初始化CPU的寄存器和C语言的运行环境。
代码78~87行,从GDT_START开始是CPU工作模式所需要的数据。
2.3 主函数
#include "vgastr.h" void main() { printf("Hello OS!"); return; }
其中的printf不是应用程序库中的那个printf,而是需要我们自己实现。
2.4 控制计算机屏幕
我们要在屏幕上显示字符,就要编程操作显卡。
显卡把屏幕分成24行,每行80个字符,把这(24*80)个位置映射到以0xb8000地址开始的内存中,每两个字节对应一个字符,其中一个字节是字符的ASCII码,另一个字节为字符的颜色值。
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; }
_strwrite函数正是将字符串里每个字符依次定入到0xb8000地址开始的显存中,而p_strdst每次加2,这也是为了跳过字符的颜色信息的空间。
2.5 编译过程