1. 程式人生 > >VV的作業系統筆記(一)作業系統I SeeYou!!!!

VV的作業系統筆記(一)作業系統I SeeYou!!!!

開發十年,就只剩下這套架構體系了! >>>   

注:與本系列部落格同時同步的還有後面需要學習和研究的FreeRTOS和linux0.11-linux1.0核心程式碼VV的Linux作業系統核心筆記系列,即使筆者已經自己寫了個作業系統了,但是為了能夠使部落格能讀懂,筆者需要把每一個lab和程式碼打出來做出解釋同時筆者也有自己繁重的學習和工作(本科狗),所以進度會非常非常慢

準備工作

Ubuntu16.04-i386 32位作業系統映象

話不多說,迅雷下載下載地址

  1. 安裝映象到到虛擬機器

安裝過程不多贅述,安裝完成如圖所示:

  1. 安裝ubuntu的一些軟體和包
apt-get install docker docker.io docker-compose qemu virtualbox 
  1. 安裝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/
[計算機