Linux系統是怎樣初始化的

前言:建議閱讀上一篇文章《計算機體系結構變遷》瞭解體系結構,實模式與保護模式的區別和由來。需要向下衍生的一個知識點是體系結構中的存儲器包括內存和外存。內存除了經常講的RAM之外,還有ROM(只讀存儲器)。

一、從BIOS講起

通電伊始,系統處在實模式之下只有1M的尋址空間,其中最上面的64K會被用作去映射到ROM中去,而ROM中存放的是我們經常掛在嘴上的BIOS,接下來CPU就會依次執行BIOS中設置的指令,主要做一下幾件事:

  • 檢查系統的硬件
  • 建立中斷向量表以及對應的中斷服務程序
  • 加載在啓動扇區(MBR)的boot.img鏡像文件,開始操作系統安裝工作(其中boot.img是boot.S編譯生成,並由grub2程序放在啓動扇區的)
  • boot.img嘗試加載core.img相關的文件
  • real_to_prot 嘗試切換到保護模式

通常來說,boot.img文件主要做的事情就是加載grub2的另一個鏡像core.img。core.img由諸多模塊組成,包括diskboot.img,lzma_decompress.img,kernel.img等等。所以說若一切順利的話boot.img所謂的加載core.img就是首先加載diskboot.img,然後diskboot.img會將剩餘部分全部加載進來。在進行這些工作的時候1M的地址空間還是夠使用的,然而接下來,我們就要使用lzma_decompress.img進行對相關文件進行解壓縮,考慮到也許會發生地址空間不夠用的情況,所以lzma_decompress.img還會嘗試進行real_to_prot操作,即切換到保護模式去,這樣就可以使用更多的地址空間了。
從實模式切換到保護模式發生了什麼

  • 啓用分段與分頁機制。(這一塊剖析過內存管理模塊應該特別熟悉了)
  • 打開Gate A20,即第21根地址線的控制線
  • 接下來執行kernel.img對應的代碼startup.S及其他一堆c文件。
  • 經過startup.S的一系列騷操作就會慢慢的設置好我們的系統運行環境,真正的啓動內核。

二、內核初始化

需要掌握0,1,2號進程基本意義及設計思想。

內核初始化工作起始於init/main.c中的start_kernel函數:

//"__init"僅告訴kernel,此函數僅在初始化階段使用,使用後所佔用的內存資源會釋放 
asmlinkage __visible void __init start_kernel(void)
{//linux-4.13.16\init\main.c
    ...
    set_task_stack_end_magic(&init_task);//該函數去創建0號進程
    ...
	trap_init();//中斷
	mm_init();  //內存管理
    sched_init();//調度
    time_init();
    kmem_cache_init_late();
    vfs_caches_init();//初始化基於內存的文件系統rootfs
    //other_init...
    rest_init();//其他初始化,創建1和2號進程
}
static noinline void __ref rest_init(void)
{//linux-4.13.16\init\main.c
	struct task_struct *tsk;
	int pid;
	......
	pid = kernel_thread(kernel_init, NULL, CLONE_FS);//1號init進程,用戶態進程祖先
	......
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//2號進程,內核態祖先
......

可以看到start_kernel首先會使用set_task_stack_end_magic創建0號進程,這也是唯一一個內核不使用fork/kernel_thread函數創建的進程。然後是各種各樣的初始化,作用註釋中已給出。

(1)一號進程的意義

用戶態進程的實現基石是在保護模式之上的,須知x86對資源提供了分層管理權限,分爲4個ring,其中越往裏權限越高,內核是ring 0,用戶態進程是ring 4。保護模式的含義有2,第一是擁有了更大的尋址空間,第二是實現了對資源的保護,當用戶態進程企圖去訪問核心資源時就會被禁止。

(2)從內核態返回用戶態

由上面的講述可知在執行rest_init函數之時仍舊處在內核態,那如何在執行kernel_thread函數創建用戶態祖先進程之後進入用戶態模式哩?我們所熟知的函數調用邏輯是“用戶態函數——系統調用——保護進程上下文——內核態執行系統調用——恢復進程上下文——系統調用返回——用戶態函數”,現在我們想要從內核態返回用戶態其實只要做後半段工作時機就可以了。具體實現的時候其實都知道當創建了一個進程之後必須要做的事情就是調用do_execve系統調用加載elf文件,在這個過程中會調用load_elf_binary在這之後會進行上下文設置,進而陷入用戶態。

(3)ramdisk(基於內存的文件系統)

爲什麼會有ramdisk?
由第(2)問知道1號進程想要返回用戶態就要調用do_execve系統調用加載elf文件,這個elf文件可以是任意的文件系統上的某個特定文件,如“/sbin/init” ,“/etc/init ” ,"/bin/init" ,"/bin/sh"但正是由於存在在文件系統上,Linux若想要在訪問就需要將所有的驅動程序也要加載到內核裏面,這在存儲系統越來越多的情況下若都放在內核裏面,內核就太大了。故提出了這樣一種折中的方法,先調用ramdisk/init進入用戶態,然後此/init程序會加載對應存儲文件系統的的驅動這樣就可以設置真正的根文件系統了,此後就會調用對應根文件系統的/init程序。
在這之後,就是各種系統的初始化工作開始展開。如啓動系統服務,啓動控制檯等。

(4)fork與kernel_thread的區別
//linux-4.13.16\kernel\fork.c
SYSCALL_DEFINE0(fork)
{
	return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
}
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
	return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
		(unsigned long)arg, NULL, NULL, 0);
}

emmmm,顯然調用的都是相同的底層函數,本質上沒有區別。只是kernel_thread可以仔細的設置創建進程的屬性罷了,在Linux中類似的設計還有signal與signaction。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章