1. 程式人生 > >《深入理解計算機系統》筆記:幾個重要概念

《深入理解計算機系統》筆記:幾個重要概念

執行程式系統

編譯系統

  • 從原始碼文字檔案到可執行目標檔案的轉化由編譯系統(compilation system)完成
    • 前處理器(cpp):hello.c —> hello.i
      • 根據以字元 # 開頭的指令修改原始程式
    • 編譯器(cc1):hello.i —> hello.s
      • 將預處理之後的文字檔案翻譯為組合語言程式(依然是文字檔案)
    • 彙編器(as):hello.s —> hello.o
      • 將組合語言程式翻譯成一系列機器語言指令,然後將機器語言指令打包成可重定位目標程式
    • 連結器(ld):hello.o + printf.o —> hello
      • 將程式用到的其他模組與當前程式連結到一起,最終得到可執行目標檔案

程式計數器 PC

  • 程式計數器為一個字長(64 位系統中是 8 個位元組)的儲存裝置,是 CPU 的核心
  • 在任何時刻,PC 都指向記憶體中的某條機器語言指令,也就是說,PC 中儲存著一條機器語言指令的地址
  • CPU 不斷地執行 PC 指向的指令,然後更新 PC,使其指向下一條指令(前後指令不一定相鄰)

儲存器層次結構

  • L0:CPU 暫存器
  • L1 ~ L3:L1 ~ L3 快取記憶體(SRAM,靜態隨機訪問儲存器)
  • L4:主存(DRAM,動態隨機訪問儲存器)
  • L5:本地磁碟
  • L6:遠端儲存裝置(分散式檔案系統、Web 伺服器)

區域性性原理

  • 程式具有訪問區域性區域裡的資料的趨勢

作業系統抽象

程序:對處理器、主存和 I/O 裝置的抽象

  • 處理器在多個程序間快速切換,稱為上下文切換
    • 單處理器在任一時刻只能執行一個程序的程式碼
    • 當作業系統把控制權從一個程序轉移到另一個程序時,便進行上下文切換:
      • 儲存當前程序的上下文
      • 恢復新程序的上下文
      • 將控制權轉移到新程序

執行緒

  • 一個程序可以由多個執行緒組成,每個執行緒都執行在程序的上下文中,並共享相同的程式碼和全域性資料
  • 執行緒一般比程序高效,多執行緒之間共享資料也比程序之間容易

虛擬儲存器:對主存和磁碟 I/O 的抽象

  • 每個程序使用專有的虛擬地址空間,從而形成每個程序獨佔主存的假象
  • 每個程序看到的虛擬地址空間由一組準確定義的區構成,從低地址到高地址依次為:
    • 程式程式碼和資料
    • 執行時堆
    • 共享庫
    • 使用者棧
    • 核心虛擬儲存器

檔案:對 I/O 裝置的抽象

  • 檔案就是位元組序列,別無其他
  • 每個 I/O 裝置都可視為檔案
  • 系統的所有輸入輸出都通過一組 Unix I/O 系統函式呼叫讀寫檔案來實現

資訊儲存

  • 任一計算機的字長指明瞭整數和指標資料的標稱大小
  • 字長決定了虛擬地址空間的最大大小,對於字長為 ww 的機器,其虛擬地址的範圍是 02w10 \sim 2^w - 1

無符號數

問題程式碼一

  • length0 時,length - 1 轉為最大的無符號數,而 i 作為 int 型整數,所能包含的數永遠小於 length - 1
  • 同樣,當 length 大於 int 所能表示的最大數時,也會形成無限迴圈
float sum_elements(float a[], unsigned length)
{
  int i;
  float result = 0.0;
  for (i = 0; i <= length - 1; i++) // 正確寫法:i < length
    result += a[i];
  return result;
}

問題程式碼二

  • strlen() 返回的 size_t 為無符號型別,有無符號型別參與運算的結果依然是無符號型別,因此 strlen(str1) - strlen(str2) 永遠不會小於 0
size_t strlen(const char* str);

int strlonger(const char* str1, const char* str2)
{
  return strlen(str1) - strlen(str2) > 0; // 正確寫法 return strlen(str1) > strlen(str2);
}

問題程式碼三

  • 如果 maxlen 是一個負值,len 便被賦為負值,接著再賦給 memcpy 函式中的 n 引數,但是引數 n 是無符號值,從而轉為一個很大的整數
void* memcpy(void* dest, void* src, size_t n);

#define KSIZE 1024
char kbuf[KSIZE];

int copy_from_kernel(void* user_dest, int maxlen)
{
  int len = KSIZE < maxlen ? KSIZE : maxlen;
  memcpy(user_dest, kbuf, len);
  return len;
}

整數乘法的優化

  • 整數乘法(至少 10 個時鐘週期)通常比加法、減法、移位和位級運算(1 個時鐘週期)慢得多

乘以 2 的冪

  • 對於整數變數 x,C 表示式 x << k 等價於 x×2kx \times 2^k
    • x×14x \times 14 可優化為 (x << 3) + (x << 2) + (x << 1),或更進一步優化為 (x << 4) - (x << 1)

除以 2 的冪

  • 整數除法(至少 30 個時鐘週期)比整數乘法更慢