qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          一個操作系統的實現(1):分析linux下如何運行一個執行文件

           本文只為整理思路,供自己日后參考?,F在就從從一個執行文件a.out的運行開始,自上而下地分析linux是如何運行一個執行文件的。

            1、首先,需要了解一下a.out這個目標文件。a.out在linux下是ELF(Executable Linkable Format)文件格式,該目標文件由一個文件頭、代碼段、數據段(已初始化)、從定位信息區、符號表及符號名字字符串構成,如下左圖所示,經過鏈接后生成執行文件如下右圖所示,需要說明的是1).bss段在目標文件和執行文件中并不占用文件的空間,但是它在加載時占用地址空間;2)鏈接后各個段在虛擬空間上的地址就確定了,并且linux下,ELF可執行文件默認從地址0x080480000開始分配。

            我們知道在linux下運行一個程序只要在shell中執行 ./a.out 這個命令就OK了,剩下的事情操作系統會替我們完成。但是操作系統到底做了什么,它是怎么做的呢,接下來就來解析一下。

            2、linux系統中每個程序都運行在一個進程上下文中,這個進程上下文有自己的虛擬地址空間。當shell運行一個程序時,父shell進程生成一個子進程,它是父進程的一個復制品。子進程通過execve系統調用啟動加載器。加載器刪除子進程已有的虛擬存儲段,并創建一組新的代碼、數據、堆、棧段,新的堆和棧被初始化為零。通過將虛擬地址空間中的頁映射到可執行文件的頁大小組塊,新的代碼和數據段被初始化為可執行文件的內容,最后將CUP指令寄存器設置成可執行文件入口,啟動運行。

            執行完上述操作后,其實可執行文件的真正指令和數據都沒有別裝入內存中。操作系統只是通過可執行文件頭部的信息建立起可執行文件和進程虛擬內存之間的映射關系而已?,F在程序的入口地址為0x08048000,剛好是代碼段的起始地址。當CPU打算執行這個地址的指令時,發現頁面0x8048000~0x08049000(一個頁面一般是4K)是個空頁面,于是它就認為是個頁錯誤。此時操作系統根據虛擬地址空間與可執行文件間的映射關系找到頁面在可執行文件中的偏移,然后在物理內存中分配一個物理頁面,并在虛擬地址頁面與物理頁面間建立映射,最后把文件中頁面拷貝到物理頁面,進程重新開始執行。該過程如下圖所示:

            3、這里比較難理解的就是這個分頁機制,講到分頁機制,就不得不提linux的分段與分頁機制,這也是這篇文章的重點。我們先來看一張圖:

            這張圖展示了虛擬地址進過分段、分頁機制后轉化成物理地址的簡單過程。其實分段機制是intel芯片為兼容以前產品而保留下來的,然后linux中弱化了這一機制。下面我們先簡單介紹一下分段機制:

            分段提供了隔絕各個代碼、數據和堆棧區域的機制,它把處理器可尋址的線性地址空間劃分成一些較小的稱為段的受保護地址空間區域。如果處理器中有多個程序在運行,那么每個程序可分配各自的一套段。此時處理器就可以加強這些段之間的界限,并確保一個程序不會通過訪問另一個程序的段而干擾程序的執行。為了定位指定段中的一個字節,程序必須提供一個邏輯地址,該地址包括一個段選擇符和一個偏移量。實模式下,段值還是可以看作地址的一部分,段值位XXXXh表示以XXXX0h開始的一段內存。而保護模式下,段值僅僅變成了一個索引,只想一個數據結構的一個表項,該表項中定義了段的起始地址、界限、屬性等內容。cs、ds等寄存器中存的就是這個段選擇符,用段選擇符中的段索引在GDT或LDT表中定位相應的段描述符,把段描述符中取得的段基地址加上偏移量,就形成了一個線性地址。

            得到了線性地址之后,我們再來看看分頁機制如何把它轉換成物理地址。處理器分頁機制會把線性地址空間(段已映射到其中)劃分成頁面,然后這些線性地址空間頁面被映射到物理地址空間的頁面上。分頁與分段最大的不同之處在于分頁是用來固定長度的頁面(一般為4KB)。如果僅適用分段地址轉換,那么存儲在物理內存中的一個數據結構將包含器所有部分。但如果適用了分頁,那么一個數據結構就可以一部分存儲在物理內存中,而另一部分保存在磁盤中。

            處理器把線性地址轉換成物理地址和用于產生頁錯誤異常的信息包含在存儲與內存中的頁目錄和頁表中。也變可看作簡單的4K為單位的物理地址數組。線性地址的高20位構成這個數組的引索值,用于選擇對應頁面的物理基地址。線性地址的低12位給出 了頁面中的偏移量。頁表中的頁表項大小為32位。由于只需要其中20位來存放頁面的物理基地址,因此剩下的12位可用于存放諸如頁面是否存在等屬性信息。如果線性地址引索的頁表項被標注為存在,我們就從頁面中取得物理地址。如果表項中不存在,那么訪問對應物理頁面時就會產生異常。

            頁表含有2^20(1M)個表項,而每項占用4個字節。如果作為一個表來存放的話,最多將占用4MB內存。因此為了減少內存占用量,80x86適用了兩級表。由此,高20位線性地址到物理地址的轉換也被分成兩步進行,每部適用其中10個比特。

            第一級表稱為頁目錄。它被存放在1頁4k 頁面中,具有2^10(1k)個4字節長度的表項。這些表項指向二級表。它們由線性地址最高10位作為引索。

            第二級表稱為頁表,長度也是1個頁面。線性地址高10位獲取指向第二級頁表的指針,再加上中間10位,就可以在相應頁表中獲得物理地址的高20位。而為地址的低12位就是線性地址的低12,這樣就組成了一個完整的32位物理地址。分段、分頁的整個過程可見下面這張圖:



          posted on 2013-08-12 09:48 順其自然EVO 閱讀(328) 評論(0)  編輯  收藏 所屬分類: linux

          <2013年8月>
          28293031123
          45678910
          11121314151617
          18192021222324
          25262728293031
          1234567

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 新郑市| 镇江市| 华蓥市| 盖州市| 东辽县| 洱源县| 沙河市| 盐城市| 新丰县| 巫溪县| 习水县| 梓潼县| 仁怀市| 北海市| 古浪县| 大港区| 高雄市| 高陵县| 那坡县| 鹤峰县| 郸城县| 宁安市| 务川| 嘉黎县| 平度市| 溧阳市| 古浪县| 泰安市| 汽车| 南川市| 若尔盖县| 东平县| 盐池县| 黄山市| 冕宁县| 体育| 景谷| 武义县| 呼玛县| 朝阳市| 南昌县|