2011年12月25日 星期日

從 Linux Kernel 出發看!探討 Process 組成結構!

Standard
對所有電腦使用者而言,執行一支應用程式就像吃蛋糕一樣簡單。對於程式開發者而言,寫支 Hello World Program 並跑起來,也是三十秒內可以完成的事。但是,這樣容易的動作,其實底層有著複雜的機制,否則短短十行程式碼的 Hello World 能夠動起來,真的是奇跡了。想要知曉程式是怎麼被作業系統執行,要追溯到 Process 的機制,在研究過 Linux Kernel 的 Process 相關機制後,一切就將明朗。

註:在本篇文章內,都將會假設 binary program 已經被作業系統解析並放到記憶體執行,說明只著重於 Process 的部份 。雖然 Process 與 binary program 和其載體息息相關,但本文不會討論 ELF 的細節,有興趣的讀者可以自己去尋找相關文件閱讀。

若你是程式開發者,對『Process(程序)』一詞應該不陌生。嚴格來說, Process 就是處於執行狀態的程式,如果參考一些原文書或英文文獻,它們也許會這樣定義:『Process is a program in execution』。所以我們可以認定,Process 的組成,是擁有著『程式執行檔的 binary code + 記錄資料的記憶體』。

本文將 Process 分成內外兩個角度來探討:
  1. 從 Process 內部,程式執行的觀點 
  2. 從 Process 外部,也就是作業系統的觀點 

程式執行的觀點

單純以 Process 內部的角度來看,Process 就是一般的程式碼被放到記憶體執行。曾於舊文『Linux 下程序的記憶體映射』略為提及,一支程式擁有著數個 segments(區段),並仰賴著這些 segment 來持續運作。大致上來說,每個 segment 有不同的用途:
  • Code Segment - 存放主要程式
  • Data Segment - 存放已被初始化並賦予值的全域變數
  • BSS Segment - 紀錄尚未被賦予值的全域變數
  • Stack Segment(Stack/Heap) - 紀錄 Process 在執行時動態註冊的變數包括 function 中的 local variable

對程式本身來說,會動態並隨機使用的是 Stack Segment,程式向作業系統(OS) 要記憶體空間後,就可以在 Stack Segment 讀寫該記憶體空間。而實際上 OS 底層的行為,是配置記憶體, 然後映射到 Stack Segment 供程式使用。

作業系統的觀點

與程式觀點相呼應,作業系統有記憶體管理機制,其建立虛擬記憶體空間,將程式對映進去該空間後,開始執行。作業系統管理著 Process 所擁有的 Segments,為每個 Segment 都配置了『虛擬記憶體區域(VMA, Virtual Memory Area)』。

在 Linux 之下,我們可以透過 cat /proc/<Process ID>/maps 的方式去取得一個 Process 所用到的記憶體,以下是觀察系統上的 bash(PID=18165):
$ cat /proc/18165/maps
08048000-0810a000 r-xp 00000000 08:01 1851399    /bin/bash
0810a000-0810f000 rw-p 000c1000 08:01 1851399    /bin/bash
0810f000-08114000 rw-p 00000000 00:00 0
08752000-08a46000 rw-p 00000000 00:00 0          [heap]
b737b000-b7396000 r--p 00000000 08:01 28598806   /usr/share/locale/zh_TW/LC_MESSAGES/libc.mo
b7396000-b73a0000 r-xp 00000000 08:01 12755293   /lib/i386-linux-gnu/i686/cmov/libnss_files-2.13.so
b73a0000-b73a1000 r--p 00009000 08:01 12755293   /lib/i386-linux-gnu/i686/cmov/libnss_files-2.13.so
b73a1000-b73a2000 rw-p 0000a000 08:01 12755293   /lib/i386-linux-gnu/i686/cmov/libnss_files-2.13.so
b73a2000-b73b5000 r-xp 00000000 08:01 12755189   /lib/i386-linux-gnu/i686/cmov/libnsl-2.13.so
b73b5000-b73b6000 r--p 00012000 08:01 12755189   /lib/i386-linux-gnu/i686/cmov/libnsl-2.13.so
b73b6000-b73b7000 rw-p 00013000 08:01 12755189   /lib/i386-linux-gnu/i686/cmov/libnsl-2.13.so
b73b7000-b73b9000 rw-p 00000000 00:00 0
b73ca000-b73cc000 r--p 00000000 08:01 28600275   /usr/share/locale/zh_TW/LC_MESSAGES/bash.mo
b73cc000-b73d3000 r--s 00000000 08:01 28666899   /usr/lib/i386-linux-gnu/gconv/gconv-modules.cache
b73d3000-b754a000 r--p 00000000 08:01 28615144   /usr/lib/locale/locale-archive
b754a000-b754b000 rw-p 00000000 00:00 0
b754b000-b7568000 r-xp 00000000 08:01 12755040   /lib/i386-linux-gnu/libtinfo.so.5.9
b7568000-b756a000 r--p 0001c000 08:01 12755040   /lib/i386-linux-gnu/libtinfo.so.5.9
b756a000-b756b000 rw-p 0001e000 08:01 12755040   /lib/i386-linux-gnu/libtinfo.so.5.9
b756b000-b76be000 r-xp 00000000 08:01 12755289   /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b76be000-b76bf000 ---p 00153000 08:01 12755289   /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b76bf000-b76c1000 r--p 00153000 08:01 12755289   /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b76c1000-b76c2000 rw-p 00155000 08:01 12755289   /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b76c2000-b76c6000 rw-p 00000000 00:00 0
b76c6000-b76c8000 r-xp 00000000 08:01 12755049   /lib/i386-linux-gnu/i686/cmov/libdl-2.13.so
b76c8000-b76c9000 r--p 00001000 08:01 12755049   /lib/i386-linux-gnu/i686/cmov/libdl-2.13.so
b76c9000-b76ca000 rw-p 00002000 08:01 12755049   /lib/i386-linux-gnu/i686/cmov/libdl-2.13.so
b76ca000-b76ec000 r-xp 00000000 08:01 12755034   /lib/i386-linux-gnu/libncurses.so.5.9
b76ec000-b76ed000 r--p 00021000 08:01 12755034   /lib/i386-linux-gnu/libncurses.so.5.9
b76ed000-b76ee000 rw-p 00022000 08:01 12755034   /lib/i386-linux-gnu/libncurses.so.5.9
b76f4000-b76fd000 r-xp 00000000 08:01 12754995   /lib/i386-linux-gnu/i686/cmov/libnss_nis-2.13.so
b76fd000-b76fe000 r--p 00008000 08:01 12754995   /lib/i386-linux-gnu/i686/cmov/libnss_nis-2.13.so
b76fe000-b76ff000 rw-p 00009000 08:01 12754995   /lib/i386-linux-gnu/i686/cmov/libnss_nis-2.13.so
b76ff000-b7705000 r-xp 00000000 08:01 12755103   /lib/i386-linux-gnu/i686/cmov/libnss_compat-2.13.so
b7705000-b7706000 r--p 00005000 08:01 12755103   /lib/i386-linux-gnu/i686/cmov/libnss_compat-2.13.so
b7706000-b7707000 rw-p 00006000 08:01 12755103   /lib/i386-linux-gnu/i686/cmov/libnss_compat-2.13.so
b7707000-b7708000 r--p 00176000 08:01 28615144   /usr/lib/locale/locale-archive
b7708000-b770a000 rw-p 00000000 00:00 0
b770a000-b770b000 r-xp 00000000 00:00 0          [vdso]
b770b000-b7726000 r-xp 00000000 08:01 12755299   /lib/i386-linux-gnu/ld-2.13.so
b7726000-b7727000 r--p 0001b000 08:01 12755299   /lib/i386-linux-gnu/ld-2.13.so
b7727000-b7728000 rw-p 0001c000 08:01 12755299   /lib/i386-linux-gnu/ld-2.13.so
bf916000-bf937000 rw-p 00000000 00:00 0          [stack]

大致上來說可以看到有這些 VMA 的存在(已省略會偏離主題的 VMA):
08048000-0810a000    Code
0810a000-0810f000    Data
08752000-08a46000    Heap
bf916000-bf937000    Stack

對於作業系統而言,管理這些記憶體和程式狀態,才是真正的重點。所以,就作業系統核心的角度來看 Process,可以從排程的程式來切入瞭解。翻閱 Linux Kernel 原始程式碼的檔案『include/linux/sched.h』,Process 的狀態結構被定義在 struct task_struct 之中,而其中的 mm 就是記錄著該 Process 的記憶體配置資訊,我們可以從中得到 Process 當前所擁有的全部 VMA 和 Segments 所在位址:
struct task_struct {
        ...
        struct mm_struct *mm, *active_mm;
        ...
};
struct mm_struct {
        struct vm_area_struct * mmap;           /* list of VMAs */
        ...
        unsigned long start_code, end_code, start_data, end_data;
        unsigned long start_brk, brk, start_stack;
        ...
};

另外,從『include/linux/mm.h』可以找到 VMA 的串列資料結構定義:
struct vm_area_struct {
        struct mm_struct * vm_mm;       /* The address space we belong to. */
        unsigned long vm_start;         /* Our start address within vm_mm. */
        unsigned long vm_end;           /* The first byte after our end address
                                           within vm_mm. */
        ....
        /* linked list of VM areas per task, sorted by address */
        struct vm_area_struct *vm_next;
        ....
}

後記

若要繼續追查下去,還有動態連結的部份要探討,會牽涉到更多機制,由於篇幅有限,下次有時間再來寫完。