一步一步學linux操作系統: 07 進程與程序運行

代碼實現用系統調用創建進程

開發套件安裝,yum -y groupinstall "Development Tools"

process.c文件,用一個函數封裝通用的創建進程的邏輯

#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 ();	// fork 創建新的進程
    if (child_pid != 0)	//根據返回 區分子父進程
        return child_pid;
    else {
        execvp (program, arg_list);	//execvp 運行一個新的程序
        abort ();
    }
}

createprocess.c 文件,調用上面這個函數

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

extern int create_process (char* program, char** arg_list);

int main ()
{
    char* arg_list[] = {
        "ls",
        "-l",
        "/etc/yum.repos.d/",
        NULL
    };	//參數
    create_process ("ls", arg_list);	//調用函數創建進程 執行ls
    return 0;
}

編譯與程序的二進制格式

文本文件通過編譯生成二進制文件

在 Linux 下面,二進制的程序也要有嚴格的格式,這個格式我們稱爲 ELF(Executeable and Linkable Format,可執行與可鏈接格式)。這個格式可以根據編譯的結果不同,分爲不同的格式。

如何從文本文件編譯成二進制格式的過程

圖片來自極客時間趣談linux操作系統
編譯上面的兩個文件

gcc -c -fPIC process.c
gcc -c -fPIC createprocess.c

在這裏插入圖片描述
在編譯的時候,先做預處理工作,例如將頭文件嵌入到正文中,將定義的宏展開,然後就是真正的編譯過程,最終編譯成爲.o 文件,這就是 ELF 的第一種類型,可重定位文件(Relocatable File)

可重定位文件格式

圖片來自極客時間趣談linux操作系統

ELF 文件的頭是用於描述整個文件的:
在內核中的定義分別爲 struct elf32_hdr 和 struct elf64_hdr。
在這裏插入圖片描述

接下來是一個一個的 section ,節:

.text:放編譯好的二進制可執行代碼
.data:已經初始化好的全局變量
.rodata:只讀數據,例如字符串常量、const 的變量
.bss:未初始化全局變量,運行時會置 0
.symtab:符號表,記錄的則是函數和變量
.strtab:字符串表、字符串常量和變量名

這裏只有全局變量,局部變量是放在棧裏面的,程序運行過程中隨時分配

節頭部表(Section Header Table):
用於保存節的元數據信息,這個表裏面,每一個 section 都有一項,代碼裏定義爲 struct elf32_shdr 和 struct elf64_shdr。在這裏插入圖片描述
.o 裏面的位置是不確定的,但是必須是可重新定位的,例如這裏的 create_process 函數,將來被誰調用,在哪裏調用都不清楚,有的 section,例如.rel.text, .rel.data 就與重定位有關,例如createprocess.o調用了create_process,create_process它在另外一個.o中,不知道被調函數位置只好在rel.text中標註,create_process這個函數需要重新定位

要想讓 create_process 這個函數作爲庫文件被重用,不能以.o 的形式存在,而是要形成庫文件,最簡單的類型是靜態鏈接庫.a 文件(Archives),僅僅將一系列對象文件(.o)(可以有多個.o)歸檔爲一個文件,使用命令 ar 創建。

ar cr libstaticprocess.a process.o

在這裏插入圖片描述

當有程序要使用這個靜態連接庫的時候,會將.o 文件提取出來,鏈接到程序中。

gcc -o staticcreateprocess createprocess.o -L. -lstaticprocess

在這裏插入圖片描述
-L 表示在當前目錄下找.a 文件,-lstaticprocess 會自動補全文件名,比如加前綴 lib,後綴.a,變成 libstaticprocess.a,找到這個.a 文件後,將裏面的 process.o 取出來,和 createprocess.o 做一個鏈接,形成二進制執行文件 staticcreateprocess

可執行文件

上面生成的二進制文件staticcreateprocess叫可執行文件,是 ELF 的第二種格式

圖片來自極客時間趣談linux操作系統
和.o 文件大致相似,還是分成一個個的 section,並且被節頭表描述。只不過這些 section 是多個.o 文件合併過的。在內核代碼裏面的定義爲 struct elf32_phdr 和 struct elf64_phdr
在這裏插入圖片描述
除了有對於段的描述之外,最重要的是 p_vaddr,這個是這個段加載到內存的虛擬地址。

在 ELF 頭裏面,有一項 e_entry,也是個虛擬地址,是這個程序運行的入口。見上面 struct elf32_hdr 和 struct elf64_hdr

動態鏈接庫

靜態鏈接庫一旦鏈接進去,代碼和變量的 section 都合併了,因而程序運行的時候,就不依賴於這個庫是否存在。

另一種,動態鏈接庫(Shared Libraries),不僅僅是一組對象文件的簡單歸檔,而是多個對象文件的重新組合,可被多個程序共享。

gcc -shared -fPIC -o libdynamicprocess.so process.o

在這裏插入圖片描述
當一個動態鏈接庫被鏈接到一個程序文件中的時候,最後的程序文件並不包括動態鏈接庫中的代碼,而僅僅包括對動態鏈接庫的引用,並且不保存動態鏈接庫的全路徑,僅僅保存動態鏈接庫的名稱。

gcc -o dynamiccreateprocess createprocess.o -L. -ldynamicprocess

在這裏插入圖片描述

當運行這個程序的時候,首先尋找動態鏈接庫,然後加載它。默認情況下,系統在 /lib 和 /usr/lib 文件夾下尋找動態鏈接庫。如果找不到就會報錯,可以設定 LD_LIBRARY_PATH 環境變量,在此環境變量指定的文件夾下尋找動態鏈接庫。


# export LD_LIBRARY_PATH=.
# ./dynamiccreateprocess
# total 40
-rw-r--r--. 1 root root 1572 Oct 24 18:38 CentOS-Base.repo
......

在這裏插入圖片描述

動態鏈接庫,就是 ELF 的第三種類型,共享對象文件(Shared Object)

多了一個.interp 的 Segment,這裏面是 ld-linux.so,這是動態鏈接器,運行時的鏈接動作都是它做的。

ELF 文件中還多了兩個 section,一個是.plt,過程鏈接表(Procedure Linkage Table,PLT),一個是.got.plt,全局偏移量表(Global Offset Table,GOT)

它們是怎麼工作使得程序運行的時候將 so 文件動態鏈接到進程空間?

以dynamiccreateprocess 這個程序要調用 libdynamicprocess.so 裏的 create_process 函數爲例

  1. 不知道這個函數在哪裏,所以就在 PLT 裏面建立一項 PLT[x]
    在二進制程序裏面,不直接調用 create_process 函數,而是調用 PLT[x]裏面的代理代碼,這個代理代碼會在運行的時候找真正的 create_process 函數。
  2. 去哪裏找代理代碼呢?
    用到了 GOT爲 create_process 函數創建一項 GOT[y]。這一項是運行時 create_process 函數在內存中真正的地址。
  3. GOT 怎麼知道函數在內存中真正的地址呢?
    對於 create_process 函數,GOT 一開始就會創建一項 GOT[y],裏面沒有真正的地址,通過回調PLT告訴PLT不知道真實地址,PLT 這個時候會轉而調用 PLT[0],也即第一項,PLT[0]轉而調用 GOT[2],這裏面是 ld-linux.so 的入口函數,這個函數會找到加載到內存中的 libdynamicprocess.so 裏面的 create_process 函數的地址,然後把這個地址放在 GOT[y]裏面。下次,PLT[x]的代理函數就能夠直接調用了。
    PLT就是用來放代理代碼的,也即stub代碼的,GOT是用來存放so對應的真實代碼的地址的

ld-linux.so雖然默認會被加載,但是也是一個so,所以會放在GOT裏面。要調用這個so裏面的代碼,也是需要從stub裏面統一調用進去的,所以要回到PLT去調用。

運行程序爲進程

知道了 ELF 這個格式,那怎麼把這個文件加載到內存裏面呢?

內核數據結構linux_binfmt,定義加載二進制文件的方法

struct linux_binfmt {
        struct list_head lh;
        struct module *module;
        int (*load_binary)(struct linux_binprm *);
        int (*load_shlib)(struct file *);
        int (*core_dump)(struct coredump_params *cprm);
        unsigned long min_coredump;     /* minimal dump size */
} __randomize_layout;

在這裏插入圖片描述

對於 ELF 文件格式,linux_binfmt有對應的實現


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,
};

在這裏插入圖片描述

load_elf_binary,06 系統調用可知 do_execve->do_execveat_common->exec_binprm->search_binary_handler

do_execve 通過

SYSCALL_DEFINE3(execve,
    const char __user *, filename,
    const char __user *const __user *, argv,
    const char __user *const __user *, envp)
{
  return do_execve(getname(filename), argv, envp);
}

調用

exec 這個系統調用最終調用的是load_elf_binary

exec 它是一組函數

  • 包含 p 的函數(execvp, execlp)會在 PATH 路徑下面尋找程序;
  • 不包含 p 的函數需要輸入程序的全路徑;
  • 包含 v 的函數(execv, execvp, execve)以數組的形式接收參數;
  • 包含 l 的函數(execl, execlp, execle)以列表的形式接收參數;
  • 包含 e 的函數(execve, execle)以數組的形式接收環境變量。

圖片來自極客時間趣談linux操作系統

進程樹

所有的進程都是從父進程 fork 過來的,其祖宗進程,這就是系統啓動的 init 進程。

圖片來自極客時間趣談linux操作系統
系統啓動之後,init 進程會啓動很多的 daemon 進程,爲系統運行提供服務,然後就是啓動 getty,讓用戶登錄,登錄後運行 shell,用戶啓動的進程都是通過 shell 運行的,從而形成了一棵進程樹。

1 號進程是 /sbin/init,在 centOS 7 裏面, ls 一下可以看到,這個進程是被軟鏈接到 systemd

在這裏插入圖片描述

ps -ef 命令查看當前系統啓動的進程
在這裏插入圖片描述
PID 1 的進程就是 init 進程 systemd,PID 2 的進程是內核線程 kthreadd,
用戶態的不帶中括號,內核態的帶中括號。
TTY 那一列,是問號的,說明不是前臺啓動的,一般都是後臺的服務。

一個進程從代碼到二進制到運行時的過程

圖片來自極客時間趣談linux操作系統

圖右邊的文件編譯過程,生成 so 文件和可執行文件,放在硬盤上
圖左邊的用戶態的進程 A 執行 fork,創建進程 B,在進程 B 的處理邏輯中,執行 exec 系列系統調用。
系統調用會通過 load_elf_binary 方法,將生成的可執行文件,加載到進程 B 的內存中執行。

參考資料:

趣談Linux操作系統(極客時間)鏈接:
http://gk.link/a/10iXZ
歡迎大家來一起交流學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章