下面是對俞甲子等著的書籍《程式員的自我修養》第6章主要內容的整理,
可執行檔案的裝載與行程
6.1 行程虛擬地址空間
作者介紹了程式和行程的區別,程式是靜態的概念,是一些預先編譯好的指令和資料集合的檔案,比如說Linux中可執行的ELF檔案,Windows的PE檔案,
每個程式運行起來以后都有自己獨立的虛擬地址空間(Virtual Address Space),虛擬地址空間由CPU的位數決定,硬體的尋址空間大小,比如32位平臺的硬體決定的虛擬地址空間最大范圍是0到2^32-1,即0x00000000~0xFFFFFFFF,一共4GB,64位的硬體平臺可支持的虛擬地址空間達到17 179 869 184 GB,一般來說,在32位平臺,C語言指標是4個位元組,64位平臺指標是8位元組,
32位平臺下的4GB虛擬地址空間被分為兩部分,一部分給用戶行程,一部分給作業系統,Linux 4GB虛擬空間3GB給用戶行程,1GB給作業系統,
3GB的虛擬地址空間對于一些大型程式確實不夠用,想要擴充記憶體,最直接的方法就是升級64位處理器,把虛擬地址擴展到17 179 869 184 GB,但是32位CPU只能使用4G的虛擬地址空間,但是可以使用超過4GB的物理地址空間,如果32位CPU的地址線使用36位,修改頁映射方式,可以訪問高達64GB的物理記憶體,Intel把這種擴展方式叫做PAE(Physical Address Extension)技術,
與之對應,在作業系統層,提供一個視窗映射的方法,把額外的記憶體映射到行程地址空間來,比如把0x10000000~0x20000000這一段256MB的虛擬地址空間用來做視窗,可以從高于4GB的物理地址空間申請多個256MB的物理空間,比如有A、B、C這三個256MB的物理地址空間,作業系統根據應用程式的記憶體空間申請,將0x10000000~0x20000000的虛擬地址空間映射到A、B、C其中一個,并且根據程式的要求,在A、B、C直接重復操作,Windows下這種訪問記憶體的方法叫做AWE(Address Windowing Extensions),
Intel的PAE和Windows的AWE是位于Wintel架構兩個不同層對虛擬地址空間擴展的的支持,一個是硬體支持,一個是作業系統支持,
6.2 裝載的方式
程式執行時所需要的指令和資料必須在記憶體中才能夠正常運行,最簡單的辦法是將程式運行所需要的指令和資料全部裝入記憶體中,這就是靜態裝入的辦法,但是很多情況下程式所需要的記憶體數量大于物理記憶體的數量,要解決程式所需要記憶體的數量日益增長與CPU記憶體由于昂貴且稀缺的矛盾,研究發現,程式運行時是有區域性原理,可以將程式最常用的部分駐留在記憶體中,而將不太常用的資料放在磁盤里,通過動態裝入記憶體,
動態裝入的思想是程式用到哪個模塊就將哪個模塊裝入記憶體,如果不用就暫時不裝如,存放到磁盤中,
有兩種典型的動態裝載方法:覆寫裝入(Overlay)和頁映射(Paging)
6.2.1 覆寫裝入
現在幾乎被淘汰,這里不介紹了,
6.2.2 頁映射
頁映射是將記憶體和所有磁盤中的資料和指令按照“頁”為單位劃分成若干個頁,以后所有的裝載和操作的單位就是頁,硬體規定的頁大小有4096位元組、8192位元組、2M位元組、4M位元組等,程式運行需要哪個頁就將該頁調入物理記憶體,但是將新的頁調入記憶體,有時候需要替換物理記憶體中已經占用的頁,裝載器(裝載管理器)必須做出抉擇,有很多中演算法可以用來決定選擇哪個頁作為替換頁,比如FIFO(選擇最先裝入記憶體的頁,先進先出演算法),比如LUR(選擇最少被訪問到的頁,最少使用演算法),
6.3 從作業系統角度看可執行檔案的裝載
如果程式直接操作物理地址,那么每次頁被裝入時,都需要重新定位,在虛擬記憶體機制中,現在的硬體MMU(記憶體管理單元)都提供了地址轉換和頁映射機制,從作業系統的角度看,一個行程最關鍵的特征是它擁有獨立的虛擬地址空間,一個程式的執行同時伴隨著新行程的創建,在虛擬記憶體的機器中,行程的創建程序分三步:
- 創建一個獨立的虛擬地址空間
虛擬地址空間是一組頁映射函式,將虛擬空間的各個頁映射到相應的物理空間,創建虛擬地址空間實際上不是創建空間,而是創建映射函式所需要的資料結構,在i386的Linux下,創建虛擬地址空間實際上是分配一個頁目錄,甚至不設定頁映射關系,這些映射關系要等到程式發生頁錯誤時再進行設定,
- 讀取可執行檔案頭,并建立虛擬地址空間與可執行檔案的映射關系
上面那一步是虛擬空間到物理記憶體的映射,接下來就要將虛擬地址空間與可執行檔案進行映射,當程式發生頁錯誤時,作業系統將從物理記憶體中分配一個物理頁,然后將該缺頁從磁盤中讀到物理記憶體中,再設定缺頁的虛擬頁與物理頁的映射關系,
雖然是在程式出現缺頁錯誤時才干這事,但是作業系統在捕獲缺頁錯誤時,它已經知道程式當前所需要的頁在可執行檔案中的哪個位置,即,需要事先建立虛擬地址空間與可執行檔案的映射關系,虛擬地址空間與物理地址空間的映射是在運行時出現缺頁時建立,從這個解釋,可以明白可執行檔案為啥又叫做映像檔案(Image),應為可執行檔案實際上是被作業系統映射到了虛擬地址空間,
可執行檔案與執行該可執行檔案的行程虛擬空間的映射,是作業系統通過內部的資料結構完成的,Linux中的行程虛擬空間中一個段叫做虛擬記憶體區域(VMA, Virtual Memory Area),Windows中行程虛擬空間中的一個段叫做虛擬段(Virtual Section),
- 將CPU的指令暫存器設定成可執行檔案的入口地址,啟動程式
作業系統通過設定CPU的指令暫存器,將控制權交給行程(CPU),此行程開始執行,
第二步說到”缺頁錯誤“,什么是頁錯誤?
作業系統通過可執行檔案的頭部資訊建立可知行檔案和行程虛擬記憶體之間的映射關系,
比如說作業系統告訴CPU程式的入口地址是0x08048000,CPU開始打算執行這個地址的指令時發現頁面0x08048000~0x08049000是個空頁面,于是CPU認為這是個頁錯誤(Page Fault),CPU將控制權交給作業系統,作業系統有專門的頁錯誤處理例程來處理這種情況,這個時候就需要用到第二步建立的虛擬地址空間與可執行檔案的映射關系資料結構了,作業系統查詢這個資料結構,然后找到頁面所在的VMA,計算出相應頁面在可執行檔案中的偏移,然后在物理記憶體中分配一個物理頁面,將行程虛擬地址空間該虛擬頁(VMA)與分配的物理頁之間建立映射關系,一旦建立了虛擬地址空間的虛擬頁與物理記憶體的物理頁的映射關系,物理記憶體中的空頁面已經有行程即將執行的指令和資料了,此時作業系統把控制權還給行程(CPU),行程(CPU)從剛才出現錯誤的位置重新開始執行,
隨著程式的執行,頁錯誤不斷產生,作業系統也會分配相應的物理頁來滿足行程執行的需求,有可能行程所需要的記憶體會超過可用的物理記憶體數量,特別是多個行程同時執行時,這時候作業系統需要精心設計,有時候需要決定將分配給行程的物理頁暫時回收,這些”高級操作“就涉及到作業系統的虛擬記憶體管理了,
6.4 行程虛存空間分布
Linux上的ELF檔案是一個程式,ELF檔案在構建時有鏈接視圖,也即是將多個目標檔案拼裝成一個可執行可定位二進制ELF時,將各個目標檔案相同的section放到一起,
ELF檔案也包含了執行視圖,可執行檔案實際上不止代碼段,還有資料段、BSS段等,映射到虛擬地址空間的往往不止一個段,ELF檔案被映射時是以系統的頁長度為單位,也就是說站在作業系統的角度,作業系統實際上不關心可執行檔案各個段的實際內容,因為這些可執行二進制指令,最終是由硬體CPU去解釋去執行,作業系統只需準備好指令和資料即可,它只關心一些與裝載相關的問題,最重要的是段的權限(可讀,可寫,可執行),對作業系統而言,段的權限只有幾種組合,基本上是三種:
- 以代碼段為代表的權限為可讀可執行段
- 以資料段和BSS段為代表的權限為可讀可寫的段
- 以只讀資料段為代表的權限為只讀的段
相同權限的段(這里是指鏈接視圖的section),合并到一起當作一個段(執行視圖的segment)進行映射,
作業系統的VMA除了用于映射可執行檔案中的segment以外,還用于對行程的虛擬地址空間進行管理,比如程式執行時需要用到堆疊(stack)和堆(heap)等空間,在行程的虛擬空間中是以VMA段的形式存在,每一個行程的堆疊和堆都有一個VMA與之對應,
一個行程基本上有以下幾種型別的VMA區域:
- 代碼VMA,只讀、可執行,有映像檔案(ELF中有section與之對應)
- 資料VMA,可讀寫、可執行,有映像檔案
- 堆VMA,可讀寫、可執行,無印象檔案(ELF中沒有section與之對應),匿名,可向上擴展,
- 堆疊VMA,可讀寫、不可執行,無映像檔案,匿名,可向下擴展
6.5 Linux 內核裝載ELF程序簡介
介紹了在Linux系統的bash shell下輸入一個命令執行某個ELF程式時的程序:
1、bash行程呼叫fork()系統呼叫創建一個新的行程
2、新的行程呼叫execve()系統呼叫執行指定的ELF檔案,bash行程繼續回傳等待剛才啟動的新行程結束,繼續等待用戶輸入命令,
3、在execve()系統呼叫中,sys_execve()進行一些引數檢查和復制之后呼叫do_execve(),do_execve()首先查找被執行檔案,讀取前128個位元組,根據前4個位元組可以確定可執行檔案格式和型別,比如ELFW檔案、Java可執行檔案、shell腳本、perl、python等等,并且確定具體的解釋程式的路徑,接著do_execve()根據這128個位元組呼叫search_binary_handle()去搜索和匹配合適的可執行檔案裝載處理程式,
如ELF的裝載處理程式叫做load_elf_binary(),a.out可執行檔案的裝載程序叫做load_aout_binary(),可執行腳本的裝載處理程序叫做load_script(),
load_xxx_binary()會尋找動態鏈接的.interp段,設定動態聯結器路徑,根據ELF可執行檔案的程式頭表,對ELF檔案進行映射,將ELF檔案映射到行程的虛擬地址空間,初始化ELF的行程環境,將系統呼叫的回傳地址修改成ELF可執行檔案的入口點,對于靜態鏈接的ELF可執行檔案,這個入口點是ELF檔案的檔案頭中e_entry所指的地址,對于動態鏈接的ELF可執行檔案,程式入口點是動態聯結器,
當load_xxx_binary()執行完,load_xxx_binary回傳的地址改成了被裝載的ELF程式的入口地址(靜態鏈接),load_xxx_binary回傳到do_execve(),再回傳到sys_execve(),所有系統呼叫從內核態回傳到用戶態時,EIP暫存器直接跳轉到了ELF程式的入口地址,于是ELF程式開始執行,ELF可執行檔案裝載完畢,
6.6 Windows PE的裝載
null
6.7 本章小結
null
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/158171.html
標籤:其他
