1. 程式人生 > 實用技巧 >Linux — 程序管理

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

,通過節頭表來描述。這裡除了段描述之外,最重要的是p_vaddr——載入到記憶體的虛擬地址。

靜態庫特點:

  • 一旦連結,程式碼和變數的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下,使用者態不帶中括號,核心態帶中括號