程式的裝入和連結

來源:互聯網
上載者:User
文章目錄
  • 1.絕對裝入方式(Absolute Loading Mode)
  • 3.動態地址重地位(動態運行時裝入方式 Dynamic Run-time Loading) 
  • 2.裝入時動態連結(Load-time Dynamic Linking) 
  • 3.運行時動態連結(Run-time Dynamic Linking)
  • 4.1. 構造動態連結程式庫
  •  4.2. DLL的裝入方法

       

1. 地址相關概念

1. 物理地址(physical address)

      實體記憶體,真實存在的插在主板記憶體槽上的記憶體條的容量的大小.

      記憶體是由若干個儲存單元組成的,每個儲存單元有一個編號,這種編號可唯一標識一個儲存單元,稱為記憶體位址(或物理地址)。我們可以把記憶體看成一個從0位元組一直到記憶體最大容量逐位元組編號的儲存單元數組,即每個儲存單元與記憶體位址的編號相對應。

  
2. 虛擬記憶體(Virtual memory)(也叫虛擬儲存空間)

 

        虛擬記憶體地址就是每個進程可以直接定址的地址空間,不受其他進程幹擾。每個指令或資料單元都在這個虛擬空間中擁有確定的地址。      

      虛擬記憶體就是進程中的目標代碼,資料等虛擬位址組成的虛擬空間     

      虛擬記憶體不考慮實體記憶體的大小和資訊存放的實際位置,只規定進程中相互關聯資訊的相對位置。每個進程都擁有自己的虛擬記憶體,且虛擬記憶體的大小由處理機的地址結構和定址方式決定。

       如直接定址,如果cpu的有效地址長度為16位,則其定址範圍0 -64k。

       再比如32位機器可以直接定址4G空間,意思是每個應用程式都有4G記憶體空間可用。但是顯然機器記憶體罕有如此之大,可以支援每個程式使用4G記憶體的。
      虛擬記憶體與實體記憶體的區別:虛擬記憶體就與實體記憶體相反,是指根據系統需要從硬碟虛擬地勻出來的記憶體空間,是一種電腦系統記憶體管理技術,屬於電腦程式,而實體記憶體為硬體。因為有時候當你處理大的程式時候系統記憶體不夠用,此時就會把硬碟當記憶體來使用,來交換資料做緩衝區,不過實體記憶體的處理速度是虛擬記憶體的30倍以上。

3. 邏輯地址(logical address)

        來源程式經過彙編或編譯後,形成目標代碼,每個目標代碼都是以0為基址順序進行編址的,原來用符號名訪問的單元用具體的資料——單元號取代。這樣產生的目標程式佔據一定的地址空間,稱為作業的邏輯地址空間,簡稱邏輯空間。

       在邏輯空間中每條指令的地址和指令中要訪問的運算元地址統稱為邏輯地址。即應用程式中使用的地址。要經過定址方式的計算或變換才得到記憶體中的物理地址。

       很簡單,邏輯地址就是你來源程式裡使用的地址,或者原始碼經過編譯以後編譯器將一些標號,變數轉換成的地址,或者相對於當前段的位移地址。

      邏輯地址是指由程式產生的與段相關的位移地址部分。例如,你在進行C語言指標編程中,可以讀取指標變數本身值(&操作),實際上這個值就是邏輯地址,它是相對於你當前進程資料區段的地址,不和絕對物理地址相干。只有在Intel實模式下,邏輯地址才和物理地址相等(因為實模式沒有分段或分頁機制,Cpu不進行自動地址轉換);邏輯也就是在Intel保護模式下程式執行程式碼片段限長內的位移地址(假定程式碼片段、資料區段如果完全一樣)。應用程式員僅需與邏輯地址打交道,而分段和分頁機制對您來說是完全透明的,僅由系統編程人員涉及。應用程式員雖然自己可以直接操作記憶體,那也只能在作業系統給你分配的記憶體段操作。

     不過有些資料是直接把邏輯地址當成虛擬位址,兩者並沒有明確的界限。

   
在linux核心,虛擬位址是3G-4G這段地址,它與物理地址通過頁表來映射,邏輯地址是指3G-3G+main_memory_size這段虛擬位址,它與物理地址的映射是線性,當然也可以通過頁表映射。所以邏輯地址是虛擬位址的一部分。

        邏輯地址的組成:是由一個段標識符加上一個指定段內相對位址的位移量,表示為 [段標識符:段內位移量]

 

   

                      圖4.1  作業的名空間、邏輯地址空間和裝入後的物理空間

4. 線性地址或Linux下也叫虛擬位址(virtual address)

         這個地址很重要,也很不容易理解。分段機制下CPU定址是二維的地址即,段地址:位移地址,CPU不可能認識二維地址,因此需要轉化成一維地址即,段地址*16+位移地址,這樣得到的地址便是線性地址(在未開啟分頁機制的情況下也是物理地址)。這樣有什麼意義呢?或者說這個一維地址的計算方法隨便一個學電腦的人都知道,但是你真的理解它的意思嗎?要想理解它的意思,必須要知道什麼是地址空間,下文詳述。

       線性地址是邏輯地址到物理地址變換之間的中介層。程式碼會產生邏輯地址,或者說是段中的位移地址,加上相應段的基地址就產生了一個線性地址。如果啟用了分頁機制,那麼線性地址可以再經變換以產生一個物理地址。若沒有啟用分頁機制,那麼線性地址直接就是物理地址。Intel 80386的線性地址空間容量為4G(2的32次方即32根地址匯流排定址)。

       跟邏輯地址類似,它也是一個不真實的地址,如果邏輯地址是對應的硬體平台段式管理轉換前地址的話,那麼線性地址則對應了硬體頁式記憶體的轉換前地址。

       CPU將一個虛擬記憶體空間中的地址轉換為物理地址,需要進行兩步:首先將給定一個邏輯地址(其實是段內位移量=),CPU要利用其段式記憶體管理單元,先將為個邏輯地址轉換成一個線程地址,再利用其頁式記憶體管理單元,轉換為最終物理地址。

 

程式如何運行

       在多道程式環境下,要使程式運行,必須先為之建立進程。而建立進程的第一件事,便是將程式和資料裝入記憶體。如何將一個使用者來源程式變為一個可在記憶體中執行的程式,通常都要經過以下幾個步驟:

        首先是要編譯,由編譯器(Compiler)將使用者原始碼編譯成cpu可執行檔目標代碼,產生了若干個目標模組(Object  Module)(即若干程式段),

        其次是連結,由連結程式(Linker)將編譯後形成的一組目標模組(程式段),以及它們所需要的庫函數連結在一起,形成一個完整的裝入模組(Load  Module);

        最後是裝入,由裝入程式(Loader)將裝入模組裝入記憶體。圖 4-2 示出了這樣的三步過程。

         

                                                                       圖4-2  對使用者程式的處理步驟

2. 程式的裝入(地址的變換)

         為了闡述上的方便,我們先介紹一個無需進行連結的單個目標模組的裝入過程。該目標模組也就是裝入模組。在將一個裝入模組裝入記憶體時,可以有絕對裝入方式、可重定位裝入方式和動態運行時裝入方式,下面分別簡述之。

1.絕對裝入方式(Absolute Loading Mode)

 

          
         在編譯時間,如果知道程式將駐留在記憶體的什麼位置,那麼,編譯器將產生絕對位址的目標代碼。即按照實體記憶體的位置賦予實際的物理地址。例如,事先已知使用者程式(進程)駐留在從R處開始的位置,則編譯器所產生的目標模組(即裝入模組)便從R處開始向上擴充。絕對裝入程式按照裝入模組中的地址,將程式和資料裝入記憶體。裝入模組被裝入記憶體後,由於程式中的邏輯地址與實際記憶體位址完全相同,故不須對程式和資料的地址進行修改。程式中所使用的絕對位址,既可在編譯或彙編時給出,也可由程式員直接賦予。

        這個方式的優點:是CPU執行目標代碼快。

       缺點:1)是由於記憶體大小限制,能裝入記憶體並發執行的進程數大大減少
                   2)編譯器必須知道記憶體的當前空閑地址部分和其地址,並且把進程的不同程式段連續地存放起來,編譯非常複雜。由於程式  

       因此,通常是寧可在程式中採用符號地址,然後在編譯或彙編時,再將這些符號地址轉換為絕對位址。

       如何把虛擬記憶體地址空間變換到記憶體唯一的一維物理線性空間?涉及到兩個問題:
        一是虛擬空間的劃分問題。
        二是把虛擬空間中已經連結和劃分好的內容裝入記憶體,並將虛擬空間地址映射記憶體位址的問題。即地址映射。
        地址映射就是建立虛擬位址與記憶體位址的關係。

2.靜態地址重定位(可重定位裝入方式 Relocation Loading Mode)

 

       絕對裝入方式只能將目標模組裝入到記憶體中事先指定的位置。在多道程式環境下,編譯器不可能預知所編譯的目標模組應放在記憶體的何處,因此,絕對裝入方式只適用於單道程式環境。在多道程式環境下,所得到的目標模組的起始地址通常是從 0 開始的,程式中的其它地址也都是相對於起始地址計算的。此時應採用可重定位裝入方式,根據記憶體的當前情況,將裝入模組裝入到記憶體的適當位置。 

       靜態地址重定位:即在程式裝入對目標代碼裝入記憶體的過程中完成,是指在程式開始運行前,程式中指令和資料的各個地址均已完成重定位,即完成虛擬位址到記憶體位址映射。地址變換通常是在裝入時一次完成的,以後不再改變。

       值得注意的是, 在採用可重定位裝入程式將裝入模組裝入記憶體後, 會使裝入模組中的所有邏輯地址與實際裝入記憶體的物理地址不同,圖4-3示出了這一情況。

    

                   
圖4-3  作業裝入記憶體時的情況

     例如,在使用者程式的 1000 號單元處有一條指令LOAD 1,2500,該指令的功能是將 2500 單元中的整數 365 取至寄存器 1。但若將該使用者程式裝入到記憶體的 10000~15000號單元而不進行地址變換, 則在執行11000號單元中的指令時,它將仍從 2500 號單元中把資料取至寄存器1而導致資料錯誤。由圖4-3 可見,正確的方法應該是將取數指令中的地址 2500 修改成 12500,即把指令中的相對位址 2500 與本程式在記憶體中的起始地址
10000 相加,才得到正確的物理地址12500。除了資料地址應修改外,指令地址也須做同樣的修改,即將指令的相對位址 1000 與起始地址 10000 相加,得到絕對位址 11000。

優點:無需硬體支援

缺點:1)程式重定位之後就不能在記憶體中搬動了;

            2)要求程式的儲存空間是連續的,不能把程式放在若干個不連續的地區中。

 

3.動態地址重地位(動態運行時裝入方式 Dynamic Run-time Loading) 

        可重定位裝入方式可將裝入模組裝入到記憶體中任何允許的位置,故可用於多道程式環境;但這種方式並不允許程式運行時在記憶體中移動位置。因為,程式在記憶體中的移動,意味著它的物理位置發生了變化, 這時必須對程式和資料的地址(是絕對位址)進行修改後方能運行。然而,實際情況是,在運行過程中它在記憶體中的位置可能經常要改變,此時就應採用動態運行時裝入的方式。

 

     動態地址重定位:不是在程式執行之前而是在程式執行過程中進行地址變換。更確切的說,是把這種地址轉換延遲到程式真正要執行時才進行,即在每次訪問記憶體單元前才將要訪問的程式或資料地址變換成記憶體位址。動態重定位可使裝配模組不加任何修改而裝入記憶體。為使地址轉換不影響指令的執行速度,這種方式需要一個重定位寄存器的支援,

優點:1)目標模組裝入記憶體時無需任何修改,因而裝入之後再搬遷也不會影響其正確執行,這對於儲存空間緊縮、解決片段問題是極其有利的;

      2)一個程式由若干個相對獨立的目標模組組成時,每個目標模組各裝入一個儲存地區,這些儲存地區可以不是順序相鄰的,只要各個模組有自己對應的定位寄存器就行。

缺點:需要硬體支援。

3. 程式的連結

     

  來源程式經過編譯後,可得到一組目標模組,再利用連結程式將這組目標模組連結,形成裝入模組。根據連結時間的不同,可把連結分成如下三種:
       (1) 、 靜態連結。在程式運行之前,先將各目標模組及它們所需的庫函數,連結成一個完整的裝配模組,以後不再拆開。我們把這種事先進行連結的方式稱為靜態連結方式。
       (2)、  裝入時動態連結。這是指將使用者來源程式編譯後所得到的一組目標模組,在裝入記憶體時,採用邊裝入邊連結的連結方式。
       (3)、  運行時動態連結。這是指對某些目標模組的連結,是在程式執行中需要該(目標)模組時,才對它進行的連結。

 
1.靜態連結方式(Static Linking)

       我們通過一個例子來說明在實現靜態連結時應解決的一些問題。在圖 4-4(a)中示出了經過編譯後所得到的三個目標模組A、B、C,它們的長度分別為 L、M和N。在模組A中有一條語句CALL B,用於調用模組B。在模組B中有一條語句CALL C,用於調用模組C。B和C都屬於外部調用符號,在將這幾個目標模組裝配成一個裝入模組時,須解決以下兩個問題:  
         (1)  對相對位址進行修改。在由編譯器所產生的所有目標模組中,使用的都是相對位址,其起始地址都為 0,每個模組中的地址都是相對於起始地址計算的。在連結成一個裝入模組後,原模組B和 C在裝入模組的起始地址不再是 0,而分別是 L和 L+M,所以此時須修改模組B和C中的相對位址,即把原B中的所有相對位址都加上 L,把原 C中的所有相對位址都加上L+M。 
          (2)  變換外部調用符號。將每個模組中所用的外部調用符號也都變換為相對位址,如把B 的起始地址變換為 L,把 C 的起始地址變換為 L+M, 4-4(b)所示。這種先進行連結所形成的一個完整的裝入模組,又稱為可執行檔。通常都不再拆開它,要運行時可直接將它裝入記憶體。這種事先進行連結,以後不再拆開的連結方式,稱為靜態連結方式。

       

                                     圖  4-4  程式連結

 

2.裝入時動態連結(Load-time Dynamic Linking) 


       使用者來源程式經編譯後所得的目標模組,是在裝入記憶體時邊裝入邊連結的,即在裝入一個目標模組時,若發生一個外部模組呼叫事件,將引起裝入程式去找出相應的外部目標模組,並將它裝入記憶體,還要按照圖4-4所示的方式來修改目標模組中的相對位址。裝入時動態連結方式有以下優點:
        (1) 、 便於修改和更新。對於經靜態連結裝配在一起的裝入模組,如果要修改或更新其中的某個目標模組,則要求重新開啟裝入模組。這不僅是低效的,而且有時是不可能的。若採用動態連結方式,由於各目標模組是分開存放的,所以要修改或更新各目標模組是件非常容易的事。
        (2)、  便於實現對目標模組的共用。在採用靜態連結方式時,每個應用模組都必須含有其目標模組的拷貝,無法實現對目標模組的共用。但採用裝入時動態連結方式,OS則很容易將一個目標模組連結到幾個應用模組上,實現多個應用程式對該模組的共用。

3.運行時動態連結(Run-time Dynamic Linking)

        在許多情況下,應用程式在運行時,每次要啟動並執行模組可能是不相同的。但由於事先無法知道本次要運行哪些模組,故只能是將所有可能要運行到的模組都全部裝入記憶體,並在裝入時全部連結在一起。顯然這是低效的,因為往往會有些目標模組根本就不運行。比較典型的例子是作為錯誤處理用的目標模組,如果程式在整個運行過程中都不出現錯誤,則顯然就不會用到該模組。 近幾年流行起來的運行時動態連結方式,是對上述在裝入時連結方式的一種改進。這種連結方式是將對某些模組的連結延遲到程式執行時才進行連結,亦即,在執行過程中,當發現一個被調用模組尚未裝入記憶體時,立即由OS去找到該模組並將之裝入記憶體,把它連結到調用者模組上。凡在執行過程中未被用到的目標模組,都不會被調入記憶體和被連結到裝入模組上,這樣不僅可加快程式的裝入過程,而且可節省大量的記憶體空間。

 

4. Windows NT動態連結程式庫

4.1. 構造動態連結程式庫

        DLL是包含函數和資料的模組,它的調用模組可為EXE或DLL,它由調用模組在運行時載入;載入時,它被映射到調用進程的地址空間。在VC中有一類工程用於建立DLL。

      •庫程式檔案 .C:相當於給出一組函數定義的原始碼;      •模組定義檔案 .DEF:相當於定義連結選項,也可在原始碼中定義;如:DLL中函數的引入和引出(dllimport和dllexport)。      •編譯器利用 .C檔案產生目標模組 .OBJ      •庫管理程式利用 .DEF檔案產生DLL輸入庫 .LIB和輸出檔案 .EXP      •連結程式利用 .OBJ和 .EXP檔案產生動態連結程式庫 .DLL。 4.2. DLL的裝入方法 1)裝入時動態連結(load-time):           –在編程時顯式調用某個DLL函數,該DLL函數在可執行檔中稱為引入(import)函數。          –連結時需利用 .LIB檔案。在可執行檔中為引入的每個DLL建立一個IMAGE_IMPORT_DESCRIPTOR結構。

      在裝入時由系統根據該DLL映射在進程中的地址改寫Import Address Table中的各項函數指標。Hint是DLL函數在DLL檔案中的序號,當DLL檔案修改後,就未必指向原先的DLL函數。在裝入時,系統會尋找相應DLL,並把它映射到進程地址空間,獲得DLL中各函數的入口地址,定位本進程中對這些函數的引用

 

裝入時動態連結過程

 (註:Import Address Table是在裝入時依據DLL模組的載入位置確定)。

 

 

DLL函數的調用過程:

2)運行時動態連結(run-time):       在編程時通過LoadLibrary(給出DLL名稱,返回裝入和連結之後該DLL的控制代碼), FreeLibrary, GetProcAddress(其參數包括函數的符號名稱,返回該函數的入口指標)等API來使用DLL函數。這時不再需要引入庫(import library)。      –LoadLibrary或LoadLibraryEx把可執行模組映射到調用進程的地址空間,返回模組控制代碼;      –GetProcAddress獲得DLL中特定函數的指標,返回函數指標;      –FreeLibrary把DLL模組的引用計數減1;當引用計數為0時,拆除DLL模組到進程地址空間的映射; 運行時動態連結的例子
HINSTANCE hInstLibrary;//模組控制代碼定義DWORD (WINAPI *InstallStatusMIF)(char*, char*, char*, char*, char*, char*, char*, BOOL);//函數指標定義if (hInstLibrary = LoadLibrary("ismif32.dll"))//映射 {   InstallStatusMIF = (DWORD (WINAPI *)(char*,char*,char*, char*, char*, char*, char*, BOOL)) GetProcAddress(hInstLibrary, "InstallStatusMIF");//獲得函數指標if (InstallStatusMIF){        if (InstallStatusMIF(“office97”, “Microsoft”, “Office 97”, “999.999”, “ENU”, “1234”, ”Completed successfully”, TRUE) !=0)//調用DLL模組中的函數{}}FreeLibrary(hInstLibrary);//拆除映射 }

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.