1. 程式人生 > >深入理解計算機系統----第一張計算機系統漫遊

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

轉載地址 https://www.jianshu.com/p/f0fd1473344e

 

資訊的表示

資訊就是位+上下文,系統中的所有資訊,包括磁碟檔案,程式,儲存器中資料以及網路傳輸的資料,都是一串位表示的.區分不同資料物件的唯一方法就是判斷其上下文.
比如11011101這串二進位制,可以表示221,在java的class檔案裡可能就代表一個JVM指令.

程式的編譯

一個簡單的C語言程式來說,一般要經過前處理器、編譯器、彙編器和連結器的處理,才能被翻譯成一段可執行的二進位制檔案。例如一段c語言helloworld程式.

1

 

程式的執行

hello.c已經被編譯成可執行檔案hello.如果在Unix系統中執行,需要一個叫sheel(外殼)的命令列直譯器載入執行.

 unix> ./hello
 hello, world
 unix>
  • 系統硬體

我們首先需要了解系統的硬體.

2


匯流排:貫穿整個系統的是一組電子管道,也就是匯流排。匯流排傳送的是字,字中的位元組數字長與系統相關,比如在32位作業系統當中,一個字是4個位元組,而在64位則是8個位元組。
I/O裝置:I/O裝置是系統與外部聯絡的通道.I/O裝置(鍵盤、滑鼠、顯示器等)由控制器(USB控制器)或者介面卡(圖形介面卡)與I/O匯流排相連,兩者的區別在於一個是主機板上的晶片組,一個是主機板插槽上的卡。
主存:它是計算機中的一個臨時儲存裝置,在處理器執行程式的時候,主存就是臨時存放資料的地方。物理上來說,它是由動態隨即存取儲存器晶片DRAM組成,邏輯上來說,它是一組連續的位元組陣列,每一個位元組都有唯一的地址,地址從0開始。
處理器

:中央處理單元,是解釋儲存在主存中指令的引擎.處理器的核心是一個程式計數器(PC),它在整個計算機執行的期間都會指向一個主存中的一個記憶體地址,而地址當中則是一個計算機指令。處理器所做的,就是不停的執行程式計數器所指向的每一條指令。處理器所做的操作是圍繞主存、暫存器檔案以及算術/邏輯單元(ALU)進行的,這裡面處理器做的最多的動作就是載入(從主存將資料複製到暫存器)、儲存(從暫存器將資料複製到主存)、操作(將兩個暫存器的內容複製到算術/邏輯單元進行操作,結果會再次複製到暫存器)以及跳轉(改變程式計數器當中的內容)。
處理器當中提到的是指令集結構,不過實際當中指令集的實現是非常複雜的,這麼做的目的是為了加速CPU的運算速度。我們可以這樣去區分指令集機構以及微體系結構,指令集結構是指令集的抽象描述,而微體系結構則是這個抽象描述的某一個具體實現
,類似於JAVA虛擬機器與JAVA虛擬機器實現的關係。

 

  • 執行過程

掃描:當我們在鍵盤輸入./hello時,linux的外殼程式(也就是命令列)會掃描我們輸入的字元,將這些字元讀入到暫存器當中,然後再放入主存。換句話說,./hello這幾個字元是經過了CPU中的暫存器從而到達了主存。

3


載入:好了外殼程式知道我們要執行hello這個程式了,開始載入,此過程會利用一種叫做儲存器存取的技術,使得資料不通過暫存器直接到達主存

4


執行並輸出:程式碼以及程式所需要的資料載入到主存後,CPU就開始從main函式的起始位置,依次執行程式中的機器語言指令。這些指令將"hello,world"這個字串依次載入到暫存器,然後傳輸到顯示器終端顯示.

5

 

快取記憶體

系統在資料的傳輸上花費了大量的時間。硬體開發商為了減少這種資料傳輸的時間成本,採用一種快取記憶體的技術去減少這種時間成本.

6


位於處理器晶片上的L1快取記憶體速度和暫存器幾乎一樣,容量更大的L2告訴快取則通過一條特殊匯流排連線到處理器,速度慢一些,但仍比主存快.它們採用靜態隨機訪問儲存器(SRAM)實現,有些系統甚至有L3告訴快取.

 

裝置層次

 

7


每一層上的儲存器都可以作為低一層儲存器的快取記憶體.

 

程序與執行緒

程序是作業系統對一個正在執行的程式的抽象。併發執行,指的是程序交錯執行.作業系統會記錄每一個程序的狀態,這些狀態就稱作程序的上下文。這些狀態主要包括了PC,暫存器以及主存的當前內容。當作業系統在程序間切換的時候,也會切換相應的上下文,從而保證程序恢復到之前的狀態.

8


現代系統中,一個程序可以由多個稱為執行緒的執行單元組成,執行緒執行在程序的上下文,共享相同的程式碼和資料.由於共享資料更容易,所以執行緒更高效.

 

虛擬儲存器

虛擬儲存器是一種抽象概念,為每個程序提供了一個假象,即每個程序看到的都是一致的儲存器.從物理上講,它包含了I/O裝置以及主存。在邏輯上講,虛擬儲存器被描述為虛擬地址空間.

9


這裡的地址自下向上依次增大,可以看出,圖中標註了起始地址,分別為0x08048000(32位)以及0x00400000(64位),然後向上分別是只讀程式碼和資料、讀寫資料、執行時堆、共享庫的記憶體對映區間、使用者棧以及核心虛擬記憶體區域.
程式程式碼和資料:這些內容的起始地址就是0x08048000,首先是程式碼,然後是一些全域性變數。
:是執行時可以動態擴充套件的一部分記憶體區域,它可以由malloc和free這樣的標準庫函式操作。
共享庫:用於存放共享庫的程式碼和資料。
:在使用者虛擬地址空間的頂部是棧,這部分割槽域與函式的執行有密切的關係。
核心虛擬儲存區域:核心是作業系統的一部分,核心也可以看做是一個程序,它在計算機執行期間總是在執行著,因此這部分記憶體區域對使用者程式是不可見的,通俗的說就是不能用。

 

檔案

檔案是I/O裝置邏輯上的概念,它其實就是位元組序列,也就是1和0組成的一些資訊。因此所有的I/O裝置,包括磁碟、鍵盤、滑鼠、顯示器都可以看成是檔案.系統中所有輸入輸出都是通過Unix I/O的系統函式呼叫讀寫檔案實現.

網路

所有的I/O裝置其實都是檔案這一抽象概念的具體表現,那麼網路其實也是檔案的一種,因為說到底,它也可以被看做是一系列的位元組序列。網路介面卡的作用就是給計算機輸入一堆被傳送過來的位元組序列,這裡面可能包括圖片、文字,甚至可能是程式碼等等.

併發與並行

書中的解釋是併發是指一個同時具有多個活動的系統,而並行則是指的用併發使得一個系統執行的更快.實際上通俗來講,在單cpu,併發是一種切換來切換去執行任務過程,而如果有多cpu,並行是真正可以同時執行不同任務的.
我們按照系統層次結構由高到低強調三個層次.

  1. 執行緒級併發:
    在程序的抽象概念下引入了執行緒,而執行緒級併發的概念,就是指的多個執行緒在同一時間(並非是絕對同時的)活動
    作業系統從單處理器,直到現在多核多處理器系統,乃至超執行緒技術,已經經歷了很大的變化。這也使得針對多執行緒程式設計變得更加重要,否則就無法利用多處理器帶來的好處。
    針對多處理器系統來說,比較好理解,其實就是物理上將多個CPU集中在一個積體電路的晶片上。而對於超執行緒技術來說,則是利用N個物理核心,模擬出2N個邏輯核心的技術。在硬體上來講,超執行緒需要CPU的某些部分有多個備份,比如暫存器和程式計數器,但是其它部分只有一份,比如ALU.
  2. 指令級併發
    指令級並行的解釋是,如果處理器可以同時執行多條指令,則稱這種屬性為指令級並行。其實指令級並行就是利用了指令的執行過程中會有不同的階段,或者更精確的說,是在同一時間只會利用部分CPU的硬體,因此可以利用這一點做到多個指令並行執行。
    更好的情況下,現代的很多處理器能夠做到執行一條指令的平均時間尚且不到一個週期,這種處理器就稱為超標量處理器。
  3. 單指令,多資料並行
    單指令、多資料的概念是指一條指令可以產生多個並行執行的操作的方式。當今的一些處理器中配備了特殊的硬體,可以達到這個效果。由於產生了多個並行執行的操作,因此就會涉及到多個數據,通俗的講也可以理解為,一條指令操作多個數據。比如書中所提到的例子,一些處理器具有並行地對4對單精度浮點數做加法的指令.

層次結構從高到低,從併發到並行,區別應該很明顯了.

抽象

抽象的重要性就不需要再強調了,它在電腦科學領域有著不言而喻的地位。抽象可以使得一些具體的實現變的更加易於描述,而且也可以針對一些實現的方式作出規定.
計算機系統提供的一些抽象.

10

 

思考幾個問題

先上一幅圖再說,假設我們要對一些數字做相加運算,顯然我們需要一個加法器,圖中選取了全加器:

11


這幅圖和前面書上的圖類似,但是多添加了幾點,需要考慮的地方
加電自檢:首先作業系統是怎麼執行起來的,通電時首先進行自檢,通過post程式檢查有沒有哪裡壞了,完了晶片bios上的rom寫入到記憶體的特定區域,完了再另外一個單獨區域(Kernal Space)載入核心系統,才能開始運作.
中斷:我們想要通過鍵盤輸入,那麼系統怎麼知道我們敲的是鍵盤呢?難道每時每刻保持監視鍵盤麼?其實是我們敲鍵盤時候會給出一箇中斷,訊號到南橋後,內部會整合一個可程式設計中斷控制器,得知做出行為的是鍵盤.然後再到北橋,控制器等等...
二進位制?:為什麼計算機要用二進位制?假設我們用八進位制,那麼我們也許需要八根線.因為元器件是通過電壓來標識自身,那麼可以設計類似1v,2v,..8v分別代表八進位制的1,2..8.但是如果是小數怎麼辦?一個高精度小數,用電壓來標識十分困難.而二進位制異常簡單,有電和沒電就能標識1和0.
加法器:圖上我們畫的是全加器,它會由兩個輸入數字,一個輸出和,一個輸入低位,一個輸出高位(注意這裡的一根線可不代表實際上的一根線,一個數字用二進位制表示意味著可能會需要多根線).看上去很不錯,但是如果我們除了輸入數字需要線以外,輸入指令也需要線.那怎麼樣一根線才能又輸入資料又可以輸入指令呢.可以用線路複用,新增一個控制位,標識輸入型別.
暫存器:要處理連續的運算,前面得出的結果要重新作為被加數,而後面的電壓因為新數字的輸入不斷變化,那麼前面的數存到哪裡去?暫存器應運而生.
快取:需要了解高度快取出現的理由,cpu運算速度太快,而主存運算速度太慢,時間都被浪費了,才需要快取記憶體.而之所以快取記憶體有存在價值,在於程式區域性性原理.這和伸展樹是很類似的嘛...

12


多工:早期的系統是所謂單道批處理系統,也就是一次執行一個作業,然後一個接著一個直到完成.後來有了所謂多道批處理,也就是多工了.那麼考慮一個問題,不同的作業之間可能需要的記憶體也不一樣,怎麼分配呢,分配的話每次訪問的地址怎麼辦,把前面的記憶體空間地址加進來?我們在對cpu進行時間切片的同時也會對記憶體進行切片(slice),地址則會重新編寫各自內部的地址,如圖中都是從101開始...
虛擬記憶體地址:前面說過,虛擬儲存空間為讓每個程序都看到一致的儲存空間.試想一個問題,程式設計時候每個程式需要記憶體空間,但每臺機器的各自不同,那怎麼統一呢?為每臺機器的記憶體容量分別編一個?這就用到虛擬記憶體地址了,32位的機器不管實際記憶體是多少,預設就是4g,64位同樣.
系統層次:前面提過儲存器層次,那麼現在會有系統層次,底部自然是一堆硬體,其次我們需要核心(系統)對硬體進行管理,完了就是程式,程式可以對核心進行直接操作,稱為系統呼叫但是這種程式編碼十分複雜.而庫則是進一層的封裝,所以有更多的程式是呼叫庫進行編寫的.另外一個需要提的是,系統怎麼知道使用者對哪個程式進行了操作?這時候需要shell這種程式了,shell分為圖形化(GUI)與命令列模式(CLI).
相容性:顯然對於相容的庫開發的程式在不同平臺上也能執行,只要庫一樣.而如果程式是直接呼叫系統核心的,那麼在不同系統由於硬體可能不相容的問題,那麼也許就沒法運行了.這在另一方面也就是API(Application Programming Interface)和ABI(Application Binary Interface)比較大的區別,前者是定義了原始碼和庫之間的介面,後者則是描述了應用程式(或者其他型別)和核心之間的低階介面.