內核的啟動從入口函數 start_kernel() 開始。在 init/main.c 文件中,start_kernel 相當於內核的main 函數。打開這個函數,你會發現,里面是各種各樣初始化函數 XXXX_init

第一步:在操作系統里面,先要有個創始進程,有一行指令 set_task_stack_end_magic(&init_task)。這里
面有一個參數 init_task,它的定義是 struct task_struct init_task = INIT_TASK(init_task)。它是系
統創建的第一個進程,我們稱為0 號進程。這是唯一一個沒有通過 fork 或者 kernel_thread 產生
的進程,是進程列表的第一個
第二步:對應的函數是 trap_init(),里面設置了很多中斷門(Interrupt Gate),用於處理各種中
斷。其中有一個 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32),這是系統調用
的中斷門。系統調用也是通過發送中斷的方式進行的
第三步:初始化的會議室管理系統。對應的,mm_init() 就是用來初始化內存管理模塊。
第四步:項目需要項目管理進行調度,需要執行一定的調度策略。sched_init() 就是用於初始化調度模塊。
vfs_caches_init() 會用來初始化基於內存的文件系統 rootfs。在這個函數里面,會調用 mnt_init()-init_rootfs()。這里面有一行代碼,register_filesystem(&rootfs_fs_type)。在 VFS 虛擬文件系統里面注冊了一種類型,我們定義為 struct file_system_type rootfs_fs_type。文件系統是我們的項目資料庫,為了兼容各種各樣的文件系統,我們需要將文件的相關數據結構和操作抽象出來,形成一個抽象層對上提供統一的接口,這個抽象層就是 VFS(Virtual FileSystem),虛擬文件系統。
第五步:start_kernel() 調用的是 rest_init(),用來做其他方面的初始化,
在其他初始化中,主要是創建1號進程和2號進程
初始化 1 號進程
rest_init 的第一大工作是,用 kernel_thread(kernel_init, NULL, CLONE_FS) 創建第二個進程,這個是1 號進程。
pid=1 :init進程,系統啟動的第一個用戶級進程,是所有其它進程的父進程,引導用戶空間服務。
pid為 1 的一定是init進程.
它是內核運行后的第一個進程.
內核態和用戶態
將能夠訪問關鍵資源的代碼放在 Ring0,我們稱為內核態(Kernel Mode);將普通的程序代碼放在 Ring3,我們稱為用戶態(User Mode)
用戶態的代碼想要訪問核心資源 ?
請求系統調用
比如當一個用戶態的程序運行到一半,要訪問一個核心資源,例如訪問網卡發一個網絡包,就需要暫停當前的運行,調用系統調用,接下來就輪到內核中的代碼運行了。
首先,內核將從系統調用傳過來的包,在網卡上排隊,輪到的時候就發送。發送完了,系統調用就結束了,返回用戶態,讓暫停運行的程序接着運行
這個暫停怎么實現呢?其實就是把程序運行到一半的情況保存下來。例如,我們知道,內存是用來保存程運行時候的中間結果的,現在要暫時停下來,這些中間結果不能丟,因為再次運行的時候,還要基於這些中間結果接着來。另外就是,當前運行到代碼的哪一行了,當前的棧在哪里,這些都是在寄存器里面的。
所以,暫停的那一刻,要把當時 CPU 的寄存器的值全部暫存到一個地方,這個地方可以放在進程管理系統很容易獲取的地方。在后面討論進程管理數據結構的時候,我們還會詳細講。當系統調用完畢,返回的時候,再從這個地方將寄存器的值恢復回去,就能接着運行了
這個過程就是這樣的:用戶態 - 系統調用 - 保存寄存器 - 內核態執行系統調用 - 恢復寄存器 - 返回
用戶態,然后接着運行


從內核態到用戶態
1,調用kernel_thread ,kernel_thread 的參數是一個函數kernel_init ,在 kernel_init里面,會調用 kernel_init_freeable(), 然后調用run_init_process 函數,會發現它調用的是 do_execve。
其中kernel_init_freeable()會去判斷是否存在ramdisk_execute_command ,如果ramdisk_execute_command變量指定了要運行的程序,啟動它。
ramdisk_execute_command的取值分為三種情況:
a.如果命令行參數中指定了“rdinit=...”,則ramdisk_execute_command等於這個參數指定的程序。
b.否則,如果/init程序存在,ramdisk_execute_command就等於/init
c.否則,ramdisk_execute_command為空
execve 是一個系統調用,它的作用是運行一個執行文件。加一個 do_ 的往往是內核系統調用的實現。沒錯,這就是一個系統調用,它會嘗試運行 ramdisk 的“/init”,或者普通文件系的“/sbin/init”“/etc/init”“/bin/init”“/bin/sh”。不同版本的 Linux 會選擇不同的文件啟動
后續執行do_execve->do_execveat_common->exec_binprm->search_binary_handler
ramdisk 的作用
init 終於從內核到用戶態了。一開始到用戶態的是 ramdisk 的 init,后來會啟動真正根文件系統上的 init,成為所有用戶態進程的祖先。
為什么會有 ramdisk 這個東西呢?還記得上一節咱們內核啟動的時候,配置過這個參數:
init 程序是在文件系統上的,文件系統一定是在一個存儲設備上的,例如硬盤。Linux 訪問存儲設備,要有驅動才能訪問。如果存儲系統數目很有限,那驅動可以直接放到內核里面,反正前面我們加載過內核到內存里了,現在可以直接對存儲系統進行訪問。但是存儲系統越來越多了,如果所有市面上的存儲系統的驅動都默認放進內核,內核就太大了。為了解決這個問題,只好先弄一個基於內存的文件系統。內存訪問是不需要驅動的,這個就是ramdisk。這個時候,ramdisk 是根文件系統。然后,我們開始運行 ramdisk 上的 /init。等它運行完了就已經在用戶態了。/init 這個程序會先根據存儲系統的類型加載驅動,有了驅動就可以設置真正的根文件系統了。有了真正的根文件系統,ramdisk 上的 /init 會啟動文件系統上的 init。
接下來就是各種系統的初始化。啟動系統的服務,啟動控制台,用戶就可以登錄進來了。
2號進程
rest_init第二大事情就是第三個進程,就是 2 號進程。進程。這里需要指出一點,函數名 thread 可以翻譯成“線程”,這也是操作系統很重要的一個概念。它和進程有什么區別呢?為什么這里創建的是進程,函數名卻是線程呢?從用戶態來看,創建進程其實就是立項,也就是啟動一個項目。這個項目包含很多資源,例如會議室、資料庫等。這些東西都屬於這個項目,但是這個項目需要人去執行。有多個人並行執行不同的部分,這就叫多線程(Multithreading)。如果只有一個人,那它就是這個項目的主線程。但是從內核態來看,無論是進程,還是線程,我們都可以統稱為任(Task)都使用相同的數據結構,平放在同一個鏈表中。這些在進程的那一章節,我會更加詳細地講。這里的kthreadd,負責所有內核態的線程的調度和管理,是內核態所有線程運行的祖先
