當前位置:首頁 > 公眾號精選 > 程序喵大人
[導讀]開篇先拋出幾個問題,之后逐個擊破: 什么是進程的虛擬地址空間?為什么進程要有自己的虛擬地址空間,這樣做有什么好處? 我們都聽說過頁映射,什么是頁映射,操作系統(tǒng)為什么要以頁映射方式將程序映射到進程地址空間,這樣做有什么好處?程序運行過程中發(fā)生頁


開篇先拋出幾個問題,之后逐個擊破:

  1. 什么是進程的虛擬地址空間?為什么進程要有自己的虛擬地址空間,這樣做有什么好處?

  2. 我們都聽說過頁映射,什么是頁映射,操作系統(tǒng)為什么要以頁映射方式將程序映射到進程地址空間,這樣做有什么好處?程序運行過程中發(fā)生頁錯誤如何處理?

  3. 什么是進程?從操作系統(tǒng)的角度來看,進程是如何被建立的?

  4. 進程虛擬地址空間的分布是什么樣的?

  5. Linux是如何裝載并運行ELF程序的?






虛擬地址空間

what:虛擬地址空間就是我們常說的虛擬內(nèi)存,虛擬內(nèi)存是計算機系統(tǒng)內(nèi)存管理的一種技術(shù)。它使得應用程序認為它擁有連續(xù)可用的內(nèi)存(一個連續(xù)完整的地址空間),而實際上,它通常是被分隔成多個物理內(nèi)存碎片,還有部分暫時存儲在外部磁盤存儲器上,在需要時進行數(shù)據(jù)交換。與沒有使用虛擬內(nèi)存技術(shù)的系統(tǒng)相比,使用這種技術(shù)的系統(tǒng)使得大型程序的編寫變得更容易,對真正的物理內(nèi)存的使用也更有效率。當處理器讀取或?qū)懭雰?nèi)存位置時,都會使用虛擬地址。在讀取或?qū)懭氩僮鬟^程中,處理器會將虛擬地址轉(zhuǎn)換為物理地址。

why:使用虛擬內(nèi)存有如下好處:

  • 程序員無需操心如何存儲數(shù)據(jù)或者程序等內(nèi)容。

  • 程序可以使用一系列連續(xù)的虛擬地址來訪問物理內(nèi)存中不連續(xù)的大內(nèi)存區(qū)域,用戶看到的是連續(xù)地址,而無需關(guān)心更底層物理地址的排布。

  • 通過使用虛擬內(nèi)存,程序可以使用大于實際可用物理內(nèi)存的空間,當物理內(nèi)存不夠用時,操作系統(tǒng)會將物理內(nèi)存頁保存在磁盤文件,數(shù)據(jù)頁或者代碼頁會根據(jù)需要在物理內(nèi)存和磁盤之間移動。

  • 不同進程使用的虛擬地址彼此隔離,用戶無需擔心會影響到其它程序內(nèi)存地址中的數(shù)據(jù),操作系統(tǒng)的內(nèi)存管理模塊會將虛擬地址映射到物理地址。

更多詳解虛擬內(nèi)存的內(nèi)容可以看我之前的文章:

深入淺出虛擬內(nèi)存

深入淺出虛擬內(nèi)存(二)繪制虛擬內(nèi)存排布圖

深入淺出虛擬內(nèi)存(三)堆內(nèi)存分配及malloc實現(xiàn)原理

頁映射

background:程序運行時所需要的指令和數(shù)據(jù)必須放在內(nèi)存中才可以正常執(zhí)行,最簡單的辦法就是將運行所需要的指令和數(shù)據(jù)全部裝進內(nèi)存,但是很多時候程序需要的內(nèi)存可能大于實際可用的物理內(nèi)存,為了解決這種不夠用的問題引入了動態(tài)裝入的概念,可以將程序最常用的部分駐留在內(nèi)存中,而將一些不常用的數(shù)據(jù)存在磁盤中。

what:頁映射不是一次性將所有的程序和數(shù)據(jù)裝入內(nèi)存,而是將內(nèi)存和磁盤中的數(shù)據(jù)和指令按頁為單位分成若干份,以后所有的裝載和操作的單位就是頁。頁的大小不固定,但是一般都是4096字節(jié)。

how:如下圖,舉個例子,可執(zhí)行程序所需要的指令和數(shù)據(jù)總和占8個頁,編號為VP0-VP7,而實際的物理內(nèi)存只有4個頁,編號為PP0-PP3,4個頁的物理內(nèi)存無法同時將8個頁的程序都裝載進去,所以需要動態(tài)裝入,假設程序入口地址在VP0,這時內(nèi)核發(fā)現(xiàn)VP0不在內(nèi)存中,所以將VP0分配給了PP0,將VP0的內(nèi)容裝入了PP0,運行一段后程序需要用到VP2,內(nèi)核又將VP2分配給了PP1,之后又用到VP4和VP6,內(nèi)核又分別分配給了PP2和PP3。這時候程序只需要VP0、VP2、VP4和VP6這四個頁就可以一直運行下去,如果程序又需要VP5,那內(nèi)核就必須會放棄正在使用的四個內(nèi)存頁中的一個才可以把VP5裝載進去繼續(xù)執(zhí)行,至于選擇哪個,操作系統(tǒng)內(nèi)核會有多種換出算法來處理這種問題。


why:其實上面已經(jīng)介紹了原因,如果一次性把所有指令和數(shù)據(jù)都加載到內(nèi)存中,物理內(nèi)存可能不夠用,所以需要使用動態(tài)裝入,所以引入了頁映射的方法。

進程如何被建立

這里首先需要弄清楚程序和進程的區(qū)別?程序(可執(zhí)行文件)是一個靜態(tài)的概念,它就是預先編譯好的指令和數(shù)據(jù)集合的一個文件,進程則是一個動態(tài)的概念,它是程序運行的一個過程。

從操作系統(tǒng)角度看,一個進程最關(guān)鍵的特征是它擁有獨立的虛擬地址空間,很多時候一個程序被執(zhí)行都伴隨著一個新的進程被創(chuàng)建,之后裝載相應的可執(zhí)行文件并運行。上述經(jīng)歷了什么步驟?

  1. 創(chuàng)建一個獨立的虛擬地址空間:這里的創(chuàng)建空間并不是真正的創(chuàng)建空間,而是創(chuàng)建映射函數(shù)所需要的數(shù)據(jù)結(jié)構(gòu),方便后面映射需要。

  2. 讀取可執(zhí)行文件頭,建立虛擬空間和可執(zhí)行文件的映射關(guān)系:上面的映射數(shù)據(jù)結(jié)構(gòu)是為了建立虛擬空間到物理內(nèi)存的映射關(guān)系,這一步是虛擬空間與可執(zhí)行文件的映射關(guān)系。

  3. 將CPU的指令寄存器設置成可執(zhí)行文件入口,啟動運行:這里可以簡單的理解為操作系統(tǒng)執(zhí)行了一條跳轉(zhuǎn)指令,跳轉(zhuǎn)到可執(zhí)行文件的入口地址。


頁錯誤:當程序執(zhí)行一個地址的指令時,發(fā)現(xiàn)是個空頁面,所以就認為是個頁錯誤,這時候控制權(quán)交由操作系統(tǒng),操作系統(tǒng)有專門的錯誤處理程序處理這種情況,查詢第二步驟建立的映射數(shù)據(jù)結(jié)構(gòu),找到空頁面所在的虛擬內(nèi)存區(qū)域,計算出相應的頁面在可執(zhí)行文件中的偏移,然后在物理內(nèi)存中分配一個物理頁面,將進程中的該虛擬頁與物理頁建立映射關(guān)系,控制權(quán)返還給進程,進程從頁錯誤的位置繼續(xù)執(zhí)行。

進程虛擬空間分布

如果您讀過我之前的文章應該就知道,一個正常的進程,可執(zhí)行文件中不只包含數(shù)據(jù)段和代碼段,還有好多個段,這里通過readelf可以查看:

$ readelf -S testThere are 9 section headers, starting at offset 0x1208:
Section Headers:[Nr] Name Type Address Offset Size EntSize Flags Link Info Align[ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0[ 1] .text PROGBITS 00000000004000e8 000000e8 0000000000000056 0000000000000000 AX 0 0 1[ 2] .rodata PROGBITS 000000000040013e 0000013e 0000000000000006 0000000000000000 A 0 0 1[ 3] .eh_frame PROGBITS 0000000000400148 00000148 0000000000000078 0000000000000000 A 0 0 8[ 4] .data PROGBITS 0000000000601000 00001000 0000000000000008 0000000000000000 WA 0 0 8[ 5] .comment PROGBITS 0000000000000000 00001008 0000000000000029 0000000000000001 MS 0 0 1[ 6] .symtab SYMTAB 0000000000000000 00001038 0000000000000150 0000000000000018 7 7 8[ 7] .strtab STRTAB 0000000000000000 00001188 000000000000003a 0000000000000000 0 0 1[ 8] .shstrtab STRTAB 0000000000000000 000011c2 0000000000000042 0000000000000000 0 0 1Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), l (large), p (processor specific

通過上面的結(jié)果可以看出這里的段叫Section,拿這個舉例,ELF文件映射是以系統(tǒng)頁為單位,每個段在映射時的長度都是系統(tǒng)頁的整數(shù)倍,假設程序有8個段,每個段都占512字節(jié),占用了8個頁,但是一個頁卻有4K的大小,空間利用率只有1/8,造成極大的空間浪費。實際上,從操作系統(tǒng)裝載可執(zhí)行文件的角度看,可以發(fā)現(xiàn)它實際上并不關(guān)心可執(zhí)行文件各個段所包含的實際內(nèi)容,它主要就是關(guān)心段的權(quán)限(可讀、可寫、可執(zhí)行),ELF文件中段的權(quán)限主要就有幾種組合:

  • 以代碼段為代表的權(quán)限為可讀可執(zhí)行的段

  • 以數(shù)據(jù)段和BSS段位代表的權(quán)限為可讀可寫的段

  • 以只讀數(shù)據(jù)段位代表的權(quán)限為只讀的段

對于相同權(quán)限的段,可以把它們(Section)合并到一起當作一個段(Segment)進行映射。拿前面的例子,之前8個section需要8個頁,而這種方式8個Section可能會被合并成2個Segment,占用2個頁。

Segment的概念實際上是從裝載的角度重新劃分了ELF的各個段,在將目標文件鏈接成可執(zhí)行文件的時候,鏈接器盡量把相同權(quán)限屬性的段分配在同一空間,多個Section變成一個Segment,而系統(tǒng)就是按這種Segment來映射可執(zhí)行文件的。

Segment和Section是從不同的角度劃分一個ELF文件,稱為不同的視圖,從Section的角度來看ELF文件就是鏈接視圖,從Segment的角度來看ELF文件就是執(zhí)行視圖,當我們在談到ELF裝載時,段專門指Segment,其它情況下,段指的是Section。

通過readelf命令可以查看可執(zhí)行文件的Segment。

$ readelf -l test
Elf file type is EXEC (Executable file)Entry point 0x400123There are 3 program headers, starting at offset 64
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000001c0 0x00000000000001c0 R E 0x200000 LOAD 0x0000000000001000 0x0000000000601000 0x0000000000601000 0x0000000000000008 0x0000000000000008 RW 0x200000 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10
Section to Segment mapping: Segment Sections... 00 .text .rodata .eh_frame 01 .data 02

這里有很少的Segment,描述Segment屬性的結(jié)構(gòu)叫程序頭,這里只需要知道ELF可執(zhí)行文件中有一個專門的數(shù)據(jù)結(jié)構(gòu)叫程序頭表,它用來保存Segment的信息,因為目標文件不需要被裝載,所以沒有程序頭表,而可執(zhí)行文件和共享庫文件都有頭表,他們會被用于裝載,這里的各個Segment都是通過匿名虛擬內(nèi)存區(qū)域(VMA)來映射。

可以通過cat來查看VMA:

cat /proc/72/maps7f445f4b0000-7f445f4c7000 r-xp 00000000 00:00 516559 /lib/x86_64-linux-gnu/libgcc_s.so.17f445f4c7000-7f445f4c8000 ---p 00017000 00:00 516559 /lib/x86_64-linux-gnu/libgcc_s.so.17f445f4c8000-7f445f6c6000 ---p 00000018 00:00 516559 /lib/x86_64-linux-gnu/libgcc_s.so.17f445f6c6000-7f445f6c7000 r--p 00016000 00:00 516559 /lib/x86_64-linux-gnu/libgcc_s.so.17f445f6c7000-7f445f6c8000 rw-p 00017000 00:00 516559 /lib/x86_64-linux-gnu/libgcc_s.so.17f445f6d0000-7f445f86d000 r-xp 00000000 00:00 516611 /lib/x86_64-linux-gnu/libm-2.27.so7f445f86d000-7f445f870000 ---p 0019d000 00:00 516611 /lib/x86_64-linux-gnu/libm-2.27.so7f445f870000-7f445fa6c000 ---p 000001a0 00:00 516611 /lib/x86_64-linux-gnu/libm-2.27.so7f445fa6c000-7f445fa6d000 r--p 0019c000 00:00 516611 /lib/x86_64-linux-gnu/libm-2.27.so7f445fa6d000-7f445fa6e000 rw-p 0019d000 00:00 516611 /lib/x86_64-linux-gnu/libm-2.27.so7f445fa70000-7f445fc57000 r-xp 00000000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so7f445fc57000-7f445fc60000 ---p 001e7000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so7f445fc60000-7f445fe57000 ---p 000001f0 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so7f445fe57000-7f445fe5b000 r--p 001e7000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so7f445fe5b000-7f445fe5d000 rw-p 001eb000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so7f445fe5d000-7f445fe61000 rw-p 00000000 00:00 07f445fe70000-7f445ffe9000 r-xp 00000000 00:00 540399 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.257f445ffe9000-7f445fff6000 ---p 00179000 00:00 540399 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.257f445fff6000-7f44601e9000 ---p 00000186 00:00 540399 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.257f44601e9000-7f44601f3000 r--p 00179000 00:00 540399 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.257f44601f3000-7f44601f5000 rw-p 00183000 00:00 540399 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.257f44601f5000-7f44601f9000 rw-p 00000000 00:00 07f4460200000-7f4460226000 r-xp 00000000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so7f4460226000-7f4460227000 r-xp 00026000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so7f4460427000-7f4460428000 r--p 00027000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so7f4460428000-7f4460429000 rw-p 00028000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so7f4460429000-7f446042a000 rw-p 00000000 00:00 07f4460440000-7f4460442000 rw-p 00000000 00:00 07f4460450000-7f4460452000 rw-p 00000000 00:00 07f4460460000-7f4460462000 rw-p 00000000 00:00 07f4460600000-7f4460601000 r-xp 00000000 00:00 205513 /mnt/d/wzq/wzq/util/test/a.out7f4460800000-7f4460801000 r--p 00000000 00:00 205513 /mnt/d/wzq/wzq/util/test/a.out7f4460801000-7f4460802000 rw-p 00001000 00:00 205513 /mnt/d/wzq/wzq/util/test/a.out7fffc7576000-7fffc7597000 rw-p 00000000 00:00 0 [heap]7fffcf186000-7fffcf986000 rw-p 00000000 00:00 0 [stack]7fffcfe84000-7fffcfe85000 r-xp 00000000 00:00 0 [vdso]

上面的我們可以先忽略,看最下面,主要有堆和棧,這兩個VMA幾乎在所有的進程中都存在,而[vdso]是一個內(nèi)核模塊,程序通過這個模塊和內(nèi)核進行通信。

小總結(jié):

圖片來自網(wǎng)絡,侵權(quán)刪

操作系統(tǒng)通過給進程空間劃分出一個個VMA來管理進程的虛擬空間,基本原則是將相同權(quán)限屬性的、有相同映射文件的映射成一個VMA,一個進程主要可以分成以下幾種VMA區(qū)域:

  • 代碼VMA:權(quán)限只讀可執(zhí)行,有映射文件

  • 數(shù)據(jù)VMA:權(quán)限可讀寫可執(zhí)行,有映射文件

  • 堆VMA:權(quán)限可讀寫可執(zhí)行,無映射文件,匿名,向上擴展

  • 棧VMA:權(quán)限可讀寫不可執(zhí)行,無映射文件,匿名,向下擴展


Linux如何裝載并運行ELF程序

Linux內(nèi)核裝載ELF文件主要有兩步:

  1. 通過fork系統(tǒng)調(diào)用創(chuàng)建一個新的進程

  2. 通過execve系統(tǒng)調(diào)用執(zhí)行指定的ELF文件,附帶環(huán)境變量和參數(shù)

    1. 檢查ELF可執(zhí)行文件的有效性,比如魔數(shù)(通過魔數(shù)可以確定文件格式)、Segment的數(shù)量等

    2. 尋找動態(tài)鏈接的段,設置動態(tài)鏈接器路徑

    3. 根據(jù)ELF可執(zhí)行文件的程序頭表描述,對ELF文件進行映射,比如代碼、數(shù)據(jù)、只讀數(shù)據(jù)

    4. 初始化ELF進程環(huán)境

    5. 將系統(tǒng)調(diào)用的返回地址修改為ELF可執(zhí)行文件的入口地址

參考資料

《程序員的自我修養(yǎng):鏈接裝載與庫》
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/gettingstarted/virtual-address-spaces
https://blog.csdn.net/chenlycly/article/details/53367336
https://www.zhihu.com/question/290504400
https://zh.wikipedia.org/wiki/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98
https://www.cnblogs.com/Tan-sir/p/7488796.html



c++11新特性,所有知識點都在這了!

你的c++團隊還在禁用異常處理嗎?

內(nèi)存對齊之格式修訂版

c++11新特性之智能指針

gcc a.c 究竟經(jīng)歷了什么?

談談程序鏈接及分段那些事

免責聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉