VV的作業系統筆記(一)作業系統I SeeYou!!!!
注:與本系列部落格同時同步的還有後面需要學習和研究的FreeRTOS和linux0.11-linux1.0核心程式碼VV的Linux作業系統核心筆記系列,即使筆者已經自己寫了個作業系統了,但是為了能夠使部落格能讀懂,筆者需要把每一個lab和程式碼打出來做出解釋同時筆者也有自己繁重的學習和工作(本科狗),所以進度會非常非常慢
準備工作
Ubuntu16.04-i386 32位作業系統映象
話不多說,迅雷下載下載地址
- 安裝映象到到虛擬機器
安裝過程不多贅述,安裝完成如圖所示:
- 安裝ubuntu的一些軟體和包
apt-get install docker docker.io docker-compose qemu virtualbox
- 安裝IDE
Eclipse的CDT原生支援Makefile工程,而且虛擬機器記憶體佔用較小,所以這裡我們就用Eclipse CDT,注意是32位的Eclipse表問我為啥安裝Eclipse Indigo這種老玩意,因為最新版本的EC並不支援32位。
在我們編寫核心的過程中,我們使用GRUB來啟動我們的核心。 至於為什麼用GRUB,因為它可以設定多系統共存,這樣的話你就可以打包多個系統核心同時存在並且啟動的映象檔案。
作業系統啟動流程
為了直觀和形象,我們直接上圖
-
BIOS(Basic Input/Output System),基本輸入輸出系統,該系統儲存於主機板的ROM晶片上,計算機在開機時,會最先讀取該系統,然後會有一個加電自檢過程,這個過程其實就是檢查CPU和記憶體,計算機最基本的組成單元(控制器、運算器和儲存器),還會檢查其他硬體,若沒有異常就開始載入BIOS程式到記憶體當中。詳細的BIOS功能,這邊就不說了,BIOS主要的一個功能就是儲存了磁碟的啟動順序,BIOS會按照啟動順序去查詢第一個磁碟頭的MBR資訊,並載入和執行MBR中的Bootloader程式,若第一個磁碟不存在MBR,則會繼續查詢第二個磁碟(PS:啟動順序可以在BIOS的介面中進行設定),一旦BootLoader程式被檢測並載入記憶體中,BIOS就將控制權交接給了BootLoader程式。
-
MBR(Master Boot Record),主引導記錄,MBR儲存於磁碟的頭部,大小為512bytes,其中,446bytes用於儲存BootLoader程式,64bytes用於儲存分割槽表資訊,最後2bytes用於MBR的有效性檢查。
-
GRUB(Grand Unified Bootloader),多系統啟動程式,其執行過程可分為三個步驟:
- Stage1:這個其實就是MBR,它的主要工作就是查詢並載入第二段Bootloader程式(stage2),但系統在沒啟動時,MBR根本找不到檔案系統,也就找不到stage2所存放的位置,因此,就有了stage1_5
- Stage1_5:該步驟就是為了識別檔案系統
- Stage2:GRUB程式會根據/boot/grub/grub.conf檔案查詢Kernel的資訊,然後開始載入Kernel程式,當Kernel程式被檢測並在載入到記憶體中,GRUB就將控制權交接給了Kernel程式。
注意!現代作業系統使用了UEFI啟動,但是我們現在不說UEFI,請自行忽略
但是這樣也需要我們的Boot程式按照Mutileboot 規範來編譯核心,才可以被GRUB引導。
按照Mutileboot規範,核心必須在起始的8KB中的(512位元組)包含這一個多引導項頭(Multiboot header)。
而且,這個多引導項頭裡面必須有3個4位元組對齊的塊。
一個魔術塊:包含了魔數[0x1BADB002],是多引導項頭結構的定義值。
一個標誌塊:我們不關心這個塊的內容,我們簡單設定為0。
一個校檢塊:校檢塊,魔術塊和標誌塊的數值的總和必須是0。
我的核心啟動程式碼如下:
boot.s
.set MAGIC, 0x1badb002;GRUB魔術塊
.set FLAGS, (1<<0 | 1<<1);GRUB標誌塊
.set CHECKSUM, -(MAGIC + FLAGS);校驗塊
.section .multboot
.long MAGIC
.long FLAGS
.long CHECKSUM
.section .text
.extern kernel_main;匯入kernel_main
.extern system_constructors;匯入系統建構函式
.global laoder
loader:
mov $kernel_stack, %esp
call system_constructors
push %eax
push %ebx
call kernel_main
stop:
cli
hlt
jmp stop
.section .bss
.space 2*1024*1024
kernel_stack:
一些code解釋:
- CLI:將IF置0,遮蔽掉“可遮蔽中斷”,當可遮蔽中斷到來時CPU不響應,繼續執行原指令
- STI:將IF置1,允許“可遮蔽中斷”,中斷到來轉而處理中斷
- HLT:本指令是處理器“暫停”指令。
- JMP:命令跳轉指令
- .global .global 用來讓一個符號對連結器可見,可以供其他連結物件模組使用。 .global boot 讓_start符號成為可見的標示符,這樣連結器就知道跳轉到程式中的什麼地方並開始執行。linux尋找這個 bootbootbootstart標籤作為程式的預設進入點。 在彙編和C混合程式設計中,彙編程式中要使用.global偽操作宣告彙編程式為全域性的函式,意即可被外部函式呼叫,同時C程式中要使用extern宣告要呼叫的組合語言程式。
- .extern .extern XXXX 說明xxxx為外部函式,呼叫的時候可以遍訪所有檔案找到該函式並且使用它。
- .long MAGIG .long指示宣告變數佔用空間,佔32位
- .set 給一個全域性變數或區域性變數賦值
現在建立符號連結來Link我們的所有object檔案
linker.ld
ENTRY(boot)
OUTPUT_FORMAT(elf32-i386)
OUTPUT_ARCH(i386:i386)
SECTIONS {
. = 0x0100000;
.text :{
*(.muiltboot)
*(.text*)
*(.rodata)
}
.data :
{
start_ctors = .;
KEEP(*(.init_array ));
KEEP(*(SORT_BY_INIT_PRIORITY( .init_array.* )));
end_ctors = .;
*(.data)
}
.bss :
{
*(.bss)
}
/DISCARD/ : {
*(.fini_array*) *(.comment)
}
}
Makefile 沒什麼好說的,Makefile負責C/C++的 編譯依賴過程
GCCPARAMS = -m32 -W -fno-use-cxa-atexit -nostdlib -fno-builtin -fno-builtin -fno-rtti -fno-exceptions -fno-leading-underscore
ASPPARAMS = --32
LDPARAMS = -melf_i386
GCC = g++
ASM = as
LINKER = ld
CFLAGS = -o $@ -c $<
ASMFLAGS = -o $@ $<
LINKERFLAGS = -T $< -o $@
objects = boot.o kernel.o
%.o: %.c
$(GCC) $(GCCPARAMS) $(CFLAGS)
%.o: %.s
$(ASM) $(ASPPARAMS) $(ASMFLAGS)
kernel_lab.bin: linker.ld $(objects)
$(LINKER) $(LINKERFLAGS) $(objects)
all: kernel_lab.bin
echo "build successed"
clean:
rm -rf *.o
rm -rf *.out
rm -rf iso
rm -rf *.iso
rm -rf *.bin
rebuild: clean all
echo "rebuild"
install: kernel_lab.bin
sudo cp $< /boot/kernel_lab.bin
kernel_lab.iso: rebuild
mkdir iso
mkdir iso/boot
mkdir iso/boot/grub
cp kernel_lab.bin iso/boot/
cp boot/grub.cfg iso/boot/grub/grub.cfg
grub-mkrescue -o $@ iso
rm -rf iso
kernel_vm: kernel_lab.iso
(killall virtualbox) || true
virtualbox -startvm "kernel_lab" &
下面是作業系統的主要程式,我們由C++編寫,用extern "C"匯出我們的函式符號 kernel.cpp
#include "kernel.h"
//因為我們的作業系統沒有TTY IO,所以我們需要重新寫一個printf函式
extern "C" void printf(char *str){
u_short *monitor_io_memory=(u_short *)0xb8000;//注意!重點來啦!0xb8000記憶體地址是顯示器地址,往這裡寫資料就直接能夠輸出到螢幕上
for(int i=0;str[i]!='\0';++i){
//寫入字串,取或0xff00的意思是我們需要把螢幕高四位拉低,否則就是黑色的字型,黑色的字型黑色的螢幕是啥也看不到的
monitor_io_memory[i]=(monitor_io_memory[i] & 0xff00) | str[i];
}
}
//作業系統建構函式委託方法
typedef void(*constructor)();
//全域性定義構造委託
constructor start_ctors;
//全域性定義析構委託
constructor end_ctors;
//輪詢函式,並且執行
extern "C" void system_constructors(){
for(constructor* i=&start_ctors;i!=&end_ctors;i+=1){
(*i)();
}
}
//作業系統主啟動函式,這裡我們列印一個字串然後讓作業系統進入等待
extern "C" void kernel_main(const void *multiboot_structure,u_int magicnumber){
printf("Hello Pulsar-V");
while(1);
}
別忘了/boot/下的grub.cfg檔案,這個是GRUB的配置檔案,負責在啟動器中列出我們需要啟動的核心列表
grub.cfg
set timeout=10 #超時時間
set default=0 #預設啟動項
menuentry "PulsarV's OS" {
multiboot /boot/kernel_lab.bin
boot
}
現在,來看看我們的工程目錄結構
在Ubuntu16.04的grub生成.iso映象檔案的時候如果出現
grub-mkrescue: warning: Your xorriso doesn't support `--grub2-boot-info'. Some features are disabled
的時候,就通過
sudo apt-get install xorriso
來安裝好你的xorriso
然後通過
grub-mkrescue -o kernel_lab.iso iso
來打包我們的操作核心 接下來要做的事情就是控制檯下輸入編譯命令先執行一下最基本的kernel
make kernel_vm
現在是show time!
首先看到的是我們的GRUB啟動介面
接下來按下回車或者等待10s
大功告成,現在可以看見了我們的作業系統的Hello World了。
後記
如果啟動失敗了,就用壓縮檔案(Ubuntu歸檔管理器)的形式開啟iso檔案,檢查你的目錄結構和GRUB Config
參考文件:
[Grub配置檔案]http://www.jinbuguo.com/linux/grub.cfg.html
[MIT公開課]https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-828-operating-system-engineering-fall-2012/
[計算機