現代計算機系統由一個或多個處理器、主存、打印機、鍵盤、鼠標、顯示器、網絡接口以及各類輸入/輸出設備構成。java
然而,程序員不會直接和這些硬件打交道,並且每位程序員不可能會掌握全部計算機系統的細節,這樣咱們就不用再編寫代碼了,因此在硬件的基礎之上,計算機安裝了一層軟件,這層軟件可以經過響應用戶輸入的指令達到控制硬件的效果,從而知足用戶需求,這種軟件稱之爲 操做系統
,它的任務就是爲用戶程序提供一個更好、更簡單、更清晰的計算機模型。node
咱們通常常見的操做系統主要有 Windows、Linux、FreeBSD 或 OS X ,這種帶有圖形界面的操做系統被稱爲 圖形用戶界面(Graphical User Interface, GUI)
,而基於文本、命令行的一般稱爲 Shell
。下面是咱們所要探討的操做系統的部件git
這是一個操做系統的簡化圖,最下面的是硬件,硬件包括芯片、電路板、磁盤、鍵盤、顯示器等咱們上面提到的設備,在硬件之上是軟件。大部分計算機有兩種運行模式:內核態
和 用戶態
,軟件中最基礎的部分是操做系統
,它運行在 內核態
中,內核態也稱爲管態
和 核心態
,它們都是操做系統的運行狀態,只不過是不一樣的叫法而已。操做系統具備硬件的訪問權,能夠執行機器可以運行的任何指令。軟件的其他部分運行在 用戶態
下。
程序員
用戶接口程序(shell 或者 GUI)
處於用戶態中,而且它們位於用戶態的最低層,容許用戶運行其餘程序,例如 Web 瀏覽器、電子郵件閱讀器、音樂播放器等。並且,越靠近用戶態的應用程序越容易編寫,若是你不喜歡某個電子郵件閱讀器你能夠從新寫一個或者換一個,但你不能自行寫一個操做系統或者是中斷處理程序。這個程序由硬件保護,防止外部對其進行修改。web
操做系統與運行操做系統的內核硬件關係密切。操做系統擴展了計算機指令集並管理計算機的資源。所以,操做系統所以必須足夠了解硬件的運行,這裏咱們先簡要介紹一下現代計算機中的計算機硬件。算法
從概念上來看,一臺簡單的我的電腦能夠被抽象爲上面這種類似的模型,CPU、內存、I/O 設備都和總線串聯起來並經過總線與其餘設備進行通訊。現代操做系統有着更爲複雜的結構,會設計不少條總線,咱們稍後會看到。暫時來說,這個模型可以知足咱們的討論。shell
CPU 是計算機的大腦,它主要和內存進行交互,從內存中提取指令並執行它。一個 CPU 的執行週期是從內存中提取第一條指令、解碼並決定它的類型和操做數,執行,而後再提取、解碼執行後續的指令。重複該循環直到程序運行完畢。編程
每一個 CPU 都有一組能夠執行的特定指令集。所以,x86 的 CPU 不能執行 ARM 的程序而且 ARM 的 CPU 也不能執行 x86 的程序。因爲訪問內存獲取執行或數據要比執行指令花費的時間長,所以全部的 CPU 內部都會包含一些寄存器
來保存關鍵變量和臨時結果。所以,在指令集中一般會有一些指令用於把關鍵字從內存中加載到寄存器中,以及把關鍵字從寄存器存入到內存中。還有一些其餘的指令會把來自寄存器和內存的操做數進行組合,例如 add 操做就會把兩個操做數相加並把結果保存到內存中。bootstrap
除了用於保存變量和臨時結果的通用寄存器外,大多數計算機還具備幾個特殊的寄存器,這些寄存器對於程序員是可見的。其中之一就是 程序計數器(program counter)
,程序計數器會指示下一條須要從內存提取指令的地址。提取指令後,程序計數器將更新爲下一條須要提取的地址。數組
另外一個寄存器是 堆棧指針(stack pointer)
,它指向內存中當前棧的頂端。堆棧指針會包含輸入過程當中的有關參數、局部變量以及沒有保存在寄存器中的臨時變量。
還有一個寄存器是 PSW(Program Status Word)
程序狀態字寄存器,這個寄存器是由操做系統維護的8個字節(64位) long 類型的數據集合。它會跟蹤當前系統的狀態。除非發生系統結束,不然咱們能夠忽略 PSW 。用戶程序一般能夠讀取整個PSW,但一般只能寫入其某些字段。PSW 在系統調用和 I / O 中起着重要做用。
操做系統必須瞭解全部的寄存器。在時間多路複用(time multiplexing)
的 CPU 中,操做系統每每中止運行一個程序轉而運行另一個。每次當操做系統中止運行一個程序時,操做系統會保存全部寄存器的值,以便於後續從新運行該程序。
爲了提高性能, CPU 設計人員早就放棄了同時去讀取、解碼和執行一條簡單的指令。許多現代的 CPU 都具備同時讀取多條指令的機制。例如,一個 CPU 可能會有單獨訪問、解碼和執行單元,因此,當 CPU 執行第 N 條指令時,還能夠對 N + 1 條指令解碼,還能夠讀取 N + 2 條指令。像這樣的組織形式被稱爲 流水線(pipeline)。
比流水線更先進的設計是 超標量(superscalar)
CPU,下面是超標量 CPU 的設計
在上面這個設計中,存在多個執行單元,例如,一個用來進行整數運算、一個用來浮點數運算、一個用來布爾運算。兩個或者更多的指令被一次性取出、解碼並放入緩衝區中,直至它們執行完畢。只要一個執行單元空閒,就會去檢查緩衝區是否有能夠執行的指令。若是有,就把指令從緩衝區中取出並執行。這種設計的含義是應用程序一般是無序執行的。在大多數狀況下,硬件負責保證這種運算的結果與順序執行指令時的結果相同。
除了用在嵌入式系統中很是簡單的 CPU 以外,多數 CPU 都有兩種模式
,即前面已經提到的內核態和用戶態。一般狀況下,PSW 寄存器
中的一個二進制位會控制當前狀態是內核態仍是用戶態。當運行在內核態時,CPU 可以執行任何指令集中的指令而且可以使用硬件的功能。在臺式機和服務器上,操做系統一般之內核模式運行,從而能夠訪問完整的硬件。在大多數嵌入式系統中,一部分運行在內核態下,剩下的一部分運行在用戶態下。
用戶應用程序一般運行在用戶態下,在用戶態下,CPU 只能執行指令集中的一部分而且只能訪問硬件的一部分功能。通常狀況下,在用戶態下,有關 I/O 和內存保護的全部指令是禁止執行的。固然,設置 PSW 模式的二進制位爲內核態也是禁止的。
爲了獲取操做系統的服務,用戶程序必須使用 系統調用(system call)
,系統調用會轉換爲內核態而且調用操做系統。TRAP
指令用於把用戶態切換爲內核態並啓用操做系統。當有關工做完成以後,在系統調用後面的指令會把控制權交給用戶程序。咱們會在後面探討操做系統的調用細節。
須要注意的是操做系統在進行系統調用時會存在陷阱。大部分的陷阱會致使硬件發出警告,好比說試圖被零除或浮點下溢等你。在全部的狀況下,操做系統都能獲得控制權並決定如何處理異常狀況。有時,因爲出錯的緣由,程序不得不中止。
Intel Pentinum 4也就是奔騰處理器引入了被稱爲多線程(multithreading)
或 超線程(hyperthreading, Intel 公司的命名)
的特性,x86 處理器和其餘一些 CPU 芯片就是這樣作的。包括 SSPARC、Power五、Intel Xeon 和 Intel Core 系列 。近似地說,多線程容許 CPU 保持兩個不一樣的線程狀態而且在納秒級(nanosecond)
的時間完成切換。線程是一種輕量級的進程,咱們會在後面說到。例如,若是一個進程想要從內存中讀取指令(這一般會經歷幾個時鐘週期),多線程 CPU 則能夠切換至另外一個線程。多線程不會提供真正的並行處理。在一個時刻只有一個進程在運行。
對於操做系統來說,多線程是有意義的,由於每一個線程對操做系統來講都像是一個單個的 CPU。好比一個有兩個 CPU 的操做系統,而且每一個 CPU 運行兩個線程,那麼這對於操做系統來講就多是 4 個 CPU。
除了多線程以外,如今許多 CPU 芯片上都具備四個、八個或更多完整的處理器或內核。多核芯片在其上有效地承載了四個微型芯片,每一個微型芯片都有本身的獨立CPU。
若是要說在絕對核心數量方面,沒有什麼能贏過現代 GPU(Graphics Processing Unit)
,GPU 是指由成千上萬個微核組成的處理器。它們擅長處理大量並行的簡單計算。
計算機中第二個主要的組件就是內存。理想狀況下,內存應該很是快速(比執行一條指令要快,從而不會拖慢 CPU 執行效率),並且足夠大且便宜,可是目前的技術手段沒法知足三者的需求。因而採用了不一樣的處理方式,存儲器系統採用一種分層次的結構
頂層的存儲器速度最高,可是容量最小,成本很是高,層級結構越向下,其訪問效率越慢,容量越大,可是造價也就越便宜。
存儲器的頂層是 CPU 中的寄存器
,它們用和 CPU 同樣的材料製成,因此和 CPU 同樣快。程序必須在軟件中自行管理這些寄存器(即決定如何使用它們)
位於寄存器下面的是高速緩存
,它多數由硬件控制。主存被分割成高速緩存行(cache lines)
爲 64 字節,內存地址的 0 - 63 對應高速緩存行 0 ,地址 64 - 127 對應高速緩存行的 1,等等。使用最頻繁的高速緩存行保存在位於 CPU 內部或很是靠近 CPU 的高速緩存中。當應用程序須要從內存中讀取關鍵詞的時候,高速緩存的硬件會檢查所須要的高速緩存行是否在高速緩存中。若是在的話,那麼這就是高速緩存命中(cache hit)
。高速緩存知足了該請求,而且沒有經過總線將內存請求發送到主內存。高速緩存命中一般須要花費兩個時鐘週期。緩存未命中須要從內存中提取,這會消耗大量的時間。高速緩存行會限制容量的大小由於它的造價很是昂貴。有一些機器會有兩個或者三個高速緩存級別,每一級高速緩存比前一級慢且容量更大。
緩存在計算機不少領域都扮演了很是重要的角色,不只僅是 RAM 緩存行。
「隨機存儲器(RAM):內存中最重要的一種,表示既能夠從中讀取數據,也能夠寫入數據。當機器關閉時,內存中的信息會
丟失
。
大量的可用資源被劃分爲小的部分,這些可用資源的一部分會得到比其餘資源更頻繁的使用權,緩存常常用來提高性能。操做系統無時無刻的不在使用緩存。例如,大多數操做系統在主機內存中保留(部分)頻繁使用的文件,以免重複從磁盤重複獲取。舉個例子,相似於 /home/ast/projects/minix3/src/kernel/clock.c
這樣的場路徑名轉換成的文件所在磁盤地址的結果也能夠保存緩存中,以免重複尋址。另外,當一個 Web 頁面(URL) 的地址轉換爲網絡地址(IP地址)後,這個轉換結果也能夠緩存起來供未來使用。
在任何緩存系統中,都會有下面這幾個噬需解決的問題
並非每一個問題都與每種緩存狀況有關。對於 CPU 緩存中的主存緩存行,當有緩存未命中時,就會調入新的內容。一般經過所引用內存地址的高位計算應該使用的緩存行。
緩存是解決問題的一種好的方式,因此現代 CPU 設計了兩種緩存。第一級緩存或者說是 L1 cache
老是位於 CPU 內部,用來將已解碼的指令調入 CPU 的執行引擎。對於那些頻繁使用的關鍵字,多數芯片有第二個 L1 cache 。典型的 L1 cache 的大小爲 16 KB。另外,每每還設有二級緩存,也就是 L2 cache
,用來存放最近使用過的關鍵字,通常是兆字節爲單位。L1 cache 和 L2 cache 最大的不一樣在因而否存在延遲。訪問 L1 cache 沒有任何的延遲,然而訪問 L2 cache 會有 1 - 2 個時鐘週期的延後。
「什麼是時鐘週期?計算機處理器或 CPU 的速度由時鐘週期來肯定,該時鐘週期是振盪器兩個脈衝之間的時間量。通常而言,每秒脈衝數越高,計算機處理器處理信息的速度就越快。時鐘速度以 Hz 爲單位測量,一般爲兆赫(MHz)或千兆赫(GHz)。例如,一個4 GHz處理器每秒執行4,000,000,000個時鐘週期。
計算機處理器能夠在每一個時鐘週期執行一條或多條指令,這具體取決於處理器的類型。早期的計算機處理器和較慢的 CPU 在每一個時鐘週期只能執行一條指令,而現代處理器在每一個時鐘週期能夠執行多條指令。
在上面的層次結構中再下一層是主存
,這是內存系統的主力軍,主存一般叫作RAM(Random Access Memory)
,因爲 1950 年代和 1960 年代的計算機使用微小的可磁化鐵氧體磁芯做爲主存儲器,所以舊時有時將其稱爲核心存儲器。全部不能再高速緩存中獲得知足的內存訪問請求都會轉往主存中。
除了主存以外,許多計算機還具備少許的非易失性隨機存取存儲器。它們與 RAM 不一樣,在電源斷電後,非易失性隨機訪問存儲器並不會丟失內容。ROM(Read Only Memory)
中的內容一旦存儲後就不會再被修改。它很是快並且便宜。(若是有人問你,有沒有什麼又快又便宜的內存設備,那就是 ROM 了)在計算機中,用於啓動計算機的引導加載模塊(也就是 bootstrap )就存放在 ROM 中。另外,一些 I/O 卡也採用 ROM 處理底層設備控制。
EEPROM(Electrically Erasable PROM,)
和 閃存(flash memory)
也是非易失性的,可是與 ROM 相反,它們能夠擦除和重寫。不太重寫它們須要比寫入 RAM 更多的時間,因此它們的使用方式與 ROM 相同,可是與 ROM 不一樣的是他們能夠經過重寫字段來糾正程序中出現的錯誤。
閃存也一般用來做爲便攜性的存儲媒介。閃存是數碼相機中的膠捲,是便攜式音樂播放器的磁盤。閃存的速度介於 RAM 和磁盤之間。另外,與磁盤存儲器不一樣的是,若是閃存擦除的次數太多,會出現磨損。
還有一類是 CMOS,它是易失性的。許多計算機都會使用 CMOS 存儲器保持當前時間和日期。
下一個層次是磁盤(硬盤)
,磁盤同 RAM 相比,每一個二進制位的成本低了兩個數量級,並且常常也有兩個數量級大的容量。磁盤惟一的問題是隨機訪問數據時間大約慢了三個數量級。磁盤訪問慢的緣由是由於磁盤的構造不一樣
磁盤是一種機械裝置,在一個磁盤中有一個或多個金屬盤片,它們以 5400rpm、7200rpm、10800rpm 或更高的速度旋轉。從邊緣開始有一個機械臂懸橫在盤面上,這相似於老式播放塑料唱片 33 轉唱機上的拾音臂。信息會寫在磁盤一系列的同心圓上。在任意一個給定臂的位置,每一個磁頭能夠讀取一段環形區域,稱爲磁道(track)
。把一個給定臂的位置上的全部磁道合併起來,組成了一個柱面(cylinder)
。
每一個磁道劃分若干扇區,扇區的值是 512 字節。在現代磁盤中,較外部的柱面比較內部的柱面有更多的扇區。機械臂從一個柱面移動到相鄰的柱面大約須要 1ms。而隨機移到一個柱面的典型時間爲 5ms 至 10ms,具體狀況以驅動器爲準。一旦磁臂到達正確的磁道上,驅動器必須等待所需的扇區旋轉到磁頭之下,就開始讀寫,低端硬盤的速率是50MB/s
,而高速磁盤的速率是 160MB/s
。
「須要注意,
固態硬盤(Solid State Disk, SSD)
不是磁盤,固態硬盤並無能夠移動的部分,外形也不像唱片,而且數據是存儲在存儲器(閃存)
中,與磁盤惟一的類似之處就是它也存儲了大量即便在電源關閉也不會丟失的數據。
許多計算機支持一種著名的虛擬內存
機制,這種機制使得指望運行的存儲空間大於實際的物理存儲空間。其方法是將程序放在磁盤上,而將主存做爲一部分緩存,用來保存最頻繁使用的部分程序,這種機制須要快速映像內存地址,用來把程序生成的地址轉換爲有關字節在 RAM 中的物理地址。這種映像由 CPU 中的一個稱爲 存儲器管理單元(Memory Management Unit, MMU)
的部件來完成。
緩存和 MMU 的出現是對系統的性能有很重要的影響,在多道程序系統中,從一個程序切換到另外一個程序的機制稱爲 上下文切換(context switch)
,對來自緩存中的資源進行修改並把其寫回磁盤是頗有必要的。
CPU 和存儲器不是操做系統須要管理的所有,I/O
設備也與操做系統關係密切。能夠參考上面這個圖片,I/O 設備通常包括兩個部分:設備控制器和設備自己。控制器自己是一塊芯片或者一組芯片,它可以控制物理設備。它可以接收操做系統的指令,例如,從設備中讀取數據並完成數據的處理。
在許多狀況下,實際控制設備的過程是很是複雜並且存在諸多細節。所以控制器的工做就是爲操做系統提供一個更簡單(但仍然很是複雜)的接口。也就是屏蔽物理細節。任何複雜的東西均可以加一層代理來解決,這是計算機或者人類社會很普世的一個解決方案
I/O 設備另外一部分是設備自己,設備自己有一個相對簡單的接口,這是由於接口既不能作不少工做,並且也已經被標準化了。例如,標準化後任何一個 SATA 磁盤控制器就能夠適配任意一種 SATA 磁盤,因此標準化是必要的。ATA
表明 高級技術附件(AT Attachment)
,而 SATA 表示串行高級技術附件(Serial ATA)
。
「AT 是啥?它是 IBM 公司的第二代我的計算機的
高級
技術成果,使用 1984 年推出的 6MHz 80286 處理器,這個處理器是當時最強大的。
像是高級這種詞彙應該慎用,不然 20 年後再回首極可能會被無情打臉。
如今 SATA 是不少計算機的標準硬盤接口。因爲實際的設備接口隱藏在控制器中,因此操做系統看到的是對控制器的接口,這個接口和設備接口有很大區別。
每種類型的設備控制器都是不一樣的,因此須要不一樣的軟件進行控制。專門與控制器進行信息交流,發出命令處理指令接收響應的軟件,稱爲 設備驅動程序(device driver)
。每一個控制器廠家都應該針對不一樣的操做系統提供不一樣的設備驅動程序。
爲了使設備驅動程序可以工做,必須把它安裝在操做系統中,這樣可以使它在內核態中運行。要將設備驅動程序裝入操做系統,通常有三個途徑
UNIX
系統採用的工做方式Windows
採用的工做方式 每一個設備控制器都有少許用於通訊的寄存器,例如,一個最小的磁盤控制器也會有用於指定磁盤地址、內存地址、扇區計數的寄存器。要激活控制器,設備驅動程序回從操做系統獲取一條指令,而後翻譯成對應的值,並寫入設備寄存器中,全部設備寄存器的結合構成了 I/O 端口空間
。
在一些計算機中,設備寄存器會被映射到操做系統的可用地址空間,使他們可以向內存同樣完成讀寫操做。在這種計算機中,不須要專門的 I/O 指令,用戶程序能夠被硬件阻擋在外,防止其接觸這些存儲器地址(例如,採用基址寄存器和變址寄存器)。在另外一些計算機中,設備寄存器被放入一個專門的 I/O 端口空間,每一個寄存器都有一個端口地址。在這些計算機中,特殊的 IN
和 OUT
指令會在內核態下啓用,它可以容許設備驅動程序和寄存器進行讀寫。前面第一種方式會限制特殊的 I/O 指令可是容許一些地址空間;後者不須要地址空間可是須要特殊的指令,這兩種應用都很普遍。
實現輸入和輸出的方式有三種。
忙等待(busy waiting)
,這種方式的缺點是要一直佔據 CPU,CPU 會一直輪詢 I/O 設備直到 I/O 操做完成。中斷
通知操做完成。在操做系統中,中斷是很是重要的,因此這須要更加細緻的討論一下。
如上圖所示,這是一個三步的 I/O 過程,第一步,設備驅動程序會經過寫入設備寄存器告訴控制器應該作什麼。而後,控制器啓動設備。當控制器完成讀取或寫入被告知須要傳輸的字節後,它會在步驟 2 中使用某些總線向中斷控制器發送信號。若是中斷控制器準備好了接收中斷信號(若是正忙於一個優先級較高的中斷,則可能不會接收),那麼它就會在 CPU 的一個引腳上面聲明。這就是步驟3
在第四步中,中斷控制器把該設備的編號放在總線上,這樣 CPU 能夠讀取總線,而且知道哪一個設備完成了操做(可能同時有多個設備同時運行)。
一旦 CPU 決定去實施中斷後,程序計數器和 PSW 就會被壓入到當前堆棧中而且 CPU 會切換到內核態。設備編號能夠做爲內存的一個引用,用來尋找該設備中斷處理程序的地址。這部份內存稱做中斷向量(interrupt vector)
。一旦中斷處理程序(中斷設備的設備驅動程序的一部分)開始後,它會移除棧中的程序計數器和 PSW 寄存器,並把它們進行保存,而後查詢設備的狀態。在中斷處理程序所有完成後,它會返回到先前用戶程序還沒有執行的第一條指令,這個過程以下
直接存儲器訪問(Direct Memory Access, DMA)
芯片。它能夠控制內存和某些控制器之間的位流,而無需 CPU 的干預。CPU 會對 DMA 芯片進行設置,說明須要傳送的字節數,有關的設備和內存地址以及操做方向。當 DMA 芯片完成後,會形成中斷,中斷過程就像上面描述的那樣。咱們會在後面具體討論中斷過程當另外一箇中斷處理程序正在運行時,中斷可能(而且常常)發生在不合宜的時間。所以,CPU 能夠禁用中斷,而且能夠在以後重啓中斷。在 CPU 關閉中斷後,任何已經發出中斷的設備,能夠繼續保持其中斷信號處理,可是 CPU 不會中斷,直至中斷再次啓用爲止。若是在關閉中斷時,已經有多個設備發出了中斷信號,中斷控制器將決定優先處理哪一個中斷,一般這取決於事先賦予每一個設備的優先級,最高優先級的設備優先贏得中斷權,其餘設備則必須等待。
上面的結構(簡單我的計算機的組件圖)在小型計算機已經使用了多年,並用在早期的 IBM PC 中。然而,隨着處理器核內存變得愈來愈快,單個總線處理全部請求的能力也達到了上線,其中也包括 IBM PC 總線。必須放棄使用這種模式。其結果致使了其餘總線的出現,它們處理 I/O 設備以及 CPU 到存儲器的速度都更快。這種演變的結果致使了下面這種結構的出現。
上圖中的 x86 系統包含不少總線,高速緩存、內存、PCIe、PCI、USB、SATA 和 DMI,每條總線都有不一樣的傳輸速率和功能。操做系統必須瞭解全部的總線配置和管理。其中最主要的總線是 PCIe(Peripheral Component Interconnect Express)
總線。
Intel 發明的 PCIe 總線也是做爲以前古老的 PCI 總線的繼承者,而古老的 PCI 總線也是爲了取代古董級別的 ISA(Industry Standard Architecture)
總線而設立的。數十 Gb/s 的傳輸能力使得 PCIe 比它的前身快不少,並且它們本質上也十分不一樣。直到發明 PCIe 的 2004 年,大多數總線都是並行且共享的。共享總線架構(shared bus architeture)
表示多個設備使用一些相同的電線傳輸數據。所以,當多個設備同時發送數據時,此時你須要一個決策者來決定誰可以使用總線。而 PCIe 則不同,它使用專門的端到端鏈路。傳統 PCI 中使用的並行總線架構(parallel bus architecture)
表示經過多條電線發送相同的數據字。例如,在傳統的 PCI 總線上,一個 32 位數據經過 32 條並行的電線發送。而 PCIe 則不一樣,它選用了串行總線架構(serial bus architecture)
,並經過單個鏈接(稱爲通道)發送消息中的全部比特數據,就像網絡數據包同樣。這樣作會簡化不少,由於再也不確保全部 32 位數據在同一時刻準確到達相同的目的地。經過將多個數據通路並行起來,並行性仍能夠有效利用。例如,能夠使用 32 條數據通道並行傳輸 32 條消息。
在上圖結構中,CPU 經過 DDR3 總線與內存對話,經過 PCIe 總線與外圍圖形設備 (GPU)對話,經過 DMI(Direct Media Interface)
總線經集成中心與全部其餘設備對話。而集成控制中心經過串行總線與 USB 設備對話,經過 SATA 總線與硬盤和 DVD 驅動器對話,經過 PCIe 傳輸以太網絡幀。
USB(Univversal Serial Bus)
是用來將全部慢速 I/O 設備(好比鍵盤和鼠標)與計算機相連的設備。USB 1.0 能夠處理總計 12 Mb/s 的負載,而 USB 2.0 將總線速度提升到 480Mb/s ,而 USB 3.0 能達到不小於 5Gb/s 的速率。全部的 USB 設備均可以直接鏈接到計算機並可以馬上開始工做,而不像以前那樣要求重啓計算機。
SCSI(Small Computer System Interface)
總線是一種高速總線,用在高速硬盤,掃描儀和其餘須要較大帶寬的設備上。如今,它們主要用在服務器和工做站中,速度能夠達到 640MB/s 。
那麼有了上面一些硬件再加上操做系統的支持,咱們的計算機就能夠開始工做了,那麼計算機的啓動過程是怎樣的呢?下面只是一個簡要版的啓動過程
在每臺計算機上有一塊雙親板,也就是母板,母板也就是主板,它是計算機最基本也就是最重要的部件之一。主板通常爲矩形電路板,上面安裝了組成計算機的主要電路系統,通常有 BIOS 芯片、I/O 控制芯片、鍵盤和麪板控制開關接口、指示燈插接件、擴充插槽、主板及插卡的直流電源供電接插件等元件。
在母板上有一個稱爲 基本輸入輸出系統(Basic Input Output System, BIOS)
的程序。在 BIOS 內有底層 I/O 軟件,包括讀鍵盤、寫屏幕、磁盤I/O 以及其餘過程。現在,它被保存在閃存中,它是非易失性的,可是當BIOS 中發現錯誤時,能夠由操做系統進行更新。
在計算機啓動(booted)
時,BIOS 開啓,它會首先檢查所安裝的 RAM 的數量,鍵盤和其餘基礎設備是否已安裝而且正常響應。接着,它開始掃描 PCIe 和 PCI 總線並找出連在上面的全部設備。即插即用的設備也會被記錄下來。若是現有的設備和系統上一次啓動時的設備不一樣,則新的設備將被從新配置。而後,BIOS 經過嘗試存儲在 CMOS
存儲器中的設備清單嘗試啓動設備
「CMOS是
Complementary Metal Oxide Semiconductor(互補金屬氧化物半導體)
的縮寫。它是指製造大規模集成電路芯片用的一種技術或用這種技術製造出來的芯片,是電腦主板上的一塊可讀寫的RAM
芯片。由於可讀寫的特性,因此在電腦主板上用來保存 BIOS 設置完電腦硬件參數後的數據,這個芯片僅僅是用來存放數據的。而對 BIOS 中各項參數的設定要經過專門的程序。BIOS 設置程序通常都被廠商整合在芯片中,在開機時經過特定的按鍵就可進入 BIOS 設置程序,方便地對系統進行設置。所以 BIOS 設置有時也被叫作 CMOS 設置。
用戶能夠在系統啓動後進入一個 BIOS 配置程序,對設備清單進行修改。而後,判斷是否可以從外部 CD-ROM
和 USB 驅動程序啓動,若是啓動失敗的話(也就是沒有),系統將從硬盤啓動,boots 設備中的第一個扇區被讀入內存並執行。該扇區包含一個程序,該程序一般在引導扇區末尾檢查分區表以肯定哪一個分區處於活動狀態。而後從該分區讀入第二個啓動加載程序,該加載器從活動分區中讀取操做系統並啓動它。
而後操做系統會詢問 BIOS 獲取配置信息。對於每一個設備來講,會檢查是否有設備驅動程序。若是沒有,則會向用戶詢問是否須要插入 CD-ROM
驅動(由設備製造商提供)或者從 Internet 上下載。一旦有了設備驅動程序,操做系統會把它們加載到內核中,而後初始化表,建立所需的後臺進程,並啓動登陸程序或GUI。
操做系統已經存在了大半個世紀,在這段時期內,出現了各類類型的操做系統,但並非全部的操做系統都很出名,下面就羅列一些比較出名的操做系統
高端一些的操做系統是大型機操做系統,這些大型操做系統可在大型公司的數據中心找到。這些計算機的 I/O 容量與我的計算機不一樣。一個大型計算機有 1000 個磁盤和數百萬 G 字節的容量是很正常,若是有這樣一臺我的計算機朋友會很羨慕。大型機也在高端 Web 服務器、大型電子商務服務站點上。
下一個層次是服務器操做系統。它們運行在服務器上,服務器能夠是大型我的計算機、工做站甚至是大型機。它們經過網絡爲若干用戶服務,而且容許用戶共享硬件和軟件資源。服務器可提供打印服務、文件服務或 Web 服務。Internet 服務商運行着許多臺服務器機器,爲用戶提供支持,使 Web 站點保存 Web 頁面並處理進來的請求。典型的服務器操做系統有 Solaris、FreeBSD、Linux 和 Windows Server 201x
得到大型計算能力的一種愈來愈廣泛的方式是將多個 CPU 鏈接到一個系統中。依據它們鏈接方式和共享方式的不一樣,這些系統稱爲並行計算機,多計算機或多處理器。他們須要專門的操做系統,不過一般採用的操做系統是配有通訊、鏈接和一致性等專門功能的服務器操做系統的變體。
我的計算機中近來出現了多核芯片,因此常規的臺式機和筆記本電腦操做系統也開始與小規模多處理器打交道,而核的數量正在與時俱進。許多主流操做系統好比 Windows 和 Linux 均可以運行在多核處理器上。
接下來一類是我的計算機操做系統。現代我的計算機操做系統支持多道處理程序。在啓動時,一般有幾十個程序開始運行,它們的功能是爲單個用戶提供良好的支持。這類系統普遍用於字處理、電子表格、遊戲和 Internet 訪問。常見的例子是 Linux、FreeBSD、Windows 七、Windows 8 和蘋果公司的 OS X 。
隨着硬件愈來愈小化,咱們看到了平板電腦、智能手機和其餘掌上計算機系統。掌上計算機或者 PDA(Personal Digital Assistant),我的數字助理
是一種能夠握在手中操做的小型計算機。這部分市場已經被谷歌的 Android
系統和蘋果的 IOS
主導。
嵌入式操做系統用來控制設備的計算機中運行,這種設備不是通常意義上的計算機,而且不容許用戶安裝軟件。典型的例子有微波爐、汽車、DVD 刻錄機、移動電話以及 MP3 播放器一類的設備。全部的軟件都運行在 ROM 中,這意味着應用程序之間不存在保護,從而得到某種簡化。主要的嵌入式系統有 Linux、QNX 和 VxWorks
有許多用途須要配置微小傳感器節點網絡。這些節點是一種能夠彼此通訊而且使用無線通訊基站的微型計算機。這類傳感器網絡能夠用於建築物周邊保護、國土邊界保衛、森林火災探測、氣象預測用的溫度和降水測量等。
每一個傳感器節點是一個配有 CPU、RAM、ROM 以及一個或多個環境傳感器的實實在在的計算機。節點上運行一個小型可是真是的操做系統,一般這個操做系統是事件驅動的,能夠響應外部事件。
另外一類操做系統是實時操做系統,這些系統的特徵是將時間做爲關鍵參數。例如,在工業過程控制系統中,工廠中的實時計算機必須收集生產過程的數據並用有關數據控制機器。若是某個動做必需要在規定的時刻發生,這就是硬實時系統
。能夠在工業控制、民用航空、軍事以及相似應用中看到不少這樣的系統。另外一類系統是 軟實時系統
,在這種系統中,雖然不但願偶爾違反最終時限,但仍能夠接受,並不會引發任何永久性損害。數字音頻或多媒體系統就是這類系統。智能手機也是軟實時系統。
最小的操做系統運行在智能卡上。智能卡是一種包含一塊 CPU 芯片的信用卡。它有很是嚴格的運行能耗和存儲空間的限制。有些卡具備單項功能,如電子支付;有些智能卡是面向 Java 的。這意味着在智能卡的 ROM 中有一個 Java 虛擬機(Java Virtual Machine, JVM)解釋器。
大部分操做系統提供了特定的基礎概念和抽象,例如進程、地址空間、文件等,它們是須要理解的核心內容。下面咱們會簡要介紹一些基本概念,爲了說明這些概念,咱們會不時的從 UNIX
中提出示例,相同的示例也會存在於其餘系統中,咱們後面會進行介紹。
操做系統一個很關鍵的概念就是 進程(Process)
。進程的本質就是操做系統執行的一個程序。與每一個進程相關的是地址空間(address space)
,這是從某個最小值的存儲位置(一般是零)到某個最大值的存儲位置的列表。在這個地址空間中,進程能夠進行讀寫操做。地址空間中存放有可執行程序,程序所須要的數據和它的棧。與每一個進程相關的還有資源集,一般包括寄存器(registers)
(寄存器通常包括程序計數器(program counter)
和堆棧指針(stack pointer)
)、打開文件的清單、突發的報警、有關的進程清單和其餘須要執行程序的信息。你能夠把進程看做是容納運行一個程序全部信息的一個容器。
對進程創建一種直觀感受的方式是考慮創建一種多程序的系統。考慮下面這種狀況:用戶啓動一個視頻編輯程序,指示它按照某種格式轉換視頻,而後再去瀏覽網頁。同時,一個檢查電子郵件的後臺進程被喚醒並開始運行,這樣,咱們目前就會有三個活動進程:視頻編輯器、Web 瀏覽器和電子郵件接收程序。操做系統週期性的掛起一個進程而後啓動運行另外一個進程,這多是因爲過去一兩秒鐘程序用完了 CPU 分配的時間片,而 CPU 轉而運行另外的程序。
像這樣暫時中斷進程後,下次應用程序在此啓動時,必需要恢復到與中斷時刻相同的狀態,這在咱們用戶看起來是習覺得常的事情,可是操做系統內部卻作了巨大的事情。這就像和足球比賽同樣,一場完美精彩的比賽是能夠忽略裁判的存在的。這也意味着在掛起時該進程的全部信息都要被保存下來。例如,進程可能打開了多個文件進行讀取。與每一個文件相關聯的是提供當前位置的指針(即下一個須要讀取的字節或記錄的編號)。當進程被掛起時,必需要保存這些指針,以便在從新啓動進程後執行的 read
調用將可以正確的讀取數據。在許多操做系統中,與一個進程有關的全部信息,除了該進程自身地址空間的內容之外,均存放在操做系統的一張表中,稱爲 進程表(process table)
,進程表是數組或者鏈表結構,當前存在每一個進程都要佔據其中的一項。
因此,一個掛起的進程包括:進程的地址空間(每每稱做磁芯映像
, core image,記念過去的磁芯存儲器),以及對應的進程表項(其中包括寄存器以及稍後啓動該進程所須要的許多其餘信息)。
與進程管理有關的最關鍵的系統調用每每是決定着進程的建立和終止的系統調用。考慮一個典型的例子,有一個稱爲 命令解釋器(command interpreter)
或 shell
的進程從終端上讀取命令。此時,用戶剛鍵入一條命令要求編譯一個程序。shell 必須先建立一個新進程來執行編譯程序,當編譯程序結束時,它執行一個系統調用來終止本身的進程。
若是一個進程可以建立一個或多個進程(稱爲子進程
),並且這些進程又能夠建立子進程,則很容易找到進程數,以下所示
上圖表示一個進程樹的示意圖,進程 A 建立了兩個子進程 B 和進程 C,子進程 B 又建立了三個子進程 D、E、F。
合做完成某些做業的相關進程常常須要彼此通訊來完成做業,這種通訊稱爲進程間通訊(interprocess communication)
。咱們在後面會探討進程間通訊。
其餘可用的進程系統調用包括:申請更多的內存(或釋放再也不須要的內存),等待一個子進程結束,用另外一個程序覆蓋該程序。
有時,須要向一個正在運行的進程傳遞信息,而該進程並無等待接收信息。例如,一個進程經過網絡向另外一臺機器上的進程發送消息進行通訊。爲了保證一條消息或消息的應答不丟失。發送者要求它所在的操做系統在指定的若干秒後發送一個通知,這樣若是對方還沒有收到確認消息就能夠進行從新發送。在設定該定時器後,程序能夠繼續作其餘工做。
在限定的時間到達後,操做系統會向進程發送一個 警告信號(alarm signal)
。這個信號引發該進程暫時掛起,不管該進程正在作什麼,系統將其寄存器的值保存到堆棧中,並開始從新啓動一個特殊的信號處理程,好比從新發送可能丟失的消息。這些信號是軟件模擬的硬件中斷,除了定時器到期以外,該信號能夠經過各類緣由產生。許多由硬件檢測出來的陷阱,如執行了非法指令或使用了無效地址等,也被轉換成該信號並交給這個進程。
系統管理器受權每一個進程使用一個給定的 UID(User IDentification)
。每一個啓動的進程都會有一個操做系統賦予的 UID,子進程擁有與父進程同樣的 UID。用戶能夠是某個組的成員,每一個組也有一個 GID(Group IDentification)
。
在 UNIX 操做系統中,有一個 UID 是 超級用戶(superuser)
,或者 Windows 中的管理員(administrator)
,它具備特殊的權利,能夠違背一些保護規則。在大型系統中,只有系統管理員掌握着那些用戶能夠稱爲超級用戶。
每臺計算機都有一些主存用來保存正在執行的程序。在一個很是簡單的操做系統中,僅僅有一個應用程序運行在內存中。爲了運行第二個應用程序,須要把第一個應用程序移除才能把第二個程序裝入內存。
複雜一些的操做系統會容許多個應用程序同時裝入內存中運行。爲了防止應用程序之間相互干擾(包括操做系統),須要有某種保護機制。雖然此機制是在硬件中實現,但倒是由操做系統控制的。
上述觀點涉及對計算機主存的管理和保護。另外一種同等重要並與存儲器有關的內容是管理進程的地址空間。一般,每一個進程有一些能夠使用的地址集合,典型值從 0 開始直到某個最大值。一個進程可擁有的最大地址空間小於主存。在這種狀況下,即便進程用完其地址空間,內存也會有足夠的內存運行該進程。
可是,在許多 32 位或 64 位地址的計算機中,分別有 2^32 或 2^64 字節的地址空間。若是一個進程有比計算機擁有的主存還大的地址空間,並且該進程但願使用所有的內存,那該怎麼處理?在早期的計算機中是沒法處理的。可是如今有了一種虛擬內存
的技術,正如前面講到過的,操做系統能夠把部分地址空間裝入主存,部分留在磁盤上,而且在須要時來回交換它們。
幾乎全部操做系統都支持的另外一個關鍵概念就是文件系統。如前所述,操做系統的一項主要功能是屏蔽磁盤和其餘 I/O 設備的細節特性,給程序員提供一個良好、清晰的獨立於設備的抽象文件模型。建立文件、刪除文件、讀文件和寫文件 都須要系統調用。在文件能夠讀取以前,必須先在磁盤上定位和打開文件,在文件讀過以後應該關閉該文件,有關的系統調用則用於完成這類操做。
爲了提供保存文件的地方,大多數我的計算機操做系統都有目錄(directory)
的概念,從而能夠把文件分組。好比,學生能夠給每一個課程都建立一個目錄,用於保存該學科的資源,另外一個目錄能夠存放電子郵件,再有一個目錄能夠存放萬維網主頁。這就須要系統調用建立和刪除目錄、將已有文件放入目錄中,從目錄中刪除文件等。目錄項能夠是文件或者目錄,目錄和目錄之間也能夠嵌套,這樣就產生了文件系統
進程和文件層次都是以樹狀的結構組織,但這兩種樹狀結構有很多不一樣之處。通常進程的樹狀結構層次不深(不多超過三層),而文件系統的樹狀結構要深一些,一般會到四層甚至五層。進程樹層次結構是暫時的,一般最多存在幾分鐘,而目錄層次則可能存在很長時間。進程和文件在權限保護方面也是有區別的。通常來講,父進程能控制和訪問子進程,而在文件和目錄中一般存在一種機制,使文件全部者以外的其餘用戶也能訪問該文件。
目錄層結構中的每個文件均可以經過從目錄的頂部即 根目錄(Root directory)
開始的路徑名(path name)
來肯定。絕對路徑名包含了從根目錄到該文件的全部目錄清單,它們之間用斜槓分隔符分開,在上面的大學院系文件系統中,文件 CS101 的路徑名是/Faculty/Prof.Brown/Courses/CS101
。最開始的斜槓分隔符表明的是根目錄 /
,也就是文件系統的絕對路徑。
「出於歷史緣由,Windows 下面的文件系統以
\
來做爲分隔符,可是 Linux 會以/
做爲分隔符。
在上面的系統中,每一個進程會有一個 工做目錄(working directory)
,對於沒有以斜線開頭給出絕對地址的路徑,將在這個工做目錄下尋找。若是 /Faculty/Prof.Brown
是工做目錄,那麼 /Courses/CS101
與上面給定的絕對路徑名錶示的是同一個文件。進程能夠經過使用系統調用指定新的工做目錄,從而變動其工做目錄。
在讀寫文件以前,首先須要打開文件,檢查其訪問權限。若權限許可,系統將返回一個小整數,稱做文件描述符(file descriptor)
,供後續操做使用。若禁止訪問,系統則返回一個錯誤碼。
在 UNIX 中,另外一個重要的概念是 特殊文件(special file)
。提供特殊文件是爲了使 I/O 設備看起來像文件通常。這樣,就像使用系統調用讀寫文件同樣,I/O 設備也能夠經過一樣的系統調用進行讀寫。特殊文件有兩種,一種是塊兒特殊文件(block special file)
和 字符特殊文件(character special file)
。塊特殊文件指那些由可隨機存取的塊組成的設備,如磁盤等。好比打開一個塊特殊文件,而後讀取第4塊,程序能夠直接訪問設備的第4塊而沒必要考慮存放在該文件的文件系統結構。相似的,字符特殊文件用於打印機、調製解調起和其餘接受或輸出字符流的設備。按照慣例,特殊文件保存在 /dev
目錄中。例如,/devv/lp 是打印機。
還有一種與進程和文件相關的特性是管道,管道(pipe)
是一種虛文件,他能夠鏈接兩個進程
若是 A 和 B 但願經過管道對話,他們必須提早設置管道。當進程 A 相對進程 B 發送數據時,它把數據寫到管道上,至關於管道就是輸出文件。這樣,在 UNIX 中兩個進程之間的通訊就很是相似於普通文件的讀寫了。
計算機中含有大量的信息,用戶但願可以對這些信息中有用並且重要的信息加以保護,這些信息包括電子郵件、商業計劃等,管理這些信息的安全性徹底依靠操做系統來保證。例如,文件提供受權用戶訪問。
好比 UNIX 操做系統,UNIX 操做系統經過對每一個文件賦予一個 9 位二進制保護代碼,對 UNIX 中的文件實現保護。該保護代碼有三個位子段,一個用於全部者,一個用於與全部者同組(用戶被系統管理員劃分紅組)的其餘成員,一個用於其餘人。每一個字段中有一位用於讀訪問,一位用於寫訪問,一位用於執行訪問。這些位就是著名的 rwx位
。例如,保護代碼 rwxr-x--x
的含義是全部者能夠讀、寫或執行該文件,其餘的組成員能夠讀或執行(但不能寫)此文件、而其餘人能夠執行(但不能讀和寫)該文件。
操做系統是執行系統調用的代碼。編輯器、編譯器、彙編程序、連接程序、使用程序以及命令解釋符等,儘管很是重要,很是有用,可是它們確實不是操做系統的組成部分。下面咱們着重介紹一下 UNIX 下的命令提示符,也就是 shell
,shell 雖然有用,但它也不是操做系統的一部分,然而它卻能很好的說明操做系統不少特性,下面咱們就來探討一下。
shell 有許多種,例如 sh、csh、ksh 以及 bash等,它們都支持下面這些功能,最先起的 shell 能夠追溯到 sh
用戶登陸時,會同時啓動一個 shell,它以終端做爲標準輸入和標準輸出。首先顯示提示符(prompt)
,它多是一個美圓符號($)
,提示用戶 shell 正在等待接收命令,假如用戶輸入date
shell 會建立一個子進程,並運行 date 作爲子進程。在該子進程運行期間,shell 將等待它結束。在子進程完成時,shell 會顯示提示符並等待下一行輸入。
用戶能夠將標準輸出重定向到一個文件中,例如
date > file
一樣的,也能夠將標準輸入做爲重定向
sort <file1> file2
這會調用 sort 程序來接收 file1 的內容並把結果輸出到 file2。
能夠將一個應用程序的輸出經過管道做爲另外一個程序的輸入,所以有
cat file1 file2 file3 | sort > /dev/lp
這會調用 cat 應用程序來合併三個文件,將其結果輸送到 sort 程序中並按照字典進行排序。sort 應用程序又被重定向到 /dev/lp ,顯然這是一個打印操做。
咱們已經能夠看到操做系統提供了兩種功能:爲用戶提供應用程序抽象和管理計算機資源。對於大部分在應用程序和操做系統之間的交互主要是應用程序的抽象,例如建立、寫入、讀取和刪除文件。計算機的資源管理對用戶來講基本上是透明的。所以,用戶程序和操做系統之間的接口主要是處理抽象。爲了真正理解操做系統的行爲,咱們必須仔細的分析這個接口。
多數現代操做系統都有功能相同可是細節不一樣的系統調用,引起操做系統的調用依賴於計算機自身的機制,並且必須用匯編代碼表達。任何單 CPU 計算機一次執行執行一條指令。若是一個進程在用戶態下運行用戶程序,例如從文件中讀取數據。那麼若是想要把控制權交給操做系統控制,那麼必須執行一個異常指令或者系統調用指令。操做系統緊接着須要參數檢查找出所須要的調用進程。操做系統緊接着進行參數檢查找出所須要的調用進程。而後執行系統調用,把控制權移交給系統調用下面的指令。大體來講,系統調用就像是執行了一個特殊的過程調用,可是隻有系統調用可以進入內核態而過程調用則不能進入內核態。
爲了可以瞭解具體的調用過程,下面咱們以 read
方法爲例來看一下調用過程。像上面提到的那樣,會有三個參數,第一個參數是指定文件、第二個是指向緩衝區、第三個參數是給定須要讀取的字節數。就像幾乎全部系統調用同樣,它經過使用與系統調用相同的名稱來調用一個函數庫,從而從C程序中調用:read。
count = read(fd,buffer,nbytes);
系統調用在 count 中返回實際讀出的字節數。這個值一般與 nbytes 相同,但也可能更小。好比在讀過程當中遇到了文件尾的狀況。
若是系統調用不能執行,無論是由於無效的參數仍是磁盤錯誤,count 的值都會被置成 -1,而後在全局變量 errno
中放入錯誤信號。程序應該進場檢查系統調用的結果以瞭解是否出錯。
系統調用是經過一系列的步驟實現的,爲了更清楚的說明這個概念,咱們還以 read 調用爲例,在準備系統調用前,首先會把參數壓入堆棧,以下所示
C 和 C++ 編譯器使用逆序(必須把第一個參數賦值給 printf(格式字符串),放在堆棧的頂部)。第一個參數和第三個參數都是值調用,可是第二個參數經過引用傳遞,即傳遞的是緩衝區的地址(由 & 指示),而不是緩衝的內容。而後是 C 調用系統庫的 read 函數,這也是第四步。
在由彙編語言寫成的庫過程當中,通常把系統調用的編號放在操做系統所指望的地方,如寄存器(第五步)。而後執行一個 TRAP
指令,將用戶態切換到內核態,並在內核中的一個固定地址開始執行第六步。TRAP 指令實際上與過程調用指令很是類似,它們後面都跟隨一個來自遠處位置的指令,以及供之後使用的一個保存在棧中的返回地址。
TRAP 指令與過程調用指令存在兩個方面的不一樣
跟隨在 TRAP 指令後的內核代碼開始檢查系統調用編號,而後dispatch
給正確的系統調用處理器,這一般是經過一張由系統調用編號所引用的、指向系統調用處理器的指針表來完成第七步。此時,系統調用處理器運行第八步,一旦系統調用處理器完成工做,控制權會根據 TRAP 指令後面的指令中返回給函數調用庫第九步。這個過程接着以一般的過程調用返回的方式,返回到客戶應用程序,這是第十步。而後調用完成後,操做系統還必須清除用戶堆棧,而後增長堆棧指針(increment stackpointer)
,用來清除調用 read 以前壓入的參數。從而完成整個 read 調用過程。
在上面的第九步中咱們說道,控制可能返回 TRAP 指令後面的指令,把控制權再移交給調用者這個過程當中,系統調用會發生阻塞,從而避免應用程序繼續執行。這麼作是有緣由的。例如,若是試圖讀鍵盤,此時並無任何輸入,那麼調用者就必須被阻塞。在這種情形下,操做系統會檢查是否有其餘能夠運行的進程。這樣,當有用戶輸入 時候,進程會提醒操做系統,而後返回第 9 步繼續運行。
下面,咱們會列出一些經常使用的 POSIX
系統調用,POSIX 系統調用大概有 100 多個,它們之中最重要的一些調用見下表
進程管理
調用 | 說明 |
---|---|
pid = fork() | 建立與父進程相同的子進程 |
pid = waitpid(pid, &statloc,options) | 等待一個子進程終止 |
s = execve(name,argv,environp) | 替換一個進程的核心映像 |
exit(status) | 終止進程執行並返回狀態 |
文件管理
調用 | 說明 |
---|---|
fd = open(file, how,...) | 打開一個文件使用讀、寫 |
s = close(fd) | 關閉一個打開的文件 |
n = read(fd,buffer,nbytes) | 把數據從一個文件讀到緩衝區中 |
n = write(fd,buffer,nbytes) | 把數據從緩衝區寫到一個文件中 |
position = iseek(fd,offset,whence) | 移動文件指針 |
s = stat(name,&buf) | 取得文件狀態信息 |
目錄和文件系統管理
調用 | 說明 |
---|---|
s = mkdir(nname,mode) | 建立一個新目錄 |
s = rmdir(name) | 刪去一個空目錄 |
s = link(name1,name2) | 建立一個新目錄項 name2,並指向 name1 |
s = unlink(name) | 刪去一個目錄項 |
s = mount(special,name,flag) | 安裝一個文件系統 |
s = umount(special) | 卸載一個文件系統 |
其餘
調用 | 說明 |
---|---|
s = chdir(dirname) | 改變工做目錄 |
s = chmod(name,mode) | 修改一個文件的保護位 |
s = kill(pid, signal) | 發送信號給進程 |
seconds = time(&seconds) | 獲取從 1970 年1月1日至今的時間 |
上面的系統調用參數中有一些公共部分,例如 pid 系統進程 id,fd 是文件描述符,n 是字節數,position 是在文件中的偏移量、seconds 是流逝時間。
從宏觀角度上看,這些系統調所提供的服務肯定了多數操做系統應該具備的功能,下面分別來對不一樣的系統調用進行解釋
在 UNIX 中,fork
是惟一能夠在 POSIX 中建立進程的途徑,它建立一個原有進程的副本,包括全部的文件描述符、寄存器等內容。在 fork 以後,原有進程以及副本(父與子)就分開了。在 fork 過程當中,全部的變量都有相同的值,雖然父進程的數據經過複製給子進程,可是後續對其中任何一個進程的修改不會影響到另一個。fork 調用會返回一個值,在子進程中該值爲 0 ,而且在父進程中等於子進程的 進程標識符(Process IDentified,PID)
。使用返回的 PID,就能夠看出來哪一個是父進程和子進程。
在多數狀況下, 在 fork 以後,子進程須要執行和父進程不同的代碼。從終端讀取命令,建立一個子進程,等待子進程執行命令,當子進程結束後再讀取下一個輸入的指令。爲了等待子進程完成,父進程須要執行 waitpid
系統調用,父進程會等待直至子進程終止(如有多個子進程的話,則直至任何一個子進程終止)。waitpid 能夠等待一個特定的子進程,或者經過將第一個參數設爲 -1 的方式,等待任何一個比較老的子進程。當 waitpid 完成後,會將第二個參數 statloc
所指向的地址設置爲子進程的退出狀態(正常或異常終止以及退出值)。有各類可以使用的選項,它們由第三個參數肯定。例如,若是沒有已經退出的子進程則馬上返回。
那麼 shell 該如何使用 fork 呢?在鍵入一條命令後,shell 會調用 fork 命令建立一個新的進程。這個子進程會執行用戶的指令。經過使用 execve
系統調用能夠實現系統執行,這個系統調用會引發整個核心映像被一個文件所替代,該文件由第一個參數給定。下面是一個簡化版的例子說明 fork、waitpid 和 execve 的使用
#define TRUE 1 /* 一直循環下去 */ while(TRUE){ /* 在屏幕上顯示提示符 */ type_prompt(); /* 從終端讀取輸入 */ read_command(command,parameters) /* fork 子進程 */ if(fork() != 0){ /* 父代碼 */ /* 等待子進程執行完畢 */ waitpid(-1, &status, 0); }else{ /* 執行命令 */ /* 子代碼 */ execve(command,parameters,0) } }
通常狀況下,execve 有三個參數:將要執行的文件名稱,一個指向變量數組的指針,以及一個指向環境數組的指針。這裏對這些參數作一個簡要的說明。
先看一個 shell 指令
cp file1 file2
此命令把 file1 複製到 file2 文件中,在 shell 執行 fork 以後,子進程定位並執行文件拷貝,並將源文件和目標文件的名稱傳遞給它。
cp 的主程序(以及包含其餘大多數 C 程序的主程序)包含聲明
main(argc,argv,envp)
其中 argc 是命令行中參數數目的計數,包括程序名稱。對於上面的例子,argc
是3。第二個參數argv
是數組的指針。該數組的元素 i 是指向該命令行第 i 個字符串的指針。在上面的例子中,argv[0] 指向字符串 cp,argv[1] 指向字符串 file1,argv[2] 指向字符串 file2。main 的第三個參數是指向環境的指針,該環境是一個數組,含有 name = value
的賦值形式,用以將諸如終端類型以及根目錄等信息傳送給程序。這些變量一般用來肯定用戶但願如何完成特定的任務(例如,使用默認打印機)。在上面的例子中,沒有環境參數傳遞給 execve ,因此環境變量是 0 ,因此 execve 的第三個參數爲 0 。
可能你以爲 execve 過於複雜,這時候我要鼓勵一下你,execve 多是 POSIX 的所有系統調用中最複雜的一個了,其餘都比較簡單。做爲一個簡單的例子,咱們再來看一下exit
,這是進程在執行完成後應執行的系統調用。這個系統調用有一個參數,它的退出狀態是 0 - 255 之間,它經過 waitpid 系統調用中的 statloc 返回給父級。
UNIX 中的進程將內存劃分紅三個部分:text segment,文本區
,例如程序代碼,data segment,數據區
,例如變量,stack segment
,棧區域。數據向上增加而堆棧向下增加,以下圖所示
上圖能說明三個部分的內存分配狀況,夾在中間的是空閒區,也就是未分配的區域,堆棧在須要時自動的擠壓空閒區域,不過數據段的擴展是顯示地經過系統調用 brk
進行的,在數據段擴充後,該系統調用指向一個新地址。可是,這個調用不是 POSIX 標準中定義的,對於存儲器的動態分配,鼓勵程序員使用 malloc
函數,而 malloc 的內部實現則不是一個適合標準化的主題,由於幾乎沒有程序員直接使用它。
許多系統調用都與文件系統有關,要讀寫一個文件,必須先將其打開。這個系統調用經過絕對路徑名或指向工做目錄的相對路徑名指定要打開文件的名稱,而代碼O_RDONLY
、 O_WRONLY
或 O_RDWR
的含義分別是隻讀、只寫或者二者均可以,爲了建立一個新文件,使用 O_CREATE
參數。而後可以使用返回的文件描述符進行讀寫操做。接着,能夠使用 close 關閉文件,這個調用使得文件描述符在後續的 open 中被再次使用。
最經常使用的調用仍是 read
和 write
,咱們再前面探討過 read 調用,write 具備與 read 相同的參數。
儘管多數程序頻繁的讀寫文件,可是仍有一些應用程序須要可以隨機訪問一個文件的任意部分。與每一個文件相關的是一個指向文件當前位置的指針。在順序讀寫時,該指針一般指向要讀出(寫入)的下一個字節。Iseek
調用能夠改變該位置指針的值,這樣後續的 read 或 write 調用就能夠在文件的任何地方開始。
Iseek 有三個參數,position = iseek(fd,offset,whence)
,第一個是文件描述符,第二個是文件位置,第三個是說明該文件位置是相對於文件起始位置,當前位置仍是文件的結尾。在修改了指針以後,Iseek 所返回的值是文件中的絕對位置。
UNIX 爲每一個文件保存了該文件的類型(普通文件、特殊文件、目錄等)、大小,最後修改時間以及其餘信息,程序能夠經過 stat
系統調用查看這些信息。s = stat(name,&buf)
,第一個參數指定了被檢查的文件;第二個參數是一個指針,該指針指向存放這些信息的結構。對於一個打開的文件而言,fstat 調用完成一樣的工做。
下面咱們探討目錄和整個文件系統的系統調用,上面探討的是和某個文件有關的系統調用。mkdir
和 rmdir
分別用於建立s = mkdir(nname,mode)
和刪除 s = rmdir(name)
空目錄,下一個調用是 s = link(name1,name2)
它的做用是容許同一個文件以兩個或者多個名稱出現,多數狀況下是在不一樣的目錄中使用 link ,下面咱們探討一下 link 是如何工做的
圖中有兩個用戶 ast
和 jim
,每一個用戶都有他本身的一個目錄和一些文件,若是 ast 要執行一個包含下面系統調用的應用程序
link("/usr/jim/memo", "/usr/ast/note");
jim 中的 memo 文件如今會進入到 ast 的目錄中,在 note 名稱下。此後,/usr/jim/memo
和 /usr/ast/note
會有相同的名稱。
「用戶目錄是保存在 /usr,/user,/home 仍是其餘位置,都是由本地系統管理員決定的。
要理解 link 是如何工做的須要清楚 link 作了什麼操做。UNIX 中的每一個文件都有一個獨一無二的版本,也稱做 i - number,i-編號
,它標示着不一樣文件的版本。這個 i - 編號是i-nodes,i-節點
表的索引。每一個文件都會代表誰擁有這個文件,這個磁盤塊的位置在哪,等等。目錄只是一個包含一組(i編號,ASCII名稱)對應的文件。UNIX 中的第一個版本中,每一個目錄項都會有 16 個字節,2 個字節對應 i - 編號和 14 個字節對應其名稱。如今須要一個更復雜的結構須要支持長文件名,可是從概念上講一個目錄還是一系列(i-編號,ASCII 名稱)的集合。在上圖中,mail
的 i-編號爲 16,依此類推。link 只是利用某個已有文件的 i-編號,建立一個新目錄項(也許用一個新名稱)。在上圖 b 中,你會發現有兩個相同的 70 i-編號的文件,所以它們須要有相同的文件。若是其中一個使用了 unlink
系統調用的話,其中一個會被移除,另外一個將保留。若是兩個文件都移除了,則 UNIX 會發現該文件不存在任何沒有目錄項(i-節點中的一個域記錄着指向該文件的目錄項),就會把該文件從磁盤中移除。
就像咱們上面提到過的那樣,mount
系統 s = mount(special,name,flag)
調用會將兩個文件系統合併爲一個。一般的狀況是將根文件系統分佈在硬盤(子)分區上,並將用戶文件分佈在另外一個(子)分區上,該根文件系統包含經常使用命令的二進制(可執行)版本和其餘使用頻繁的文件。而後,用戶就會插入可讀取的 USB 硬盤。
經過執行 mount 系統調用,USB 文件系統能夠被添加到根文件系統中
若是用 C 語言來執行那就是
mount("/dev/sdb0","/mnt",0)
這裏,第一個參數是 USB 驅動器 0 的塊特殊文件名稱,第二個參數是被安裝在樹中的位置,第三個參數說明將要安裝的文件系統是可讀寫的仍是隻讀的。
當再也不須要一個文件系統時,能夠使用 umount 移除之。
除了進程、文件、目錄系統調用,也存在其餘系統調用的狀況,下面咱們來探討一下。咱們能夠看到上面其餘系統調用只有四種,首先來看第一個 chdir,chdir 調用更改當前工做目錄,在調用
chdir("/usr/ast/test");
後,打開 xyz 文件,會打開 /usr/ast/test/xyz
文件,工做目錄的概念消除了老是須要輸入長文件名的須要。
在 UNIX 系統中,每一個文件都會有保護模式,這個模式會有一個讀-寫-執行
位,它用來區分全部者、組和其餘成員。chmod
系統調用提供改變文件模式的操做。例如,要使一個文件除了對全部者以外的用戶可讀,你能夠執行
chmod("file",0644);
kill
系統調用是用戶和用戶進程發送信號的方式,若是一個進程準備好捕捉一個特定的信號,那麼在信號捕捉以前,會運行一個信號處理程序。若是進程沒有準備好捕捉特定的信號,那麼信號的到來會殺掉該進程(此名字的由來)。
POSIX 定義了若干時間處理的進程。例如,time
以秒爲單位返回當前時間,0 對應着 1970 年 1月 1日。在一臺 32 位字的計算機中,time 的最大值是 (2^32) - 1秒,這個數字對應 136 年多一點。因此在 2106 年,32 位的 UNIX 系統會發飆。若是讀者如今有 32 位 UNIX 系統,建議在 2106 年更換位 64 位操做系統(偷笑~)。
上面咱們提到的都是 UNIX 系統調用,如今咱們來聊聊 Win 32 中的系統調用。Windows 和 UNIX 在各自的編程方式上有着根本的不一樣。UNIX 程序由執行某些操做或執行其餘操做的代碼組成,進行系統調用以執行某些服務。Windows 系統則不一樣,Windows 應用程序一般是由事件驅動的。主程序會等待一些事件發生,而後調用程序去處理。最簡單的事件處理是鍵盤敲擊和鼠標滑過,或者是鼠標點擊,或者是插入 USB 驅動,而後操做系統調用處理器去處理事件,更新屏幕和更新程序內部狀態。這是與 UNIX 不一樣的設計風格。
固然,Windows 也有系統調用。在 UNIX 中,系統調用(好比 read)和系統調用所使用的調用庫(例如 read)幾乎是一對一的關係。而在 Windows 中,狀況則大不相同。首先,函數庫的調用和實際的系統調用幾乎是不對應的。微軟定義了一系列過程,稱爲Win32應用編程接口(Application Programming Interface)
,程序員經過這套標準的接口來實現系統調用。這個接口支持從 Windows 95 版本以來全部的 Windows 版本。
Win32 API 調用的數量是很是巨大的,有數千個多。但這些調用並不都是在內核態的模式下運行時,有一些是在用戶態的模型下運行。Win32 API 有大量的調用,用來管理視窗、幾何圖形、文本、字體、滾動條、對話框、菜單以及 GUI 的其餘功能。爲了使圖形子系統在內核態下運行,須要系統調用,不然就只有函數庫調用。
咱們把關注點放在和 Win32 系統調用中來,咱們能夠簡單看一下 Win32 API 中的系統調用和 UNIX 中有什麼不一樣(並非全部的系統調用)
上表中是 UNIX 調用大體對應的 Win32 API 系統調用,簡述一下上表。CreateProcess
用於建立一個新進程,它把 UNIX 中的 fork 和 execve 兩個指令合成一個,一塊兒執行。它有許多參數用來指定新建立進程的性質。Windows 中沒有相似 UNIX 中的進程層次,因此不存在父進程和子進程的概念。在進程建立以後,建立者和被建立者是平等的。WaitForSingleObject
用於等待一個事件,等待的事件能夠是多種可能的事件。若是有參數指定了某個進程,那麼調用者將等待指定的進程退出,這經過 ExitProcess
來完成。
而後是6個文件操做,在功能上和 UNIX 的調用相似,然而在參數和細節上是不一樣的。和 UNIX 中同樣,文件能夠打開,讀取,寫入,關閉。SetFilePointer
和GetFileAttributesEx
設置文件的位置並取得文件的屬性。
Windows 中有目錄,目錄分別用 CreateDirectory
以及 RemoveDirectory
API 調用建立和刪除。也有對當前的目錄的標記,這能夠經過 SetCurrentDirectory
來設置。使用GetLocalTime
可得到當前時間。
Win32 接口中沒有文件的連接、文件系統的 mount、umount 和 stat ,固然, Win32 中也有大量 UNIX 中沒有的系統調用,特別是對 GUI 的管理和調用。
下面咱們會探討操做系統的幾種結構,主要包括單體結構、分層系統、微內核、客戶-服務端系統、虛擬機和外核等。下面以此來探討一下
到目前爲止,在大多數系統中,整個系統在內核態以單一程序的方式運行。整個操做系統是以程序集合來編寫的,連接在一塊造成一個大的二進制可執行程序。使用此技術時,若是系統中的每一個過程都提供了前者所需的一些有用的計算,則它能夠自由調用任何其餘過程。在單體系統中,調用任何一個所須要的程序都很是高效,可是上千個不受限制的彼此調用每每很是臃腫和笨拙,並且單體系統必然存在單體問題,那就是隻要系統發生故障,那麼任何系統和應用程序將不可用,這每每是災難性的。
在單體系統中構造實際目標程序時,會首先編譯全部單個過程(或包含這些過程的文件),而後使用系統連接器將它們所有綁定到一個可執行文件中
對於單體系統,每每有下面幾種建議
在單體系統中,對於每一個系統調用都會有一個服務程序來保障和運行。須要一組實用程序來彌補服務程序須要的功能,例如從用戶程序中獲取數據。可將各類過程劃分爲一個三層模型
除了在計算機初啓動時所裝載的核心操做系統外,許多操做系統還支持額外的擴展。好比 I/O 設備驅動和文件系統。這些部件能夠按需裝載。在 UNIX 中把它們叫作 共享庫(shared library)
,在 Windows 中則被稱爲 動態連接庫(Dynamic Link Library,DLL)
。他們的擴展名爲 .dll
,在 C:\Windows\system32
目錄下存在 1000 多個 DLL 文件,因此不要輕易刪除 C 盤文件,不然可能就炸了哦。
分層系統使用層來分隔不一樣的功能單元。每一層只與該層的上層和下層通訊。每一層都使用下面的層來執行其功能。層之間的通訊經過預約義的固定接口通訊。
分層系統是由 E.W.Dijkstar
和他的學生在荷蘭技術學院所開發的 THE 系統。
把上面單體系統進一步通用化,就變爲了一個層次式結構的操做系統,它的上層軟件都是在下層軟件的基礎之上構建的。該系統分爲六層,以下所示
層號 | 功能 |
---|---|
5 | 操做員 |
4 | 用戶程序 |
3 | 輸入/輸出管理 |
2 | 操做員-進程通訊 |
1 | 存儲器和磁鼓管理 |
0 | 處理器分配和多道程序編程 |
處理器在 0 層運行,當中斷髮生或定時器到期時,由該層完成進程切換;在第 0 層之上,系統由一些連續的進程組成,編寫這些進程時不用再考慮在單處理器上多進程運行的細節。內存管理在第 1 層,它分配進程的主存空間。第 1 層軟件保證一旦須要訪問某一頁面,該頁面一定已經在內存中,而且在頁面不須要的時候將其移出。
第 2 層處理進程與操做員控制檯(即用戶)之間的通訊。第 3 層管理 I/O 設備和相關的信息流緩衝區。第 4 層是用戶程序層,用戶程序不用考慮進程、內存、控制檯或 I/O 設備管理等細節。系統操做員在第 5 層。
在分層方式中,設計者要肯定在哪裏劃分 內核-用戶
的邊界。傳統上,全部的層都在內核中,可是這樣作沒有必要。事實上,儘量減小內核態中功能多是更好的作法。由於內核中的錯誤很難處理,一旦內核態中出錯誤會拖累整個系統。
因此,爲了實現高可靠性,將操做系統劃分紅小的、層級之間可以更好定義的模塊是頗有必要的,只有一個模塊 --- 微內核 --- 運行在內核態,其他模塊能夠做爲普通用戶進程運行。因爲把每一個設備驅動和文件系統分別做爲普通用戶進程,這些模塊中的錯誤雖然會使這些模塊崩潰,可是不會使整個系統死機。
MINIX 3
是微內核的表明做,它的具體結構以下
在內核的外部,系統的構造有三層,它們都在用戶態下運行,最底層是設備驅動器。因爲它們都在用戶態下運行,因此不能物理的訪問 I/O 端口空間,也不能直接發出 I/O 命令。相反,爲了可以對 I/O 設備編程,驅動器構建一個結構,指明哪一個參數值寫到哪一個 I/O 端口,並聲稱一個內核調用,這樣就完成了一次調用過程。
位於用戶態的驅動程序上面是服務器
層,包含有服務器,它們完成操做系統的多數工做。由一個或多個文件服務器管理着文件系統,進程管理器建立、銷燬和管理進程。服務器中有一個特殊的服務器稱爲 再生服務器(reincarnation server)
,它的任務就是檢查服務器和驅動程序的功能是否正確,一旦檢查出來錯誤,它就會補上去,無需用戶干預。這種方式使得系統具備可恢復性,並具備較高的可靠性。
微內核中的內核還具備一種 機制
與 策略
分離的思想。好比系統調度,一個比較簡單的調度算法是,對每一個進程賦予一個優先級,並讓內核執行具備最高優先級的進程。這裏,內核機制就是尋找最高的優先級進程並運行。而策略(賦予進程優先級)能夠在用戶態中的進程完成。在這種模式中,策略和機制是分離的,從而使內核變得更小。
微內核思想的策略是把進程劃分爲兩類:服務器
,每一個服務器用來提供服務;客戶端
,使用這些服務。這個模式就是所謂的 客戶-服務器
模式。
客戶-服務器模式會有兩種載體,一種狀況是一臺計算機既是客戶又是服務器,在這種方式下,操做系統會有某種優化;可是廣泛狀況下是客戶端和服務器在不一樣的機器上,它們經過局域網或廣域網鏈接。
客戶經過發送消息與服務器通訊,客戶端並不須要知道這些消息是在本地機器上處理,仍是經過網絡被送到遠程機器上處理。對於客戶端而言,這兩種情形是同樣的:都是發送請求並獲得迴應。
愈來愈多的系統,包括家裏的 PC,都成爲客戶端,而在某地運行的大型機器則成爲服務器。許多 web 就是以這種方式運行的。一臺 PC 向某個服務器請求一個 Web 頁面,服務器把 Web 頁面返回給客戶端,這就是典型的客服-服務器模式。
來源: 程序員DD, 程序員小灰, m