linux 运行可执行文件
本文只為整理思路,供自己日后參考?,F(xiàn)在就從從一個(gè)執(zhí)行文件a.out的運(yùn)行開始,自上而下地分析linux是如何運(yùn)行一個(gè)執(zhí)行文件的。
1、首先,需要了解一下a.out這個(gè)目標(biāo)文件。a.out在linux下是ELF(Executable Linkable Format)文件格式,該目標(biāo)文件由一個(gè)文件頭、代碼段、數(shù)據(jù)段(已初始化)、從定位信息區(qū)、符號(hào)表及符號(hào)名字字符串構(gòu)成,如下左圖所示,經(jīng)過鏈接后生成執(zhí)行文件如下右圖所示,需要說(shuō)明的是1).bss段在目標(biāo)文件和執(zhí)行文件中并不占用文件的空間,但是它在加載時(shí)占用地址空間;2)鏈接后各個(gè)段在虛擬空間上的地址就確定了,并且linux下,ELF可執(zhí)行文件默認(rèn)從地址0x080480000開始分配。
我們知道在linux下運(yùn)行一個(gè)程序只要在shell中執(zhí)行 ./a.out 這個(gè)命令就OK了,剩下的事情操作系統(tǒng)會(huì)替我們完成。但是操作系統(tǒng)到底做了什么,它是怎么做的呢,接下來(lái)就來(lái)解析一下。
2、linux系統(tǒng)中每個(gè)程序都運(yùn)行在一個(gè)進(jìn)程上下文中,這個(gè)進(jìn)程上下文有自己的虛擬地址空間。當(dāng)shell運(yùn)行一個(gè)程序時(shí),父shell進(jìn)程生成一個(gè)子進(jìn)程,它是父進(jìn)程的一個(gè)復(fù)制品。子進(jìn)程通過execve系統(tǒng)調(diào)用啟動(dòng)加載器。加載器刪除子進(jìn)程已有的虛擬存儲(chǔ)段,并創(chuàng)建一組新的代碼、數(shù)據(jù)、堆、棧段,新的堆和棧被初始化為零。通過將虛擬地址空間中的頁(yè)映射到可執(zhí)行文件的頁(yè)大小組塊,新的代碼和數(shù)據(jù)段被初始化為可執(zhí)行文件的內(nèi)容,最后將CUP指令寄存器設(shè)置成可執(zhí)行文件入口,啟動(dòng)運(yùn)行。
執(zhí)行完上述操作后,其實(shí)可執(zhí)行文件的真正指令和數(shù)據(jù)都沒有別裝入內(nèi)存中。操作系統(tǒng)只是通過可執(zhí)行文件頭部的信息建立起可執(zhí)行文件和進(jìn)程虛擬內(nèi)存之間的映射關(guān)系而已?,F(xiàn)在程序的入口地址為0x08048000,剛好是代碼段的起始地址。當(dāng)CPU打算執(zhí)行這個(gè)地址的指令時(shí),發(fā)現(xiàn)頁(yè)面0x8048000~0x08049000(一個(gè)頁(yè)面一般是4K)是個(gè)空頁(yè)面,于是它就認(rèn)為是個(gè)頁(yè)錯(cuò)誤。此時(shí)操作系統(tǒng)根據(jù)虛擬地址空間與可執(zhí)行文件間的映射關(guān)系找到頁(yè)面在可執(zhí)行文件中的偏移,然后在物理內(nèi)存中分配一個(gè)物理頁(yè)面,并在虛擬地址頁(yè)面與物理頁(yè)面間建立映射,最后把文件中頁(yè)面拷貝到物理頁(yè)面,進(jìn)程重新開始執(zhí)行。該過程如下圖所示:
3、這里比較難理解的就是這個(gè)分頁(yè)機(jī)制,講到分頁(yè)機(jī)制,就不得不提linux的分段與分頁(yè)機(jī)制,這也是這篇文章的重點(diǎn)。我們先來(lái)看一張圖:
這張圖展示了虛擬地址進(jìn)過分段、分頁(yè)機(jī)制后轉(zhuǎn)化成物理地址的簡(jiǎn)單過程。其實(shí)分段機(jī)制是intel芯片為兼容以前產(chǎn)品而保留下來(lái)的,然后linux中弱化了這一機(jī)制。下面我們先簡(jiǎn)單介紹一下分段機(jī)制:
分段提供了隔絕各個(gè)代碼、數(shù)據(jù)和堆棧區(qū)域的機(jī)制,它把處理器可尋址的線性地址空間劃分成一些較小的稱為段的受保護(hù)地址空間區(qū)域。如果處理器中有多個(gè)程序在運(yùn)行,那么每個(gè)程序可分配各自的一套段。此時(shí)處理器就可以加強(qiáng)這些段之間的界限,并確保一個(gè)程序不會(huì)通過訪問另一個(gè)程序的段而干擾程序的執(zhí)行。為了定位指定段中的一個(gè)字節(jié),程序必須提供一個(gè)邏輯地址,該地址包括一個(gè)段選擇符和一個(gè)偏移量。實(shí)模式下,段值還是可以看作地址的一部分,段值位XXXXh表示以XXXX0h開始的一段內(nèi)存。而保護(hù)模式下,段值僅僅變成了一個(gè)索引,只想一個(gè)數(shù)據(jù)結(jié)構(gòu)的一個(gè)表項(xiàng),該表項(xiàng)中定義了段的起始地址、界限、屬性等內(nèi)容。cs、ds等寄存器中存的就是這個(gè)段選擇符,用段選擇符中的段索引在GDT或LDT表中定位相應(yīng)的段描述符,把段描述符中取得的段基地址加上偏移量,就形成了一個(gè)線性地址。
得到了線性地址之后,我們?cè)賮?lái)看看分頁(yè)機(jī)制如何把它轉(zhuǎn)換成物理地址。處理器分頁(yè)機(jī)制會(huì)把線性地址空間(段已映射到其中)劃分成頁(yè)面,然后這些線性地址空間頁(yè)面被映射到物理地址空間的頁(yè)面上。分頁(yè)與分段最大的不同之處在于分頁(yè)是用來(lái)固定長(zhǎng)度的頁(yè)面(一般為4KB)。如果僅適用分段地址轉(zhuǎn)換,那么存儲(chǔ)在物理內(nèi)存中的一個(gè)數(shù)據(jù)結(jié)構(gòu)將包含器所有部分。但如果適用了分頁(yè),那么一個(gè)數(shù)據(jù)結(jié)構(gòu)就可以一部分存儲(chǔ)在物理內(nèi)存中,而另一部分保存在磁盤中。
處理器把線性地址轉(zhuǎn)換成物理地址和用于產(chǎn)生頁(yè)錯(cuò)誤異常的信息包含在存儲(chǔ)與內(nèi)存中的頁(yè)目錄和頁(yè)表中。也變可看作簡(jiǎn)單的4K為單位的物理地址數(shù)組。線性地址的高20位構(gòu)成這個(gè)數(shù)組的引索值,用于選擇對(duì)應(yīng)頁(yè)面的物理基地址。線性地址的低12位給出 了頁(yè)面中的偏移量。頁(yè)表中的頁(yè)表項(xiàng)大小為32位。由于只需要其中20位來(lái)存放頁(yè)面的物理基地址,因此剩下的12位可用于存放諸如頁(yè)面是否存在等屬性信息。如果線性地址引索的頁(yè)表項(xiàng)被標(biāo)注為存在,我們就從頁(yè)面中取得物理地址。如果表項(xiàng)中不存在,那么訪問對(duì)應(yīng)物理頁(yè)面時(shí)就會(huì)產(chǎn)生異常。
頁(yè)表含有2^20(1M)個(gè)表項(xiàng),而每項(xiàng)占用4個(gè)字節(jié)。如果作為一個(gè)表來(lái)存放的話,最多將占用4MB內(nèi)存。因此為了減少內(nèi)存占用量,80x86適用了兩級(jí)表。由此,高20位線性地址到物理地址的轉(zhuǎn)換也被分成兩步進(jìn)行,每部適用其中10個(gè)比特。
第一級(jí)表稱為頁(yè)目錄。它被存放在1頁(yè)4k 頁(yè)面中,具有2^10(1k)個(gè)4字節(jié)長(zhǎng)度的表項(xiàng)。這些表項(xiàng)指向二級(jí)表。它們由線性地址最高10位作為引索。
第二級(jí)表稱為頁(yè)表,長(zhǎng)度也是1個(gè)頁(yè)面。線性地址高10位獲取指向第二級(jí)頁(yè)表的指針,再加上中間10位,就可以在相應(yīng)頁(yè)表中獲得物理地址的高20位。而為地址的低12位就是線性地址的低12,這樣就組成了一個(gè)完整的32位物理地址。分段、分頁(yè)的整個(gè)過程可見下面這張圖:
以上就是良許教程網(wǎng)為各位朋友分享的Linux相關(guān)知識(shí)。
總結(jié)
以上是生活随笔為你收集整理的linux 运行可执行文件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dscms源码分析笔记
- 下一篇: 寄生电容/寄生电阻/寄生电感