深入理解計算機系統學習筆記(一)

 

1.1 對於C/C++編寫的程序,從源代碼到可執行文件,一般經過下面四個步驟:

1).預處理,產生.ii文件

預處理主要包含下面的內容:

a.對所有的“#define”進行宏展開;

b.處理所有的條件編譯指令,比如“#if”,“#ifdef”,“#elif”,“#else”,“#endif”

c.處理“#include”指令,這個過程是遞歸的,也就是說被包含的文件可能還包含其他文件

d.刪除所有的註釋“//”和“/**/”

e.添加行號和文件標識

f.保留所有的“#pragma”編譯器指令

    經過預處理後的.ii文件不包含任何宏定義,因爲所有的宏已經被展開,並且包含的文件也已經被插入到.ii文件中。

2).編譯,產生彙編文件(.s文件)

編譯的過程就是將預處理完的文件進行一系列詞法分析,語法分析,語義分析及優化後生成相應的彙編代碼文件(.s文件)

3).彙編,產生目標文件(.o或.obj文件)

彙編器是將彙編代碼轉變成機器可以執行的代碼,每一個彙編語句幾乎都對應一條機器指令。最終產生目標文件(.o或.obj文件)。

4).鏈接,產生可執行文件(.out或.exe文件)

鏈接的過程主要包括了地址和空間分配(Address and Storage Allocation)、符號決議(Symbol Resolution)和重定位(Relocation)

 

 

1.2 爲什麼需要做的編譯系統是如何工作的

1.優化程序性能。爲了在C程序中做出更好的編碼選擇,我們確實需要了解一些機器代碼以及編譯將不同C語句轉化成機器代碼的方式。比如,一個switch語句是否總是比一系列的if-else語句高效得多?一個函數調用的開銷有多大?while循環總是比for循環更有效嗎?指針引用比數組索引更有效嗎?爲什麼將循環求和的結果放到一個本地變量,會比將其放到一個通過引用傳遞過來的參數中,運行起來快很多?爲什麼我們只是簡單重新排列一下算術表達式的括號就能讓程序運行更快?

2.理解鏈接時出現的錯誤。根據我們的經驗,一些令人困擾的程序錯誤往往都與連接器操作有關,尤其是當你試圖構建大型的軟件系統時。比如,連接器報告說它無法解析一個引用,這是什麼意思?靜態變量和全局變量的區別是什麼?如果你在不同的C文件中定義了相同名字的兩個全局變量會發生什麼?靜態庫和動態庫的區別是什麼?我們在命令行上排列庫的順序有什麼影響?最嚴重的是有些鏈接錯誤直到運行時纔出現?(第7章)

3.避免安全漏洞。多年來,緩衝區溢出錯誤是造成大多數網絡和Internet服務器上安全漏洞的主要原因。存在這些錯誤是因爲很少有程序員能夠理解需要限制從不受信任的源接收數據的數量和格式。學習安全編程的第一步就是理解數據和控制信息存儲在程序上的方式會引起的後果。(第3章)

 

1.3 系統的硬件組成

1. 總線

貫穿整個系統的是一組電子管道,稱作總線,它攜帶信息字節並負責在各個部門間傳遞。通常總線被設計成傳送定長的字節塊,也就是字。字中的字節數(即字長)是一個基本的系統參數,各個系統中都不盡相同。大多數機器字長要麼是4個字節(32位),要麼是8個字節(64位)。

2 I/O設備

I/O(輸入/輸出)設備是系統與外部世界的聯繫通道。每一個I/O設備都通過一個控制器或適配器與I/O總線相連,控制器和適配器之間的區別主要在於它們的封裝方式。控制器是I/O設備本身或者系統的主印製電路板(通常稱爲主板)上的芯片組。而適配器則是一塊插在主板插槽上的卡,無論如何,它們的功能都是在I/O總線和I/O設備之間傳遞信息。

3. 主存

主存是一個臨時存儲設備,在處理器執行程序時,用來存放程序和程序處理的數據,從物理上來說,主存是由一組動態隨機存儲器(DRAM)芯片組成的。從邏輯上來說,存儲器是一個線性的字節數組,每個字節都有其唯一的地址(數組索引),這些地址是從零開始的。一般來說,組成程序的每條機器指令都由不同數量的字節構成。與C程序變量相對應的數據項的大小是根據類型變化的。

4. 處理器

中央處理單元(CPU),簡稱處理器,是解釋(或執行)存儲在主存中指令的引擎。處理的核心是一個大小爲一個字的存儲設備(或寄存器),稱爲程序計數器(PC)。在任何時刻,PC都指向主存中的某條機器語言指令(即含有該條指令的地址)。寄存器是一個小的存儲設備,由一些單個字長的寄存器組成,每個寄存器都有唯一的名字。ALU(邏輯單元)計算新的數據和地址值。CPU在指令的要求下可能會執行這些操作。

加載:從主存複製一個字節或者一個字到寄存器,以覆蓋寄存器原來的內容。

存儲:從寄存器複製一個字節或者一個字到主存的某個位置,以覆蓋這個位置上原來的內容。

操作:把兩個寄存器的內容複製到ALU,ALU對這兩個字做算術運算,並將結果存放到一個寄存器中,以覆蓋該寄存器中原來的內容。

跳轉:從指令本身中抽取一個字,並將這個字複製到程序計數器(PC)中,以覆蓋PC中原來的值。

我們將處理器的指令集架構和處理器的微體系結構區分開來:指令集架構描述的是每條機器代碼指令的效果;而微體系結構描述的是處理器實際上是如何實現的(第3、4、5章有介紹)。

 

根據機械原理,較大的存儲設備要比較小的存儲設備運行得慢,而快速設備的造價遠比高於同類的低速設備。

針對處理器與主存之間的差異,系統設計在者採用了更小更快的存儲設備,稱爲高速緩存存儲器(cache memory,簡稱cache或者高速緩存),作爲暫時的集結區域,存放處理器近期可能會需要的信息。

1.4 存儲設備形成層次結構

 

1.5 操作系統管理硬件

操作系統有兩個基本功能:

(1)防止硬件被失控的應用程序濫用;

(2)嚮應用程序提供簡單一致的機制來控制複雜而又通常大不相同的低級硬件設備。

操作系統通過幾個基本的抽象概念(進程、虛擬內存和文件)來實現這兩個功能。

文件是對I/O設備的抽象表示,虛擬內存是對主存和磁盤I/O設備的抽象表示,進程則是對處理器、主存和I/O設備的抽象表示。

進程

進程是操作系統對一個正在運行的程序的一種抽象。在一個系統上可以同時運行多個進程,而每個進程都好像在獨佔地使用硬件。而併發運行,則是說一個進程的指令和另外一個進程的指令是交錯執行的。大多數系統中,需要運行的進程數是多於可以運行它們的CPU數的。

從一個進程到另外一個進程的轉換是由操作系統內核(kernel)管理的。內核是操作系統代碼常駐主存的部分。當應用程序需要操作系統的某些操作時,比如讀寫文件,它就執行一條特殊的系統調用(system call)指令,將控制權傳遞給內核。然後內核執行被請求的操作並返回應用程序。注意,內核不是一個獨立的進程,相反,它是系統管理全部進程所用代碼和數據結構的集合。

 

線程

在現代系統中,一個進程實際上可以由多個稱爲線程的執行單元組成,每個線程都運行在進程的上下文中,並共享同樣的代碼和全局數據。

虛擬內存

虛擬內存是一個抽象概念,它爲每個過程提供了一個假象,即每個進程都在獨佔地使用主存。每個進程看到的內存都是一致的,稱爲虛擬地址空間。下圖是Linux進程的虛擬地址空間。在Linux中,地址空間最上面的區域是保留給操作系統中的代碼和數據,這對所有進程來說都是一樣,地址空間的底部區域存放用戶進程定義的代碼和數據。注意,圖中的地址是從下往上增大的。

 

程序代碼和數據:對所有的進程來說,代碼是從同一個固定地址開始,緊接着的是和C全局變量相對應的數據位置。代碼和數據區是直接按照可執行目標文件的內容初始化的,在實例中就是可執行文件hello。(第7章)

堆:代碼和數據區後緊接着的是運行時堆。代碼和數據區在進程一開始運行時就被指定了大小,與此不同,當調用malloc和free這樣的C標準庫函數時,堆可以在運行時動態地擴展和收縮。(第9章)

共享庫:大約在地址空間的中間部分是一塊用來存放像C標準庫和數學庫這樣的共享庫的代碼和數據的區域。共享庫的概念非常強大(第7章)。

棧:位於用戶虛擬地址空間頂部的是用戶棧,編譯器用它來實現函數調用,和堆一樣,用戶棧在程序執行期間可以動態地擴展和收縮。特別的,每次我們調用一個函數時,棧就會增長;從一個函數返回時,棧就會收縮(第3章)。

內核虛擬內存:地址空間頂部的區域是爲內核保留的。不允許應用程序讀寫這個區域的內容或者直接調用內核代碼定義的函數。相反,它們必須調用內核來執行這些操作。虛擬內存運作的基本思想是把一個進程虛擬內存的內容存儲在磁盤上,然後用主存作爲磁盤的高速緩存。(第9章)

文件

文件就是字節序列,僅此而已。它嚮應用程序提供了一個統一的視圖,來看待系統中可能含有的所有各式各樣的I/O設備。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章