深入理解計算機系統 ---計算機系統漫遊

hello.c源程序
本書的目的: 瞭解你在系統上執行hello程序時,系統發生了什麼 以及 爲什麼會這樣。
(這句話的背後,隱藏了不爲人知,令人抓狂的故事)

1.1 信息就是上下文 + 位

計算機系統是由硬件和系統軟件 組成的,它們共同工作來運行應用程序。

源程序(hello.c)就是由0和1組成的位(bit,又稱爲比特)序列,8個位被組織成一組,成爲字節。每個字節表示程序中的某些文本字符。

8bit = 1byte(B)
1kb = 1024byte(b)
1mb = 1024kb
類推。

大部分計算機都使用ASCLL標準來表示文本字符,用一個唯一的字節大小的整數值來表每個字符。

ASCLL表
Hello.c程序是以字節序列的方式存儲在文件中的。
Hello.c的表示方法說明了一個基本思想:系統中所有的信息,都是一串比特表示的。

1.2程序被其它程序翻譯成不同的格式

在這裏插入圖片描述
爲了在系統上運行hello.c程序,每條C語句都必須被其它程序轉換爲一系列低級機器語言指令,這些指令按照一種稱爲**可執行目標程序的格式(windows PE, Linux ELF)**打好包並以二進制文件的形式存儲起來。

預處理階段,預處理器根據以字符#開頭的命令,修改原始的C程序
比如: #include <stdio.h> 命令告訴預處理器讀取系統頭文件stdio.h的內容
並把它直接插入程序文本中,然後得到了另一個C程序, .i 作爲文件擴展名

編譯階段,編譯器將文本文件hello.i翻譯成文本文件hello.s,它包含一個會變語言程序
該程序包含一個main的定義
在這裏插入圖片描述
2-7行, 每條語句都以一種文本格式(彙編語言)描述了一條低級機器語言指令。

彙編階段,彙編器將hello.s翻譯成機器指令,把這些指令打包成一種叫做可重定位目標程序的格式,保存在目標文件hello.o中
Hello.o是一個二進制文件,包含指令編碼

鏈接階段,hello程序調用了printf函數,標準c庫中的一個函數,位於printf.o的單獨編譯好了的目標文件中,這個文件已某種方式合併到我們的hello.o程序中,鏈接器負責合併,結果得到hello文件,可執行目標文件,可以被加載到內存中,由系統執行

1.3瞭解編譯系統如何工作是大有益處的

優化程序性能
switch 爲什麼 比 if…else… 高效,什麼情況下高效
while 比 for 更有效嗎?
等等諸多問題

理解鏈接時出現的錯
靜態變量 全局變量 區別,等等諸多問題

避免安全漏洞
如 緩衝區溢出 等等諸多問題

1.4處理器讀並解釋存儲在內存中的指令
Hello.c被翻譯成了可執行目標文件hello,Linux中執行這個目標文件:
在這裏插入圖片描述
Shell命令行解釋器,輸入並執行這個指令,如果不是一個指令,shell會假設這是可執行目標文件的名字,加載並運行這個文件。

1.4.1系統的硬件組成

在這裏插入圖片描述

  1. 總線
    貫穿整個系統的是一組電子管道,稱爲總線,攜帶信息字節負責在各個部件間傳遞
  2. I/O設備
    輸入/輸出設備是系統與外部世界聯繫的通道
  3. 主存
    主存是一個臨時設備,在處理器執行程序時,用來存放程序和程序處理的數據
    從物理上來說,主存是由一組動態隨機存取存儲器(DRAM)芯片組成
    從邏輯上來說,存儲器是一個線性的字節數組,每個字節都有其唯一的地址(數組索引)
    這些地址是從0開始,一般來說,組成程序的每條機器指令都由不同數量的字節構成
  4. 處理器
    中央處理單元,是解釋(執行)存儲在主存中的指令引擎
    處理器核心是存儲設備(寄存器),成爲程序計數器(PC)
    在任何時候,PC都指向主存中的某條機器語言指令(即含有該條指令的地址)

從通電開始,指點系統斷電,處理器一直在不斷地執行程序計數器指向的指令
再更新程序計數器,使其只想下一條指令

處理器看上去是按照一個非常簡單的指令模型來操作的,這個模型是由 指令集架構 決定的

在這個模型中,指令按照嚴格的順序執行,而執行一條指令包含執行一系列的步驟
處理器從程序計數器指向的內存處讀取指令,解釋指令中的位,執行該指令的簡單操作
然後更新PC,使其指向下一條指令,而這條指令並不一定和在內存中剛剛執行的指令相鄰

它們圍繞着主存,寄存器文件,算術/邏輯單元(ALU)進行。
在這裏插入圖片描述

1.4.2運行hello程序

在這裏插入圖片描述
初始時,shell程序執行它的指令,等待我們輸入一個命令
Shell程序將字符逐一讀入寄存器再把它放到內存中
當我們在鍵盤上敲擊回車鍵時,shell程序知道我們結束了命令的輸入

然後shell執行一系列指令來加載可執行的hello文件
這些指令將hello文件中的數據和代碼從磁盤複製到主存
利用直接存儲器存取(DMA)技術,數據可以不通過處理器直接從磁盤到達主存

在這裏插入圖片描述
一旦目標文件hello中的代碼和數據被加載到主存,處理器開始執行hello程序的main程序中的機器語言指令,這些指令將”hello world\n”,字符串中的字節從主存複製到寄存器文件,再從寄存器文件中複製到顯示設備,最終顯示在屏幕上
在這裏插入圖片描述

1.5高速緩衝至關重要

上邊簡單的示例揭示了一個重要的問題,系統花費了大量的時間把信息從一個地方挪到另一個地方,對程序運行的效率有很大的阻礙
在這裏插入圖片描述
針對這種處理器與只存之間的差異,設計了採用更小更快的存儲設備
稱爲高速緩存存儲器(cache memory),作爲暫時的集結區域,存放處理器近期可能會需要的信息
在這裏插入圖片描述
訪問速度幾乎和訪問寄存器文件一樣快
L1和L2高速緩存是用一種叫做靜態隨機訪問存儲器(SRAM)的硬件技術實現的

告訴緩存存在的應用程序員能夠利用高速緩存將程序的性能提高一個數量級

1.6存儲設備形成層次結構

在這裏插入圖片描述

1.7操作系統管理硬件

當shell加載和運行hello程序時,以及hello程序輸出自己的消息時,shell和hello程序都沒有直接訪問鍵盤,顯示器,磁盤或者主存
取而代之的是,他們依靠操作系統提供的服務
我們可以把操作系統看作是應用程序和硬件之間插入的一層軟件
在這裏插入圖片描述
所有應用程序對硬件的操作嘗試都必須通過操作系統

操作系統兩大基本功能:
1.防止硬件被失控的應用程序濫用
2.嚮應用程序提供簡單一致的機制控制複雜而又通常大不相同的低級硬件設備
操作系統通過幾個基本的抽象概念(進程,虛擬內存和文件),來實現這兩個功能
在這裏插入圖片描述
文件是對I/O設備的抽象表示
虛擬內存是對主存和磁盤I/O設備的抽象表示
進程則是對處理器,主存和I/O設備的抽象表示

1.7.1進程

像hello這樣的程序在現代系統上運行時,操作系統會提供一種假象,就好像系統上只有這個程序在運行
程序看上去是獨佔地使用處理器,主存和I/O設備
處理器看上去就像在不間斷地一條接一條地執行程序中的指令
即程序的代碼和數據是系統內存中唯一的對象
這些假象是通過進程的概念來實現的

進程是操作系統對一個正在運行的程序的一種抽象,一個系統可以同時運行多個進程
而每個進程都好像獨佔地使用硬件
而併發運行,一個進程的指令和另一個進程的指令是交錯執行的

大多數操作系統中,需要運行的進程數是多於可以運行它們的CPU個數的
傳統系統在一個時刻只能運行一個程序,多核處理器同時能夠執行多個程序
無論是在單核還是多核系統中,一個CPU看上去都像是在併發地執行多個進程
這是通過處理器在進程間切換來實現的,操作系統實現這種交錯執行的機制稱爲上下文切換

操作系統保持跟蹤進程運行所需的所以狀態信息,這種狀態也就是上下文
包括許多信息,比如PC和寄存器文件的當前值,主存的內容
在任何一個時刻,單處理器系統都只能執行一個進程的代碼
當操作系統決定要把控制權從當前進程轉移到某個新進程時,就會進行上下文切換
即保存當前進程的上下文,恢復新進程的上下文,然後將控制權轉移到新進程
新進程就會從它上次停止的地方開始

在這裏插入圖片描述
A爲shell程序,B爲hello程序。
最開始只有shell程序在運行,等待命令輸入,讓它運行hello程序時,shell通過調用一個專門的函數,即系統調用,來執行我們的請求,系統調用會將控制權傳遞給操作系統
操作系統保存shell進程的上下文,創建一個新的hello程序及其上下文,然後將控制權給新的hello進程,hello進程終止後,操作系統恢復shell進程的上下文,並將控制權傳回給它,shell進程會繼續等待下一個命令行輸入

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

實現進程這個抽象的概念需要低級硬件和操作系統之間的緊密合作

1.7.2線程

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

通常來說,進程是容器-空間,存放資源的
線程是真正執行東西的

1.7.3虛擬內存

虛擬內存是一個抽象概念,爲每個進程提供了一個假象,即每個進程都在獨佔使用主存
每個進程看到的內存都是一致的,稱爲虛擬地址空間
在這裏插入圖片描述
圖爲Linux進程的虛擬地址空間,地址從下往上增長
地址空間最上面的區域是保留給操作系統中的代碼和數據的,這對所有進程來說都是一樣的

每個進程看到的虛擬地址空間由大量準確定義的區構成,每個區都有專門的功能

程序代碼和數據。 對所有的進程來說,代碼是從同一固定地址開始
緊接着的是和C全局變量相對應的數據位置,代碼和數據區是直接按照可執行文件內容初始化的

堆。 代碼和數據區後緊隨着的是運行時 堆。代碼和數據區在進程一開始時就被指定了大小
與此不同,當調用像malloc和free這樣的函數時,堆可以在運行時動態地擴展和收縮

共享庫。 大約在地址空間的中間部分是一塊用來存放像C標準庫和數學庫的共享庫的代碼和數據的區域

棧。 位於用戶虛擬空間頂部的就是用戶棧,編譯器用它來實現函數調用
和堆一樣可動態擴展和收縮,每次調用一個函數時,就會增長,返回時,就會收縮

內核虛擬內存。 地址空間頂部區域是爲內核保留的,不允許應用程序讀寫這個區域的內容
或者直接調用內核代碼定義的函數,它們必須調用內核來執行這些操作

虛擬內存的運作需要硬件和操作系統軟件之間精密複雜的交互,包括對處理器生成的每個地址的硬件翻譯
基本思想是把一個進程虛擬內存的內容存儲在磁盤上,然後用主存作爲磁盤的高速緩存

1.7.4文件

文件就是字節序列。

1.8系統之間利用網路通信

系統漫遊就到這裏了
系統是一個孤立的硬件和軟件的集合體

現在系統經常通過網絡和其他系統連接到一起,從一個單獨的系統看,網絡可視爲一個I/O設備,當從主存複製一串字節到網絡適配器時,數據流經過網絡到達另一臺機器,相似地,系統可以讀取從其他機器發送來的數據,並把數據複製到自己的主存
在這裏插入圖片描述

1.9.1Amdahl定律

……………

1.9.2併發和並行

…………….

1.9.3計算機系統中抽象的重要性

例如,爲一組函數規定一個簡單的應用程序接口(API)就是一個良好的變成習慣
程序員無需瞭解它內部的工作便可以使用這些代碼,不同變成語言提供不同形式和等級的抽象支持,例如JAVA類聲明和C語言的函數原型

在處理器中指令集架構提供了對實際處理器硬件的抽象,使用這個抽象,機器代碼程序表現得就好像運行在一個一次只執行一條指令的處理器上
底層硬件永遠比抽象描述的要複雜精細,它並行地執行多條指令,但又總是與那個簡單有序的模型保持一致,只要執行模型一樣,不用的處理器也能執行相同的機器代碼,提供不同的開銷和性能
在這裏插入圖片描述
文件是對I/O設備的抽象
虛擬內存是對程序存儲器的抽象
進程是對一個正在運行的程序的抽象
虛擬機是對整個計算機的抽象,包括操作系統,處理器和程序

本章小結:

在這裏插入圖片描述
所有你看起來非常厲害的技術,都是建立在基礎上的

永遠不要對知識侷限於書本上的內容

規則,是可以被打破的!

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