Linux內(nèi)核深度解析-進程管理-讀書筆記

進程
Linux內(nèi)核把進程稱為任務(task),進程的虛擬地址空間分為用戶虛擬地址空間和內(nèi)核虛擬地址空間,所有進程共享內(nèi)核虛擬地址空間,每個進程有獨立的用戶虛擬地址空間。
進程有兩種特殊形式:沒有用戶虛擬地址空間的進程稱為內(nèi)核線程,共享用戶虛擬地址空間的進程稱為用戶線程,通常在不會引起混淆的情況下把用戶線程簡稱為線程。共享同一個用戶虛擬地址空間的所有用戶線程組成一個線程組。

命名空間
Linux的命名空間機制提供了一種資源隔離的解決方案。PID,IPC,Network等系統(tǒng)資源不再是全局性的,而是屬于特定的Namespace。Linux Namespace機制為實現(xiàn)基于容器的虛擬化技術(shù)提供了很好的基礎,LXC(Linux containers)就是利用這一特性實現(xiàn)了資源的隔離。不同Container內(nèi)的進程屬于不同的Namespace,彼此透明,互不干擾。
Namespace是對全局系統(tǒng)資源的一種封裝隔離,使得處于不同namespace的進程擁有獨立的全局系統(tǒng)資源,改變一個namespace中的系統(tǒng)資源只會影響當前namespace里的進程,對其他namespace中的進程沒有影響。

進程有以下標識符
(1)進程標識符(pid):進程所屬的進程號命名空間到根的每層命名空間,都會給進程分配一個標識符。
(2)線程組標識符(tgid):多個共享用戶虛擬地址空間的進程組成一個線程組,線程組中的主進程稱為組長,線程組標識符就是組長的進程標識符。當調(diào)用系統(tǒng)調(diào)用clone傳入標志CLONE_THREAD以創(chuàng)建新進程時,新進程和當前進程屬于一個線程組。進程描述符的成員tgid存放線程組標識符,成員group_leader指向組長的進程描述符。
(3)進程組標識符:多個進程可以組成一個進程組,進程組標識符是組長的進程標識符。進程可以使用系統(tǒng)調(diào)用setpgid創(chuàng)建或者加入一個進程組。會話和進程組被設計用來支持shell作業(yè)控制,shell為執(zhí)行單一命令或者管道的進程創(chuàng)建一個進程組。進程組簡化了向進程組的所有成員發(fā)送信號的操作。
(4)會話標識符(sid):多個進程組可以組成一個會話。當進程調(diào)用系統(tǒng)調(diào)用setsid的時候,創(chuàng)建一個新的會話,會話標識符是該進程的進程標識符。創(chuàng)建會話的進程是會話的首進程。

線程組結(jié)構(gòu):一個線程組的所有線程鏈接在一條線程鏈表上,頭節(jié)點是組長的成員thread_group,鏈表節(jié)點是線程的成員thread_group。線程的成員group_leader指向組長的進程描述符,成員tgid是線程組標識符,成員pid存放自己的進程標識符。

內(nèi)核線程創(chuàng)建過程:在Linux內(nèi)核中,新進程是從一個已經(jīng)存在的進程復制出來的。內(nèi)核使用靜態(tài)數(shù)據(jù)構(gòu)造出0號內(nèi)核線程,0號內(nèi)核線程分叉生成1號內(nèi)核線程和2號內(nèi)核線程(kthreadd線程)。1號內(nèi)核線程完成初始化以后裝載用戶程序,變成1號進程,其他進程都是1號進程或者它的子孫進程分叉生成的;其他內(nèi)核線程是kthreadd線程分叉生成的。

創(chuàng)建新的進程:
(1)fork(分叉):子進程是父進程的一個副本,采用了寫時復制的技術(shù)。
(3)clone(克?。嚎梢跃_地控制子進程和父進程共享哪些資源。這個系統(tǒng)調(diào)用的主要用處是可供pthread庫用來創(chuàng)建線程。
clone是功能最齊全的函數(shù),參數(shù)多,使用復雜,fork是clone的簡化函數(shù)。

創(chuàng)建子進程的流程(_do_fork)
(1)調(diào)用函數(shù)copy_process以創(chuàng)建新進程。
(2)如果參數(shù)clone_flags設置了標志CLONE_PARENT_SETTID,那么把新線程的進程標識符寫到參數(shù)parent_tidptr指定的位置。
(3)調(diào)用函數(shù)wake_up_new_task以喚醒新進程。
(4)如果是系統(tǒng)調(diào)用vfork,那么當前進程等待子進程裝載程序。

copy_process流程
1.調(diào)用 dup_task_struct 復制當前的 task_struct
2.檢查進程數(shù)是否超過限制
3.初始化自旋鎖、掛起信號、CPU 定時器等
4.調(diào)用 sched_fork 初始化進程數(shù)據(jù)結(jié)構(gòu),新進程設置優(yōu)先級、調(diào)度策略、運行CPU等參數(shù),并把進程狀態(tài)設置為 TASK_RUNNING
5.復制所有進程信息,包括文件系統(tǒng)、信號處理函數(shù)、信號、內(nèi)存管理等
6.調(diào)用 copy_thread_tls 初始化子進程內(nèi)核棧
7.為新進程分配并設置新的 pid

函數(shù)dup_task_struct:函數(shù)dup_task_struct為新進程的進程描述符分配內(nèi)存,把當前進程的進程描述符復制一份(及復制task_struct),為新進程分配內(nèi)核棧。

函數(shù)wake_up_new_task:負責喚醒剛剛創(chuàng)建的新進程,把新進程的狀態(tài)從TASK_NEW切換到TASK_RUNNING,為新進程選擇一個負載最輕的處理器。把新進程插入運行隊列。

裝載程序
如果程序的main函數(shù)被定義為下面的形式,參數(shù)指針數(shù)組和環(huán)境指針數(shù)組可以被程序的main函數(shù)訪問:
int main(int argc,char *argc[],char *envp[]);
最終都調(diào)用函數(shù)do_execveat_common
1.調(diào)用open_exec()查找并打開二進制文件
2.調(diào)用sched_exec()找到最小負載的CPU,用來執(zhí)行該二進制文件
3.調(diào)用bprm_mm_init()創(chuàng)建進程的內(nèi)存地址空間,為新程序初始化內(nèi)存管理.并調(diào)用init_new_context()檢查當前進程是否使用4.自定義的局部描述符表;如果是,那么分配和準備一個新的LDT
5.調(diào)用prepare_binprm()檢查該二進制文件的可執(zhí)行權(quán)限;最后,kernel_read()讀取二進制文件的頭128字節(jié)(這些字節(jié)用于識別二進制文件的格式及其他信息,后續(xù)會使用到)
6.調(diào)用copy_strings_kernel()從內(nèi)核空間獲取二進制文件的路徑名稱
7.調(diào)用copy_string()從用戶空間拷貝環(huán)境變量和命令行參數(shù)
8.至此,二進制文件已經(jīng)被打開,struct linux_binprm結(jié)構(gòu)體中也記錄了重要信息, 內(nèi)核開始調(diào)用exec_binprm執(zhí)行可執(zhí)行程序
釋放linux_binprm數(shù)據(jù)結(jié)構(gòu),返回從該文件可執(zhí)行格式的load_binary中獲得的代碼

每種二進制格式都表示為下面的數(shù)據(jù)結(jié)構(gòu)


binfmts.png

ELF文件:ELF(Executable and Linkable Format)是可執(zhí)行與可鏈接格式,主要有以下4種類型。
? 目標文件(object file),也稱為可重定位文件(relocatable file),擴展名是“.o”,多個目標文件可以鏈接生成可執(zhí)行文件或者共享庫。
? 可執(zhí)行文件(executable file)。
? 共享庫(shared object file),擴展名是“.so”。
? 核心轉(zhuǎn)儲文件(core dump file)。

只有ELF首部的位置是固定的,其余各部分的位置和大小由ELF首部的成員決定。


elf.png

程序首部表就是我們所說的段表(segment table),段(segment)是從運行的角度描述,節(jié)(section)是從鏈接的角度描述,一個段包含一個或多個節(jié)。在不會混淆的情況下,我們通常把節(jié)稱為段,例如代碼段(text section),不稱為代碼節(jié)。

加載elf文件流程load_elf_binary
1.填充并且檢查目標程序ELF頭部
2.load_elf_phdrs加載目標程序的程序頭表
3.如果需要動態(tài)鏈接, 則尋找和處理解釋器段
4.檢查并讀取解釋器的程序表頭
5.flush_old_exec終止線程組中的其他線程
6.setup_new_exec設置內(nèi)存映射布局
7.setup_arg_pages更新用戶棧的標志位和訪問位,把用戶棧移到最終位置,并且擴大用戶棧
8.把所有可加載段映射到進程的虛擬地址空間
9.setbrk把未初始化數(shù)據(jù)段映射到進程的用戶虛擬地址空間,并設置堆的起始地址
10.得到程序的入口
11.create_elf_tables依次把傳遞ELF解釋器信息的輔助向量、環(huán)境指針數(shù)組envp、參數(shù)指針數(shù)組argv和參數(shù)個數(shù)argc壓到進程用戶棧
12.start_thread開始程序

裝載腳本程序
腳本程序的主要特征是:前兩字節(jié)是“#!”,后面是解釋程序的名稱和參數(shù)。解釋程序用來解釋執(zhí)行腳本程序。
源文件“fs/binfmt_script.c”定義的函數(shù)load_script負責裝載腳本程序,主要步驟如下。

load_script.png

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容