1. 程式人生 > >CSAPP的零碎知識點總結

CSAPP的零碎知識點總結

CSAPP

從原始碼到可執行程式的步驟

C前處理器將.c檔案翻譯成.i檔案(ASCII碼)
處理所有#define,#include等帶井號的預編譯符號,刪除註釋(除了#pragma編譯器指令),新增行號和檔案標示符以便提示錯誤警告等

C編譯器將.i檔案翻譯成一個.s檔案(彙編)
詞法分析,語法分析,語義分析,原始碼優化,目的碼生成,目的碼優化

彙編器將.s檔案翻譯成.o檔案(可重定位目標檔案)

連結器將相關的所以.o檔案以及一些必要的系統目標檔案組合起來生成一個可執行目標檔案,同時要負責符號的解析和重定位,符號解析將目標檔案中的每個全域性符號都繫結到一個唯一的定義,而重定位確定每個符號最終的記憶體地址,並修改對那些目標的引用
靜態連結

:編譯階段將靜態庫加入可執行檔案,檔案大,簡而言之就是可執行檔案包含執行時所需的全部程式碼,不易於維護,只要庫變了,整個可執行檔案都得重新連結
動態連結:連結階段緊緊加入描述資訊,執行時在將相應的動態庫(也稱共享庫)載入到記憶體,只要呼叫介面不變,庫和程式碼本身就是相互獨立的,動態連結還可以分為裝載時連結和執行時連結,區別簡單來說就是編譯器是否知道程序要呼叫的DLL模組(動態連結庫),編譯動態庫必須是以位置無關程式碼方式編譯-fpic

ELF可重定向目標檔案結構(.o) 圖7-3
可執行目標檔案 圖7-13

載入器(exec函式家族來呼叫)將可執行目標檔案中的程式碼段和資料段從磁碟複製到記憶體中,然後跳轉到入口點(ELF頭中)來執行程式

程式執行時記憶體映像(程序地址空間)—圖7-15 棧位於核心記憶體(不可見)之下,棧的增長是伴隨著地址減小,堆的增長伴隨著地址的增大,中間還有一塊區域是共享庫的記憶體對映區域

打樁機制就是截獲動態庫函式的呼叫,轉而執行年自己的程式碼,你可以建立該函式的包裝函式,以此可以方便追蹤資訊

每個程序執行邏輯控制流的一部分,然後被搶佔(暫時掛起),然後輪到其他程序執行(時間分片),但看上去是在獨佔的使用處理器

兩個邏輯流在時間上重疊,那麼他們就是併發的,當兩個流併發的執行在不同處理器核上,那麼稱他們為並行流

程序為每個程式提供它自己的私有地址空間,和這個空間中某個地址相關聯的那個記憶體自己是不能被其他程序讀或者寫的

程式初始時都在使用者模式,使用者模式的程序不允許使用特權指令,程序從使用者模式變為核心模式的方法是中斷,故障或諸如陷入系統呼叫這樣的異常,異常發生時,控制傳遞到異常處理程式,處理器從使用者模式變為核心模式,處理程式執行在核心模式中,當他返回應用程式碼時,處理器從核心模式變回使用者模式
核心搶佔當前程序並重新開始先前被搶佔的程序,這中決策叫做排程,由核心中的排程器處理。在核心排程了一個新的程序執行後他就搶佔當前程序(舊的),並使用上下文切換機制將控制轉移到新的程序,儲存當前程序的上下文
如果系統呼叫因為等待事件發生而阻塞,核心可以讓當前程序休眠切換到另一程序,就發生了上下文切換 圖8-14

虛擬記憶體

主機中的所有程序共享主存(記憶體),這就伴隨這很多問題的出現,比如程序太多需要太多主存時,許多程序實際上就不能得到有效的執行。記憶體容易被破壞,如果多個程序一起工作時,某個程序王另一個程序的記憶體中寫入了東西,則會另一個程序就會感覺發生了莫名其妙的錯誤,為了解決多個程序使用記憶體出現的這些問題,就要用到虛擬記憶體

虛擬記憶體機制將主存看成是一個儲存在磁碟上的地址空間的快取記憶體,主存只儲存活動區域,根據需要在磁碟和主存之間來回傳遞資料,這樣便高效的使用了主存,並且他為每個程序提供了一致的地址空間,從而簡化了記憶體管理,保護了每個程序的地址空間不被其他程序破壞

頁面替換演算法
最佳置換演算法(OPT)
先進先出(FIFO)頁面置換演算法
最近最久未使用(LRU)置換演算法
時鐘(CLOCK)置換演算法

記憶體維護一個頁表,在實體記憶體和虛擬記憶體之間扮演一個橋樑的作用,頁表中的每個頁可能不為空也可能為空,為空表示該虛擬頁還未被分配,而每個虛擬頁還有一個有效位,當頁不為空時同時標記為1時,該頁的地址欄位就表示主存中相應的物理頁的起始地址,如果未被標記,則地址欄位就表示虛擬記憶體(磁碟)中相應的起始地址 ——–圖9-4

如果發生缺頁(主存不命中),則將虛擬記憶體中的相應的條目替換到主存中並將頁表中對應的頁標記為1
分配頁面時將頁表中某個地址位null的頁的地址替換為其在虛擬記憶體中的起始地址

整個程式在執行過程中需要引用的不同頁面很可能超過實體記憶體的總大小,這也許會導致缺頁的頻繁發生,導致效率變低,但是有個原則叫做區域性性原則,它保證了在任意時刻,程式將趨向與在一個較小的活動頁面集合上工作,這個集合叫做工作集(常駐集合)

每個程序都有一個獨立的虛擬地址空間,並且不同虛擬地址空間中的頁可以對映同一個物理頁,這個頁叫做共享頁面

如果我們僅僅是使用一個單獨的頁表來進行地址翻譯,那麼有時將會導致頁表的佔用太多的記憶體空間(頁表總是駐留在記憶體中),假設地址空間為32位,頁面位4kb,每個頁表條目4位元組,那將會需要2^32 / 2^12 * 4位元組也就是4mb的頁表,然而如果是64位呢,頁表將會更大,於是我們引入了多級頁表一級頁表對映多個二級頁表,二級頁表中的二級頁表中的PTE對映頁面,此時只有一級頁表是總駐留在主存中的,根據需要再建立,調入或調出2級頁表,當然最經常使用的二級頁表是會駐留在主存中的

(Linux)虛擬記憶體區域與一個磁碟上的物件(普通檔案或匿名檔案)關聯起來,以初始化虛擬記憶體區域的內容,這個過程叫做記憶體對映

動態記憶體分配

當執行是需要額外虛擬記憶體時可以使用mmap和munmap來建立和刪除虛擬記憶體,也可以使用方便的動態記憶體分配器來分配管理,動態記憶體分配器可以分為顯示分配器(C的malloc和free等,c++的new和delete等)和隱式分配器(也叫垃圾收集器,代表:JAVA)

分配器必須立即響應分配請求,不能為了提高效率重新排列或者緩衝請求

碎片分為內部碎片外部碎片
內部碎片 是指分配的區域大於其有效載荷,比如申請了8個字的空間而實際上8個字的空間並沒有完全用上,可能是因為使用者沒有正確估量自己所需要的實際大小,也可能是因為需要記憶體對齊等導致有些空間用不上
外部碎片 常常是因為一個並不大的空間被申請了一個更小的空間,而導致這個並不大的空間剩下的區域已經不能滿足分配需要,導致這塊剩下的小區域就沒用了,除非其前面或後面的區域被釋放,然後這塊小區域和剛釋放的區域合併,至於如何合併就又要引入一個概念叫 邊界標記

一塊連續的堆空間中,以分配的塊是用連結串列連結起來的,合併下一個塊很簡單,將指向下一個塊頭部的指標指向下下個塊頭部就行,那麼上一個塊需要合併呢,於是便需要引入邊界標記 圖9-39

堆空間中空閒的區域總是零散的,並且有大有小,並不能保證當前空閒區域大小等於所需要申請的大小,如果盲目的分配則會導致碎片等,如果挨個比較選取最優的那麼效率又會很低,如何抉擇呢?
隱式空閒連結串列
——–首次適配
——–下一次適配
——–最佳適配
——–最壞適配
顯示空閒連結串列 將空閒塊組織成顯式資料結構–雙向連結串列來維護
——–後進先出的順序維護,將新釋放的塊放置在連結串列開始處,結合首次適配使用
——–按地址順序維護,連結串列每個塊的地址都小於其後繼塊的大小
分離儲存的空閒連結串列 單向空閒連結串列的分配效率常常跟空閒塊數量成線性相關,如果我們將大小大致相同的空閒塊歸為同一個大小類,每個大小類用一個單向連結串列維護,然後用一個數組來維護多個空閒連結串列

垃圾收集器 像java之類的語言使用叫做垃圾收集器的動態記憶體分配器來分配記憶體,他嚴格精確的集iang記憶體視為一張有向圖,並區分出可達節點和不可達節點,而不可達節點將被視為垃圾,從而精確的回收所有垃圾,像C語言這樣的語言也有垃圾收集器,不過它並不能像JAVA之類的語言維持可達圖的精確表示,這樣的垃圾收集器叫做 保守的垃圾收集器 所以還是需要我們手動的控制