一步一步學linux操作系統: 05 內核初始化

內核初始化中幾個重要的 init

內核下載 http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/v4.x/

內核的啓動從入口函數 start_kernel() 開始,在 init/main.c 中
在這裏插入圖片描述
start_kernel 相當於內核的 main 函數,裏面是各種各樣初始化函數 XXXX_init。
圖片來自極客時間趣談linux操作系統

1、0號進程創建

set_task_stack_end_magic(&init_task)函數 ,設置操作系統的第一個進程init。系統創建的第一個進程,唯一一個沒有通過 fork 或者 kernel_thread 產生的進程,是進程列表的第一個。內核態下執行的0號進程,0號進程是所有進程的祖先。

init_task變量,從init/init_task.c中初始化的,它的定義是 struct task_struct init_task = INIT_TASK(init_task)。
在這裏插入圖片描述

2、 初始化中斷

trap_init()裏面設置了很多中斷門(Interrupt Gate),用於處理各種中斷。

在這裏插入圖片描述
其中有一個 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32),這是32位系統的系統調用中斷門,64 位的有另外的系統調用方法。

3、初始化內存管理模塊

mm_init() 用來初始化內存管理模塊

在這裏插入圖片描述

4、初始化調度模塊

sched_init() 用於初始化調度模塊

在這裏插入圖片描述

5、初始化基於內存的文件系統

vfs_caches_init() 初始化基於內存的文件系統 rootfs

在這裏插入圖片描述
在這裏插入圖片描述
其中 mnt_init()->init_rootfs()
在這裏插入圖片描述
會調用 register_filesystem(&rootfs_fs_type); 在 VFS 虛擬文件系統裏面註冊了一種類型,定義爲 struct file_system_type rootfs_fs_type。VFS(Virtual File System),虛擬文件系統,是一個抽象層對上提供統一的接口,來支持兼容各種各樣的文件系統(抽象 文件的相關數據結構和操作)

在這裏插入圖片描述

6、其他方面的初始化

rest_init(),用來做其他方面的初始化

在這裏插入圖片描述

初始化 1 號進程

rest_init 的第一大工作是,用 kernel_thread(kernel_init, NULL, CLONE_FS) 創建第二個進程,這個是 1 號進程,用戶進程。

在這裏插入圖片描述

x86 的分層權限機制

圖片來自極客時間趣談linux操作系統
分成了四個 Ring,越往裏權限越高,越往外權限越低。能夠訪問關鍵資源的代碼放在Ring0,稱爲內核態(Kernel Mode);普通的程序代碼放在 Ring3,稱爲用戶態(User Mode)

用戶態的代碼想要訪問核心資源,通過系統調用運行內核中的相關代碼。這就需要進行用戶態和內核態的切換,就需要把用戶態程序運行到一半的情況保存下來。當系統調用完畢,返回的時候將保存的值恢復回去,就能接着運行了。

圖片來自極客時間趣談linux操作系統
流程:用戶態 - 系統調用 - 保存寄存器 - 內核態執行系統調用 - 恢復寄存器 - 返回用戶態,然後接着運行。
圖片來自極客時間趣談linux操作系統

從內核態到用戶態,1 號進程的啓動

當前執行 kernel_thread 這個函數的時候,還在內核態,如何在“先內核態再用戶態”情況下到用戶態去運行一個程序?

函數 pid = kernel_thread(kernel_init, NULL, CLONE_FS); 參數是一個函數 kernel_init,

在這裏插入圖片描述

其中的 kernel_init_freeable函數有如圖代碼

在這裏插入圖片描述
回到 kernel_init 中

	if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
		if (!ret)
			return 0;
		pr_err("Failed to execute %s (error %d)\n",
		       ramdisk_execute_command, ret);
	}
	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;

進一步看函數 try_to_run_init_process 函數run_init_process,調用的是 do_execve
在這裏插入圖片描述
execve 是一個系統調用,它的作用是運行一個執行文件。加一個 do_ 的往往是內核系統調用的實現。說明1 號進程運行的是一個文件。

do_execve,它會嘗試運行** ramdisk 的“/init”**,或者普通文件系統上的“/sbin/init”“/etc/init”“/bin/init”“/bin/sh”。不同版本的 Linux 會選擇不同的文件啓動,但是隻要有一個起來了就可以。

如何利用執行 init 文件的機會,從內核態回到用戶態呢?

系統調用的過程,“用戶態 - 系統調用 - 保存寄存器 - 內核態執行系統調用 - 恢復寄存器 - 返回用戶態”
剛纔運行 init,是調用 do_execve,正是上面的過程的後半部分,從內核態執行系統調用開始。

do_execve->do_execveat_common->exec_binprm->search_binary_handler,這裏面會調用這段內容:
在這裏插入圖片描述
要運行一個程序,需要加載這個程序的二進制文件

二進制文件,Linux 下一個常用的格式是 ELF(Executable and Linkable Format,可執行與可鏈接格式)。就有了下面這個定義:


static struct linux_binfmt elf_format = {
.module  = THIS_MODULE,
.load_binary  = load_elf_binary,
.load_shlib  = load_elf_library,
.core_dump  = elf_core_dump,
.min_coredump  = ELF_EXEC_PAGESIZE,
};

先調用 load_elf_binary,最後調用 start_thread


void
start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
{
set_user_gs(regs, 0);
regs->fs  = 0;
regs->ds  = __USER_DS;
regs->es  = __USER_DS;
regs->ss  = __USER_DS;
regs->cs  = __USER_CS;
regs->ip  = new_ip;
regs->sp  = new_sp;
regs->flags  = X86_EFLAGS_IF;
force_iret();
}
EXPORT_SYMBOL_GPL(start_thread);

struct pt_regs,看名字裏的 register,是寄存器。這個結構就是在系統調用的時候,內核中保存用戶態運行上下文的,裏面將用戶態的代碼段 CS 設置爲 __USER_CS,將用戶態的數據段 DS 設置爲 __USER_DS,以及指令指針寄存器 IP、棧指針寄存器 SP。這裏相當於補上了原來系統調用裏,保存寄存器的一個步驟。

iret用於從系統調用中返回,這個時候會恢復寄存器,從進入系統調用的時候,保存的寄存器裏面拿出,也就是上面函數補上的寄存器。

CS 和指令指針寄存器 IP 恢復了,指向用戶態下一個要執行的語句。DS 和函數棧指針 SP 也被恢復了,指向用戶態函數棧的棧頂。所以,下一條指令,就從用戶態開始運行了。

ramdisk 的作用

init 從內核到用戶態了。一開始到用戶態的是 ramdisk 的 init,後來會啓動真正根文件系統上的 init,成爲所有用戶態進程的祖先。

基於內存的文件系統,內存訪問是不需要驅動的,這個就是 ramdisk。這個時候,ramdisk 是根文件系統。沒有真正的根文件系統了,使用基於內存的文件系統,開始運行 ramdisk 上的 /init。等它運行完了就已經在用戶態了。/init 這個程序會先根據存儲系統的類型加載驅動,有了驅動就可以設置真正的根文件系統了。有了真正的根文件系統,ramdisk 上的 /init 會啓動文件系統上的 init。

創建 2 號進程

rest_init 第二大事情就是第三個進程,就是 2 號進程
在這裏插入圖片描述

通過 kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)創建 2號進程(工作在內核態)

函數 kthreadd,負責所有內核態的線程的調度和管理,是內核態所有線程運行的祖先。

int kthreadd(void *unused)
{
	struct task_struct *tsk = current;

	/* Setup a clean context for our children to inherit. */
	set_task_comm(tsk, "kthreadd");
	ignore_signals(tsk);
	set_cpus_allowed_ptr(tsk, cpu_all_mask);
	set_mems_allowed(node_states[N_MEMORY]);

	current->flags |= PF_NOFREEZE;
	cgroup_init_kthreadd();

	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE);
		if (list_empty(&kthread_create_list))
			schedule();
		__set_current_state(TASK_RUNNING);

		spin_lock(&kthread_create_lock);
		while (!list_empty(&kthread_create_list)) {
			struct kthread_create_info *create;

			create = list_entry(kthread_create_list.next,
					    struct kthread_create_info, list);
			list_del_init(&create->list);
			spin_unlock(&kthread_create_lock);

			create_kthread(create);

			spin_lock(&kthread_create_lock);
		}
		spin_unlock(&kthread_create_lock);
	}

	return 0;
}

參考資料:

趣談Linux操作系統(極客時間)鏈接:
http://gk.link/a/10iXZ
歡迎大家來一起交流學習

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