深入理解計(jì)算機(jī)系統(tǒng):第一章 計(jì)算機(jī)系統(tǒng)漫游

計(jì)算機(jī)系統(tǒng)漫游


計(jì)算機(jī)系統(tǒng)是由硬件和系統(tǒng)軟件組成的,它們共同工作來運(yùn)行應(yīng)用程序。雖然系統(tǒng)的具體實(shí)現(xiàn)方式隨著時(shí)間不斷變化,但是系統(tǒng)內(nèi)在的概念卻沒有改變。所有計(jì)算機(jī)系統(tǒng)都有相似的硬件和軟件組件,它們又執(zhí)行著相似的功能。一些程序員希望深入了解這些組件是如何工作的以及這些組件是如何影響程序的正確性和性能的,以此來提高自身的技能。本書便是為這些讀者而寫的。

現(xiàn)在就要開始一次有趣的漫游歷程了。如果你全力投身學(xué)習(xí)本書中的概念,完全理解底層計(jì)算機(jī)系統(tǒng)以及它對(duì)應(yīng)用程序的影響,那么你會(huì)步上成為為數(shù)不多的“大?!钡牡缆贰?/p>

你將會(huì)學(xué)習(xí)一些實(shí)踐技巧,比如如何避免由計(jì)算機(jī)表示數(shù)字的方式引起的奇怪的數(shù)字錯(cuò)誤。你將學(xué)會(huì)怎樣通過一些小竅門來優(yōu)化自己的 C 代碼,以充分利用現(xiàn)代處理器和存儲(chǔ)器系統(tǒng)的設(shè)計(jì)。你將了解編譯器是如何實(shí)現(xiàn)過程調(diào)用的,以及如何利用這些知識(shí)來避免緩沖區(qū)溢出錯(cuò)誤帶來的安全漏洞,這些弱點(diǎn)給網(wǎng)絡(luò)和因特網(wǎng)軟件帶來了巨大的麻煩。你將學(xué)會(huì)如何識(shí)別和避免鏈接時(shí)那些令人討厭的錯(cuò)誤,它們困擾著普通的程序員。你將學(xué)會(huì)如何編寫自己的 Unix shell、自己的動(dòng)態(tài)存儲(chǔ)分配包,甚至于自己的 Web 服務(wù)器。你會(huì)認(rèn)識(shí)并發(fā)帶來的希望和陷阱,這個(gè)主題隨著單個(gè)芯片上集成了多個(gè)處理器核變得越來越重要。

KernighanRitchie的關(guān)于 C 編程語言的經(jīng)典教材中,他們通過圖 1-1 中所示的 hello 程序來向讀者介紹 C。盡管 hello 程序非常簡單,但是為了讓它實(shí)現(xiàn)運(yùn)行,系統(tǒng)的每個(gè)主要組成部分都需要協(xié)調(diào)工作。從某種意義上來說,本書的目的就是要幫助你了解當(dāng)你在系統(tǒng)上執(zhí)行 hello 程序時(shí),系統(tǒng)發(fā)生了什么以及為什么會(huì)這樣。

———————————————————————————— code/intro/hello.c
#include <stdio.h>

int main()
{
  print("hello, world\n");
  return 0;
}
———————————————————————————— code/intro/hello.c

圖 1-1 hello 程序

我們通過跟蹤 hello 程序的生命周期來開始對(duì)系統(tǒng)的學(xué)習(xí)——從它被程序員創(chuàng)建開始,到在系統(tǒng)上運(yùn)行,輸出簡單的消息,然后終止。我們將沿著這個(gè)程序的生命周期,簡要地介紹一些逐步出現(xiàn)的關(guān)鍵概念、專業(yè)術(shù)語和組成部分。后面的章節(jié)將圍繞這些內(nèi)容展開。

1.1 信息就是位+上下文

hello程序的生命周期是從一個(gè)源程序(或者說源文件)開始的,即程序員通過編輯器創(chuàng)建并保存的文本文件,文件名是 hello.c。源程序?qū)嶋H上就是一個(gè)由值0和1組成的位(又稱為比特)序列,8個(gè)位被組織成一組,稱為字節(jié)。每個(gè)字節(jié)表示程序中的某些文本字符。

大部分的現(xiàn)代計(jì)算機(jī)系統(tǒng)都使用ASCII標(biāo)準(zhǔn)來表示文本字符,這種方式實(shí)際上就是用一個(gè)唯一的單字節(jié)大小的整數(shù)值來表示每個(gè)字符。比如,圖1-2 中給出了hello.c程序 的ASCII碼表示。

圖1-2 hello.c的ASCII文本表示

hello.c 程序是以字節(jié)序列的方式儲(chǔ)存在文件中的。每個(gè)字節(jié)都有一個(gè)整數(shù)值,對(duì)應(yīng)于某些字符。例如,第一個(gè)字節(jié)的整數(shù)值是35,它對(duì)應(yīng)的就是字符 “#”。第二個(gè)字節(jié)的整數(shù)值為105,它對(duì)應(yīng)的字符是‘i’,依此類推。注意,每個(gè)文本行都是以一個(gè)看不見的換行符‘\n’來結(jié)束的,它所對(duì)應(yīng)的整數(shù)值為10。像 hello.c 這樣只由 ASCII 字符構(gòu)成的文件稱為文本文件,所有其他文件都稱為二進(jìn)制文件。

旁注 C 編程語言的起源

C 語言是貝爾實(shí)驗(yàn)室的Dennis Ritchie于1969年?1973年間創(chuàng)建的。美國國家標(biāo)準(zhǔn)學(xué)會(huì) (American National Standards Institute, ANSI)在1989年頒布了 ANSI C 的標(biāo)準(zhǔn),后來 C 語言的標(biāo)準(zhǔn)化成了國際標(biāo)準(zhǔn)化組織 (International Standards Organization,ISO) 的責(zé)任。這些標(biāo)準(zhǔn)定義了 C 語言和一系列函數(shù)庫,即所謂的C標(biāo)準(zhǔn)庫。Kernighan 和 Ritchie 在他們的經(jīng)典著作中描述了 ANSI C,這本著作被人們滿懷感情地稱為 “K&R”。用 Ritchie 的話來說,C 語言是“古怪的、有缺陷的,但同時(shí)也是一個(gè)巨大的成功”。為什么會(huì)成功呢?

  • C語言與Unix操作系統(tǒng)關(guān)系密切。C 從一開始就是作為一種用于 Unix 系統(tǒng)的程序語言開發(fā)出來的。大部分 Unix 內(nèi)核(操作系統(tǒng)的核心部分),以及所有支撐工具和函數(shù)庫都是用C語言編寫的。20世紀(jì)70年代后期到80年代初期,Unix 風(fēng)行于高 等院校,許多人開始接觸C語言并喜歡上它。因?yàn)?Unix 幾乎全部是用C編寫的,它可以很方便地移植到新的機(jī)器上,這種特點(diǎn)為 C 和 Unix 贏得了更為廣泛的支持。
  • C語言小而簡單。C 語言的設(shè)計(jì)是由一個(gè)人而非一個(gè)協(xié)會(huì)掌控的,因此這是一個(gè)簡潔明了、沒有什么冗贅的設(shè)計(jì)。K&R 這本書用大量的例子和練習(xí)描述了完整的 C 語言及其標(biāo)準(zhǔn)庫,而全書不過261頁。C 語言的簡單使它相對(duì)而言易于學(xué)習(xí),也易于移植到不同的計(jì)算機(jī)上。
  • C語言是為實(shí)踐目的設(shè)計(jì)的。C 語言是設(shè)計(jì)用來實(shí)現(xiàn) Unix 操作系統(tǒng)的。后來,其他人發(fā)現(xiàn)能夠用這門語言無障礙地編寫他們想要的程序。

C 語言是系統(tǒng)級(jí)編程的首選,同時(shí)它也非常適用于應(yīng)用級(jí)程序的編寫。然而,它也并非適用于所有的程序員和所有的情況。C 語言的指針是造成程序員困惑和程序錯(cuò)誤的一個(gè)常見原因。同時(shí),C 語言還缺乏對(duì)非常有用的抽象的顯式支持,例如類、對(duì)象和異常。像 C++ 和 Java 這樣針對(duì)應(yīng)用級(jí)程序的新程序語言解決了這些問題。

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

hello 程序的生命周期是從一個(gè)高級(jí)C語言程序開始的,因?yàn)檫@種形式能夠被人讀懂。然而,為了在系統(tǒng)上運(yùn)行 hello.c 程序,每條 C 語句都必須被其他程序轉(zhuǎn)化為一系列的低級(jí)機(jī)器語言指令。然后這些指令按照一種稱為可執(zhí)行目標(biāo)程序的格式打好包,并以二進(jìn)制磁盤文件的形式存放起來。目標(biāo)程序也稱為可執(zhí)行目標(biāo)文件。

在 Unix 系統(tǒng)上,從源文件到目標(biāo)文件的轉(zhuǎn)化是由編譯器驅(qū)動(dòng)程序完成的:

linux> gcc -o hello hello.c

在這里,GCC 編譯器驅(qū)動(dòng)程序讀取源程序文件 hello.c 并把它翻譯成一個(gè)可執(zhí)行目標(biāo)文件 hello 這個(gè)翻譯過程可分為四個(gè)階段完成,如圖1-3所示。執(zhí)行這四個(gè)階段的程序(預(yù)處理器、編譯器、匯編器和鏈接器)一起構(gòu)成了編譯系統(tǒng)(compilation system)。

圖1-3 編譯系統(tǒng)
  • 預(yù)處理階段。預(yù)處理器(cpp)根據(jù)以字符 # 開頭的命令,修改原始的 C 程序。比如 hello.c 中第1行的 #include<stdio.h> 命令告訴預(yù)處理器讀取系統(tǒng)頭文件 stdio.h 的內(nèi)容,并把它直接插入程序文本中。結(jié)果就得到了另一個(gè) C 程序,通常是以 .i 作為文件擴(kuò)展名。

  • 編譯階段。編譯器(ccl)將文本文件hello.i翻譯成文本文件 hello.s,它包含一個(gè)匯編語言程序。該程序包含函數(shù)main的定義,如下所示:

main:
  subq  $8, %rsp
  movl  $.LCO, %edi
  call  puts
  movl  $0, %eax
  addq  $8, %rsp
  ret
  • 定義中2~7行的每行語句都以一種文本格式描述了一條低級(jí)機(jī)器語言指令。匯編語言是非常有用的,因?yàn)樗鼮椴煌呒?jí)語言的不同編譯器提供了通用的輸出語言。例如,C 編譯器和 Fortran 編譯器產(chǎn)生的輸出文件用的都是一樣的匯編語言。

  • 匯編階段。接下來,匯編器 (as) 將 hello.s 翻譯成機(jī)器語言指令,把這些指令打包成一種叫做可重定位目標(biāo)程序 (relocatable object program) 的格式,并將結(jié)果保存在目標(biāo)文件 hello.o 中。hello.o 文件是一個(gè)二進(jìn)制文件,它包含的17個(gè)字節(jié)是函數(shù) main 的指令編碼。如果我們在文本編輯器中打開 hello.o 文件,將看到一堆亂碼。

  • ·鏈接階段。請(qǐng)注意,hello 程序調(diào)用了 printf 函數(shù),它是每個(gè) C 編譯器都提供的標(biāo)準(zhǔn) C 庫中的一個(gè)函數(shù)。printf 函數(shù)存在于一個(gè)名為 printf.o 的單獨(dú)的預(yù)編譯好了的目標(biāo)文件中,而這個(gè)文件必須以某種方式合并到我們的 hello.o 程序中。鏈接器 (Id) 就負(fù)責(zé)處理這種合并。結(jié)果就得到 hello 文件,它是一個(gè)可執(zhí)行目標(biāo)文件(或者簡稱為可執(zhí)行文件),可以被加載到內(nèi)存中,由系統(tǒng)執(zhí)行。


旁注 GNU項(xiàng)目

GCC 是 GNU(GNU是GNU's Not Unix的縮寫) 項(xiàng)目開發(fā)出來的眾多有用工具之一。GNU 項(xiàng)目是1984年由 Richard Stallman 發(fā)起的一個(gè)免稅的慈善項(xiàng)目。該項(xiàng)目的目標(biāo)非常宏大,就是開發(fā)出一個(gè)完整的類 Unix 的系統(tǒng),其源代碼能夠不受限制地被修改和傳播。GNU 項(xiàng)目已經(jīng)開發(fā)出了一個(gè)包含 Unix 操作系統(tǒng)的所有主要部件的環(huán)境,但內(nèi)核除外,內(nèi)核是由 Linux 項(xiàng)目獨(dú)立發(fā)展而來的。GNU 環(huán)境包括EMACS編輯器、GCC 編譯器、GDB 調(diào)試器、匯編器、鏈接器、處理二進(jìn)制文件的工具以及其他一些部件。GCC 編譯器已經(jīng)發(fā)展到支持許多不同的語言,能夠?yàn)樵S多不同的機(jī)器生成代碼。支持的語言包括 C、C++、Fortran、Java、Pascal、面向?qū)ο驝語言 (Objective-C) 和 Ada。

GNU 項(xiàng)目取得了非凡的成績,但是卻常常被忽略?,F(xiàn)代開放源碼運(yùn)動(dòng)(通常和Linux聯(lián)系在一起)的思想起源是GNU項(xiàng)目中自由軟件 (free software) 的概念。(此處的free
為自由言論 (free speech) 中的“自由”之意,而非免費(fèi)啤酒(free beer)中的“免費(fèi)”之意。)而且,Linux 如此受歡迎在很大程度上還要?dú)w功于 GNU 工具,它們給 Linux 內(nèi)核提供了環(huán)境。

1.3 了解編譯系統(tǒng)如何工作是大有益處的

對(duì)于像 hello.c 這樣簡單的程序,我們可以依靠編譯系統(tǒng)生成正確有效的機(jī)器代碼。但是,有一些重要的原因促使程序員必須知道編譯系統(tǒng)是如何工作的。

  • 優(yōu)化程序性能。 現(xiàn)代編譯器都是成熟的工具,通??梢陨珊芎玫拇a。作為程序員,我們無須為了寫出高效代碼而去了解編譯器的內(nèi)部工作。但是,為了在 C 程序中做出好的編碼選擇,我們確實(shí)需要了解一些機(jī)器代碼以及編譯器將不同的 C 語句轉(zhuǎn)化為機(jī)器代碼的方式。比如,一個(gè) switch 語句是否總是比一系列的 if-else 語句高效得多?一個(gè)函數(shù)調(diào)用的開銷有多大? while 循環(huán)比 for 循環(huán)更有效嗎?指針引用比數(shù)組索引更有效嗎?為什么將循環(huán)求和的結(jié)果放到一個(gè)本地變量中,會(huì)比將其放到一個(gè)通過引用傳遞過來的參數(shù)中,運(yùn)行起來快很多呢?為什么我們只是簡單地重新排列一下算術(shù)表達(dá)式中的括號(hào)就能讓函數(shù)運(yùn)行得更快?
    在第3章中,我們將介紹X86-64,最近幾代 Linux、Macintosh 和 Windows 計(jì)算機(jī)的機(jī)器語言。我們會(huì)講述編譯器是怎樣把不同的C語言結(jié)構(gòu)翻譯成這種機(jī)器語言的。在第5章中,你將學(xué)習(xí)如何通過簡單轉(zhuǎn)換 C 語言代碼,幫助編譯器更好地完成工作,從而調(diào)整 C 程序的性能。在第6章中,你將學(xué)習(xí)存儲(chǔ)器系統(tǒng)的層次結(jié)構(gòu)特性,C 語言編譯器如何將數(shù)組存放在內(nèi)存中,以及 C 程序又是如何能夠利用這些知識(shí)從而更高效地運(yùn)行。

  • 理解鏈接時(shí)出現(xiàn)的錯(cuò)誤。根據(jù)我們的經(jīng)驗(yàn),一些最令人困擾的程序錯(cuò)誤往往都與鏈接器操作有關(guān),尤其是當(dāng)你試圖構(gòu)建大型的軟件系統(tǒng)時(shí)。比如,鏈接器報(bào)告說它無法解析一個(gè)引用,這是什么意思?靜態(tài)變量和全局變量的區(qū)別是什么?如果你在不同的 C 文件中定義了名字相同的兩個(gè)全局變量會(huì)發(fā)生什么?靜態(tài)庫和動(dòng)態(tài)庫的區(qū)別是什么?我們在命令行上排列庫的順序有什么影響?最嚴(yán)重的是,為什么有些鏈接錯(cuò)誤直到運(yùn)行時(shí)才會(huì)出現(xiàn)?在第7章中,你將得到這些問題的答案。

  • 避免安全漏洞。 多年來,緩沖區(qū)溢出錯(cuò)誤是造成大多數(shù)網(wǎng)絡(luò)和 Internet 服務(wù)器上安全漏洞的主要原因。存在這些錯(cuò)誤是因?yàn)楹苌儆谐绦騿T能夠理解需要限制從不受信任的源接收數(shù)據(jù)的數(shù)量和格式。學(xué)習(xí)安全編程的第一步就是理解數(shù)據(jù)和控制信息存儲(chǔ)在程序棧上的方式會(huì)引起的后果。作為學(xué)習(xí)匯編語言的一部分,我們將在第3章中描述堆棧原理和緩沖區(qū)溢出錯(cuò)誤。我們還將學(xué)習(xí)程序員、編譯器和操作系統(tǒng)可以用來降低攻擊威脅的方法。

1.4 處理器讀并解釋儲(chǔ)存在內(nèi)存中的指令

此刻,hello.c 源程序已經(jīng)被編譯系統(tǒng)翻譯成了可執(zhí)行目標(biāo)文件 hello,并被存放在磁盤上。要想在 Unix 系統(tǒng)上運(yùn)行該可執(zhí)行文件,我們將它的文件名輸入到稱為 shell 的應(yīng)用程序中:

  linux> ./hello
  hello, world
  linux>

shell 是一個(gè)命令行解釋器,它輸出一個(gè)提示符,等待輸人一個(gè)命令行,然后執(zhí)行這個(gè)命令。如果該命令行的第一個(gè)單詞不是一個(gè)內(nèi)置的 shell 命令,那么 shell 就會(huì)假設(shè)這是一個(gè)可執(zhí)行文件的名字,它將加載并運(yùn)行這個(gè)文件。所以在此例中,shell將加載并運(yùn)行 hello 程序,然后等待程序終止。hello 程序在屏幕上輸出它的消息,然后終止。shell 隨后輸出一個(gè)提示符,等待下一個(gè)輸人的命令行。

1.4.1 系統(tǒng)的硬件組成

為了理解運(yùn)行 hello 程序時(shí)發(fā)生了什么,我們需要了解一個(gè)典型系統(tǒng)的硬件組織,如
圖1-4所示。這張圖是近期 Intel 系統(tǒng)產(chǎn)品族的模型,但是所有其他系統(tǒng)也有相同的外觀和特性。現(xiàn)在不要擔(dān)心這張圖很復(fù)雜——我們將在本書分階段對(duì)其進(jìn)行詳盡的介紹。

  1. 總線


    貫穿整個(gè)系統(tǒng)的是一組電子管道,稱作總線,它攜帶信息字節(jié)并負(fù)責(zé)在各個(gè)部件間傳
    遞。通??偩€被設(shè)計(jì)成傳送定長的字節(jié)塊,也就是字(word)。字中的字節(jié)數(shù)(即字長)是一個(gè)基本的系統(tǒng)參數(shù),各個(gè)系統(tǒng)中都不盡相同?,F(xiàn)在的大多數(shù)機(jī)器字長要么是4個(gè)字節(jié)(32位),要么是8個(gè)字節(jié)(64位)。本書中,我們不對(duì)字長做任何固定的假設(shè)。相反,我們將在需要明確定義的上下文中具體說明一個(gè)“字”是多大。

  2. I/0設(shè)備


    I/0(輸入/輸出)設(shè)備是系統(tǒng)與外部世界的聯(lián)系通道。我們的示例系統(tǒng)包括四個(gè)I/O設(shè)
    備:作為用戶輸入的鍵盤和鼠標(biāo),作為用戶輸出的顯示器,以及用于長期存儲(chǔ)數(shù)據(jù)和程序
    的磁盤驅(qū)動(dòng)器(簡單地說就是磁盤)。最開始,可執(zhí)行程序 hello 就存放在磁盤上。

    每個(gè)I/O設(shè)備都通過一個(gè)控制器適配器與I/O總線相連??刂破骱瓦m配器之間的區(qū)別主要在于它們的封裝方式??刂破魇荌/O設(shè)備本身或者系統(tǒng)的主印制電路板(通常稱作主板)上的芯片組。而適配器則是一塊插在主板插槽上的卡。無論如何,它們的功能都是在I/O總線和I/O設(shè)備之間傳遞信息。

    第6章會(huì)更多地說明磁盤之類的I/O設(shè)備是如何工作的。在第10章中,你將學(xué)習(xí)如何在應(yīng)用程序中利用 Unix I/O接口訪問設(shè)備。我們將特別關(guān)注網(wǎng)絡(luò)類設(shè)備,不過這些技術(shù)對(duì)于其他設(shè)備來說也是通用的。

圖1-4 一個(gè)典型系統(tǒng)的硬件組成

cpu:中央處理單元;ALU:算術(shù)/邏輯單元;PC:程序計(jì)數(shù)器;USB:通用串行總線

  1. 主存


    主存是一個(gè)臨時(shí)存儲(chǔ)設(shè)備,在處理器執(zhí)行程序時(shí),用來存放程序和程序處理的數(shù)據(jù)。從
    物理上來說,主存是由一組動(dòng)態(tài)隨機(jī)存取存儲(chǔ)器(DRAM)芯片組成的。從邏輯上來說,存儲(chǔ)器是一個(gè)線性的字節(jié)數(shù)組,每個(gè)字節(jié)都有其唯一的地址(數(shù)組索引),這些地址是從零開始的。一般來說,組成程序的每條機(jī)器指令都由不同數(shù)量的字節(jié)構(gòu)成。與 C 程序變量相對(duì)應(yīng)的數(shù)據(jù)項(xiàng)的大小是根據(jù)類型變化的。比如,在運(yùn)行 Linux 的x86-64機(jī)器上,short 類型的數(shù)據(jù)需要2個(gè)字節(jié),int 和 float 類型需要4個(gè)字節(jié),而 long 和 double 類型需要8個(gè)字節(jié)。


    第6章將具體介紹存儲(chǔ)器技術(shù),比如 DRAM 芯片是如何工作的,它們又是如何組合起來構(gòu)成主存的。

  2. 處理器


    中央處理單元(CPU),簡稱處理器,是解釋(或執(zhí)行)存儲(chǔ)在主存中指令的引擎。處理器的核心是一個(gè)大小為一個(gè)字的存儲(chǔ)設(shè)備(或寄存器),稱為程序計(jì)數(shù)器(PC)。在任何時(shí)刻,PC都指向主存中的某條機(jī)器語言指令(即含有該條指令的地址)。


    從系統(tǒng)通電開始,直到系統(tǒng)斷電,處理器一直在不斷地執(zhí)行程序計(jì)數(shù)器指向的指令,再更新程序計(jì)數(shù)器,使其指向下一條指令。處理器看上去是按照一個(gè)非常簡單的指令執(zhí)行模型來操作的,這個(gè)模型是由指令集架構(gòu)決定的。在這個(gè)模型中,指令按照嚴(yán)格的順序執(zhí)行,而執(zhí)行一條指令包含執(zhí)行一系列的步驟。處理器從程序計(jì)數(shù)器指向的內(nèi)存處讀取指令,解釋指令中的位,執(zhí)行該指令指示的簡單操作,然后更新 PC,使其指向下一條指令,而這條指令并不一定和在內(nèi)存中剛剛執(zhí)行的指令相鄰。


    這樣的簡單操作并不多,它們圍繞著主存寄存器文件(register file)和算術(shù)/邏輯單元(ALU)進(jìn)行。寄存器文件是一個(gè)小的存儲(chǔ)設(shè)備,由一些單個(gè)字長的寄存器組成,每個(gè)
    寄存器都有唯一的名字。ALU 計(jì)算新的數(shù)據(jù)和地址值。下面是一些簡單操作的例子,CPU 在指令的要求下可能會(huì)執(zhí)行這些操作。

  • 加載:從主存復(fù)制一個(gè)字節(jié)或者一個(gè)字到寄存器,以覆蓋寄存器原來的內(nèi)容。
  • 存儲(chǔ):從寄存器復(fù)制一個(gè)字節(jié)或者一個(gè)字到主存的某個(gè)位置,以覆蓋這個(gè)位置上原來的內(nèi)容。
  • 操作:把兩個(gè)寄存器的內(nèi)容復(fù)制到 ALU, ALU 對(duì)這兩個(gè)字做算術(shù)運(yùn)算,并將結(jié)果存放到一個(gè)寄存器中,以覆蓋該寄存器中原來的內(nèi)容。
  • 跳轉(zhuǎn):從指令本身中抽取一個(gè)字,并將這個(gè)字復(fù)制到程序計(jì)數(shù)器 (PC) 中,以覆蓋PC中原來的值。

處理器看上去是它的指令集架構(gòu)的簡單實(shí)現(xiàn),但是實(shí)際上現(xiàn)代處理器使用了非常復(fù)雜
的機(jī)制來加速程序的執(zhí)行。因此,我們將處理器的指令集架構(gòu)和處理器的微體系結(jié)構(gòu)區(qū)分
開來:指令集架構(gòu)描述的是每條機(jī)器代碼指令的效果;而微體系結(jié)構(gòu)描述的是處理器實(shí)際
上是如何實(shí)現(xiàn)的。在第3章研究機(jī)器代碼時(shí),我們考慮的是機(jī)器的指令集架構(gòu)所提供的抽
象性。第4章將更詳細(xì)地介紹處理器實(shí)際上是如何實(shí)現(xiàn)的。第5章用一個(gè)模型說明現(xiàn)代處
理器是如何工作的,從而能預(yù)測和優(yōu)化機(jī)器語言程序的性能。

1.4.2 運(yùn)行hello程序

前面簡單描述了系統(tǒng)的硬件組成和操作,現(xiàn)在開始介紹當(dāng)我們運(yùn)行示例程序時(shí)到底發(fā)生了些什么。在這里必須省略很多細(xì)節(jié),稍后會(huì)做補(bǔ)充,但是現(xiàn)在我們將很滿意于這種整體上的描述。

初始時(shí),shell 程序執(zhí)行它的指令,等待我們輸入一個(gè)命令。當(dāng)我們在鍵盤上輸入字符串
“./hello”后,shell 程序?qū)⒆址鹨蛔x入寄存器,再把它存放到內(nèi)存中,如圖1-5所示。

圖1-5 從鍵盤上讀取hello命令

當(dāng)我們在鍵盤上敲回車鍵時(shí),shell 程序就知道我們已經(jīng)結(jié)束了命令的輸入。然后shell執(zhí)行一系列指令來加載可執(zhí)行的 hello 文件,這些指令將 hello 目標(biāo)文件中的代碼和數(shù)據(jù)從磁盤復(fù)制到主存。數(shù)據(jù)包括最終會(huì)被輸出成字符串“hello, world\n”。

利用直接存儲(chǔ)器存取(DMA,將在第6章中討論)技術(shù),數(shù)據(jù)可以不通過處理器而直
接從磁盤到達(dá)主存。這個(gè)步驟如圖1-6所示。

圖1-6 從磁盤加載可執(zhí)行文件到主存

一旦目標(biāo)文件 hello 中的代碼和數(shù)據(jù)被加載到主存,處理器就開始執(zhí)行 hello 程序的main程序中的機(jī)器語言指令。這些指令將 “hello, world\n” 字符串中的字節(jié)從主存復(fù)制到寄存器文件,再從寄存器文件中復(fù)制到顯示設(shè)備,最終顯示在屏幕上。這個(gè)步驟如圖1-7所示。

圖1-7 將輸出字符串從存儲(chǔ)器寫到顯示器

1.5 高速緩存至關(guān)重要

這個(gè)簡單的示例揭示了一個(gè)重要的問題,即系統(tǒng)花費(fèi)了大量的時(shí)間把信息從一個(gè)地方挪到另一個(gè)地方。hello 程序的機(jī)器指令最初是存放在磁盤上,當(dāng)程序加載時(shí),它們被復(fù)制到主存;當(dāng)處理器運(yùn)行程序時(shí),指令又從主存復(fù)制到處理器。相似地,數(shù)據(jù)串 “hello,world/n” 開始時(shí)在磁盤上,然后被復(fù)制到主存,最后從主存上復(fù)制到顯示設(shè)備。從程序員的角度來看,這些復(fù)制就是開銷,減慢了程序“真正”的工作。因此,系統(tǒng)設(shè)計(jì)者的一個(gè)主要目標(biāo)就是使這些復(fù)制操作盡可能快地完成。

根據(jù)機(jī)械原理,較大的存儲(chǔ)設(shè)備要比較小的存儲(chǔ)設(shè)備運(yùn)行得慢,而快速設(shè)備的造價(jià)遠(yuǎn)高于同類的低速設(shè)備。比如說,一個(gè)典型系統(tǒng)上的磁盤驅(qū)動(dòng)器可能比主存大1000倍,但是對(duì)處理器而言,從磁盤驅(qū)動(dòng)器上讀取一個(gè)字的時(shí)間開銷要比從主存中讀取的開銷大1000萬倍。

類似地,一個(gè)典型的寄存器文件只存儲(chǔ)幾百字節(jié)的信息,而主存里可存放幾十億字節(jié)。然而,處理器從寄存器文件中讀數(shù)據(jù)比從主存中讀取幾乎要快100倍。更麻煩的是,隨著這些年半導(dǎo)體技術(shù)的進(jìn)步,這種處理器與主存之間的差距還在持續(xù)增大。加快處理器的運(yùn)行速度比加快主存的運(yùn)行速度要容易和便宜得多。

針對(duì)這種處理器與主存之間的差異,系統(tǒng)設(shè)計(jì)者采用了更小更快的存儲(chǔ)設(shè)備,稱為高速緩存存儲(chǔ)器(cache memory,簡稱為 cache 或高速緩存),作為暫時(shí)的集結(jié)區(qū)域,存放處理器近期可能會(huì)需要的信息。圖1-8展示了一個(gè)典型系統(tǒng)中的高速緩存存儲(chǔ)器。位于處理器芯片上的L1高速緩存的容量可以達(dá)到數(shù)萬字節(jié),訪問速度幾乎和訪問寄存器文件一樣快。

一個(gè)容量為數(shù)十萬到數(shù)百萬字節(jié)的更大的L2高速緩存通過一條特殊的總線連接到處理器。進(jìn)程訪問L2高速緩存的時(shí)間要比訪問L1高速緩存的時(shí)間長5倍,但是這仍然比訪問主存的時(shí)間快5?10倍。L1和L2高速緩存是用一種叫做靜態(tài)隨機(jī)訪問存儲(chǔ)器(SRAM)的硬件技術(shù)實(shí)現(xiàn)的。比較新的、處理能力更強(qiáng)大的系統(tǒng)甚至有三級(jí)高速緩存:LI、L2和L3。系統(tǒng)可以獲得一個(gè)很大的存儲(chǔ)器,同時(shí)訪問速度也很快,原因是利用了高速緩存的局部性原理,即程序具有訪問局部區(qū)域里的數(shù)據(jù)和代碼的趨勢。通過讓高速緩存里存放可能經(jīng)常訪問的數(shù)據(jù),大部分的內(nèi)存操作都能在快速的高速緩存中完成。

圖1-8 高速緩存存儲(chǔ)器

本書得出的重要結(jié)論之一就是,意識(shí)到高速緩存存儲(chǔ)器存在的應(yīng)用程序員能夠利用高速緩存將程序的性能提高一個(gè)數(shù)量級(jí)。你將在第6章里學(xué)習(xí)這些重要的設(shè)備以及如何利用它們。

1.6 存儲(chǔ)設(shè)備形成層次結(jié)構(gòu)

在處理器和一個(gè)較大較慢的設(shè)備(例如主存)之間插入一個(gè)更小更快的存儲(chǔ)設(shè)備(例如高速緩存)的想法已經(jīng)成為一個(gè)普遍的觀念。實(shí)際上,每個(gè)計(jì)算機(jī)系統(tǒng)中的存儲(chǔ)設(shè)備都被組織成了一個(gè)存儲(chǔ)器層次結(jié)構(gòu),如圖1-9所示。在這個(gè)層次結(jié)構(gòu)中,從上至下,設(shè)備的訪問速度越來越慢、容量越來越大,并且每字節(jié)的造價(jià)也越來越便宜。寄存器文件在層次結(jié)構(gòu)中位于最頂部,也就是第0級(jí)或記為L0。這里我們展示的是三層高速緩存L1到L3,占據(jù)存儲(chǔ)器層次結(jié)構(gòu)的第1層到第3層。主存在第4層,以此類推。

圖1-9 一個(gè)存儲(chǔ)器層次結(jié)構(gòu)的示例

存儲(chǔ)器層次結(jié)構(gòu)的主要思想是上一層的存儲(chǔ)器作為低一層存儲(chǔ)器的高速緩存。因此,寄存器文件就是L1的高速緩存,L1是L2的高速緩存,L2是L3的高速緩存,L3是主存的高速緩存,而主存又是磁盤的高速緩存。在某些具有分布式文件系統(tǒng)的網(wǎng)絡(luò)系統(tǒng)中,本地磁盤就是存儲(chǔ)在其他系統(tǒng)中磁盤上的數(shù)據(jù)的高速緩存。

正如可以運(yùn)用不同的高速緩存的知識(shí)來提高程序性能一樣,程序員同樣可以利用對(duì)整個(gè)存儲(chǔ)器層次結(jié)構(gòu)的理解來提高程序性能。第6章將更詳細(xì)地討論這個(gè)問題。

1.7 操作系統(tǒng)管理硬件

讓我們回到 hello 程序的例子。當(dāng) shell 加載和運(yùn)行hello程序時(shí),以及 hello 程序輸出自己的消息時(shí),shell 和 hello 程序都沒有直接訪問鍵盤、顯示器、磁盤或者主存。取而代之的是,它們依靠操作系統(tǒng)提供的服務(wù)。我們可以把操作系統(tǒng)看成是應(yīng)用程序和硬件之間插入的一層軟件,如圖1-10所示。所有應(yīng)用程序?qū)τ布牟僮鲊L試都必須通過操作系統(tǒng)。

圖1-10 計(jì)算機(jī)系統(tǒng)的分層視圖

操作系統(tǒng)有兩個(gè)基本功能:(1)防止硬件被失控的應(yīng)用程序?yàn)E用;(2)向應(yīng)用程序
提供簡單一致的機(jī)制來控制復(fù)雜而又通常大不相同的低級(jí)硬件設(shè)備。操作系統(tǒng)通過幾個(gè)
基本的抽象概念(進(jìn)程、虛擬內(nèi)存和文件)來實(shí)現(xiàn)這兩個(gè)功能。如圖1-11所示,文件是對(duì)I/O設(shè)備的抽象表示,虛擬內(nèi)存是對(duì)主存和磁盤I/O設(shè)備的抽象表示,進(jìn)程則是對(duì)處理
器、主存和I/O設(shè)備的抽象表示。我們將依次討論每種抽象表示。

圖1-11 操作系統(tǒng)提供的抽象表示

旁注 Unix、Posix和標(biāo)準(zhǔn)Unix規(guī)范

20世紀(jì)60年代是大型、復(fù)雜操作系統(tǒng)盛行的年代,比如IBM的 OS/360 和 Honeywell的 Multics 系統(tǒng)。OS/360 是歷史上最成功的軟件項(xiàng)目之一,而 Multics 雖然持續(xù)存在了多年,卻從來沒有被廣泛應(yīng)用過。貝爾實(shí)驗(yàn)室曾經(jīng)是 Multics 項(xiàng)目的最初參與者,但是因?yàn)榭紤]到該項(xiàng)目的復(fù)雜性和缺乏進(jìn)展而于1969年退出。鑒于Mutics項(xiàng)目不愉快的經(jīng)歷,一群貝爾實(shí)驗(yàn)室的研究人員———— Ken Thompson、Dennis Ritchie、Doug Mcllroy 和Joe Ossanna,從1969年開始在DEC PDP-7 計(jì)算機(jī)上完全用機(jī)器語言編寫了一個(gè)簡單得多的操作系統(tǒng)。這個(gè)新系統(tǒng)中的很多思想,比如層次文件系統(tǒng)、作為用戶級(jí)進(jìn)程的shell概念,都是來自于Multics,只不過在一個(gè)更小、更簡單的程序包里實(shí)現(xiàn)。1970年,Brian Kernighan 給新系統(tǒng)命名為 “Unix”,這也是一個(gè)雙關(guān)語,暗指 “Multics” 的復(fù)雜性。1973年用 C 重新編寫其內(nèi)核,1974年,Unix 開始正式對(duì)外發(fā)布[93]。

貝爾實(shí)驗(yàn)室以慷慨的條件向?qū)W校提供源代碼,所以 Unix在大專院校里獲得了很多支持并得以持續(xù)發(fā)展。最有影響的工作發(fā)生在20世紀(jì)70年代晚期到80年代早期,在美國加州大學(xué)伯克利分校,研究人員在一系列發(fā)布版本中增加了虛擬內(nèi)存和Internet協(xié)議,稱為 Unix 4. xBSD(Berkeley Software Distribution)。與此同時(shí),貝爾實(shí)驗(yàn)室也在發(fā)布自己的版本,稱為 System V Unix。其他廠商的版本,比如Sun Microsystems的 Solaris 系統(tǒng),則是從這些原始的 BSD 和 System V 版本中衍生而來。

20世紀(jì)80年代中期,Unix 廠商試圖通過加入新的、往往不兼容的特性來使它們的程序與眾不同,麻煩也就隨之而來了。為了阻止這種趨勢,IEEE (電氣和電子工程師協(xié)會(huì))開始努力標(biāo)準(zhǔn)化 Unix 的開發(fā),后來由 Richard Stallman 命名為“Posix”。結(jié)果就得到了一系列的標(biāo)準(zhǔn),稱作Posix標(biāo)準(zhǔn)。這套標(biāo)準(zhǔn)涵蓋了很多方面,比如Unix系統(tǒng)調(diào)用的 C 語言接口、shell 程序和工具、線程及網(wǎng)絡(luò)編程。最近,一個(gè)被稱為“標(biāo)準(zhǔn)Unix規(guī)范”的獨(dú)立標(biāo)準(zhǔn)化工作已經(jīng)與 Posix —起創(chuàng)建了統(tǒng)一的 Unix 系統(tǒng)標(biāo)準(zhǔn)。這些標(biāo)準(zhǔn)化工作的結(jié)果是Unix版本之間的差異已經(jīng)基本消失。

1.7.1 進(jìn)程

像hello這樣的程序在現(xiàn)代系統(tǒng)上運(yùn)行時(shí),操作系統(tǒng)會(huì)提供一種假象,就好像系統(tǒng)上只有這個(gè)程序在運(yùn)行。程序看上去是獨(dú)占地使用處理器、主存和I/O設(shè)備。處理器看上去就像在不間斷地一條接一條地執(zhí)行程序中的指令,即該程序的代碼和數(shù)據(jù)是系統(tǒng)內(nèi)存中唯一的對(duì)象。這些假象是通過進(jìn)程的概念來實(shí)現(xiàn)的,進(jìn)程是計(jì)算機(jī)科學(xué)中最重要和最成功的概念之一。

進(jìn)程是操作系統(tǒng)對(duì)一個(gè)正在運(yùn)行的程序的一種抽象。在一個(gè)系統(tǒng)上可以同時(shí)運(yùn)行多個(gè)進(jìn)程,而每個(gè)進(jìn)程都好像在獨(dú)占地使用硬件。而并發(fā)運(yùn)行,則是說一個(gè)進(jìn)程的指令和另一個(gè)進(jìn)程的指令是交錯(cuò)執(zhí)行的。在大多數(shù)系統(tǒng)中,需要運(yùn)行的進(jìn)程數(shù)是多于可以運(yùn)行它們的CPU個(gè)數(shù)的。傳統(tǒng)系統(tǒng)在一個(gè)時(shí)刻只能執(zhí)行一個(gè)程序,而先進(jìn)的多核處理器同時(shí)能夠執(zhí)行多個(gè)程序。無論是在單核還是多核系統(tǒng)中,一個(gè)CPU看上去都像是在并發(fā)地執(zhí)行多個(gè)進(jìn)程,這是通過處理器在進(jìn)程間切換來實(shí)現(xiàn)的。操作系統(tǒng)實(shí)現(xiàn)這種交錯(cuò)執(zhí)行的機(jī)制稱為上下文切換。為了簡化討論,我們只考慮包含一個(gè)CPU的單處理器系統(tǒng)的情況。我們會(huì)在1.9.2節(jié)中討論多處理器系統(tǒng)。

操作系統(tǒng)保持跟蹤進(jìn)程運(yùn)行所需的所有狀態(tài)信息。這種狀態(tài),也就是上下文,包括許多信息,比如PC和寄存器文件的當(dāng)前值,以及主存的內(nèi)容。在任何一個(gè)時(shí)刻,單處理器系統(tǒng)都只能執(zhí)行一個(gè)進(jìn)程的代碼。當(dāng)操作系統(tǒng)決定要把控制權(quán)從當(dāng)前進(jìn)程轉(zhuǎn)移到某個(gè)新進(jìn)程時(shí),就會(huì)進(jìn)行上下文切換,即保存當(dāng)前進(jìn)程的上下文、恢復(fù)新進(jìn)程的上下文,然后將控制權(quán)傳遞到新進(jìn)程。新進(jìn)程就會(huì)從它上次停止的地方開始。圖1-12展示了示例hello程序運(yùn)行場景的基本理念。

示例場景中有兩個(gè)并發(fā)的進(jìn)程:shell進(jìn)程和hello進(jìn)程。最開始,只有shell進(jìn)程在運(yùn)行,即等待命令行上的輸人。當(dāng)我們讓它運(yùn)行hello程序時(shí),shell通過調(diào)用一個(gè)專門的函數(shù),即系統(tǒng)調(diào)用,來執(zhí)行我們的請(qǐng)求,系統(tǒng)調(diào)用會(huì)將控制權(quán)傳遞給操作系統(tǒng)。操作系統(tǒng)保存shell進(jìn)程的上下文,創(chuàng)建一個(gè)新的hello進(jìn)程及其上下文,然后將控制權(quán)傳給新的hello進(jìn)程。hello進(jìn)程終止后,操作系統(tǒng)恢復(fù)shell進(jìn)程的上下文,并將控制權(quán)傳回
給它,shell進(jìn)程會(huì)繼續(xù)等待下一個(gè)命令行輸人。

如圖1-12所示,從一個(gè)進(jìn)程到另一個(gè)進(jìn)程的轉(zhuǎn)換是由操作系統(tǒng)內(nèi)核(kernel)管理的。內(nèi)核是操作系統(tǒng)代碼常駐主存的部分。當(dāng)應(yīng)用程序需要操作系統(tǒng)的某些操作時(shí),比如讀寫文件,它就執(zhí)行一條特殊的系統(tǒng)調(diào)用(systemcall)指令,將控制權(quán)傳遞給內(nèi)核。然后內(nèi)核執(zhí)行被請(qǐng)求的操作并返回應(yīng)用程序。注意,內(nèi)核不是一個(gè)獨(dú)立的進(jìn)程。相反,它是系統(tǒng)管理全部進(jìn)程所用代碼和數(shù)據(jù)結(jié)構(gòu)的集合。

圖1-12 進(jìn)程的上下文切換

實(shí)現(xiàn)進(jìn)程這個(gè)抽象概念需要低級(jí)硬件和操作系統(tǒng)軟件之間的緊密合作。我們將在第8章中揭示這項(xiàng)工作的原理,以及應(yīng)用程序是如何創(chuàng)建和控制它們的進(jìn)程的。

1.7.2 線程

盡管通常我們認(rèn)為一個(gè)進(jìn)程只有單一的控制流,但是在現(xiàn)代系統(tǒng)中,一個(gè)進(jìn)程實(shí)際上可以由多個(gè)稱為線程的執(zhí)行單元組成,每個(gè)線程都運(yùn)行在進(jìn)程的上下文中,并共享同樣的代碼和全局?jǐn)?shù)據(jù)。由于網(wǎng)絡(luò)服務(wù)器中對(duì)并行處理的需求,線程成為越來越重要的編程模型,因?yàn)槎嗑€程之間比多進(jìn)程之間更容易共享數(shù)據(jù),也因?yàn)榫€程一般來說都比進(jìn)程更高效。當(dāng)有多處理器可用的時(shí)候,多線程也是一種使得程序可以運(yùn)行得更快的方法,我們將在1.9.2節(jié)中討論這個(gè)問題。在第12章中,你將學(xué)習(xí)并發(fā)的基本概念,包括如何寫線程化的程序。

1.7.3 虛擬內(nèi)存

虛擬內(nèi)存是一個(gè)抽象概念,它為每個(gè)進(jìn)程提供了一個(gè)假象,即每個(gè)進(jìn)程都在獨(dú)占地使用主存。每個(gè)進(jìn)程看到的內(nèi)存都是一致的,稱為虛擬地址空間。圖1-13所示的是Linux進(jìn)程的虛擬地址空間(其他Unix系統(tǒng)的設(shè)計(jì)也與此類似)。在Linux中,地址空間最上面的區(qū)域是保留給操作系統(tǒng)中的代碼和數(shù)據(jù)的,這對(duì)所有進(jìn)程來說都是一樣。地址空間的底部區(qū)域存放用戶進(jìn)程定義的代碼和數(shù)據(jù)。請(qǐng)注意,圖中的地址是從下往上增大的。

圖1-13 進(jìn)程的虛擬地址空間

每個(gè)進(jìn)程看到的虛擬地址空間由大量準(zhǔn)確定義的區(qū)構(gòu)成,每個(gè)區(qū)都有專門的功能。在本書的后續(xù)章節(jié)你將學(xué)到更多有關(guān)這些區(qū)的知識(shí),但是先簡單了解每一個(gè)區(qū)是非常有益的。我們從最低的地址開始,逐步向上介紹。

  • 程序代碼和數(shù)據(jù)。對(duì)所有的進(jìn)程來說,代碼是從同一固定地址開始,緊接著的是和 C 全局變量相對(duì)應(yīng)的數(shù)據(jù)位置。代碼和數(shù)據(jù)區(qū)是直接按照可執(zhí)行目標(biāo)文件的內(nèi)容初始化的,在示例中就是可執(zhí)行文件 hello。在第7章我們研究鏈接和加載時(shí),你會(huì)學(xué)習(xí)更多有關(guān)地址空間的內(nèi)容。
  • 堆。代碼和數(shù)據(jù)區(qū)后緊隨著的是運(yùn)行時(shí)堆。代碼和數(shù)據(jù)區(qū)在進(jìn)程一開始運(yùn)行時(shí)就被指定了大小,與此不同,當(dāng)調(diào)用像 malloc 和 free 這樣的 C 標(biāo)準(zhǔn)庫函數(shù)時(shí),堆可以在運(yùn)行時(shí)動(dòng)態(tài)地?cái)U(kuò)展和收縮。在第9章學(xué)習(xí)管理虛擬內(nèi)存時(shí),我們將更詳細(xì)地研究堆。
  • 共享庫。大約在地址空間的中間部分是一塊用來存放像 C 標(biāo)準(zhǔn)庫和數(shù)學(xué)庫這樣的共享庫的代碼和數(shù)據(jù)的區(qū)域。共享庫的概念非常強(qiáng)大,也相當(dāng)難懂。在第7章介紹動(dòng)態(tài)鏈接時(shí),將學(xué)習(xí)共享庫是如何工作的。
  • 棧。位于用戶虛擬地址空間頂部的是用戶棧,編譯器用它來實(shí)現(xiàn)函數(shù)調(diào)用。和堆一樣,用戶棧在程序執(zhí)行期間可以動(dòng)態(tài)地?cái)U(kuò)展和收縮。特別地,每次我們調(diào)用一個(gè)函數(shù)時(shí),棧就會(huì)增長;從一個(gè)函數(shù)返回時(shí),棧就會(huì)收縮。在第3章中將學(xué)習(xí)編譯器是如何使用棧的。
  • 內(nèi)核虛擬內(nèi)存。地址空間頂部的區(qū)域是為內(nèi)核保留的。不允許應(yīng)用程序讀寫這個(gè)區(qū)域的內(nèi)容或者直接調(diào)用內(nèi)核代碼定義的函數(shù)。相反,它們必須調(diào)用內(nèi)核來執(zhí)行這些操作。

虛擬內(nèi)存的運(yùn)作需要硬件和操作系統(tǒng)軟件之間精密復(fù)雜的交互,包括對(duì)處理器生成的每個(gè)地址的硬件翻譯。基本思想是把一個(gè)進(jìn)程虛擬內(nèi)存的內(nèi)容存儲(chǔ)在磁盤上,然后用主存作為磁盤的高速緩存。 第9章將解釋它如何工作,以及為什么對(duì)現(xiàn)代系統(tǒng)的運(yùn)行如此重要。

1.7.4 文件

文件就是字節(jié)序列,僅此而已。每個(gè) I/O 設(shè)備,包括磁盤、鍵盤、顯示器,甚至網(wǎng)絡(luò),都可以看成是文件。系統(tǒng)中的所有輸入輸出都是通過使用一小組稱為 Unix I/O的系統(tǒng)函數(shù)調(diào)用讀寫文件來實(shí)現(xiàn)的。

文件這個(gè)簡單而精致的概念是非常強(qiáng)大的,因?yàn)樗驊?yīng)用程序提供了一個(gè)統(tǒng)一的視圖,來看待系統(tǒng)中可能含有的所有各式各樣的 I/O 設(shè)備。例如,處理磁盤文件內(nèi)容的應(yīng)用程序員可以非常幸福,因?yàn)樗麄儫o須了解具體的磁盤技術(shù)。進(jìn)一步說,同一個(gè)程序可以在使用不同磁盤技術(shù)的不同系統(tǒng)上運(yùn)行。你將在第10章中學(xué)習(xí) Unix I/O。

旁注 Linux項(xiàng)目

1991年8月,芬蘭研究生 Linus Torvalds 謹(jǐn)慎地發(fā)布了一個(gè)新的類Unix的操作系統(tǒng)內(nèi)核,內(nèi)容如下。

來自:torvalds@klaava.Helsinki.FK(Linus Benedict Torvalds)

新聞組:comp.os.minix

主題:在minix中你最想看到什么?

摘要:關(guān)于我的新操作系統(tǒng)的小調(diào)查

時(shí)間:1991年8月25日20:57:08GMT

每個(gè)使用 minix 的朋友,你們好。

我正在做一個(gè)(免費(fèi)的)用在386(486)AT上的操作系統(tǒng)(只是業(yè)余愛好,它不會(huì)像GNU那樣龐大和專業(yè))。這個(gè)想法自4月份就開始醞釀,現(xiàn)在快要完成了。我希望得到各位對(duì)minix的任何反饋意見,因?yàn)槲业牟僮飨到y(tǒng)在某些方面與它相類似(其中包括相同的文件系統(tǒng)的物理設(shè)計(jì)(因?yàn)槟承?shí)際的原因))。

我現(xiàn)在已經(jīng)移植了 bash(1.08) 和 gcc(1.40),并且看上去能運(yùn)行。這意味著我需要幾個(gè)月的時(shí)間來讓它變得更實(shí)用一些,并且,我想要知道大多數(shù)人想要什么特性。歡迎任何建議,但是我無法保證我能實(shí)現(xiàn)它們。:-)

Linus(torvalds@kruuna.helsinki.fi)

就像 Torvalds 所說的,他創(chuàng)建 Linux 的起點(diǎn)是 Minix,由 Andrew S.Tanenbaum出于教育目的開發(fā)的一個(gè)操作系統(tǒng)。

接下來,如他們所說,這就成了歷史。Linux逐漸發(fā)展成為一個(gè)技術(shù)和文化現(xiàn)象。通過和 GNU 項(xiàng)目的力量結(jié)合,Linux 項(xiàng)目發(fā)展成了一個(gè)完整的、符合 Posix 標(biāo)準(zhǔn)的 Unix 操作系統(tǒng)的版本,包括內(nèi)核和所有支撐的基礎(chǔ)設(shè)施。從手持設(shè)備到大型計(jì)算機(jī),Linux 在范圍如此廣泛的計(jì)算機(jī)上得到了應(yīng)用。IBM 的一個(gè)工作組甚至把Linux移植到了一塊腕表中!

1.8 系統(tǒng)之間利用網(wǎng)絡(luò)通信

系統(tǒng)漫游至此,我們一直是把系統(tǒng)視為一個(gè)孤立的硬件和軟件的集合體。實(shí)際上,現(xiàn)代系統(tǒng)經(jīng)常通過網(wǎng)絡(luò)和其他系統(tǒng)連接到一起。從一個(gè)單獨(dú)的系統(tǒng)來看,網(wǎng)絡(luò)可視為一個(gè) I/O 設(shè)備,如圖1-14所示。當(dāng)系統(tǒng)從主存復(fù)制一串字節(jié)到網(wǎng)絡(luò)適配器時(shí),數(shù)據(jù)流經(jīng)過網(wǎng)絡(luò)到達(dá)另一臺(tái)機(jī)器,而不是比如說到達(dá)本地磁盤驅(qū)動(dòng)器。相似地,系統(tǒng)可以讀取從其他機(jī)器發(fā)送來的數(shù)據(jù),并把數(shù)據(jù)復(fù)制到自己的主存。

圖1-14 網(wǎng)絡(luò)也是一種I/O設(shè)備

隨著 Internet 這樣的全球網(wǎng)絡(luò)的出現(xiàn),從一臺(tái)主機(jī)復(fù)制信息到另外一臺(tái)主機(jī)已經(jīng)成為計(jì)算機(jī)系統(tǒng)最重要的用途之一。比如,像電子郵件、即時(shí)通信、萬維網(wǎng)、FTP 和 telnet 這樣的應(yīng)用都是基于網(wǎng)絡(luò)復(fù)制信息的功能。

回到 hello 示例,我們可以使用熟悉的 telnet 應(yīng)用在一個(gè)遠(yuǎn)程主機(jī)上運(yùn)行 hello 程序。假設(shè)用本地主機(jī)上的 telnet 客戶端連接遠(yuǎn)程主機(jī)上的 telnet 服務(wù)器。在我們登錄到遠(yuǎn)程主機(jī)并運(yùn)行 shell 后,遠(yuǎn)端的 shell 就在等待接收輸人命令。此后在遠(yuǎn)端運(yùn)行 hello 程序包括如圖1-15所示的五個(gè)基本步驟。

圖1-15 利用telnet通過網(wǎng)絡(luò)遠(yuǎn)程運(yùn)行hello

當(dāng)我們在telnet客戶端鍵人“hello”字符串并敲下回車鍵后,客戶端軟件就會(huì)將這個(gè)字符串發(fā)送到telnet的服務(wù)器。telnet服務(wù)器從網(wǎng)絡(luò)上接收到這個(gè)字符串后,會(huì)把它傳遞給遠(yuǎn)端shell程序。接下來,遠(yuǎn)端shell運(yùn)行hello程序,并將輸出行返回給telnet服務(wù)器。最后,telnet服務(wù)器通過網(wǎng)絡(luò)把輸出串轉(zhuǎn)發(fā)給telnet客戶端,客戶端就將輸出串輸出到我們的本地終端上。

這種客戶端和服務(wù)器之間交互的類型在所有的網(wǎng)絡(luò)應(yīng)用中是非常典型的。在第11章中,你將學(xué)會(huì)如何構(gòu)造網(wǎng)絡(luò)應(yīng)用程序,并利用這些知識(shí)創(chuàng)建一個(gè)簡單的 Web 服務(wù)器。

1.9 重要主題

在此,小結(jié)一下我們旋風(fēng)式的系統(tǒng)漫游。這次討論得出一個(gè)很重要的觀點(diǎn),那就是系統(tǒng)不僅僅只是硬件。系統(tǒng)是硬件和系統(tǒng)軟件互相交織的集合體,它們必須共同協(xié)作以達(dá)到運(yùn)行應(yīng)用程序的最終目的。本書的余下部分會(huì)講述硬件和軟件的詳細(xì)內(nèi)容,通過了解這些詳細(xì)內(nèi)容,你可以寫出更快速、更可靠和更安全的程序。

作為本章的結(jié)束,我們在此強(qiáng)調(diào)幾個(gè)貫穿計(jì)算機(jī)系統(tǒng)所有方面的重要概念。我們會(huì)在本書中的多處討論這些概念的重要性。

1.9.1 Amdahl 定律

Gene Amdahl,計(jì)算領(lǐng)域的早期先鋒之一,對(duì)提升系統(tǒng)某一部分性能所帶來的效果做出了簡單卻有見地的觀察。這個(gè)觀察被稱為 Amdahl 定律 (Amdahl’s law)。該定律的主要思想是,當(dāng)我們對(duì)系 統(tǒng)的某個(gè)部分加速時(shí),其對(duì)系統(tǒng)整體性能的影響取決于該部分的重要性和加速程度。若系統(tǒng)執(zhí)行某應(yīng)用程序需要時(shí)間為 T_{old} 。假設(shè)系統(tǒng)某部分所需執(zhí)行時(shí)間與該時(shí)間的比例為 \alpha,而該部分性能提升比例為 \kappa。 即該部分初始所需時(shí)間為 \alpha\,T_{old},現(xiàn)在所需時(shí)間為 \alpha\,T_{old}/\kappa因此,總的執(zhí)行時(shí)間應(yīng)為
T_{new}\,=\,(1-\alpha) T_{old}\,+\,\alpha T_{old}/\kappa\,=\,T_{old}\,[(1-\alpha)\,+\,\alpha/\kappa]
由此,可以計(jì)算加速比 S = T_{old}/T_{new}
S\;=\; \frac {1} {(1-\alpha)\,+\,\alpha/\kappa}

舉個(gè)例子,考慮這樣一種情況,系統(tǒng)的某個(gè)部分初始耗時(shí)比例為 60%(α=0.6),其加速比例因子為3(κ=3)。則我們可以獲得的加速比為 1/[0.4+0.6/3]=1.67倍。雖然我們對(duì)系統(tǒng)的一個(gè)主要部分做出了重大改進(jìn),但是獲得的系統(tǒng)加速比卻明顯小于這部分的加速比。這就是 Amdahl 定律的主要觀點(diǎn)——要想顯著加速整個(gè)系統(tǒng),必須提升全系統(tǒng)中相當(dāng)大的部分的速度。

旁注 表示相對(duì)性能

性能提升最好的表示方法就是用比例的形式 T_{old}/T_{new},其中,T_{old} 為原始系統(tǒng)所需時(shí)間,T_{new} 為修改后的系統(tǒng)所需時(shí)間。如果有所改進(jìn),則比值應(yīng)大于1。我們用后綴“Х”來表示比例,因此,“2.2Х” 讀作 “2.2倍”。

表示相對(duì)變化更傳統(tǒng)的方法是用百分比,這種方法適用于變化小的情況,但其定義是模糊的。應(yīng)該等于 100 \cdot (T_{old}-T_{new})/T_{new},還是100 \cdot (T_{old}-T_{new})/T_{old},還是其他的值?此外,它對(duì)較大的變化也沒有太大意義。與簡單地說性能提升2.2X相比,“性能提升了120%”更難理解。

  • ? 練習(xí)題1.1 假設(shè)你是個(gè)卡車司機(jī),要將土豆從愛達(dá)荷州的 Boise 運(yùn)送到明尼蘇達(dá)州的 Minneapolis,全程2500公里。在限速范圍內(nèi),你估計(jì)平均速度為100公里/小時(shí),整個(gè)行程需要25個(gè)小時(shí)。
    • A.你聽到新聞?wù)f蒙大拿州剛剛?cè)∠讼匏?,這使得行程中有1500公里卡車的速度可以為150公里/小時(shí)。那么這對(duì)整個(gè)行程的加速比是多少?
    • B.你可以在 www.fasttrucks.com 網(wǎng)站上為自己的卡車買個(gè)新的渦輪增壓器。網(wǎng)站現(xiàn)貨供應(yīng)各種型號(hào),不過速度越快,價(jià)格越高。如果想要讓整個(gè)行程的加速比為1.67X,那么你必須以多快的速度通過蒙大拿州?
  • ?練習(xí)題1.2 公司的市場部向你的客戶承諾,下一個(gè)版本的軟件性能將改進(jìn)2X。這項(xiàng)任務(wù)被分配給你。你已經(jīng)確認(rèn)只有80%的系統(tǒng)能夠被改進(jìn),那么,這部分需要被改進(jìn)多少(即κ取何值)才能達(dá)到整體性能目標(biāo)?

Amdahl定律一個(gè)有趣的特殊情況是考慮 κ 趨向于∞時(shí)的效果。這就意味著,我們可以取系統(tǒng)的某一部分將其加速到一個(gè)點(diǎn),在這個(gè)點(diǎn)上,這部分花費(fèi)的時(shí)間可以忽略不計(jì)。
于是我們得到
S_\infty \;=\; \frac {1}{(1-\alpha)}
舉個(gè)例子,如果 60% 的系統(tǒng)能夠加速到不花時(shí)間的程度,我們獲得的凈加速比將仍只有 1/0.4=2.5X。

Amdahl定律描述了改善任何過程的一般原則。除了可以用在加速計(jì)算機(jī)系統(tǒng)方面之外,它還可以用在公司試圖降低刀片制造成本,或?qū)W生想要提高自己的績點(diǎn)平均值等方面。也許它在計(jì)算機(jī)世界里是最有意義的,在這里我們常常把性能提升2倍或更高的比例因子。這么高的比例因子只有通過優(yōu)化系統(tǒng)的大部分組件才能獲得。

1.9.2 并發(fā)與并行

數(shù)字計(jì)算機(jī)的整個(gè)歷史中,有兩個(gè)需求是驅(qū)動(dòng)進(jìn)步的持續(xù)動(dòng)力:一個(gè)是我們想要計(jì)算機(jī)做得更多,另一個(gè)是我們想要計(jì)算機(jī)運(yùn)行得更快。當(dāng)處理器能夠同時(shí)做更多的事情時(shí),這兩個(gè)因素都會(huì)改進(jìn)。我們用的術(shù)語并發(fā)(concurrency)是一個(gè)通用的概念,指一個(gè)同時(shí)具有多個(gè)活動(dòng)的系統(tǒng);而術(shù)語并行(parallelism)指的是用并發(fā)來使一個(gè)系統(tǒng)運(yùn)行得更快。并行可以在計(jì)算機(jī)系統(tǒng)的多個(gè)抽象層次上運(yùn)用。在此,我們按照系統(tǒng)層次結(jié)構(gòu)中由高到低的順序重點(diǎn)強(qiáng)調(diào)三個(gè)層次。

  1. 線程級(jí)并發(fā)


    構(gòu)建在進(jìn)程這個(gè)抽象之上,我們能夠設(shè)計(jì)出同時(shí)有多個(gè)程序執(zhí)行的系統(tǒng),這就導(dǎo)致了并發(fā)。使用線程,我們甚至能夠在一個(gè)進(jìn)程中執(zhí)行多個(gè)控制流。自20世紀(jì)60年代初期出現(xiàn)時(shí)間共享以來,計(jì)算機(jī)系統(tǒng)中就開始有了對(duì)并發(fā)執(zhí)行的支持。傳統(tǒng)意義上,這種并發(fā)執(zhí)行只是模擬出來的,是通過使一臺(tái)計(jì)算機(jī)在它正在執(zhí)行的進(jìn)程間快速切換來實(shí)現(xiàn)的,就好像一個(gè)雜耍藝人保持多個(gè)球在空中飛舞一樣。這種并發(fā)形式允許多個(gè)用戶同時(shí)與系統(tǒng)交互,例如,當(dāng)許多人想要從一個(gè)Web服務(wù)器獲取頁面時(shí)。它還允許一個(gè)用戶同時(shí)從事多個(gè)任務(wù),例如,在一個(gè)窗口中開啟Web瀏覽器,在另一窗口中運(yùn)行字處理器,同時(shí)又播放音樂。在以前,即使處理器必須在多個(gè)任務(wù)間切換,大多數(shù)實(shí)際的計(jì)算也都是由一個(gè)處理器來完成的。這種配置稱為單處理器系統(tǒng)。


    當(dāng)構(gòu)建一個(gè)由單操作系統(tǒng)內(nèi)核控制的多處理器組成的系統(tǒng)時(shí),我們就得到了一個(gè)多處理器系統(tǒng)。其實(shí)從20世紀(jì)80年代開始,在大規(guī)模的計(jì)算中就有了這種系統(tǒng),但是直到最近,隨著多核處理器和超線程(hyperthreading)的出現(xiàn),這種系統(tǒng)才變得常見。圖1-16給出了這些不同處理器類型的分類。
圖1-16 不同的處理器配置分類。隨著多核處理器和超線程的出現(xiàn),多處理器變得普遍了

多核處理器是將多個(gè)CPU(稱為“核”)集成到一個(gè)集成電路芯片上。圖1-17 描述的是一個(gè)典型多核處理器的組織結(jié)構(gòu),其中微處理器芯片有4個(gè)CPU核,每個(gè)核都有自己的L1和L2高速緩存,其中的L1高速緩存分為兩個(gè)部分———— 一個(gè)保存最近取到的指令,另一個(gè)存放數(shù)據(jù)。這些核共享更高層次的高速緩存,以及到主存的接口。工業(yè)界的專家預(yù)言他們能夠?qū)资畟€(gè)、最終會(huì)是上百個(gè)核做到一個(gè)芯片上。

圖1-17 多核處理器的組織結(jié)構(gòu)。4個(gè)處理器核集成在一個(gè)芯片上

超線程,有時(shí)稱為同時(shí)多線程(simultaneous multi-threading),是一項(xiàng)允許一個(gè) CPU 執(zhí)行多個(gè)控制流的技術(shù)。它涉及 CPU 某些硬件有多個(gè)備份,比如程序計(jì)數(shù)器和寄存器文件,而其他的硬件部分只有一份,比如執(zhí)行浮點(diǎn)算術(shù)運(yùn)算的單元。常規(guī)的處理器需要大約20000個(gè)時(shí)鐘周期做不同線程間的轉(zhuǎn)換,而超線程的處理器可以在單個(gè)周期的基礎(chǔ)上決定要執(zhí)行哪一個(gè)線程。這使得CPU能夠更好地利用它的處理資源。比如,假設(shè)一個(gè)線程必須等到某些數(shù)據(jù)被裝載到高速緩存中,那CPU就可以繼續(xù)去執(zhí)行另一個(gè)線程。舉例來說,Intel Core i7 處理器可以讓每個(gè)核執(zhí)行兩個(gè)線程,所以一個(gè)4核的系統(tǒng)實(shí)際上可以并行地執(zhí)行8個(gè)線程。

多處理器的使用可以從兩方面提高系統(tǒng)性能。首先,它減少了在執(zhí)行多個(gè)任務(wù)時(shí)模擬并發(fā)的需要。正如前面提到的,即使是只有一個(gè)用戶使用的個(gè)人計(jì)算機(jī)也需要并發(fā)地執(zhí)行多個(gè)活動(dòng)。其次,它可以使應(yīng)用程序運(yùn)行得更快,當(dāng)然,這必須要求程序是以多線程方式來書寫的,這些線程可以并行地高效執(zhí)行。因此,雖然并發(fā)原理的形成和研究已經(jīng)超過50年的時(shí)間了,但是多核和超線程系統(tǒng)的出現(xiàn)才極大地激發(fā)了一種愿望,即找到書寫應(yīng)用程序的方法利用硬件開發(fā)線程級(jí)并行性。第12章會(huì)更深人地探討并發(fā),以及使用并發(fā)來提供處理器資源的共享,使程序的執(zhí)行允許有更多的并行。

  1. 指令級(jí)并行


    在較低的抽象層次上,現(xiàn)代處理器可以同時(shí)執(zhí)行多條指令的屬性稱為指令級(jí)并行。早期的微處理器,如 1978 年的 Intel 8086,需要多個(gè)(通常是310個(gè))時(shí)鐘周期來執(zhí)行一條指令。最近的處理器可以保持每個(gè)時(shí)鐘周期24條指令的執(zhí)行速率。其實(shí)每條指令從開始到結(jié)束需要長得多的時(shí)間,大約20個(gè)或者更多周期,但是處理器使用了非常多的聰明技巧來同時(shí)處理多達(dá)100條指令。在第4章中,我們會(huì)研究流水線(pipelining)的使用。在流水線中,將執(zhí)行一條指令所需要的活動(dòng)劃分成不同的步驟,將處理器的硬件組織成一系列的階段,每個(gè)階段執(zhí)行一個(gè)步驟。這些階段可以并行地操作,用來處理不同指令的不同部分。我們會(huì)看到一個(gè)相當(dāng)簡單的硬件設(shè)計(jì),它能夠達(dá)到接近于一個(gè)時(shí)鐘周期一條指令的執(zhí)行速率。


    如果處理器可以達(dá)到比一個(gè)周期一條指令更快的執(zhí)行速率,就稱之為超標(biāo)量(superscalar)處理器。大多數(shù)現(xiàn)代處理器都支持超標(biāo)量操作。第5章中,我們將描述超標(biāo)量處理器的高級(jí)模型。應(yīng)用程序員可以用這個(gè)模型來理解程序的性能。然后,他們就能寫出擁有更高程度的指令級(jí)并行性的程序代碼,因而也運(yùn)行得更快。

  2. 單指令、多數(shù)據(jù)并行


    在最低層次上,許多現(xiàn)代處理器擁有特殊的硬件,允許一條指令產(chǎn)生多個(gè)可以并行執(zhí)行的操作,這種方式稱為單指令、多數(shù)據(jù),即 SIMD 并行。例如,較新幾代的Intel和AMD處理器都具有并行地對(duì)8對(duì)單精度浮點(diǎn)數(shù)( C數(shù)據(jù)類型 float )做加法的指令。


    提供這些 SIMD 指令多是為了提高處理影像、聲音和視頻數(shù)據(jù)應(yīng)用的執(zhí)行速度。雖然有些編譯器會(huì)試圖從 C 程序中自動(dòng)抽取 SIMD 并行性,但是更可靠的方法是用編譯器支持的特殊的向量數(shù)據(jù)類型來寫程序,比如 GCC 就支持向量數(shù)據(jù)類型。作為對(duì)第5章中比較通用的程序優(yōu)化描述的補(bǔ)充,我們在網(wǎng)絡(luò)旁注 OPT:SIMD 中描述了這種編程方式。

1.9.3 計(jì)算機(jī)系統(tǒng)中抽象的重要性

抽象的使用是計(jì)算機(jī)科學(xué)中最為重要的概念之一。例如,為一組函數(shù)規(guī)定一個(gè)簡單的應(yīng)用程序接口(API)就是一個(gè)很好的編程習(xí)慣,程序員無須了解它內(nèi)部的工作便可以使用這些代碼。不同的編程語言提供不同形式和等級(jí)的抽象支持,例如Java類的聲明和C語言的函數(shù)原型。

我們已經(jīng)介紹了計(jì)算機(jī)系統(tǒng)中使用的幾個(gè)抽象,如圖1-18所示。在處理器里,指令集架構(gòu)提供了對(duì)實(shí)際處理器硬件的抽象。使用這個(gè)抽象,機(jī)器代碼程序表現(xiàn)得就好像運(yùn)行在一個(gè)一次只執(zhí)行一條指令的處理器上。底層的硬件遠(yuǎn)比抽象描述的要復(fù)雜精細(xì),它并行地執(zhí)行多條指令,但又總是與那個(gè)簡單有序的模型保持一致。只要執(zhí)行模型一樣,不同的處理器實(shí)現(xiàn)也能執(zhí)行同樣的機(jī)器代碼,而又提供不同的開銷和性能。

圖1-18 計(jì)算機(jī)系統(tǒng)提供的一些抽象。計(jì)算機(jī)系統(tǒng)中的一個(gè)重大主題就是提供不同層次的抽象表示,來隱藏實(shí)際實(shí)現(xiàn)的復(fù)雜性

在學(xué)習(xí)操作系統(tǒng)時(shí),我們介紹了三個(gè)抽象:文件是對(duì) I/O 設(shè)備的抽象,虛擬內(nèi)存是對(duì)程序存儲(chǔ)器的抽象,而進(jìn)程是對(duì)一個(gè)正在運(yùn)行的程序的抽象。我們再增加一個(gè)新的抽象:虛擬機(jī),它提供對(duì)整個(gè)計(jì)算機(jī)的抽象,包括操作系統(tǒng)、處理器和程序。虛擬機(jī)的思想是IBM在20世紀(jì)60年代提出來的,但是最近才顯示出其管理計(jì)算機(jī)方式上的優(yōu)勢,因?yàn)橐恍┯?jì)算機(jī)必須能夠運(yùn)行為不同的操作系統(tǒng) (例如,MicrosoftWindows、MacOS 和 Linux) 或同一操作系統(tǒng)的不同版本設(shè)計(jì)的程序。

在本書后續(xù)的章節(jié)中,我們會(huì)具體介紹這些抽象。

1.10 小結(jié)

計(jì)算機(jī)系統(tǒng)是由硬件和系統(tǒng)軟件組成的,它們共同協(xié)作以運(yùn)行應(yīng)用程序。計(jì)算機(jī)內(nèi)部的信息被表示為一組組的位,它們依據(jù)上下文有不同的解釋方式。程序被其他程序翻譯成不同的形式,開始時(shí)是 ASCII 文本,然后被編譯器和鏈接器翻譯成二進(jìn)制可執(zhí)行文件。

處理器讀取并解釋存放在主存里的二進(jìn)制指令。因?yàn)橛?jì)算機(jī)花費(fèi)了大量的時(shí)間在內(nèi)存、I/O 設(shè)備和 CPU 寄存器之間復(fù)制數(shù)據(jù),所以將系統(tǒng)中的存儲(chǔ)設(shè)備劃分成層次結(jié)構(gòu)—— CPU寄存器在頂部,接著是多層的硬件高速緩存存儲(chǔ)器、DRAM 主存和磁盤存儲(chǔ)器。在層次模型中,位于更高層的存儲(chǔ)設(shè)備比低層的存儲(chǔ)設(shè)備要更快,單位比特造價(jià)也更高。層次結(jié)構(gòu)中較高層次的存儲(chǔ)設(shè)備可以作為較低層次設(shè)備的高速緩存。通過理解和運(yùn)用這種存儲(chǔ)層次結(jié)構(gòu)的知識(shí),程序員可以優(yōu)化 C 程序的性能。

操作系統(tǒng)內(nèi)核是應(yīng)用程序和硬件之間的媒介。它提供三個(gè)基本的抽象:1) 文件是對(duì)I/O 設(shè)備的抽像;2) 虛擬內(nèi)存是對(duì)主存和磁盤的抽象;3) 進(jìn)程是處理器、主存和 I/O 設(shè)備的抽象。

最后,網(wǎng)絡(luò)提供了計(jì)算機(jī)系統(tǒng)之間通信的手段。從特殊系統(tǒng)的角度來看,網(wǎng)絡(luò)就是一種 I/O設(shè)備。

練習(xí)題答案

  1. 1 該問題說明Amdahl定律不僅僅適用于計(jì)算機(jī)系統(tǒng)。
    • A. 根據(jù)公式1.1,有 α = 0.6, κ = 1.5。更直接地說,在蒙大拿行駛的1500公里需要10個(gè)小時(shí),而其他行程也需要10個(gè)小時(shí)。則加速比為 25/(10+10) = 1.25X。
    • B. 根據(jù)公式1.1 ,有 α = 0.6,要求 S = l.67,則可算出κ。更直接地說,要使行程加速度達(dá)到1.67X,我們必須把全程時(shí)間減少到15個(gè)小時(shí)。蒙大拿以外仍要求為10小時(shí),因此,通過蒙大拿的時(shí)間就為5個(gè)小時(shí)。這就要求行駛速度為300公里/小時(shí),對(duì)卡車來說這個(gè)速度太快了!
  2. 2 理解 Amdahl 定律最好的方法就是解決一些實(shí)例。本題要求你從特殊的角度來看公式1.1。


    本題是公式的簡單應(yīng)用。已知 S = 2, α = 0.8,則計(jì)算κ:
    2 \;=\; \frac {1}{(1 - 0.8) \, + \, 0.8/\kappa}
    0.4 + 1.6 / \kappa \; = \; 1.0
    \kappa \; = \; 2.67
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容