1. 程式人生 > >《深入理解計算機系統》學習筆記(一)

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

一、資訊就是位 + 上下文

作者使用的標題是:資訊就是位 + 上下文,那麼問題來了:什麼是位?什麼是上下文?

計算機系統是由硬體系統軟體組成的,它們共同工作來執行應用程式。所有計算機系統都有相似的硬體和軟體元件,它們執行著相似的功能。

從某種意義上來說,本書的目的就是要幫助你瞭解當你在系統上執行 hello 程式時,系統發生了什麼以及為什麼會這樣。

// hello 程式
#include <stdio.h>

int main()
{
printf("hello, world\n");
}

hello 程式的生命週期是從一個源程式(或者說原始檔)開始的,即程式設計師利用編輯器建立並儲存的文字檔案,檔名是 hello.c。源程式實際上就是一個由值 0 和 1 組成的位(bit)序列,8 個位被組織成一組,稱為位元組。每個位元組表示程式中某個文字字元。

hello 源程式是文字編輯器編寫的一個檔案,使用 HxD (免費的十六進位制和磁碟資料編輯器)對原始檔進行原始碼檢視:

使用 HxD 檢視原始檔從上圖可以看見,所有的原始碼字元最終都會被轉為對應的數字。像 hello.c 這樣 只由 ASCII 字元構成的檔案稱為文字檔案,所有其他檔案都稱為二進位制檔案。hello.c 的表示方法說明了一個基本的思想 : 系統中所有的資訊(包括磁碟檔案、儲存器中的程式、儲存器中存放的使用者資料以及網路上傳送的資料),都是由一串位表示的。區分不同資料物件的唯一方法是我們讀到這些資料物件時的上下文。比如,在不同的上下文中,一個同樣


的位元組序列可能表示一個整數、浮點數、字串或者機器指令。

構成計算機資訊儲存單元是 bit(位),如果以 1 位為單位記錄資訊,未免有點低效。如果採用 8 個位為一個位元組單位,可以組合出 256 種不同的符號表示,這樣傳輸也方便,也便於計算機儲存資訊。

知道位的概念還不夠,計算機對當前的某一個位中的資訊可以通過字元編碼規則得知,但是文字檔案除外,還有純二進位制的檔案(圖片、音視訊),計算機如何得知某幾個位連線起來是一串有意義的資料,並知道從哪裡擷取開始到哪裡結束,這就需要通過上下文來得知。

二、ASCII 碼

ASCII(American Standard Code for Information Interchange,美國標準資訊交換程式碼)是基於拉丁字母的一套電腦編碼系統,主要用於顯示現代英語和其他西歐語言。它是現今最通用的單位元組編碼系統,並等同於國際標準ISO/IEC 646。

計算機裡,一個位元組有 8 個 bit(位),用二進位制(1 和 0)組合表示,則有 2 的 8 次方種可能數字,從零開始計數,則為 0 - 255,早期的電腦科學家們以為這 256 種數字夠表示日常使用的符號資訊了(英文字母,阿拉伯數字,控制字元等)。

  • 第一部分:ASCII 非列印控制字元表

    ASCII 表上的數字 0–31 分配給了控制字元,用於控制像印表機等一些外圍裝置。例如,12 代表換頁/新頁功能。此命令指示印表機跳到下一頁的開頭。

    ASCII 表
  • 第二部分:ASCII 列印字元

    數字 32–126 分配給了能在鍵盤上找到的字元,當您檢視或列印文件時就會出現。數字 127 代表 DELETE 命令。

  • 第三部分:擴充套件 ASCII 列印字元

    擴充套件的 ASCII 字元滿足了對更多字元的需求。擴充套件的 ASCII 包含 ASCII 中已有的 128 個字元(數字 0–32 顯示在下圖中),又增加了 128 個字元,總共是 256 個。

    ASCII 擴充套件表

ASCII 碼錶有它的侷限性:隨著全世界人都在使用計算機,而全世界使用符號的種類遠超過了 256 種,因此 ASCII 碼錶已經裝不下更多的符號資訊(非英語國家的文字元號資訊:拉丁文、中文、日韓文等),於是電腦科學家們又規定出了 Unicode(中文譯為:萬國碼、國際碼、統一碼、單一碼),即全球通用字符集的標準。

Unicode 標準規定:使用兩個位元組單位表示一個數字,因此可以表示符號的種類為 2 的16 次方,也就是 65536 種,這樣全世界人使用的符號資訊都可以囊括。

值得注意的是 Unicode 是一種計算機工業標準,而不是指定是什麼字符集實現,它涵相容了 ASCII 碼錶規則,因此目前所有的計算都會至少認識 ASCII 碼錶規則。

現在國際常用的UTF-8 編碼就是遵循了 Unicode 標準的一種字符集實現。

三、程式被其他程式翻譯成不同的格式

hello.c 是肉眼可直接識別的文字檔案,但作為機器來說,它只認識二進位制數字(物理邏輯上對應的是:不同大小電流或高低的電壓),檔案均附生在作業系統之上,我們需要一套完整的流程將原始檔翻譯成機器可以直接看懂並執行的可執行檔案,執行這四個階段的程式(前處理器、編譯器、彙編器和連結器)一起構成了編譯系統(compilation system)。

編譯系統之編譯過程

使用 gcc++ 編譯工具編譯 hello.c 原始檔:

gcc++ 編譯四步驟

gcc 常用編譯命令

1. 無選項編譯連結
用法:gcc hello.c
作用:將 hello.c 預處理、彙編、編譯並連結形成可執行文件。這裡未指定輸出檔案,預設輸出為 a.out

2. 選項 -o
用法:gcc hello.c -o hello
作用:將 hello.c 預處理、彙編、編譯並連結形成可執行檔案 hello。-o 選項用來指定輸出檔案的檔名。

3. 選項 -E
用法:gcc -E hello.c -o hello.i
作用:將 hello.c 預處理輸出 hello.i 檔案。

4. 選項 -S
用法:gcc -S hello.i
作用:將預處理輸出檔案 hello.i 彙編成 hello.s 檔案。

5. 選項 -c
用法:gcc -c hello.s
作用:將彙編輸出文件 hello.s 編譯輸出 hello.o 檔案。

6. 無選項鍊接
用法:gcc hello.o -o hello
作用:將編譯輸出檔案test.o連結成最終可執行檔案test。

7. 選項 -O
用法:gcc -O1 hello.c -o hello
作用:使用編譯優化級別1編譯程式。級別為1~3,級別越大優化效果越好,但編譯時間越長。

  • 預處理階段。前處理器(cpp)根據以字元 # 開頭的命令,修改原始的 C 程式。比如 hello.c 中第 1 行的 #include 命令告訴前處理器讀取系統標頭檔案 stdio.h 的內容,並把它直接插入到程式文字中。結果就得到了另一個 C 程式,通常是以 .i 作為副檔名。

  • 編譯階段。編譯器(cc1)將文字檔案 hello.i 翻譯成文字檔案 hello.s,它包含一個組合語言程式。組合語言程式中的每條語句都以一種標準的文字格式確切地描述了一條低階機器語言指令。組合語言是非常有用的,因為它為不同高階語言的不同編譯器提供了通用的輸出語言。例如,C 編譯器和 Fortran 編譯器產生的輸出檔案用的都是一樣的組合語言。

  • 彙編階段。接下來,彙編器(as)將 hello.s 翻譯成機器語言指令,把這些指令打包成一種叫做可重定位目標程式(relocatable object program)的格式,並將結果儲存在目標檔案 hello.o 中。hello.o 檔案是一個二進位制檔案,它的位元組編碼是機器語言指令而不是字元。如果我們在文字編輯器中開啟 hello.o 檔案,看到的將是一堆亂碼。

  • 連結階段。請注意,hello 程式呼叫了 printf 函式,它是每個 C 編譯器都會提供的標準 C 庫中的一個函式。printf 函式存在於一個名為 printf.o 的單獨的預編譯好了的目標檔案中,而這個檔案必須以某種方式合併到我們的 hello.o 程式中。連結器(ld)就負責處理這種合併。結果就得到 hello 檔案,它是一個可執行目標檔案(或者簡稱為可執行檔案),可以被載入到記憶體中,由系統執行。