進程
Linux內(nèi)核把進程稱為任務(wù)(task)稳其,進程的虛擬地址空間分為用戶虛擬地址空間和內(nèi)核虛擬地址空間,所有進程共享內(nèi)核虛擬地址空間炸卑,每個進程有獨立的用戶虛擬地址空間既鞠。
進程有兩種特殊形式:沒有用戶虛擬地址空間的進程稱為內(nèi)核線程,共享用戶虛擬地址空間的進程稱為用戶線程盖文,通常在不會引起混淆的情況下把用戶線程簡稱為線程嘱蛋。共享同一個用戶虛擬地址空間的所有用戶線程組成一個線程組。
命名空間
Linux的命名空間機制提供了一種資源隔離的解決方案五续。PID,IPC,Network等系統(tǒng)資源不再是全局性的洒敏,而是屬于特定的Namespace。Linux Namespace機制為實現(xiàn)基于容器的虛擬化技術(shù)提供了很好的基礎(chǔ)疙驾,LXC(Linux containers)就是利用這一特性實現(xiàn)了資源的隔離凶伙。不同Container內(nèi)的進程屬于不同的Namespace,彼此透明它碎,互不干擾函荣。
Namespace是對全局系統(tǒng)資源的一種封裝隔離,使得處于不同namespace的進程擁有獨立的全局系統(tǒng)資源扳肛,改變一個namespace中的系統(tǒng)資源只會影響當(dāng)前namespace里的進程偏竟,對其他namespace中的進程沒有影響。
進程有以下標識符
(1)進程標識符(pid):進程所屬的進程號命名空間到根的每層命名空間敞峭,都會給進程分配一個標識符踊谋。
(2)線程組標識符(tgid):多個共享用戶虛擬地址空間的進程組成一個線程組,線程組中的主進程稱為組長旋讹,線程組標識符就是組長的進程標識符殖蚕。當(dāng)調(diào)用系統(tǒng)調(diào)用clone傳入標志CLONE_THREAD以創(chuàng)建新進程時,新進程和當(dāng)前進程屬于一個線程組沉迹。進程描述符的成員tgid存放線程組標識符睦疫,成員group_leader指向組長的進程描述符。
(3)進程組標識符:多個進程可以組成一個進程組鞭呕,進程組標識符是組長的進程標識符蛤育。進程可以使用系統(tǒng)調(diào)用setpgid創(chuàng)建或者加入一個進程組。會話和進程組被設(shè)計用來支持shell作業(yè)控制,shell為執(zhí)行單一命令或者管道的進程創(chuàng)建一個進程組瓦糕。進程組簡化了向進程組的所有成員發(fā)送信號的操作底洗。
(4)會話標識符(sid):多個進程組可以組成一個會話。當(dāng)進程調(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)存在的進程復(fù)制出來的。內(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(分叉):子進程是父進程的一個副本肠缔,采用了寫時復(fù)制的技術(shù)夏跷。
(3)clone(克隆):可以精確地控制子進程和父進程共享哪些資源明未。這個系統(tǒng)調(diào)用的主要用處是可供pthread庫用來創(chuàng)建線程槽华。
clone是功能最齊全的函數(shù),參數(shù)多趟妥,使用復(fù)雜猫态,fork是clone的簡化函數(shù)。
創(chuàng)建子進程的流程(_do_fork):
(1)調(diào)用函數(shù)copy_process以創(chuàng)建新進程披摄。
(2)如果參數(shù)clone_flags設(shè)置了標志CLONE_PARENT_SETTID亲雪,那么把新線程的進程標識符寫到參數(shù)parent_tidptr指定的位置。
(3)調(diào)用函數(shù)wake_up_new_task以喚醒新進程疚膊。
(4)如果是系統(tǒng)調(diào)用vfork义辕,那么當(dāng)前進程等待子進程裝載程序。
copy_process流程:
1.調(diào)用 dup_task_struct 復(fù)制當(dāng)前的 task_struct
2.檢查進程數(shù)是否超過限制
3.初始化自旋鎖寓盗、掛起信號灌砖、CPU 定時器等
4.調(diào)用 sched_fork 初始化進程數(shù)據(jù)結(jié)構(gòu)璧函,新進程設(shè)置優(yōu)先級、調(diào)度策略基显、運行CPU等參數(shù)蘸吓,并把進程狀態(tài)設(shè)置為 TASK_RUNNING
5.復(fù)制所有進程信息,包括文件系統(tǒng)续镇、信號處理函數(shù)美澳、信號销部、內(nèi)存管理等
6.調(diào)用 copy_thread_tls 初始化子進程內(nèi)核棧
7.為新進程分配并設(shè)置新的 pid
函數(shù)dup_task_struct:函數(shù)dup_task_struct為新進程的進程描述符分配內(nèi)存摸航,把當(dāng)前進程的進程描述符復(fù)制一份(及復(fù)制task_struct),為新進程分配內(nèi)核棧舅桩。
函數(shù)wake_up_new_task:負責(zé)喚醒剛剛創(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()檢查當(dāng)前進程是否使用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)
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首部的成員決定肥败。
程序首部表就是我們所說的段表(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設(shè)置內(nèi)存映射布局
7.setup_arg_pages更新用戶棧的標志位和訪問位央勒,把用戶棧移到最終位置不见,并且擴大用戶棧
8.把所有可加載段映射到進程的虛擬地址空間
9.setbrk把未初始化數(shù)據(jù)段映射到進程的用戶虛擬地址空間,并設(shè)置堆的起始地址
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負責(zé)裝載腳本程序,主要步驟如下瑞你。