想要了解核心的啟動過程,就需要檢視linux原始碼了(此文是基於linux 5.2)。檢視原始碼的過程中我們發現,核心的啟動是start_kernel()函式,它是位於init包下main.c檔案中的方法。
我們在此方法裡發現很多***_init的方法,也就是做一些初始化操作。
asmlinkage __visible void __init start_kernel
(void
)
初始化第乙個程序
我們知道linux系統中的所有程序都是通過父程序fork來的,那麼核心啟動時需要乙個祖先程序,這個程序我們稱之為0號程序,它是由set_task_stack_end_magic方法設定而來,set_task_stack_end_magic方法位於fock.c檔案中。
void
set_task_stack_end_magic
(struct task_struct *tsk)
引數init_task是乙個結構體,其定義在init_task.c檔案中,做一些結構體的填充。
struct task_struct init_task
#ifdef config_arch_task_struct_on_stack
__init_task_data
#endif
=
smp_processor_id是乙個巨集,在smp的情況下獲取cpu_id ,如果不是smp,那麼返回0。
vfs_caches_init()位於fs/dcache.c檔案中,用來初始化基於記憶體的檔案系統rootfs。
void __init vfs_caches_init
(void
)
vfs_caches_init()內 會呼叫mnt_init()方法, mnt方法位於namespace.c中
void __init mnt_init
(void
)
mnt_init() 呼叫了 init_rootfs() 方法,此方法中會在vfs中註冊一種 struct file_system_type rootfs_fs_type 的型別。vfs (virtual file system) 存放著核心對外提供的介面。
此時0號程序初始化完成了
初始化第二個程序
start_kernel() 方法中的注釋我們發現,有乙個名叫 arch_call_rest_init() 的方法來做其餘初始化的事,實際呼叫的是rest_init()方法
noinline void __ref rest_init
(void
)
發現第12行pid = kernel_thread(kernel_init, null, clone_fs);
此時會建立乙個程序,我們稱之為1號程序,此程序裡執行的是使用者程序 即所有使用者態程序的祖先程序,當此程序進入使用者態後,會開枝散葉 建立出很多子程序,形成一棵程序樹。
從核心態到使用者態
kernel_thread() 會呼叫 kernel_init 引數
static
int __ref kernel_init
(void
*unused)..
.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"))
return0;
...}
///執行ramdisk
static noinline void __init kernel_init_freeable
(void
)///執行系統呼叫
static
intrun_init_process
(const
char
*init_filename)
從以上**可以看出,1號程序執行的是乙個檔案。run_init_process() 中執行了 do_execve() 。 此方法會嘗試執行remdisk的』 /init '或普通檔案系統上的"sbin/init"、"/etc/init"、 「/bin/init」、"/bin/sh"。不同版本的linux會選擇不同的檔案啟動,但是只要有乙個起來了就可以了。
那麼具體的執行流程是什麼?
//呼叫do_execve方法
intdo_execve
(struct filename *filename,
const
char __user *
const __user *__ar**,
const
char __user *
const __user *__envp)
;struct user_arg_ptr envp =
;return
do_execveat_common
(at_fdcwd, filename, ar**, envp,0)
;}//呼叫do_execveat_common
static
intdo_execveat_common
(int fd,
struct filename *filename,
struct user_arg_ptr ar**,
struct user_arg_ptr envp,
int flags)
//__do_execve_file 方法中 我們會發現執行了乙個二進位制檔案exec_binprm()
static
int__do_execve_file
(int fd,
struct filename *filename,
struct user_arg_ptr ar**,
struct user_arg_ptr envp,
int flags,
struct file *file)
//呼叫exec_binprm,此時又呼叫了search_binary_handler方法
static
intexec_binprm
(struct linux_binprm *bprm)
return ret;
}//檢視search_binary_handler,我們會發現有乙個名為linux_binfmt的結構體
intsearch_binary_handler
(struct linux_binprm *bprm)
return retval;
}//結構體linux_binfmt,定義了二進位制檔案
struct linux_binfmt __randomize_layout;
static
struct linux_binfmt elf_format =
;//load_elf_binary 載入elf檔案
static
intload_elf_binary
(struct linux_binprm *bprm)
//開啟位於/arch/x86/kernel/process_32.c檔案
void
start_thread
(struct pt_regs *regs,
unsigned
long new_ip,
unsigned
long new_sp)
export_symbol_gpl
(start_thread)
;
tip:
elf(executable and linkable format,可執行與可鏈結格式)是linux中的常用格式
最後我們會發現 start_thread把所有暫存器狀態都設定成了_user_xx, 也就是將**段cs設定成了_user_cs 將資料段的ds設定成了__user_ds, 指令指標暫存器ip和棧指標暫存器sp都做了重置,即此函式的作用 是儲存暫存器
force_iret是從系統呼叫中返回,即下一條指令是從使用者態開始執行了。
進入到使用者態之後 就需要執行 remdisk 上的/init 載入儲存裝置的驅動,有了驅動就可以設定真正的根檔案系統了。接下來remdisk 上的/init 會啟動檔案系統上的init
初始化2號程序
當系統進入使用者態之後,就也就是有了所有程序的祖程序,接下來就需要乙個核心態的程序來統一管理核心態了 這個程序我們稱之為2號程序。
在main.c 檔案中
noinline void __ref rest_init
(void
)
上述**中,我們知道kernel_thread()是用來建立程序的,那麼引數kthreadd 就是用來建立2號程序的了。
kthreadd負責所有核心態的執行緒的排程和管理,是核心態所有執行緒執行的祖先。
ok,啟動流程大體上就是這樣了。
linux啟動流程
linux系統主要通過以下步驟啟動 讀取mbr的資訊,啟動boot manager windows使用ntldr作為boot manager,如果您的系統中安裝多個版本的windows,您就需要在ntldr中選擇您要進入的系統。linux通常使用功能強大,配置靈活的grub作為boot manage...
linux啟動流程
bios basic input output system mbr main boot record kernel kernel自解壓 核心初始化 核心啟動 start kernel 自身為0 程序,建立1 程序執行,直接執行在物理記憶體空間上,沒有虛位址。1 程序裝入並執行程式 sbin ini...
Linux啟動流程
下面簡單介紹下啟動,詳細的後續再補。1.載入bios的硬體資訊並進行自我測試,並依據設定取得第乙個可以啟動的裝置 2.讀取並執行第乙個裝置內的mbr master boot record,硬碟的主引導記錄 的boot loader 即是grub,spfdisk等程式 3.依據boot loader的...