html+jsp個人網(wǎng)站模板百度認證證書
更好的閱讀體驗,請點擊 YinKai 's Blog | 實現(xiàn)一個最簡單的內(nèi)核。
? 這篇文章帶大家實現(xiàn)一個最簡單的操作系統(tǒng)內(nèi)核—— Hello OS。
PC 機的引導流程
? 我們這里將借助 Ubuntu Linux 操縱系統(tǒng)上的 GRUB 引導程序來引導我們的 Hello OS。
? 首先我們得了解一下,Hello OS 的引導流程:
? 簡單解釋一下,PC 機 BIOS 固件是固化在 PC 機主板上的 ROM 芯片中的,掉電也能保存,PC 機上電后的第一條指令就是 BIOS 固件中的,它負責檢測和初始化 CPU、內(nèi)存及主板平臺,然后加載引導設備(大概率是硬盤)中的第一個扇區(qū)數(shù)據(jù),到 0x7c00 地址開始的內(nèi)存空間,再接著跳轉(zhuǎn)到 0x7c00 處執(zhí)行指令,在我們這里的情況下就是 GRUB 引導程序。
Hello OS 引導匯編代碼
? 我們的 Hello OS 總有 6 個文件,下面一一講解。
Hello OS 的主函數(shù)
main.c
#include "vgastr.h"
void main()
{printf("Hello OS! I am YinKai");return;
}
entry.asm
; 多引導協(xié)議頭(GRUB)
MBT_HDR_FLAGS EQU 0x00010003
MBT_HDR_MAGIC EQU 0x1BADB002 ; 多引導協(xié)議頭魔數(shù)
MBT_HDR2_MAGIC EQU 0xe85250d6 ; 第二版多引導協(xié)議頭魔數(shù)global _start ; 導出 _start 符號
extern main ; 導入外部的 main 函數(shù)符號[section .start.text] ; 定義 .start.text 代碼節(jié)
[bits 32] ; 匯編成32位代碼_start:jmp _entryALIGN 8
; GRUB 所需的多引導協(xié)議頭
mbt_hdr:dd MBT_HDR_MAGICdd MBT_HDR_FLAGSdd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)dd mbt_hdrdd _startdd 0dd 0dd _entryALIGN 8
; GRUB2 所需的多引導協(xié)議頭
mbt2_hdr:DD MBT_HDR2_MAGICDD 0DD mbt2_hdr_end - mbt2_hdrDD -(MBT_HDR2_MAGIC + 0 + (mbt2_hdr_end - mbt2_hdr))DW 2, 0DD 24DD mbt2_hdrDD _startDD 0DD 0DW 3, 0DD 12DD _entryDD 0DW 0, 0DD 8mbt2_hdr_end:ALIGN 8
_entry:; 關中斷cli; 關不可屏蔽中斷in al, 0x70or al, 0x80out 0x70, al; 重新加載GDTlgdt [GDT_PTR]jmp dword 0x8 :_32bits_mode_32bits_mode:; 初始化C語言可能會用到的寄存器mov ax, 0x10mov ds, axmov ss, axmov es, axmov fs, axmov gs, axxor eax,eaxxor ebx,ebxxor ecx,ecxxor edx,edxxor edi,edixor esi,esixor ebp,ebpxor esp,esp; 初始化棧,C語言需要棧才能工作mov esp,0x9000; 調(diào)用C語言函數(shù)maincall main; 讓CPU停止執(zhí)行指令
halt_step:haltjmp halt_step; GDT 全局描述符表
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
? 這是一個引導加載程序,它是計算機啟動過程中的第一個軟件,它的主要任務是在計算機啟動時,通過 GRUB 或 GRUB2 多引導協(xié)議頭,初始化系統(tǒng)環(huán)境,設置 GDT,然后調(diào)用 C 語言的 main
函數(shù)。
控制計算機屏幕
? 首先我們得知道顯卡的字符模式的工作細節(jié)。
? 它把屏幕分成 24 行,每行 80 個字符,把這(24*80)個位置映射到以 0xb8000 地址開始的內(nèi)存中,每兩個字節(jié)對應一個字符,其中一個字節(jié)是字符的 ASCII 碼,另一個字節(jié)為字符的顏色值。如下圖所示:
? 了解原理之后,我們來自己實現(xiàn) printf 函數(shù):
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 把傳入的字符串作為參數(shù),傳給 _strwrite 函數(shù),然后把字符串中的每個字符依次寫入 0xb8000 地址開始的顯存中。p_strdst 每次加 2 ,是為了跳過表示顏色值的字符,直接指向下一個字符的 ASCII 值。
? 為了編譯器能夠正確識別我們的函數(shù),我們還需要另寫一個文件,保證函數(shù)調(diào)用的正確性。
vgastr.h
void _strwrite(char* string);
void printf(char* fmt, ...);
鏈接
hello.lds
ENTRY(_start)
OUTPUT_ARCH(i386)
OUTPUT_FORMAT(elf32-i386)
SECTIONS
{. = 0x200000;__begin_start_text = .;.start.text : ALIGN(4) { *(.start.text) }__end_start_text = .;__begin_text = .;.text : ALIGN(4) { *(.text) }__end_text = .;__begin_data = .;.data : ALIGN(4) { *(.data) }__end_data = .;__begin_rodata = .;.rodata : ALIGN(4) { *(.rodata) *(.rodata.*) }__end_rodata = .;__begin_kstrtab = .;.kstrtab : ALIGN(4) { *(.kstrtab) }__end_kstrtab = .;__begin_bss = .;.bss : ALIGN(4) { *(.bss) }__end_bss = .;
}
? 這段代碼是一個鏈接腳本,用于告訴鏈接器如何將各個目標文件組合成最終的可執(zhí)行文件。
編譯
? 我們這里使用 make 工具進行系統(tǒng)編譯,將每個代碼模塊編譯最后鏈接成可執(zhí)行的二進制文件。
Makefile
MAKEFLAGS = -sR
MKDIR = mkdir
RMDIR = rmdir
CP = cp
CD = cd
DD = dd
RM = rmASM = nasm
CC = gcc
LD = ld
OBJCOPY = objcopyASMBFLAGS = -f elf -w-orphan-labels
CFLAGS = -c -Os -std=c99 -m32 -Wall -Wshadow -W -Wconversion -Wno-sign-conversion -fno-stack-protector -fomit-frame-pointer -fno-builtin -fno-common -ffreestanding -Wno-unused-parameter -Wunused-variable
LDFLAGS = -s -static -T hello.lds -n -Map HelloOS.map
OJCYFLAGS = -S -O binaryHELLOOS_OBJS :=
HELLOOS_OBJS += entry.o main.o vgastr.o
HELLOOS_ELF = HelloOS.elf
HELLOOS_BIN = HelloOS.bin.PHONY : build clean all link binall: clean build link binclean:$(RM) -f *.o *.bin *.elfbuild: $(HELLOOS_OBJS)link: $(HELLOOS_ELF)
$(HELLOOS_ELF): $(HELLOOS_OBJS)$(LD) $(LDFLAGS) -o $@ $(HELLOOS_OBJS)
bin: $(HELLOOS_BIN)
$(HELLOOS_BIN): $(HELLOOS_ELF)$(OBJCOPY) $(OJCYFLAGS) $< $@%.o : %.asm$(ASM) $(ASMBFLAGS) -o $@ $<
%.o : %.c$(CC) $(CFLAGS) -o $@ $<
安裝 Hello OS
? 不同的系統(tǒng),可能操作不同,我這里用的是 ubuntu。
安裝編譯環(huán)境
- 安裝匯編編譯器
sudo apt-get install nasm
- 安裝gcc(該命令會安裝包括gcc在內(nèi)的所有軟件)
sudo apt install build-essential
修改啟動項等待時間
? 修改啟動項等待時間,以供我們選擇啟動項文件
? sudo vim /etc/default/grub
,打開文件,修改為 10 s
? 使用 sudo update-grub
更新我們的修改。
:::warning
? 每次使用這個命令之后,我們追加的啟動項(后面會說到)就會被清除,需要重新添加。
:::
構建 HelloOS.bin 文件
? 在自己的家目錄下創(chuàng)建一個 HelloOS 文件夾,放入我們依賴的 6 個文件,代碼及文件命名見上。
使用 make 構建
? 在 HelloOS 目錄下,使用 make 命令,即可獲得 HelloOS.bin 文件,并將該文件移動到 /boot/
目錄下。(如果原本就有要將其刪除,再放入。)
追加 GRUB 啟動項
? 使用 df /boot
獲取文件系統(tǒng)名,以及文件系統(tǒng)的掛載點,我的如下:
文件系統(tǒng) 1K-塊 已用 可用 已用% 掛載點
/dev/sda5 19947120 9921616 8986912 53% /
? 寫 grub 的引導文件,將下面的啟動項代碼插入到 /boot/grub/grub.cfg
文件末尾
menuentry 'HelloOS' {insmod part_msdos #GRUB加載分區(qū)模塊識別分區(qū)insmod ext2 #GRUB加載ext文件系統(tǒng)模塊識別ext文件系統(tǒng)set root='hd0,msdos5' #注意boot目錄掛載的分區(qū),這是我機器上的情況multiboot2 /boot/HelloOS.bin #GRUB以multiboot2協(xié)議加載HelloOS.binboot #GRUB啟動HelloOS.bin
}
:::warning
① 這里的 hd0,msdos?
需要根據(jù) (4)中的 /dev/sda?
對應起來;
② 如果掛載點是 / 就需要在文件中寫 /boot/HelloOS.bin
;如果掛載點是 /boot
,則直接寫 /HelloOS.bin
即可
③ 如果該文件不可修改,可以用 root 權限修改該文件為可寫文件。
:::
? 最后使用 reboot
命令,即可重啟系統(tǒng),看到我們的 Hello OS 選項:
? 選擇后,即可看到我們在主函數(shù) main.c 中寫的字符串啦~
小結
Hello OS 啟動的流程主要包括以下步驟:
- 計算機上電: 當計算機上電時,主板上的 BIOS 固件開始執(zhí)行。
- BIOS 初始化: BIOS 負責檢測和初始化計算機硬件,包括處理器、內(nèi)存等。
- 加載引導扇區(qū): BIOS 根據(jù)啟動設備配置加載引導扇區(qū),通常是硬盤上的 MBR。
- 引導加載程序執(zhí)行: 引導加載程序(如 GRUB)被加載,負責加載操作系統(tǒng)內(nèi)核。
- GRUB 加載 Hello OS: GRUB 通過配置文件加載 Hello OS 的二進制文件(HelloOS.bin)。
- Hello OS 入口點: Hello OS 的入口點在
entry.asm
中,負責初始化系統(tǒng)環(huán)境。 - 切換到 32 位保護模式:
_start
調(diào)用_32bits_mode
將處理器切換到 32 位保護模式。 - C 語言的 main 函數(shù):
main.c
包含操作系統(tǒng)的主要邏輯,調(diào)用了輸出字符串的函數(shù)。 - 屏幕輸出:
vgastr.c
中的_strwrite
和printf
負責向屏幕輸出字符串。 - 系統(tǒng)初始化完成: Hello OS 在初始化完成后,等待主要邏輯執(zhí)行完畢。
- CPU 停止執(zhí)行指令: 使用
halt
指令讓 CPU 停止執(zhí)行,操作系統(tǒng)啟動過程結束。 - 系統(tǒng)運行或重新啟動: 如果需要,可以繼續(xù)執(zhí)行其他操作系統(tǒng)功能或重新啟動計算機。