天天看點

01 計算機系統漫遊1 程式是如何存儲與執行的2 高速緩存3 存儲器層次結構4 作業系統管理硬體5. 網絡6 并發與并行7 抽象 [補充]相關文章

1 程式是如何存儲與執行的

在這一節中,将介紹:

  • 程式是如何儲存在計算機中,
  • 并且如何轉換成計算機可識别、可執行的資訊,
  • 然後介紹計算機硬體中是如何一步步執行程式的。

是以首先簡單介紹計算機的硬體組成,以此作為基礎後,一步步介紹程式是如何存儲并執行的。

1.1 計算機硬體簡介

典型的計算機硬體組成,可分為三部分:CPU、RAM、I/O。

01 計算機系統漫遊1 程式是如何存儲與執行的2 高速緩存3 存儲器層次結構4 作業系統管理硬體5. 網絡6 并發與并行7 抽象 [補充]相關文章

下面介紹比較重要的部分:

1.1.1 總線

總線是貫穿各個計算機硬體的橋梁。它攜帶資訊,并且負責将資訊在各個部件之間進行傳遞。總線通常被設計成傳送特定長度的位元組塊,稱為 字(Word)。這是一個基本的系統參數,不同系統中各不相同。

總線主要包含:

  • 資料總線:通常用來傳輸資料。如從主存RAM中傳輸資料到CPU中
  • 位址總線:主要用于傳輸位址。如從RAM的位址1000處擷取資料,這裡的1000就是通過位址總線進行傳輸的
  • 控制總線:主要傳輸控制、時序資訊。如讀寫、中斷等

1.1.2 I/O裝置

I/O裝置:是系統和外部世界的聯系通道。

每個I/O裝置都通過一個 控制器 或 擴充卡 與I/O總線相連,負責I/O裝置和I/O總線間的資訊傳遞。

控制器與擴充卡的差別:Linux控制器(Controller)與擴充卡(Adapter)
  • 控制器(Controller):內建在CPU主機闆上,并可以将CPU發來的邏輯指令通過特定協定轉換為裝置可以識别的控制信号;
  • 擴充卡(Adapter):獨立的外部裝置,可以實作和控制器一樣的功能,如網卡等;
一般情況下,
  • 對于磁盤來說,控制資料/指令轉換的機制都是靠控制器來實作的;
  • 但是對于網卡,USB等裝置來說,則是靠擴充卡來實作的。

1.1.3 主存(RAM)

是一個臨時儲存設備。

當程式運作時,主要儲存程式以及程式處理的資料。基本機關是位元組(Byte),從邏輯上看,對每個位元組都指定了唯一的位址,這個位址從0開始。

1.1.4 處理器

中央處理器 (英語:Central Processing Unit,縮寫:CPU)。功能主要是解釋計算機指令以及處理計算機軟體中的資料。

一個CPU由若幹部分組成:

  • 寄存器:通常為8位寄存器,用來儲存一個位元組的資料。

    CPU中有若幹寄存器,每個寄存器都有唯一的位址,用來儲存CPU中臨時運算結果。其中有兩個寄存器比較特殊:

    • 指令位址寄存器(程式計數器):儲存目前指令在記憶體中的位址,每次執行完一條指令後,會對該寄存器的值進行修改,指向下一條指令的位址。
    • 指令寄存器:儲存目前從主存中擷取的,需要執行的指令。
  • 算術邏輯單元(ALU):主要用來處理CPU中的數學和邏輯運算。

    它包含兩個二進制輸入,以及一個操作碼輸入,用來決定對兩個輸入進行的算數邏輯操作。然後會輸出對應的運算結果,以及具有各種标志位,比如結果是否為0、結果是否為負數等等。

    它有一個算術單元(Arithmetic Unit),一個邏輯單元(Logic Unit)。
    • 算術單元:負責計算機裡的所有數字操作,比如加減法、增量運算(給某個數字+1)等等。
    • 邏輯單元:執行邏輯操作,比如AND、OR和NOT操作,也能做簡單的數值測試,比如數字是不是負數。
  • 控制單元:是一系列門控電路,通過門控電路來判斷指令寄存器中儲存的指令内容,然後調整控制 主存 和 寄存器 的讀寫資料和位址,以及使用ALU進行運算。
    簡單了解為:一系列門控電路,然後根據你程式的指令來調控CPU中的各種資源。

CPU中執行指令的過程:提取、解碼、執行和寫回。

  1. 提取:首先,根據“指令位址寄存器”從記憶體中擷取對應位址的資料;
  2. 解碼:然後,将其儲存在“指令寄存器”中;
  3. 執行:“控制單元”會對指令内容進行判斷,并調用寄存器、ALU等執行指令内容;
  4. 寫回:以一定格式将執行階段的結果簡單的寫回。(運算結果經常被寫進CPU内部的寄存器,以供随後指令快速通路。)
  5. 最後,更新“指令位址寄存器”,使其指向下一個要執行的指令位址。

本小節參考内容:

[深度人工智障:讀書筆記]《計算機科學速成課》—5 算術邏輯單元-ALU

[深度人工智障:讀書筆記]《計算機科學速成課》—6 寄存器和記憶體

[深度人工智障:讀書筆記]《計算機科學速成課》—7 中央處理器CPU

1.2 程式存儲和執行

以最簡單的C程式為例:

// test.c
#include <stdio.h>
int main(){
    printf("hello world\n");
    return 0;
}
           

這段代碼需要儲存在一個檔案中,稱為源檔案,這是這段程式生命周期的開始。然而計算機隻認識二進制的0和1,它根本不認識文本裡面到底寫的什麼。

是以大部分的現代計算機系統都會使用ASCII标準來表示這些文本,簡單來說就是給每個字元都指定一個唯一的單位元組大小的編号,然後将文本中的字元都根據ASCII标準替換成對應的編号後,就轉換成了位元組序列,是以該源檔案是以位元組序列的形式儲存在檔案中的。

我們可以通過OD列印上面程式的所有字元的ASCII碼(方法示範),結果如下所示:
PS D:\E\VSOCED> od -Ax -tcd1 .\test.c
000000   /   /       t   e   s   t   .   c  \r  \n   #   i   n   c   l
         47   47   32  116  101  115  116   46   99   13   10   35  105  110   99  108
000010   u   d   e       <   s   t   d   i   o   .   h   >  \r  \n   i
        117  100  101   32   60  115  116  100  105  111   46  104   62   13   10  105
000020   n   t       m   a   i   n   (   )  \r  \n   {  \r  \n
        110  116   32  109   97  105  110   40   41   13   10  123   13   10   32   32
000030           p   r   i   n   t   f   (   "   h   e   l   l   o    
         32   32  112  114  105  110  116  102   40   34  104  101  108  108  111   32
000040   w   o   r   l   d   \   n   "   )   ;  \r  \n
        119  111  114  108  100   92  110   34   41   59   13   10   32   32   32   32
000050   r   e   t   u   r   n       0   ;  \r  \n   }
        114  101  116  117  114  110   32   48   59   13   10  125
00005c
           

從上面可知,系統中的所有資訊都是由一串比特表示的,區分不同資料對象的唯一方法就是上下文。比如:

  • 在一串數字中,0x90表示154
  • 在一串機器碼中,0x90表示nop指令
  • 在 一串字元串中,0x90表示一個特殊的字元

為了人們能夠讀懂程式的功能,是以使用了進階語言如c寫了這段程式。但是對于機器而言,它隻能執行指令集中包含的指令。是以,為了機器能夠在系統中運作這段程式,我們:

  • 首先,需要将每句c語句都轉換成一系列的低級機器語言指令;
  • 然後,将這些指令按照可執行目标程式的格式打包好後,以二進制磁盤檔案形式儲存起來,該檔案稱為目标檔案。

這種,從源檔案 ——》目标檔案 的過程由編譯器驅動。該過程主要分為4階段:(也可參考:Linux編譯過程)

01 計算機系統漫遊1 程式是如何存儲與執行的2 高速緩存3 存儲器層次結構4 作業系統管理硬體5. 網絡6 并發與并行7 抽象 [補充]相關文章

1.2.1 預處理階段

*.c

——》

*.i

C預處理器主要負責:文本替換(text substitution)、注釋剝離(stripping comments)、檔案包含(file inclusive)。

  • 在我們的源代碼中使用預處理指令來請求 文本替換 和 檔案包含。在代碼中使用**“

    #

    ”**來表示這是預處理指令。
    • 第一個是頭檔案

      stdio.h

      ,他是包含在源檔案中的。(檔案包含),即将

      #include <stdio.h>

      替換成頭檔案

      stdio.h

      中的内容。
    • 第二個是字元串替換。(文本替換)

1.2.2 編譯階段

*.i

——》

*.s

在這個階段中,Gcc首先要檢查代碼的規範性、是否有文法錯誤等,以确定代碼的實際要做的工作,在檢查無誤後,Gcc把代碼翻譯成彙編語言。

這樣做的好處在于,通過為不同語言 不同系統上配置 不同的編譯器,能夠提供通用的彙編語言,這樣對于相同的語言,就能相容不同的作業系統,而對于同一個系統上,通過安裝不同語言的編譯器,也能運作不同語言寫的程式了。

而彙編語言相對C語言更加低級,它對機器碼進行了修飾,為每一個操作碼提供了更加簡單、容易記的助記符,并且提供了很多機器碼不具有的功能,比如自動解析JUMP指令位址等等。該語言的編寫和底層硬體連接配接很密切,程式員仍需要思考使用什麼寄存器和記憶體位址。

我們這裡使用指令集架構來提供對實際處理器硬體的抽象,這樣機器代碼就好像運作在一個一次隻執行一條指令的處理器上。

可參考:[深度人工智障:讀書筆記]《計算機科學速成課》—11 程式設計語言發展史

1.2.3 彙編階段

*.s

——》

*.o

彙編階段是把編譯階段生成的彙編代碼檔案”

*.s

”轉成二進制目标代碼(機器可讀的機器語言)。

1.2.4 連結階段

連結是産生可執行程式檔案或 可與其他目标檔案結合産生可執行檔案的最後階段。

我們寫代碼時通常會使用C标準庫中提供的函數,但是我們代碼中并沒有這些函數的具體實作,是以就需要在連結階段将該函數的具體實作合并到我們的

hello.o

。比如我們程式中使用了

printf

函數,而該函數存在于一個單獨預編譯好的目标檔案

printf.o

中,是以我們隻需要将該檔案合并到我們的

hello.o

中,就能正确使用該函數了。

最終得到的

hello

檔案就是可執行目标檔案,可以被加載到記憶體中,由系統執行。

1.2.5 執行 可執行程式

通過以上的編譯過程,我們從由C語言的源檔案

hello.c

編譯得到了可執行目标檔案

hello

,接下來我們就可以運作該目标檔案了

  1. shell讀入我們輸入的字元

    ./hello

    後,将其逐一讀入到CPU的寄存器中,然後再将其存放到主存中。
  2. 輸入回車後,shell執行一系列指令将hello目标檔案中的代碼和資料從磁盤複制到主存。
  3. CPU開始執行hello的main程式中的機器指令,它将

    hello, world\n

    字元串中的位元組從主存複制到CPU寄存器,再從CPU寄存器複制到顯示裝置。

通過以上過程,我們就完成了程式的儲存和執行的完整過程。

可參考:[深度人工智障:讀書筆記]《計算機科學速成課》—7 中央處理器CPU

2 高速緩存

在運作程式的過程中,發現一個很重要的問題:系統花費了大量的時間把資訊從一個地方挪到另一個地方。

在上面執行個體中:test程式的機器指令開始是放在磁盤上

  • 程式加載時,它們被複制到主存(從磁盤到記憶體)
  • 程式運作時,它們被複制到處理器(從記憶體到處理器)

"hello, world\n"資料最開始在磁盤,後來複制到記憶體,然後是顯示裝置。

這些複制就是開銷,減緩了程式的工作。怎麼才能使這些複制工作盡快完成呢?

答:處理器和記憶體的速度差異非常大,系統設計者系統設計者采用了更小、更快的儲存設備,即高速緩存存儲器(簡稱高速緩存),作為暫時的集結區域,用來存放處理器近期可能會需要的資訊。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-uQD8SLrc-1615013591794)(https://github.com/xxg1413/CSAPP/raw/master/Chapter01/image/1-5-1.png)]

在單處理器系統中,一般含有二級緩存,最小的L1高速緩存速度幾乎和通路存儲器相當,大一些的L2高速緩存通過特殊總線連接配接到處理器,雖然比L1高速緩存慢,但是還是比直接通路主存來的快。在多核處理器中,還有一個L3高速緩存,用來共享多個核之間的資料。

問:為什麼需要n級緩存呢?

答:因為系統可以獲得一個很大的存儲器,同時通路速度也很快,原因是利用了高速緩存的局部性原理,即程式具有通路局部區域裡的資料和代碼的趨勢。通過讓高速緩存裡存放可能經常通路的資料的方法,大部分的存儲器操作都能在快速的高速緩存中完成。

一般利用了高速緩存的程式會比沒有使用高速緩存的程式的性能提高一個數量級。

3 存儲器層次結構

實際上,每個計算機系統中的儲存設備都被組織成了一個存儲器層次結構,下圖所示:

01 計算機系統漫遊1 程式是如何存儲與執行的2 高速緩存3 存儲器層次結構4 作業系統管理硬體5. 網絡6 并發與并行7 抽象 [補充]相關文章

存儲器層次結構的主要思想是一層上的存儲器作為低一層存儲器的高速緩存。是以:

  • 寄存器檔案就是L1的高速緩存,
  • L1是L2的高速緩存,
  • L2是L3的高速緩存,
  • L3是主存的高速緩存,
  • 而主存又是磁盤的高速緩存。
  • 在某些具有分布式檔案系統的網絡系統中,本地磁盤就是存儲在其他系統中磁盤上的資料的高速緩存。

4 作業系統管理硬體

作業系統的出現避免了程式員直接去操作硬體(主存、處理器、I/O裝置),它可以看成是應用程式和硬體之間的一層軟體。

給程式員提供硬體的抽象:

  • 比如将正在運作的程式抽象為程序;
  • 将程式操作的主存抽象為虛拟記憶體;
  • 将各種I/O裝置抽象為檔案的形式,讓程式員能夠直接通過這層軟體很好地調用硬體,避免了過多的硬體細節。

接下來将簡單介紹這三層抽象。

4.1 程序

程序是作業系統對一個正在運作的程式的一種抽象。

計算機執行程式時,需要将程式對應的指令儲存在記憶體中,并且使用CPU和I/O裝置,但是單核計算機一個時刻隻能處理一個程式。但是從我們的視角來看,計算機像在同時處理好多程式,比如你可以在shell中運作

hello

,此時就運作了shell程式和

hello

程式。

為了友善對運作程式時所需的硬體進行操作,作業系統對正在運作的程式提供了一種抽象——程序。

**提供了一種錯覺:**一個系統上可以同時運作多個程序,而每個程序好像在獨占地使用硬體。這樣程式員就無需考慮程式之間切換所需操作的硬體,這些由作業系統的核心進行管理。

**核心:**作業系統常駐記憶體的部分,不是一個獨立的程序,而是管理全部程序所用代碼和資料結構的集合。

并發運作:作業系統通過交錯執行若幹個程式的指令,不斷地在程序間進行切換來提供這種錯覺,這個稱為并發運作。

4.1.1 上下文切換

首先,當程序A要切換到程序B時,程序A通過系統調用,将控制權遞給作業系統,然後作業系統會儲存程序A所需的所有狀态資訊,稱為上下文,比如寄存器以及記憶體内容,然後建立程序B及其上下文,然後将控制權遞給程序B。當程序B終止後,作業系統就會恢複程序A的上下文,并将控制權還給程序A,這樣程序A就能從斷點處繼續執行。這個過程都是由作業系統的内容進行控制的。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-PpX6HVpA-1615013591799)(https://github.com/xxg1413/CSAPP/raw/master/Chapter01/image/1-7-1.png)]

4.1.2 線程

現代系統中,一個程序中可以并發多個線程,每條線程并行執行不同的任務,線程是作業系統能夠進行運算調動的最小機關,是程序中的實際運作機關。每個線程運作在程序的上下文中,并共享相同的代碼和全局資料。

**優點:**多線程之間比多程序之間更容易共享資料,并且效率更高。

4.2 虛拟記憶體

**虛拟存儲器(虛拟記憶體)**是一個抽象概念,它為每個程序提供了一個假象,即每個程序都在獨占地使用主存。

每個程序看到的是一緻的存儲器,稱為虛拟位址空間。

計算機會将多個程式的指令和資料儲存在記憶體中。當某個程式的資料增加時,可能不會儲存在記憶體的連續位址中,這就使得代碼需要對這些在記憶體中非連續存儲的資料進行讀取,會造成很大困難。如何解決?

答:為了解決這個問題,作業系統對記憶體和I/O裝置進行抽象——虛拟記憶體。**它提供了一種錯覺:**程式運作在從0開始的連續虛拟記憶體空間中,而作業系統負責将程式的虛拟記憶體位址投影到對應的真實實體記憶體中。這樣使得程式員能直接對連續的空間位址進行操作,而無需考慮非連續的實體記憶體位址。

作業系統将程序的虛拟記憶體劃分為多個區域,每個區域都有自己的功能,接下來從最低的位址開始介紹:

  • **程式代碼和資料:**對所有程序來說,代碼都是從同一固定位址開始(如下圖的“固定位址”就是程式開始的地方),然後是C全局變量。這部分在程序一開始運作時就被指定大小了。
  • 堆:當調用類似C中的

    malloc

    free

    标準庫函數時,堆會在程序運作時動态擴充和伸縮。
  • **共享庫:**用來存放像C标準庫和數學庫這樣公共庫的代碼和資料的區域。
  • **棧:**位于使用者虛拟記憶體頂部,編譯器用來實作函數調用,當調用函數時,棧就增長,當傳回一個函數時,棧就縮小。
  • **核心虛拟記憶體:**位址空間頂部的區域為核心保留,不允許應用程式讀寫這個區域的内容或者直接調用核心代碼定義的函數。
01 計算機系統漫遊1 程式是如何存儲與執行的2 高速緩存3 存儲器層次結構4 作業系統管理硬體5. 網絡6 并發與并行7 抽象 [補充]相關文章

4.3 檔案

作業系統将所有I/O裝置看成是檔案,而檔案是位元組序列,這樣系統中的所有輸入輸出都可以調用系統函數來讀寫檔案實作,簡化了對各種各樣的I/O裝置的操作。

5. 網絡

從一個單獨的系統來看,網絡可以看成一個I/O裝置,當系統從主存複制一串位元組到網絡擴充卡時,計算機就會自動将其發送到另一台機器。在後續的課程會詳細介紹。

6 并發與并行

**并發(Concurrency)**指一個同時具有多個活動的系統。**并行(Paralleism)**指的是用并發來時一個系統運作得更快。并行可以在計算機系統的多個抽象層次上運用。

  • 線程級并發:單處理器系統,一個CPU,程序間快速切換的方式實作;
  • 多處理器系統:多個CPU,要求程式運作更快,就需程式多線程 超線程,允許一個CPU執行多個控制流的技術;
  • 指令集并行:處理器可以同時執行多條指令;( 超标量處理器:處理器可以達到比一個周期一條指令更快的執行速率)。
    參考:[深度人工智障:讀書筆記]《計算機科學速成課》—9 進階CPU設計
  • 單指令,多資料并行:許多現代處理器擁有特殊的硬體,允許一條指令産生多個可以并行執行的操作,這種方式稱為單指令、多資料 ,即SIMD并行。

7 抽象 [補充]

計算機系統中的抽象: 如圖:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-9ReB6PQq-1615013591801)(https://github.com/xxg1413/CSAPP/raw/master/Chapter01/image/1-9-1.png)]

  • 檔案是對I/O的抽象
  • 虛拟存儲器(虛拟記憶體)是對程式存儲器的抽象
  • 程序是對一個正在運作程式的抽象
  • 虛拟機是對整個計算機的抽象

相關文章

  1. https://zhuanlan.zhihu.com/p/103969274
  2. https://github.com/xxg1413/CSAPP/blob/master/Chapter01/1.0.md
  3. 控制器、擴充卡:https://blog.csdn.net/u014753393/article/details/50259207

繼續閱讀