從 Linux Kernel 出發看!探討 Process 組成結構!
對所有電腦使用者而言,執行一支應用程式就像吃蛋糕一樣簡單。對於程式開發者而言,寫支 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 分成內外兩個角度來探討:
程式執行的觀點
單純以 Process 內部的角度來看,Process 就是一般的程式碼被放到記憶體執行。曾於舊文『Linux 下程序的記憶體映射』略為提及,一支程式擁有著數個 segments(區段),並仰賴著這些 segment 來持續運作。大致上來說,每個 segment 有不同的用途:
對程式本身來說,會動態並隨機使用的是 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):
大致上來說可以看到有這些 VMA 的存在(已省略會偏離主題的 VMA):
對於作業系統而言,管理這些記憶體和程式狀態,才是真正的重點。所以,就作業系統核心的角度來看 Process,可以從排程的程式來切入瞭解。翻閱 Linux Kernel 原始程式碼的檔案『include/linux/sched.h』,Process 的狀態結構被定義在 struct task_struct 之中,而其中的 mm 就是記錄著該 Process 的記憶體配置資訊,我們可以從中得到 Process 當前所擁有的全部 VMA 和 Segments 所在位址:
另外,從『include/linux/mm.h』可以找到 VMA 的串列資料結構定義:
後記
若要繼續追查下去,還有動態連結的部份要探討,會牽涉到更多機制,由於篇幅有限,下次有時間再來寫完。
註:在本篇文章內,都將會假設 binary program 已經被作業系統解析並放到記憶體執行,說明只著重於 Process 的部份 。雖然 Process 與 binary program 和其載體息息相關,但本文不會討論 ELF 的細節,有興趣的讀者可以自己去尋找相關文件閱讀。
若你是程式開發者,對『Process(程序)』一詞應該不陌生。嚴格來說, Process 就是處於執行狀態的程式,如果參考一些原文書或英文文獻,它們也許會這樣定義:『Process is a program in execution』。所以我們可以認定,Process 的組成,是擁有著『程式執行檔的 binary code + 記錄資料的記憶體』。
本文將 Process 分成內外兩個角度來探討:
- 從 Process 內部,程式執行的觀點
- 從 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; .... }
後記
若要繼續追查下去,還有動態連結的部份要探討,會牽涉到更多機制,由於篇幅有限,下次有時間再來寫完。
留言
張貼留言