內核初始化中幾個重要的 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。
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 的分層權限機制
分成了四個 Ring,越往裏權限越高,越往外權限越低。能夠訪問關鍵資源的代碼放在Ring0,稱爲內核態(Kernel Mode);普通的程序代碼放在 Ring3,稱爲用戶態(User Mode)
用戶態的代碼想要訪問核心資源,通過系統調用運行內核中的相關代碼。這就需要進行用戶態和內核態的切換,就需要把用戶態程序運行到一半的情況保存下來。當系統調用完畢,返回的時候將保存的值恢復回去,就能接着運行了。
流程:用戶態 - 系統調用 - 保存寄存器 - 內核態執行系統調用 - 恢復寄存器 - 返回用戶態,然後接着運行。
從內核態到用戶態,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
歡迎大家來一起交流學習