Linux — 程序管理
程序建立
程序通過fork()
建立的大致過程:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> extern int create_process(char* program, char** arg_list); int create_process(char* program, char** arg_list){ pid_t child_pid; child_pid = fork(); if(child_pid !=0 ){ return child_pid; }else{ execvp(program, arg_list); abort(); } }
概覽圖:
編譯知識
靜態庫
這裡額外補充一些編譯相關的內容。一個原始碼檔案要變成可執行的程式,需要經過編譯、連結。
這裡編譯出的.o
檔案,就是ELF的第一種型別,可重定位檔案,格式如下:
-
.text
:存放編譯好的二進位制可執行程式碼 -
.data
:已經初始化好的全域性變數 -
.rodata
:只讀資料,例如字元創常量、const常量 -
.bss
:未初始化全域性變數,執行時會置0 -
.symtab
:符號表,記錄的是函式和變數 -
.strtab
:字串表、字串常量和變數名
在程式中我們提到過函式棧,區域性變數是放在棧
裡的,在執行期隨時分配、隨時釋放。這裡討論的是檔案,還沒到執行階段。
如要將函式作為庫檔案被重用,不能以.o
的形式存在,而是要形成庫檔案,最簡單的型別是靜態連結庫.a
檔案。
ar cr libstaticprocess.a process.o
# 形成二進位制執行檔案staticcreateprocess
gcc -o staticcreateprocess createprocess.o -L. -lstaticprocess
上述第二條命令連結時,createprocess.o
呼叫了create_process
函式,但不知道位置,通過將process.o
合併進來,就可以確定位置了。這裡形成了ELF的第二種格式:
和.o
相似,檔案被分為多個section
靜態庫特點:
-
一旦連結,程式碼和變數的section都合併在一起。當程式執行後,就不依賴這個庫是否存在
-
但如果相同的程式碼段被多個程式使用時,在記憶體裡就存在多份
-
當靜態庫更新了,需重新編譯二進位制執行檔案以應用更新
動態庫
當動態連結庫被連結到一個程式檔案時,最後的程式檔案並不包括動態連結庫中的程式碼,而僅僅包括對動態連結庫的引用,並且不儲存動態連結庫的全路徑,僅儲存其名稱。
gcc -shared -fPIC -o libdynamicprocess.so process.o
gcc -o dynamiccreateprocess createprocess.o -L. -ldynamicprocess
執行此程式時,先尋找動態連結庫,再進行載入。預設,系統在/lib
和/usr/lib
路徑下尋找,找不到則報錯。可以通過設定LD_LIBRARY_PATH
環境變數,指定動態連結庫的路勁。
動態連結庫,就是ELF的第三種類型,共享物件檔案。
-
多個
.interp
的Segment,裡面是ld-linux.so
動態聯結器,執行連結動作 -
多個
.plt
和.got.plt
,分別是 過程連結表(PLT) 和 全域性偏移量表(GOT) 。
接下來,我們來看下程式執行時,是如何將so檔案動態連結到程序空間的?
-
dynamiccreateprocess
程式呼叫libdynamicprocess.so
裡的create_process
函式時,是執行時才去找的,編譯壓根不知道在哪,因此在PLT裡建立一個PLT[x]——在二進位制程式裡,不直接呼叫函式,而是呼叫PLT[x]裡的代理程式碼,它會在執行時找到真正的create_process
函式。 -
去哪找?去GOT裡——它會為
create_process
函式建立GOT[y],對應執行時create_process
函式在記憶體中真正的地址。 -
GOT又是咋知道的?對於
create_process
函式,GOT在開始時會建立GOT[y],但開始這裡沒有真正的地址,因為不知道...,它又回撥PLT,告訴他:你裡面的代理程式碼來找我要create_process
的真實地址,我不知道,你自己想想。 -
PLT只能去呼叫PLT[0],PLT[0]去呼叫GOT[2],這裡面儲存
ld-linux.so
入口函式,它去找到載入到記憶體中的libdynamicprocess.so
裡的create_process
函式地址,然後把這個地址放到GOT[y]中,以供下次PLT[x]呼叫。
執行程式為程序
ELF二進位制檔案是如何載入到記憶體裡的?
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,
};
具體呼叫鏈:do_execve->do_execveat_common->exec_binprm->search_binary_handler。
在系統呼叫時,exec
最終呼叫的是load_elf_binary
.
程序樹
所有程序的祖宗程序,就是系統啟動時的init
程序。init程序會啟動很多daemon程序,為系統執行提供服務。然後啟動getty
,讓使用者登入,登入後執行shell。
-
1號程序是
/sbin/init
,所有使用者態程序的祖先 -
2號程序是核心執行緒
kthreadd
,所有核心態的祖先 -
在
ps -ef
下,使用者態不帶中括號,核心態帶中括號