Linux下1号进程的前世(kernel_init)今生(init进程)----Linux进程的管理与调度
Linux下有3個(gè)特殊的進(jìn)程,idle進(jìn)程(PID=0PID=0), init進(jìn)程(PID=1PID=1)和kthreadd(PID=2PID=2)
* idle進(jìn)程由系統(tǒng)自動(dòng)創(chuàng)建, 運(yùn)行在內(nèi)核態(tài)?
idle進(jìn)程其pid=0,其前身是系統(tǒng)創(chuàng)建的第一個(gè)進(jìn)程,也是唯一一個(gè)沒有通過fork或者kernel_thread產(chǎn)生的進(jìn)程。完成加載系統(tǒng)后,演變?yōu)檫M(jìn)程調(diào)度、交換
* init進(jìn)程由idle通過kernel_thread創(chuàng)建,在內(nèi)核空間完成初始化后, 加載init程序, 并最終用戶空間?
由0進(jìn)程創(chuàng)建,完成系統(tǒng)的初始化. 是系統(tǒng)中所有其它用戶進(jìn)程的祖先進(jìn)程?
Linux中的所有進(jìn)程都是有init進(jìn)程創(chuàng)建并運(yùn)行的。首先Linux內(nèi)核啟動(dòng),然后在用戶空間中啟動(dòng)init進(jìn)程,再啟動(dòng)其他系統(tǒng)進(jìn)程。在系統(tǒng)啟動(dòng)完成完成后,init將變?yōu)槭刈o(hù)進(jìn)程監(jiān)視系統(tǒng)其他進(jìn)程。
* kthreadd進(jìn)程由idle通過kernel_thread創(chuàng)建,并始終運(yùn)行在內(nèi)核空間, 負(fù)責(zé)所有內(nèi)核線程的調(diào)度和管理?
它的任務(wù)就是管理和調(diào)度其他內(nèi)核線程kernel_thread, 會(huì)循環(huán)執(zhí)行一個(gè)kthread的函數(shù),該函數(shù)的作用就是運(yùn)行kthread_create_list全局鏈表中維護(hù)的kthread, 當(dāng)我們調(diào)用kernel_thread創(chuàng)建的內(nèi)核線程會(huì)被加入到此鏈表中,因此所有的內(nèi)核線程都是直接或者間接的以kthreadd為父進(jìn)程?
我們下面就詳解分析1號(hào)進(jìn)程的前世(kernel_init)今生(init進(jìn)程)
Linux系統(tǒng)中的init進(jìn)程(pid=1)是除了idle進(jìn)程(pid=0,也就是init_task)之外另一個(gè)比較特殊的進(jìn)程,它是Linux內(nèi)核開始建立起進(jìn)程概念時(shí)第一個(gè)通過kernel_thread產(chǎn)生的進(jìn)程,其開始在內(nèi)核態(tài)執(zhí)行,然后通過一個(gè)系統(tǒng)調(diào)用,開始執(zhí)行用戶空間的/sbin/init程序,期間Linux內(nèi)核也經(jīng)歷了從內(nèi)核態(tài)到用戶態(tài)的特權(quán)級(jí)轉(zhuǎn)變,/sbin/init極有可能產(chǎn)生出了shell,然后所有的用戶進(jìn)程都有該進(jìn)程派生出來
1號(hào)進(jìn)程
前面我們了解到了0號(hào)進(jìn)程是系統(tǒng)所有進(jìn)程的先祖, 它的進(jìn)程描述符init_task是內(nèi)核靜態(tài)創(chuàng)建的, 而它在進(jìn)行初始化的時(shí)候, 通過kernel_thread的方式創(chuàng)建了兩個(gè)內(nèi)核線程,分別是kernel_init和kthreadd,其中kernel_init進(jìn)程號(hào)為1
start_kernel在其最后一個(gè)函數(shù)rest_init的調(diào)用中,會(huì)通過kernel_thread來生成一個(gè)內(nèi)核進(jìn)程,后者則會(huì)在新進(jìn)程環(huán)境下調(diào) 用kernel_init函數(shù),kernel_init一個(gè)讓人感興趣的地方在于它會(huì)調(diào)用run_init_process來執(zhí)行根文件系統(tǒng)下的 /sbin/init等程序:
kernel_init
0號(hào)進(jìn)程創(chuàng)建1號(hào)進(jìn)程的方式如下
kernel_thread(kernel_init, NULL, CLONE_FS);- 1
我們發(fā)現(xiàn)1號(hào)進(jìn)程的執(zhí)行函數(shù)就是kernel_init, 這個(gè)函數(shù)被定義init/main.c中,如下所示
kernel_init函數(shù)將完成設(shè)備驅(qū)動(dòng)程序的初始化,并調(diào)用init_post函數(shù)啟動(dòng)用戶空間的init進(jìn)程。
由0號(hào)進(jìn)程創(chuàng)建1號(hào)進(jìn)程(內(nèi)核態(tài)),1號(hào)內(nèi)核線程負(fù)責(zé)執(zhí)行內(nèi)核的部分初始化工作及進(jìn)行系統(tǒng)配置,并創(chuàng)建若干個(gè)用于高速緩存和虛擬主存管理的內(nèi)核線程。
init進(jìn)程
隨后,1號(hào)進(jìn)程調(diào)用do_execve運(yùn)行可執(zhí)行程序init,并演變成用戶態(tài)1號(hào)進(jìn)程,即init進(jìn)程。
init進(jìn)程是linux內(nèi)核啟動(dòng)的第一個(gè)用戶級(jí)進(jìn)程。init有許多很重要的任務(wù),比如像啟動(dòng)getty(用于用戶登錄)、實(shí)現(xiàn)運(yùn)行級(jí)別、以及處理孤立進(jìn)程。
它按照配置文件/etc/initab的要求,完成系統(tǒng)啟動(dòng)工作,創(chuàng)建編號(hào)為1號(hào)、2號(hào)…的若干終端注冊(cè)進(jìn)程getty。
每個(gè)getty進(jìn)程設(shè)置其進(jìn)程組標(biāo)識(shí)號(hào),并監(jiān)視配置到系統(tǒng)終端的接口線路。當(dāng)檢測(cè)到來自終端的連接信號(hào)時(shí),getty進(jìn)程將通過函數(shù)do_execve()執(zhí)行注冊(cè)程序login,此時(shí)用戶就可輸入注冊(cè)名和密碼進(jìn)入登錄過程,如果成功,由login程序再通過函數(shù)execv()執(zhí)行shell,該shell進(jìn)程接收getty進(jìn)程的pid,取代原來的getty進(jìn)程。再由shell直接或間接地產(chǎn)生其他進(jìn)程。
上述過程可描述為:0號(hào)進(jìn)程->1號(hào)內(nèi)核進(jìn)程->1號(hào)用戶進(jìn)程(init進(jìn)程)->getty進(jìn)程->shell進(jìn)程
注意,上述過程描述中提到:1號(hào)內(nèi)核進(jìn)程調(diào)用執(zhí)行init函數(shù)并演變成1號(hào)用戶態(tài)進(jìn)程(init進(jìn)程),這里前者是init是函數(shù),后者是進(jìn)程。兩者容易混淆,區(qū)別如下:
kernel_init函數(shù)在內(nèi)核態(tài)運(yùn)行,是內(nèi)核代碼
init進(jìn)程是內(nèi)核啟動(dòng)并運(yùn)行的第一個(gè)用戶進(jìn)程,運(yùn)行在用戶態(tài)下。
一號(hào)內(nèi)核進(jìn)程調(diào)用execve()從文件/etc/inittab中加載可執(zhí)行程序init并執(zhí)行,這個(gè)過程并沒有使用調(diào)用do_fork(),因此兩個(gè)進(jìn)程都是1號(hào)進(jìn)程。
當(dāng)內(nèi)核啟動(dòng)了自己之后(已被裝入內(nèi)存、已經(jīng)開始運(yùn)行、已經(jīng)初始化了所有的設(shè)備驅(qū)動(dòng)程序和數(shù)據(jù)結(jié)構(gòu)等等),通過啟動(dòng)用戶級(jí)程序init來完成引導(dǎo)進(jìn)程的內(nèi)核部分。因此,init總是第一個(gè)進(jìn)程(它的進(jìn)程號(hào)總是1)。
當(dāng)init開始運(yùn)行,它通過執(zhí)行一些管理任務(wù)來結(jié)束引導(dǎo)進(jìn)程,例如檢查文件系統(tǒng)、清理/tmp、啟動(dòng)各種服務(wù)以及為每個(gè)終端和虛擬控制臺(tái)啟動(dòng)getty,在這些地方用戶將登錄系統(tǒng)。
在系統(tǒng)完全起來之后,init為每個(gè)用戶已退出的終端重啟getty(這樣下一個(gè)用戶就可以登錄)。init同樣也收集孤立的進(jìn)程:當(dāng)一個(gè)進(jìn)程啟動(dòng)了一個(gè)子進(jìn)程并且在子進(jìn)程之前終止了,這個(gè)子進(jìn)程立刻成為init的子進(jìn)程。對(duì)于各種技術(shù)方面的原因來說這是很重要的,知道這些也是有好處的,因?yàn)檫@便于理解進(jìn)程列表和進(jìn)程樹圖。init的變種很少。絕大多數(shù)Linux發(fā)行版本使用sysinit(由Miguel van Smoorenburg著),它是基于System V的init設(shè)計(jì)。UNIX的BSD版本有一個(gè)不同的init。最主要的不同在于運(yùn)行級(jí)別:System V有而BSD沒有(至少是傳統(tǒng)上說)。這種區(qū)別并不是主要的。在此我們僅討論sysvinit。 配置init以啟動(dòng)getty:/etc/inittab文件
關(guān)于init程序
1號(hào)進(jìn)程通過execve執(zhí)行init程序來進(jìn)入用戶空間,成為init進(jìn)程,那么這個(gè)init在哪里呢
內(nèi)核在幾個(gè)位置上來查尋init,這幾個(gè)位置以前常用來放置init,但是init的最適當(dāng)?shù)奈恢?#xff08;在Linux系統(tǒng)上)是/sbin/init。如果內(nèi)核沒有找到init,它就會(huì)試著運(yùn)行/bin/sh,如果還是失敗了,那么系統(tǒng)的啟動(dòng)就宣告失敗了。
因此init程序是一個(gè)可以又用戶編寫的進(jìn)程, 如果希望看init程序源碼的朋友,可以參見
| sysvinit | 早期一些版本使用的初始化進(jìn)程工具, 目前在逐漸淡出linux歷史舞臺(tái), sysvinit 就是 system V 風(fēng)格的 init 系統(tǒng),顧名思義,它源于 System V 系列 UNIX。它提供了比 BSD 風(fēng)格 init 系統(tǒng)更高的靈活性。是已經(jīng)風(fēng)行了幾十年的 UNIX init 系統(tǒng),一直被各類 Linux 發(fā)行版所采用。 | 淺析 Linux 初始化 init 系統(tǒng)(1):sysvinit |
| upstart | debian, Ubuntu等系統(tǒng)使用的initdaemon | 淺析 Linux 初始化 init 系統(tǒng)(2): UpStart |
| systemd | Systemd 是 Linux 系統(tǒng)中最新的初始化系統(tǒng)(init),它主要的設(shè)計(jì)目標(biāo)是克服 sysvinit 固有的缺點(diǎn),提高系統(tǒng)的啟動(dòng)速度 | 淺析 Linux 初始化 init 系統(tǒng)(3) Systemd |
Ubuntu等使用deb包的系統(tǒng)可以通過dpkg -S查看程序所在的包
CentOS等使用rpm包的系統(tǒng)可以通過rpm -qf查看系統(tǒng)程序所在的包
參見
Linux下查看并下載命令源碼包(根據(jù)命令/應(yīng)用程序逆向獲取并且安裝其所屬源碼包)
附錄
kernel_init_freeable流程分析
static noinline void __init kernel_init_freeable(void) {/** Wait until kthreadd is all set-up.*/wait_for_completion(&kthreadd_done);/* Now the scheduler is fully set up and can do blocking allocations */gfp_allowed_mask = __GFP_BITS_MASK;/** init can allocate pages on any node*/set_mems_allowed(node_states[N_MEMORY]);/** init can run on any cpu.*/set_cpus_allowed_ptr(current, cpu_all_mask);cad_pid = task_pid(current);smp_prepare_cpus(setup_max_cpus);do_pre_smp_initcalls();lockup_detector_init();smp_init();sched_init_smp();page_alloc_init_late();do_basic_setup();/* Open the /dev/console on the rootfs, this should never fail */if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)pr_err("Warning: unable to open an initial console.\n");(void) sys_dup(0);(void) sys_dup(0);/** check if there is an early userspace init. If yes, let it do all* the work*/if (!ramdisk_execute_command)ramdisk_execute_command = "/init";if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {ramdisk_execute_command = NULL;prepare_namespace();}/** Ok, we have completed the initial bootup, and* we're essentially up and running. Get rid of the* initmem segments and start the user-mode stuff..** rootfs is available now, try loading the public keys* and default modules*/integrity_load_keys();load_default_modules();}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
| wait_for_completion | 實(shí)例在kernel/sched/completion.c中, 等待Kernel Thread kthreadd (PID=2)創(chuàng)建完畢 |
| gfp_allowed_mask | __GFP_BITS_MASK;設(shè)置bitmask, 使得init進(jìn)程可以使用PM并且允許I/O阻塞操作 |
| set_mems_allowed(node_states[N_MEMORY]); | init進(jìn)程可以分配物理頁面 |
| set_cpus_allowed_ptr | 通過設(shè)置cpu_bit_mask, 可以限定task只能在特定的處理器上運(yùn)行, 而initcurrent進(jìn)程此時(shí)必然是init進(jìn)程,設(shè)置其cpu_all_mask即使得init進(jìn)程可以在任意的cpu上運(yùn)行 |
| task_pid | 設(shè)置到目前運(yùn)行進(jìn)程init的pid號(hào)給cad_pid(cad_pid是用來接收ctrl-alt-del?reboot signal的進(jìn)程, 如果設(shè)置C_A_D=1就表示可以處理來自ctl-alt-del的動(dòng)作), 最后會(huì)調(diào)用?ctrl_alt_del(void)并確認(rèn)C_A_D是否為1,確認(rèn)完成后將執(zhí)行cad_work=deferred_cad,執(zhí)行kernel_restart |
| smp_prepare_cpus | 體系結(jié)構(gòu)相關(guān)的函數(shù),實(shí)例在arch/arm/kernel/smp.c中,調(diào)用smp_prepare_cpus時(shí),會(huì)以全局變量setup_max_cpus為函式參數(shù)max_cpus,以表示在編譯核心時(shí),設(shè)定支援的最大CPU數(shù)量 |
| do_pre_smp_initcalls | 實(shí)例在init/main.c中, 會(huì)透過函式do_one_initcall,執(zhí)行Symbol中 __initcall_start與__early_initcall_end之間的函數(shù) |
| smp_init | 實(shí)例在kernel/smp.c中, 函數(shù)主要是由Bootstrap處理器,進(jìn)行Active多核心架構(gòu)下其它的處理器. 如果發(fā)生Online的處理器個(gè)數(shù)(from num_online_cpus)超過在核心編譯時(shí),所設(shè)定的最大處理器個(gè)數(shù) setup_max_cpus (from NR_CPUS),就會(huì)終止流程.如果該處理器目前屬於Present (也就是存在系統(tǒng)中),但尚未是Online的狀態(tài),就會(huì)呼叫函式cpu_up(in kernel/cpu.c)來啟動(dòng)該處理器. |
| sched_init_smp | 實(shí)例在kernel/sched.c中, (1), 呼叫g(shù)et_online_cpus,如果目前CPU Hotplug Active Write行程是自己,就直接返回.反之就把 cpu_hotplug.refcount加1 (表示多一個(gè)Reader) (2),取得Mutex Lock “sched_domains_mutex” (3),呼叫arch_init_sched_domains,設(shè)定scheduler domains與groups,參考Linux Documentation/scheduler/sched-domains.txt文件,一個(gè)Scheduling Domain會(huì)包含一個(gè)或多個(gè)CPU Groups,排程的Load-Balance就會(huì)根據(jù)Domain中的Groups來做調(diào)整. (4),釋放Mutex Lock “sched_domains_mutex” (5),呼叫put_online_cpus,如果目前CPU Hotplug Active Writer行程是自己,就直接返回.反之就把 cpu_hotplug.refcount減1,如果 cpu_hotplug.refcount減到為0,表示沒有其他Reader,此時(shí)如果有CPU Hotplug Active Writer行程在等待,就會(huì)透過wake_up_process喚醒該行程,以便讓等待中的Writer可以被執(zhí)行下去.(也可以參考_cpu_up中對(duì)於函式cpu_hotplug_begin的說明). (6)注冊(cè)CPU Notifier cpuset_cpu_active/cpuset_cpu_inactive/update_runtime? (7),呼叫set_cpus_allowed_ptr,透過這函式可以設(shè)定CPU bitmask,限定Task只能在特定的處理器上運(yùn)作.在這會(huì)用參數(shù)”non_isolated_cpus”,也就是會(huì)把init指定給non-isolated CPU. Linux Kernel可以在啟動(dòng)時(shí),透過Boot Parameters “isolcpus=“指定CPU編號(hào)或是范圍,讓這些處理器不被包含在Linux Kernel SMP balancing/scheduling算法內(nèi),可以在啟動(dòng)后指派給特定的Task運(yùn)作.而不在 “isolcpus=“ 指定范圍內(nèi)的處理器就算是non-isolated CPU. (8),呼叫sched_init_granularity,透過函式update_sysctl,讓sysctl_sched_min_granularity=normalized_sysctl_sched_min_granularity,sysctl_sched_latency=normalized_sysctl_sched_latency,sysctl_sched_wakeup_granularity=normalized_sysctl_sched_wakeup_granularit |
| do_basic_setup | 實(shí)例在init/main.c中, 1,diaousermodehelper_init (in kernel/kmod.c),產(chǎn)生khelper workqueue. 2,調(diào)用init_tmpfs (in mm/shmem.c),對(duì)VFS注冊(cè)Temp FileSystem. 3,呼叫driver_init (in drivers/base/init.c),初始化Linux Kernel Driver System Model. 4,呼叫init_irq_proc(in kernel/irq/proc.c),初始化 “/proc/irq”與其下的File Nodes. 5,呼叫do_ctors (in init/main.c),執(zhí)行位於Symbol __ctors_start 到 __ctors_end間屬於Section “.ctors” 的Constructor函式. 6,透過函式do_initcalls,執(zhí)行介於Symbol __early_initcall_end與__initcall_end之間的函式呼叫, |
| sys_open | 實(shí)例在fs/fcntl.c中,”SYSCALL_DEFINE1(dup, unsigned int, fildes)”,在這會(huì)連續(xù)執(zhí)行兩次sys_dup,復(fù)制兩個(gè)sys_open開啟/dev/console所產(chǎn)生的檔案描述0 (也就是會(huì)多生出兩個(gè)1與2),只是都對(duì)應(yīng)到”/dev/console”,我們?cè)赟ystem V streams下的Standard Stream一般而言會(huì)有如下的對(duì)應(yīng) 0:Standard input (stdin) 1:Standard output (stdout) 2:Standard error (stderr) (為方便大家參考,附上Wiki URL?http://en.wikipedia.org/wiki/Standard_streams?) |
| ramdisk_execute_command與prepare_namespace | 1,如果ramdisk_execute_command為0,就設(shè)定ramdisk_execute_command = “/init” 2,如果sys_access確認(rèn)檔案ramdisk_execute_command 失敗,就把ramdisk_execute_command 設(shè)定為0,然后呼叫prepare_namespace去mount root FileSystem. |
| integrity_load_keys | 至此我們初始化工作完成, 文件系統(tǒng)也已經(jīng)準(zhǔn)備好了,那么接下來加載 load integrity keys hook |
| load_default_modules | 加載基本的模塊 |
kernel_init分析
static int __ref kernel_init(void *unused) {int ret;kernel_init_freeable();/* need to finish all async __init code before freeing the memory */async_synchronize_full();free_initmem();mark_rodata_ro();system_state = SYSTEM_RUNNING;numa_default_policy();flush_delayed_fput();rcu_end_inkernel_boot();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);}/** We try each of these until one succeeds.** The Bourne shell can be used instead of init if we are* trying to recover a really broken machine.*/if (execute_command) {ret = run_init_process(execute_command);if (!ret)return 0;panic("Requested init %s failed (error %d).",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;panic("No working init found. Try passing init= option to kernel. ""See Linux Documentation/init.txt for guidance."); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
| kernel_init_freeable | 調(diào)用kernel_init_freeable完成初始化工作,準(zhǔn)備文件系統(tǒng),準(zhǔn)備模塊信息 |
| async_synchronize_full | 用以同步所有非同步函式呼叫的執(zhí)行,在這函數(shù)中會(huì)等待List async_running與async_pending都清空后,才會(huì)返回. Asynchronously called functions主要設(shè)計(jì)用來加速Linux Kernel開機(jī)的效率,避免在開機(jī)流程中等待硬體反應(yīng)延遲,影響到開機(jī)完成的時(shí)間 |
| free_initmem | free_initmem(in arch/arm/mm/init.c),釋放Linux Kernel介於__init_begin到 __init_end屬于init Section的函數(shù)的所有內(nèi)存.并會(huì)把Page個(gè)數(shù)加到變量totalram_pages中,作為后續(xù)Linux Kernel在配置記憶體時(shí)可以使用的Pages. (在這也可把TCM范圍(__tcm_start到__tcm_end)釋放加入到總Page中,但TCM比外部記憶體有效率,適合多媒體,中斷,…etc等對(duì)效能要求高的執(zhí)行需求,放到總Page中,成為可供一般目的配置的存儲(chǔ)范圍 |
| system_state | 設(shè)置運(yùn)行狀態(tài)SYSTEM_RUNNING |
| 加載init進(jìn)程,進(jìn)入用戶空間 | a,如果ramdisk_execute_command不為0,就執(zhí)行該命令成為init User Process. b,如果execute_command不為0,就執(zhí)行該命令成為init User Process. c,如果上述都不成立,就依序執(zhí)行如下指令 run_init_process(“/sbin/init”); run_init_process(“/etc/init”); run_init_process(“/bin/init”); run_init_process(“/bin/sh”); 也就是說會(huì)按照順序從/sbin/init, /etc/init, /bin/init 與 /bin/sh依序執(zhí)行第一個(gè) init User Process. 如果都找不到可以執(zhí)行的 init Process,就會(huì)進(jìn)入Kernel Panic.如下所示panic(“No init found. Try passing init= option to kernel. ”“See Linux Documentation/init.txt for guidance.”); |
總結(jié)
以上是生活随笔為你收集整理的Linux下1号进程的前世(kernel_init)今生(init进程)----Linux进程的管理与调度的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 下的 initrd r
- 下一篇: Linux内核调试方法总结之sysrq