可執(zhí)行文件只有裝載到內存以后才能被CPU執(zhí)行。
一、裝載的方式
頁映射:將內存和磁盤中的數據和指令按照頁為單位劃分,以后裝載和操作的單位就是頁。4kb
二、程序的裝載運行步驟
- 創(chuàng)建一個獨立的虛擬地址空間;(虛擬頁到物理頁的映射關系,此時為空即可,缺頁錯誤時會自動設置)
- 讀取可執(zhí)行文件頭,建立虛擬地址空間和可執(zhí)行文件的映射關系;
- 動態(tài)鏈接的過程;
- 將CPU指令寄存器設置為可執(zhí)行文件的入口地址(ELF文件頭有存儲),啟動運行;(這個入口是運行庫的入口函數)
- 入口函數對運行庫和程序運行環(huán)境進行初始化,包括堆、IO、線程、全局變量構造等等;
- 入口函數完成初始化過程,調用main函數,正式開始執(zhí)行程序的主體部分;
- main函數運行完畢,返回入口函數,入口函數進行清理工作:析構、堆銷毀、關閉IO,然后系統(tǒng)調用結束進程;
三、進程虛擬空間的分布
- ELF鏈接視圖和執(zhí)行視圖
ELF可執(zhí)行文件引入了Segment的概念,它是一個或多個相同屬性的section的合并,相當于裝載時重新劃分了ELF的各個段;
原因在于裝載時只關心讀寫執(zhí)行的權限,以section的粒度來映射會存在內存浪費。所以將相同權限的section合并當作一個Segment,例如.text和.init代碼段; - 堆和棧
一個進程中的堆和棧分別都有一個對應的VMA。
通過proc文件系統(tǒng)查看進程的虛擬空間分布:
- 總結
操作系統(tǒng)通過給進程空間劃分出一個個VMA來管理進程的虛擬空間,基本原則是將相同權限屬性的、有相同映像文件的映射成一個VMA:
四、動態(tài)鏈接
- 靜態(tài)鏈接和動態(tài)鏈接
- 靜態(tài)鏈接的缺陷:磁盤和內存空間浪費、程序的更新部署麻煩;
- 動態(tài)鏈接的方法:將模塊分割開,等到程序運行時再進行鏈接;
- 動態(tài)鏈接的符號
如果一個符號定義在動態(tài)共享對象中,那么連接器就會將這個符號的引用標記為宇哥動態(tài)鏈接的符號,不對它進行地址重定位,留到裝載時再進行;zhoumeng.2019@n224-024-082:~/dynamix$ readelf -s P1 Symbol table '.dynsym' contains 13 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND foobar 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 7: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) 8: 0000000000201030 0 NOTYPE GLOBAL DEFAULT 25 _edata 9: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 26 _end 10: 0000000000201030 0 NOTYPE GLOBAL DEFAULT 26 __bss_start 11: 00000000000005f8 0 FUNC GLOBAL DEFAULT 11 _init 12: 0000000000000804 0 FUNC GLOBAL DEFAULT 15 _fini
- 動態(tài)鏈接的過程
- 先啟動動態(tài)鏈接器本身;
- 裝載所需要的所有共享對象;
- 重定位和初始化;