標籤:
首先說說我為什麼要去讀這一章。這個學期開OS的課,在Morden Operating System上讀到和Process有關的內容時看到這樣一句話:“Process is fundamentally a container that holds all the information needed to run a program.”當時瞬間就想到了之前在csapp上看的模稜兩可的“目標可執行檔”這個概念,於是重新又把它的第7章給讀了一遍。
要理解linker的作用,首先要搞明白他在整個電腦系統中處於一個什麼樣的位置。
關於一個程式是怎樣從碼農們手撕的代碼變成記憶體中能跑起來的程式這個過程就不再過多的敘述,這篇文章只是著重的去講一下有關linker的這一部分。我們可以看到,linker的接受的輸入是若干個.o檔案,簡單的說就是經過彙編器編譯後產生的機器碼,學名叫“relocatable object file(可重定位的目標檔案)”,概念相近的稱呼也有“module(模組)”。而彙編器的輸出,是一個名叫“executable object program(目標可執行檔)”的二進位檔案,這個檔案的特徵就是可以直接拷貝到記憶體中不需做任何的更改便可以運行。那麼我們研究linker的作用是什麼就可以從這裡入手——為了構造最終的目標可執行檔,他需要對輸入若干可重定位的目標檔案做哪些事情?
linker的作用主要有兩個:
(1)符號解析(symbol resolution):將每個符號的定義和每個符號的引用聯絡起來。(就是讓系統明白,當這個程式run的時候,遇到的具體的變數或函數名,他們到底來自哪個檔案的定義?是自己這個?還是其他一起輸入linker的檔案?)
(2)重定位(relocation):把每個符號定義與儲存空間中的一個具體位置聯絡起來,然後修改所有對這些符號的引用,使得它們指向這個儲存空間的位置,從而重定位這些節。(在取得了每個符號的引用和定義的串連之後,要把符號的定義在儲存空間中綁定一個具體的地址)
書中對符號的解釋不是太清楚,至少我一開始的時候沒太理解這個概念,在這兒結合書本的內容我用自己的話來概括下我對這個概念的理解。“符號”可以分為3類:
1、由該模組定義的並且能被其他模組引用的“全域符號”。這裡的“全域符號”對應於C語言中的非靜態函數和全域變數。
2、由其他模組定義的由該模組引用的“全域符號”。解釋同上
3、由該模組定義的並且不能被其他模組引用的“全域符號”。對應於C語言中的靜態變數,即static變數。static關鍵字相當於C語言中的“private”,即只能被自己這個檔案(模組)使用的全域變數。
應當注意的是這裡的變數全是全域變數而不是函數內部的私人變數,私人變數由運行時stack儲存管理,linker對她並不感興趣:)
那麼在瞭解了符號的概念之後,要想具體的瞭解linker對可重定位的目標可執行檔做的一些事情,就要瞭解relocatable object file的一些結構(他是怎麼記錄自身的各種符號資訊的?)對不對?
大家第一次看到這個圖不要害怕,其實這就是彙編器(Assembler)將編譯器處理的原始碼檔案進行進一步的編譯或者說彙編之後形成的可重定位的目標可執行檔。這個檔案的一個個小格子就是一個個的“節(section)”,他們存放該program的各種資訊,在這裡我只會解釋幾個我認為對理解linker作用很有必要的section。
.text:已編譯器的機器代碼。
.data:已初始化的全域C變數。
.bss:未初始化的全域C變數。在這裡符號只是一個預留位置,它不佔用任何的記憶體空間。
.symtab:一個符號表,存放在程式中定義和引用的函數和全部全域變數的資訊。
.rel.test:存放代碼的重定位條目(relocation entry)。
.rel.data:存放資料的重定位條目。
以上都屬於本章的基礎知識鋪墊部分,理解了上述內容,就可以很容易的理解linker對可重定位的目標可執行檔所做的操作了。
1、符號解析
linker解析符號的方法是將每個符號的引用與所有輸入的relocatable object file中的.symtab節中所有的符號定義中確定的一個聯絡起來。
1.1連結器如何解釋多重定義的全域符號?
對於定義和引用都在一個module中的符號,linker的操作很簡單,不需要指來指去改來改去;而真正要深入探討的操作是對定義和引用不在同一個檔案中的符號,尤其是當尋找到的符號定義有重名時。對此linker的做法是:
(1)定義強符號和弱符號的概念。函數和已初始化的變數是強符號,為初始化的變數是弱符號。
(2)設定規則。當有多重定義衝突的時候,linker遵循的規則是:
one:不允許有多個強符號定義
two:如果有一個強符號和多個弱符號定義,那麼選擇強符號定義
three:如果有多個弱符號定義,那麼隨便選擇一個
1.2與靜態庫連結
為什麼會有“靜態庫”(static libraries)這個概念?
首先在C語言編程中,我們需要實現豐富的功能,就要使用各種各樣的函數介面。以ANSI C為例,它定義了一組廣泛的標準I/O、字串操作和整數數學函數,例如atoi、printf、scanf、strcpy、rand。他們在libc.a庫中,對每個C程式來說都是可用的。如果不使用靜態庫,我們看看編程開發人員可以用什麼其他的辦法來向使用者提供這些函數。
一種實現的方法是讓編譯器直接辨認出對函數的調用,並直接產生相應的代碼——這顯然是不可行的,C語言中有大量的函數,這樣做顯然會使得編譯器的設計變得相當複雜,每次添加、修改、刪除一個函數時,都需要一個新的編譯器版本。雖然對於編程人員而言這樣是十分方便的,因為所有的標準函數都是直接可用的。
另一種實現的方法是將所有的這些函數放到一個單獨的可重定位的目標可執行檔中,它的優點是將編譯器的設計與標準函數的實現分離開來,在一定程度上仍然便利編程人員。但是這樣做的缺點卻是每次運行程式的時候都要將該裝載函數的rof檔案copy到記憶體中去,而這樣是很浪費記憶體空間的。而且同樣將這麼一大批函數賽到一個檔案中,每次的維護都要重現編譯整個源檔案,這又是相當大的一個工作量。
何為靜態庫?
在Unix中,靜態庫以archive這種特殊的檔案格式存在於磁碟中,是一組串連起來的relocatable object file的集合。
1.3連結器如何使用靜態庫來解析引用
維護一個基於(U,E,D)三個集合的演算法
2、重定位
在這個過程中,將合併模組並為每個符號分配運行時的地址。重定位由兩個步驟組成:
在這裡有一個需要理解的概念是重定位條目(relocation entry)。在彙編器產生一個可重定位的目標模組時,當遇到UNDEFINED的符號,即不知道該資料或代碼最終該存放到儲存空間的什麼位置時,它就會為該符號產生一個重定位條目,即之前介紹的可重定位目標檔案中的.rel.text和.rel.data兩個表所記錄的內容。
連結器(linker)的作用——CSAPP第7章讀書筆記