填空題、判斷題、簡答題(10)、設計題、編程題(IPC)
引論
操作系統所處的位置
多數計算機由兩種運行模式:內核態(管態)和用戶態(目態)。軟件中最基礎的部分是操作系統,它運行在內核態,在這個模式中,操作系統具有對所有硬件的完全訪問權,可以執行機器能夠運行的任何指令。軟件的其餘部分運行在用戶態下,只使用機器指令中的一個子集。用戶接口程序(shell 或 GUI)處於用戶態程序中的最低層次,允許用戶運行其他程序。操作系統運行在裸機之上,爲所有其他軟件提供基礎的運行環境。操作系統中的程序由硬件進行保護,防止用戶試圖對其進行修改。
什麼是操作系統
操作系統主要有兩個方面的重要作用:管理系統中的各種資源,併爲用戶提供良好的界面。
操作系統有兩個基本上獨立的任務:爲應用程序提供一個資源集的清晰抽象,並管理這些硬件資源。
操作系統是硬件的擴展板,是資源的管理器。
- 作爲擴展機器:例如,硬盤驅動。
- 作爲資源管理者:資源管理包括用兩種不同的方式實現多路複用(共享)資源:時間複用和空間複用。
操作系統的歷史
- 真空管和穿孔卡片
- 晶體管和批處理系統
- 集成電路和多道程序設計(SPOOLing)
- 個人計算機
SPOOLing 假脫機
同時的外部設備聯機操作(Simultaneous Peripheral Operation On-Line)
假脫機是多道程序設計系統中處理獨佔 I/O 設備的一種方法。
SPOOLing 系統的三大組成部分:
- 輸入井和輸出井
- 輸入緩衝和輸出緩衝
- 輸入進程和輸出進程
若有進程要求對它打印輸出時,SPOOLing系統並不是將這臺打印機直接分配給進程,而是在共享設備(磁盤或磁鼓)上的輸出SPOOLing存儲區中爲其分配一塊存儲空間,進程的輸出數據以文件形式存放於此。各進程的數據輸出文件形成了一個輸出隊列,由輸出SPOOLing系統控制這臺打印機進程,依次將隊列中的輸出文件實際打印輸出。在SPOOLing 系統中,實際上並沒有爲任何進程分配,而只是在輸入井和輸出井中,爲進程分配一存儲區和建立一張I/O請求表。這樣,便把獨佔設備改造爲共享設備。
技術特點
- 提高了I/O速度。從對低速I/O設備進行的I/O操作變爲對輸入井或輸出井的操作,如同脫機操作一樣,提高了I/O速度,緩和了CPU與低速I/O設備速度不匹配的矛盾。
- 設備並沒有分配給任何進程。在輸入井或輸出井中,分配給進程的是一存儲區和建立一張I/O請求表。
- 實現了虛擬設備功能。多個進程同時使用一獨享設備,而對每一進程而言,都認爲自己獨佔這一設備,不過,該設備是邏輯上的設備。
工作原理
1、SPOOLing的含義是什麼?試述SPOOLing系統的特點、功能以及控制過程。
答:SPOOLing是Simultaneous Peripheral Operation On-Line(即外部設備聯機並行操作)的縮寫,它是關於慢速字符設備如何與計算機主機交換信息的一種技術,通常稱爲"假脫機技術"。SPOOLing技術是在通道技術和多道程序設計基礎上產生的,它由主機和相應的通道共同承擔作業的輸入輸出工作,利用磁盤作爲後援存儲器,實現外圍設備同時聯機操作。SPOOLing系統由專門負責I/O的常駐內存的進程以及輸入井、輸出井組成;它將獨佔設備改造爲共享設備,實現了虛擬設備功能。
2、SPOOLing技術如何使一臺打印機虛擬成多臺打印機?
答:將一臺獨享打印機改造爲可供多個用戶共享的打印機,是應用SPOOLing技術的典型實例。具體做法是:系統對於用戶的打印輸出,但並不真正把打印機分配給該用戶進程,而是先在輸出井中申請一個空閒盤塊區,並將要打印的數據送入其中;然後爲用戶申請並填寫請求打印表,將該表掛到請求打印隊列上。若打印機空閒,輸出程序從請求打印隊首取表,將要打印的數據從輸出井傳送到內存緩衝區,再進行打印,直到打印隊列爲空。
計算機硬件簡介
- 處理器:從內存中取出指令並執行之。
- 存儲器:寄存器、高速緩存、主存、磁盤。
- 磁盤
- I/O設備:CPU 和存儲器不是操作系統唯一需要管理的資源。
- 總線
- BIOS(基本輸入輸出系統,Basic Input Output Stream)
操作系統概念
進程
進程本質上是正在執行的一個程序。與每個進程相關的是地址空間,這是從某個最小值的存儲位置(通常是零)到某個最大值的存儲位置的列表。
與一個進程有關的所有信息,除了該進程自身地址空間的內容以外,均存放在操作系統的一張表中,稱爲進程表,進程表是數組或鏈表結構,當前存在的每個進程都要佔用其中的一項。
一個掛起的進程包括:進程的地址空間(磁芯映像,紀念過去使用的磁芯存儲器,即主存),以及對應的進程表項(其中包括寄存器以及稍後重啓該進程所需要的許多其他信息)。
與進程管理有關的最關鍵的系統調用是那些進行進程創建和進程終止的系統調用。
一個進程能夠創建一個或多個子進程,而這些子進程又可以創建子進程,得到進程樹。合作完成某些作業的相關進程經常需要彼此通信以便同步它們的行爲,這種通信稱爲進程間通信。
由於定時器到期,或各種由硬件檢測出來的陷阱,如非法指令或使用了無效地址,操作系統會向該進程發送一個警告信號,此信號引起該進程暫時掛起,系統將其寄存器的值保存到堆棧,並開始運行一個特別的信號處理過程,如重新發送可能丟失的信息。
系統管理器授權每個進程使用一個給定的UID。每個被啓動的進程都有一個啓動該進程的用戶 UID。子進程擁有和父進程一樣的 UID。用戶可以是某個組的成員,每個組也有一個 GID。在 UNIX 中,有一個 UID 稱爲超級用戶,在 Windows 中爲管理員,它具有特殊的權力,可以違背一些保護規則。
地址空間
較複雜的操作系統允許在內存中同時運行多道程序。爲了避免它們互相干擾,需要有某種保護機制。雖然這種機制必然是硬件形式的,但是由操作系統掌控。
管理進程的地址空間同樣重要,在本質上,操作系統創建了一個地址空間的抽象,作爲進程可以引用地址的集合。該地址空間與機器的物理內存解耦,可能大於也可能小於該地址空間,操作系統可以把部分地址空間裝入主存,部分留在磁盤上,並且在需要時來回交換它們。對地址空間和物理空間的管理組成了操作系統功能的一個重要部分。
文件
目錄層結構中的每一個文件都可以通過從目錄的頂部即根目錄開始的路徑名來確定。
在實例中,每個進程有一個工作目錄,對於沒有斜線開頭給出絕對地址的路徑,將在這個工作目錄下尋找。進程可以通過使用系統調用指定新的工作目錄,從而變更其工作目錄。
提供特殊文件是爲了使 I/O 設備看起來像文件一般,這樣,I/O 設備可以像使用系統調用讀寫文件一樣進行讀寫。
- 塊特殊文件:指那些由可隨機存取的塊組成的設備,如磁盤等。
- 字符特殊文件:用於打印機、調制解調器和其他接收或輸出字符流的設備。
按照慣例,特殊文件保存在/dev目錄中。
管道是一種虛文件,它可連接兩個進程。這樣在 UNIX 中兩個進程之間的通信就非常類似於普通文件的讀寫了。而且若進程想發現它所寫入的輸出文件不是真正的文件而是管道,則需要特殊的系統調用。
輸入/輸出
某些 I/O 軟件是設備獨立的,即這些 I/O軟件部分可以同樣應用於許多或者全部的 I/O 設備上。
I/O 軟件的其他部分,如設備驅動程序,是專門爲特定的 I/O 設備設計的。
保護
rwx 位:所有者、其他組成員、其他人。
shell
操作系統是進行系統調用的代碼。編輯器、編譯器、彙編程序、鏈接程序、效用程序以及命令解釋器等都不是操作系統的組成部分。
shell 本身不是操作系統的一部分,但它體現了許多操作系統的特性,並很好地說明了系統調用的具體用法。shell 同時也是終端用戶與操作系統之間的接口。現在,很多個人計算機使用 GUI,GUI 與 shell 類似,GUI 只是一個運行在操作系統頂部的程序。
系統調用
任何單 CPU 計算機一次只能執行一條指令。如果一個進程正在用戶態運行一個用戶程序,並且需要一個系統服務,比如從一個文件讀取數據,那麼它就必須執行一個陷阱(trap)或系統調用(system call)指令,將控制轉移到操作系統。操作系統接着通過參數檢查找出所需要的調用進程。然後,它執行系統調用,並把控制返回給在系統調用後面跟隨着的指令。在某種意義上,進行系統調用就像進行一個特殊的過程調用,但是隻有系統調用可以進入內核,而過程調用則不能。
TRAP 指令實際上與過程調用指令非常類似,它們後面都跟隨一個來自遠處位置的指令,以及供以後使用的一個保存在棧中的返回地址。然而,TRAP 指令與過程指令存在兩個方面的差別:
- TRAP 指令的副作用是切換到內核態,而過程調用指令並不改變模式。
- 不像給定過程所在的相對或絕對地址那樣,TRAP 指令不能跳轉到任意地址上。根據機器的體系結構,或者跳轉到一個單固定地址上,或者指令中有一8位長的字段,它給定了內存中一張表格的索引,這張表格中含有跳轉地址。
跟隨在 TRAP 指令後的內核代碼開始檢查系統調用編號,然後分派給正確的系統調用處理器。
此時,系統調用處理器運行。
一旦系統調用處理器完成其工作,控制可能會在跟隨 TRAP 指令後面的指令中返回給用戶空間庫過程。
這個過程接着以通常的過程調用返回的方式,返回到用戶程序。
什麼是陷阱指令(TRAP)?在操作系統中解釋它的用途?
答:TRAP 是由於系統調用引起處理機中斷的指令。在系統調用中,TRAP 負責由用戶模式切換爲內核模式,並將返回地址保存至堆棧中以備後用。
讀取日期-時鐘指令可以在非內核態使用。
讀取用戶地址空間可以在非內核態使用。
陷阱指令和中斷的區別?
答:陷阱指令可以使執行流程從用戶態陷入內核;而中斷是由外部事件導致並且它發生的時間是不可預測的。外部事件主要是指時鐘中斷,硬件中斷等。所以說中斷的主要作用是完成進程間切換,從而支持CPU和設備之間的並行。
系統調用的目的是?
答:操作系統編制了許多不同功能的子程序,供用戶程序執行中調用。這些由操作系統提供的子程序稱爲系統功能調用,簡稱系統調用。
操作系統特性
- 併發
- 共享
- 虛擬化
- 異步
中斷、異常和陷入
- 中斷:是爲了設備與CPU之間的通信。
- 異常:異常是由當前正在執行的進程產生。
中斷的兩種方式:外部和陷入:
- interrupt 即外中斷,指來自處理機和內存外部的中斷,包括 I/O 設備發出的 I/O中斷、外部信號中斷、各種定時器引起的時鐘中斷以及調試程序中設置的斷點等引起的調試中斷等。
- trap 即內中斷,主要指在處理機和內存內部產生的中斷。它包括程序運算引起的各種錯誤。軟中斷是通信進程之間用來模擬硬中斷的一種信號通信方式。
中斷和陷阱的主要區別:
- 陷阱通常由處理機正在執行的現行指令引起,而中斷則是由與現行指令無關的中斷源引起的。
- 陷阱處理程序提供的服務爲當前進程所用,而中斷處理程序提供的服務則不是爲了當前進程的。
- CPU 在執行完一條指令之後,下一條指令開始之前響應中斷,而在一條指令執行中也可以響應陷阱。
- 在有的系統中,陷入處理程序被規定在各自的進程上下文中執行,而中斷處理程序則在系統上下文中執行。
進程與線程
進程模型
一個進程就是一個正在執行程序的實例,包括程序計數器、寄存器和變量的當前值。
進程和程序的區別:一個進程是某種類型的一個活動,它有程序、輸入、輸出以及狀態。單個處理器可以被若干進程共享,它使用某種調度算法決定何時停止一個進程的工作,並轉而爲另一個進程提供服務。如果一個程序運行了兩遍,則算作兩個進程。
進程的創建
4種主要事件會導致進程的創建:
- 系統初始化:前臺進程,後臺進程(守護進程,daemon)。
- 正在運行的程序執行了創建進程的系統調用。
- 用戶請求創建一個新的進程。
- 一個批處理作業的初始化。
UNIX:只有一個系統調用可以用來創建新進程:fork。這個系統調用會創建一個與調用進程相同的副本。通常,子進程接着執行 execve 或一個類似的系統調用,以修改其內存映像並運行一個新的程序。(之所以要安排兩步建立進程,是爲了在 fork 之後但在 execve 之前允許孩子進程處理其文件描述符,這樣可以完成對標準輸入文件、標準輸出文件和標準錯誤文件的重定向。)
文件描述符:UNIX 內核(kernel)利用文件描述符(file descriptor)來訪問文件。文件描述符是非負整數。打開現存文件或新建文件時,內核會返回一個文件描述符。讀寫文件也需要使用文件描述符來指定待讀寫的文件。
Windows:一個 Win32 函數調用 CreateProcess 既處理進程的創建,也負責把正確的程序裝入新的進程。
在 UNIX 和 Windows 中,進程創建之後,父進程和子進程由各自不同的地址空間。在 UNIX 中,子進程的初始地址空間是父進程的一個副本,但是這裏涉及兩個不同的地址空間,不可寫的內存區是共享的。或者,子進程共享父進程的所有內存,但這種情況下內存通過寫時複製(copy-on-write)共享,這意味着一單兩者之一想要修改部分內存,則這塊內存首先被明確地複製,以確保修改發生在私有內存區域。在 Windows 中,從一開始父進程的地址空間和子進程的地址空間就是不同的。
進程的終止
進程的終止通常由下列條件引起:
- 正常退出(自願)
- 出錯退出(自願,進程發現了嚴重錯誤)
- 嚴重錯誤(非自願,由進程引起的錯誤)
- 被其他進程殺死(非自願)
UNIX:kill
Windows:TerminateProcess
進程的層次結構
- UNIX
在 UNIX 中,進程和它的所有子進程以及後裔共同組成一個進程組。當用戶從鍵盤發出一個信號時,該信號被送給當前與鍵盤相關的進程組中的所有成員(它們通常是在當前窗口創建的所有活動進程)。每個進程可以分別捕獲該信號、忽略該信號或採取默認的動作,即被該信號殺死。在 UNIX 的整個系統中,所有的進程都屬於以 init 爲根的一棵樹。
- Windows
在 Windows 中沒有進程層次的概念,所有的進程都是地位相同的。唯一類似於進程層次的暗示是在創建進程的時候,父進程得到一個特別的令牌(句柄),該句柄可以用來控制子進程。但是,它有權把這個令牌傳送給某個其他進程,這樣就不存在進程層次了。在 UNIX 中,進程不能剝奪其子進程的“繼承權 ”。
進程的狀態
- 進程因爲等待輸入而被阻塞
- 調度程序選擇另一個進程
- 調度程序選擇這個進程
- 出現有效輸入
基於進程的操作系統中最底層的是中斷和調度處理,在該層之上的是順序進程。
操作系統的最底層是調度程序,在它上面有許多進程。所以有關於中斷處理、啓動進程和停止進程的具體細節都隱藏在調度程序中。實際上,調度系統是一段非常短小的程序,操作系統的其他部分被簡單地組織成進程的形式。
進程的實現
爲實現進程模型,操作系統維護着一張表格(進程表)。每個進程佔用一個進程表項(進程控制塊),該表項包含了進程狀態的重要信息,字段分三類:進程管理、存儲管理和文件管理。其中進程的 PID 代表進程的唯一存在。
多道程序設計模型
CPU 利用率 = 1-p^n
p:一個進程等待 I/O 操作的時間與其停留在內存中時間的比。
n:進程的數目。
e.g. 假設一個內存 8GB 的計算機,操作系統佔用 2GB,每個進程佔用 2GB,且進程有 80% 的時間用於 I/O 等待,則內存空間中最多允許同時運行 3 個進程,CPU 利用率爲 1-0.8^3 ≈ 49%。若增加 8GB 的內存,CPU 利用率可增加到 79%,再增加 8GB 內存可使 CPU 利用率增加到 91%。所以增加一次內存就夠了。
線程的使用
需要多線程的主要原因:在許多應用中同時發生着多種活動,其中某些活動隨着時間的推移會被阻塞,通過將這些應用程序分解成可以準並行運行的多個順序線程,程序設計模型會變得更簡單。
多線程的優點:
- 某些應用需要並行實體擁有共享同一個地址空間和所有可用數據的能力,而這正是多進程模型(它們具有不同的地址空間)所無法表達的。
- 由於線程比進程更輕量級,所以它們比進程更容易(即更快)創建,也更容易撤銷。在有大量線程需要動態和快速修改時,具有這一特性是很有用的。
- 若多個線程都是 CPU 密集型的,那麼並不能獲得性能上的增強,但是如果存在着大量的計算的大量的 I/O 處理,擁有多個線程允許這些活動彼此重疊進行,從而會加快應用程序執行的速度。
- 在多 CPU 系統中,多線程是有益的,在這樣的系統中,真正的並行有了實現的可能。
進程和線程的區別
- 地址空間:同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間。
- 資源擁有:同一進程內的線程共享本進程的資源如內存、I/O、cpu等,但是進程之間的資源是獨立的。
- 一個進程崩潰後,在保護模式下不會對其他進程產生影響,但是一個線程崩潰整個進程都死掉。所以多進程要比多線程健壯。
- 進程切換時,消耗的資源大,效率高。所以涉及到頻繁的切換時,使用線程要好於進程。同樣如果要求同時進行並且又要共享某些變量的併發操作,只能用線程不能用進程。
- 執行過程:每個獨立的進程程有一個程序運行的入口、順序執行序列和程序入口。但是線程不能獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
- 進程是處理器調度的基本單位,但是線程不是。
- 兩者均可併發執行。
經典的線程模型
-
一個進程中的所有線程共享的內容
- 地址空間
- 全局變量
- 打開文件
- 子進程
- 即將發生的定時器
- 信號與信號處理程序
- 賬戶信息
-
每個線程中的內容
- 程序計數器
- 寄存器
- 堆棧
- 狀態
線程的實現
用戶空間
把整個線程包放在用戶空間中,內核對線程包一無所知。從內核角度考慮,就是按正常的單線程進程的方式管理。
在用戶空間管理線程時,每個進程需要有其專用的線程表,用來跟蹤該進程中的線程。該線程表由運行時系統管理,當一個線程轉換到就緒狀態或阻塞狀態時,在該線程表中存放重新啓動該線程所需的信息,與內核在進程表中存放進程的信息完全一樣。
優點:
1. 用戶級線程包可以在不支持線程的操作系統上實現。
2. 保存該線程狀態的過程和調度程序都只是本地過程,所以啓動它們比進行內核調用效率更高。而且不需要陷入內核,不需要上下文切換,也不需要對內存高速緩存進行刷新,這就使得線程調度非常快捷。
3. 允許每個進程有自己定製的調度算法。
缺點:
1. 很難實現阻塞系統調用,因爲一個線程使用阻塞調用的時候要避免被阻塞的線程影響其他的線程。
2. 缺頁中斷問題:如果有一個線程引起頁面故障,內核由於不知道有線程的存在,通常會把整個進程阻塞直到磁盤 I/O 完成爲止,儘管其他線程是可以運行的。
3. 如果一個線程開始運行,那麼在該進程中的其他線程就不能運行,除非第一個線程自動放棄 CPU。因爲在一個單獨的進程內部,沒有時鐘中斷,所以不可能使用輪轉調度的方式調度線程。
內核
考慮內核支持和管理線程的情形,此時不再需要運行時系統了。每個進程中也沒有線程表,但是在內核中有用來記錄系統中所有線程的線程表。當某個線程希望創建一個新線程或撤銷一個已有線程時,它進行一個系統調用,這個系統調用通過對線程表的更新完成線程創建或撤銷工作。
- 所有能夠阻塞線程的調用都以系統調用的形式實現,這與運行時系統相比,代價非常大。內核線程不需要新的、非阻塞的系統調用。
- 由於在內核中創建或撤銷線程的代價比較大,可以在線程被撤銷時標誌其爲不可用,但是其內核數據結構沒有受到影響,稍後再創建一個新線程的時候,就重新啓動這個舊線程。而用戶級線程管理的代價很小,沒有必要進行這項工作。
- 信號是發送給進程而不是線程的,線程可以註冊它們“感興趣”的信號,但是處理起來還有很多問題。
混合實現
使用內核級線程,然後將用戶級線程與某些或者全部內核線程多路複用起來。在這個模型中,每個內核級線程有一個可以輪流使用的用戶級線程集合。
進程間通信的一些基本概念
-
競爭條件:兩個或多個進程讀寫某些共享數據,而最後的結果取決於進程運行的精確時序,稱爲競爭條件。
-
臨界區:我們把對共享內存進行訪問的程序片段稱作臨界區域(critical region)或臨界區。即臨界區是一段代碼。如果我們能夠適當地安排,使得兩個進程不可能同時處於臨界區中,就能夠避免競爭條件。
-
互斥:以某種手段確保當一個進程在使用一個共享變量或文件時,其他進程不能做同樣的操作。
-
信號量(Semaphore):使用一個整型變量來累計喚醒次數,取值可以爲0或者正值。信號量的操作均作爲一個單一的、不可分割的原子操作完成。保證一旦一個信號量操作開始,則在該操作完成或阻塞之前,其他進程均不允許訪問該信號量。
-
原語:由若干條指令組成的,用於完成一定功能的一個過程。具有不可分割性,即原語的執行必須是連續的,在執行過程中不允許被中斷。
-
忙等待:連續測試一個變量直到某個值出現爲止。用於忙等待的鎖,稱爲自旋鎖。
-
互斥量(Mutex):如果不需要信號量的計數能力,可以使用信號量的一個簡化版本。
-
條件變量:允許線程由於一些未達到的條件而阻塞。絕大部分情況下與互斥量一起使用。
-
優先級倒置(priority inversion):在某一時刻,Low 處於臨界區中,此時 High 變到就緒態。現在 High 開始忙等待,但是由於 High 優先級高,所以 Low 不會被調度也就無法離開臨界區,所以 High 將永遠忙等待下去。
臨界區互斥訪問解決方案的4個條件
- 任何兩個進程不能同時處於臨界區。
- 不應對 CPU 的速度和數量做任何假設。
- 臨界區外運行的進程不得阻塞其他進程。
- 不得使進程無限期等待進入臨界區。
忙等待的互斥訪問
- 屏蔽中斷
在單處理器系統中,最簡單的方法是使每個進程在剛剛進入臨界區後立即屏蔽所有中斷,並在就要離開之前再打開所有中斷。屏蔽中斷後,時鐘中斷也被屏蔽。而 CPU 只有發生時鐘中斷或其他中斷時纔會進行進程切換,這樣,在屏蔽中斷之後 CPU 將不會被切換到其他進程。於是,一旦某個進程屏蔽中斷之後,它就可以檢查和修改共享內存,而不必擔心其他進程介入。
但是把屏蔽中斷的權力交給用戶進程是不明智的。但是對內核來說,當它在更新變量或列表的幾條指令期間將中斷屏蔽是很方便的。所以結論是:屏蔽中斷對於操作系統本身而言是一項很有用的技術,但對於用戶進程則不是一種合適的通用互斥機制。
- 鎖變量
設想有一個共享(鎖)變量,其初始值爲0。當一個進程想進入其臨界區時,它首先測試這把鎖。如果該鎖的值爲0,則該進程將其設置爲1並進入臨界區。若這把鎖的值已經爲,則該進程將等待直到其值變爲0。
但是,這樣還是會造成競爭條件。
- 嚴格輪換法
整型變量 turn,初始值爲0,用於記錄輪到哪個進程進入臨界區,並檢查或更新共享內存。開始時,進程0檢查 turn,發現其值爲0,於是進入臨界區。進程1也發現其值爲0,所以在一個等待循環中不斷測試 turn,看其值何時變爲1。
存在問題:忙等待(用於忙等待的鎖,稱爲自旋鎖,spin lock),兩個進程速度不匹配,進程0在進程1在非臨界區時被阻塞(違反了條件3)。
- Peterson 解法
#define FALSE 0
#define TRUE 1
#define N 2 /*number of processes*/
shared int turn; /*whose turn is it?*/
shared int interested[N]; /*all values initially 0*/
void enter_region(int process)
{
int other;
other=1-process;
interested[process]=TRUE;
turn=process;
while(turn == process && interested[other] == TRUE);
}
void leave_region(int process) {
interested[process]=FALSE;
}
- TSL 指令,測試並加鎖(test and set lock)
需要硬件支持的一種方案:TSL RX, LOCK。它將一個內存字 lock 讀到寄存器 RX 中,然後在該內存地址上存一個非零值。
enter_region:
TSL REGISTER,LOCK /複製鎖到寄存器並將鎖設爲1/
CMP REGISTER,#0 /若鎖不是0則循環/
JNE enter_region
RET
leave_region:
MOVE LOCK,#0
RET
一個可替代 TSL 的指令是 XCHG,它原子性地交換了兩個位置的內容。
enter_region:
MOVE REGISTER,#1
XCHG REGISTER,LOCK /交換寄存器與鎖變量的內容/
CMP REGISTER,#0 /若鎖不是0則循環/
JNE enter_region
RET
leave_region:
MOVE LOCK,#0
RET
信號量(semaphore)
- Down,P 操作:若其值大於0,則將其減1並繼續;若該值爲0,則進程將休眠,而且測試 down 操作並未結束。
- Up,V 操作:對信號量的值增1。如果一個或多個進程在該信號量上睡眠,無法完成先前的 down 操作,則由系統選擇其中的一個並允許該進程完成它的 down 操作。於是 up 操作之後,該信號量的值仍舊是0,但在其上睡眠的進程卻少了一個。
信號量解決生產者-消費者問題:
#define N 100 /*number of slots in the buffer*/
typedef int semaphore;
semaphore mutex=1;
semaphore empty=N;
semaphore full=0;
void producer(void){
int item;
while(TRUE){
produce_item(&item);
down(&empty);
down(&mutex);
enter_item(item);
up(&mutex);
up(&full);
}
}
void consumer(void){
int item;
while(TRUE){
down(&full);
down(&mutex);
remove_item(&item);
up(&mutex);
up(&empty);
consume_item(item)
}
}
互斥量(mutex)
如果不需要信號量的計數能力,可以使用信號量的一個簡化版本,稱爲互斥量(mutex)。由於互斥量在實現時既容易又有效,所以互斥量在實現用戶空間線程包時非常有用。
互斥量是一個可以處於兩態之一的變量:解鎖和加鎖。
用戶級線程包的 mutex_lock 和 mutex_unlock 的代碼:
mutex_lock:
TSL REGISTER,MUTEX
CMP REGISTER,#0
JZE ok
CALL thread_yield ;如果互斥信號不爲0,則調度另一個線程,稍後再嘗試
JMP mutex_lock
ok: RET
mutex_unlock:
MOVE MUTEX,#0
RET
mutex_lock 的代碼與上面 TSL 中 enter_region 的代碼類似。但是當後者在進入臨界區失敗時,會始終重複測試鎖(忙等待),而由於時鐘超時的作用,會調度其他進程運行。但在用戶線程中,沒有時鐘會停止運行時間過長的線程,所以前者需要主動放棄 CPU 給另一個線程,這樣就沒有忙等待。
條件變量(condition variable)
互斥量可以允許或阻塞對臨界區的訪問,而條件變量則允許線程由於一些未達到的條件而阻塞。
條件變量與互斥量經常一起使用。這種模式用於讓一個線程鎖住一個互斥量,然後當它不能獲得它期待的結果時等待一個條件變量。最後另一個線程會向它發信號,使它可以繼續執行。wait(condition,mutex) 原子性地調用並解鎖它持有的互斥量,所以互斥量也是 wait(condition,mutex) 的參數之一。
但是條件變量不會像信號量那樣存在內存中,如果將一個信號傳遞給一個沒有線程在等待的條件變量,那麼這個信號就會丟失。所以必須小心使用以防丟失信號。
管程(monitor)
管程是一種高級同步原語。是一個由過程、變量及數據結構等組成的一個集合,它們組成一個特殊的模塊或軟件包。進程可在任何需要的時候調用管程中的過程,但它們不能再管程之外聲明的過程中直接訪問管程內的數據結構。
管程有一個很重要的特性,即任一時刻管程中只能有一個活躍進程,這一特性使管程能有效地完成互斥。而進入管程時的互斥由編譯器負責,一般的解決方法是引入條件變量,wait 和 signal 操作。
Java 中可以使用 synchronized ,wait 和 notify 來實現管程,沒有內嵌的條件變量,而且方法 wait 會被中斷,需要顯式表示異常處理。
消息傳遞(message passing)
消息傳遞這種進程間通信的方法使用兩條原語 send(des,&msg) 和 receive(src,&msg),它們像信號量而不像管程,是系統調用而不是語言成分。send 向一個給定的目標發送一條消息,receive 從一個給定的源接收一條消息,如果沒有消息可用,則接收者可能阻塞,直到一條消息到達,或者帶着一個錯誤碼立即返回。
屏障(barrier)
用於進程組,實現除非所有進程都準備就緒進入下一個階段,否則任何進程和都不能進入。可以通過在每個階段的結尾安置屏障(barrier)來實現。當一個進程到達屏障時,它就被屏障阻攔,直到所有進程都到達該屏障爲止。屏障可以用於一組進程同步。
經典的 IPC 問題
哲學家就餐問題
哲學家就餐問題對於互斥訪問有限資源的競爭問題(如I/O設備)一類的建模過程十分有用。
#define N 5
#define LEFT (i+N-1)%N
#define RIGHT (i+1)%N
#define THINKING 0
#define HUNGRY 1
#define EATING 2
typedef int semaphore;
int state[N];
semaphore mutex=1;
semaphore s[N]; // 每個哲學家一個信號量
void philosopher(int){
while(TRUE){
think();
take_forks(i);
eat();
put_forks(i);
}
}
void take_forks(int i){
down(&mutex);
state[i]=HUNGRY;
test(i); // 嘗試獲取兩把叉子
up(&mutex);
down(&s[i]); // 如果得不到所需要的叉子則阻塞
}
void put_forks(int i){
down(&mutex);
state[i]=THINKING;
test(LEFT); // 檢查左邊的鄰居現在可以吃嗎
test(RIGHT);
up(&mutex);
}
void test(int i){
if(state[i]==HUNGRY && state[LEFT]!=EATING && state[RIGHT]!=EATING){
state[i]=EATING;
up(&s[i]);
}
}
讀者-寫者問題(可能考)
讀者-寫者問題爲數據庫訪問建立了一個模型。多個進程同時讀數據庫是可以接受的,但是如果一個進程正在更新(寫)數據庫,則所有的其他進程都不能訪問該數據庫,即使讀操作也不可以。
typedef int semaphore;
semaphore mutex=1; // 控制對 rc 的訪問
semaphore db=1; // 控制對數據庫的訪問
int rc=0; // 正在讀或者即將讀的進程數目
void reader(void){
while(TRUE){
down(&mutex);
rc=rc+1; // 現在多了一個讀者
if(rc==1) down(&db); // 如果這是第一個讀者
up(&mutex);
read_data_base();
down(&mutex);
rc=rc-1; // 現在少了一個讀者
if (rc==0) up(&db); // 如果這是最後一個讀者
up(&mutex);
use_data_read();
}
}
void writer(void){
while(TRUE){
think_up_data();
down(&db);
write_data_base();
up(&db);
}
}
睡眠的理髮師問題(可能考)
有一個理髮師,有一個理髮椅,5個等候椅,如果沒有顧客,則理髮師睡覺,如果有顧客,則叫醒理髮師;理髮師理髮時,如果有顧客過來,且有等候椅,則坐下來等候;如果沒有等候椅,則離開。
#define CHAIRS 5
typedef int semaphore;
semaphore customers=0;
semaphore barbers=0;
semaphore mutex=1;
int waiting =0;
void barber(void){
while(TRUE){
down(&customers);
down(&mutex);
waiting=waiting-1;
up(&barbers); // 如果有顧客,理髮師醒來
up(&mutex);
cut_hair();
}
}
void customer(void){
down(&mutex);
if(waiting<CHAIRS){
waiting=waiting+1;
up(&customers);
up(&mutex);
down(&barbers);
get_haircut();
}else{
up(&mutex);
}
}
調度程序
當計算機系統是多道程序設計系統時,通常就會有多個進程或線程同時競爭 CPU。只要有兩個或更多的進程處於就緒狀態,這種情形就會發生。如果只有一個 CPU 可用,那麼就必須選擇下一個要運行的進程。在操作系統中,完成選擇工作的這一部分稱爲調度程序,該程序使用的算法稱爲調度算法。
-
調度的三個層次
- 作業調度:從外存後備隊列中選擇作業進入內存就緒隊列。
- 存儲調度:在內存和外存對換區之間按照給定的策略選擇進程對換。
- 進程和線程調度:從就緒隊列中選擇一個進程來執行並由分派程序分配處理機。
-
根據如何處理時鐘中斷,可以把調度算法分爲兩類:
- 搶佔式
- 非搶佔式
何時調度
- 在創建一個新進程之後,需要決定是運行父進程還是運行子進程。(任意決定,調度程序可以選擇)
- 在一個進程退出時必須所處調度決策。
- 當一個進程阻塞在 I/O 和信號量上或由於其他原因阻塞時,必須選擇另一個進程運行。
- 在一個 I/O 中斷髮生時,必須做出調度決策。
調度算法
批處理系統中的調度
- 先來先服務
- 最短作業優先
- 最短剩餘時間優先
交互式系統中的調度
- 輪轉調度
- 優先級調度:在各個優先級類中使用輪轉調度
- 多級隊列:屬於最高優先級類的進程運行一個時間片,次高運行2個,再次運行4個。。。
- 最短進程優先:根據進程過去的行爲進程預測,並執行估計運行時間最短的那個,可以和之前的運行時間做加權和來預測
- 保證調度:確保 n 個進程中每個進程佔用 CPU 的時間約爲 1/n
- 彩票調度:反應迅速,所有的進程都是平等的,但是可以給更重要的進程額外的彩票
- 公平分享調度:以進程的所有者均分 CPU 時間而不論進程數目
實時系統中的調度
- 分類1:
- 硬實時:必須滿足絕對的截止時間
- 軟實時:雖然不希望偶爾錯失截止時間,但是可以容忍
- 分類2:
- 週期性:事件以規則的時間間隔發生
- 非週期性:事件發生的時間不可預知
- 分類3:
- 靜態調度:在系統開始運行前作出調度決策
- 動態調度:在運行過程中進行調度決策
- 最小延誤調度
- 優先級調度
- 速率單調調度
- 最早截止優先調度
- 成比例分享調度
- POSIX 實時調度
資源
資源:軟件、硬件、信息,我們把這類需要排他性使用的對象稱爲資源。當某個資源有多個實例時,其中任何一個都可以用來滿足對資源的請求。簡單來說,資源就是隨着時間的推移,必須能獲得、使用以及釋放的任何東西。
資源分爲兩類:可搶佔的和不可搶佔的。
可搶佔資源可以從擁有它的進程中搶佔而不會產生任何副作用,存儲器就是一類可搶佔的資源。
不可搶佔資源是指在不引起相關的計算失敗的情況下,無法把它從佔有它的進程處搶佔過來。
使用一個資源所需要的時間順序可以用抽象的形式表示如下:
- 請求資源
- 使用資源
- 釋放資源
死鎖定義
死鎖的規範定義如下:如果一個進程集合中的每個進程都在等待只能由該進程集合中的其他進程才能引發的事件,那麼,該進程集合就是死鎖的。
資源死鎖:競爭資源的進程集合按一定順序運行形成死鎖。
資源死鎖的四個必要條件
- 互斥條件。每個資源要麼已經分配給了一個進程,要麼就是可用的。
- 佔有和等待條件。已經的到了某個資源的進程可以再請求新的資源。
- 不可搶佔條件。已經分配給一個進程的資源不能強制性地被搶佔,它只能被佔有它的進程顯式地釋放。
- 環路等待條件。死鎖發生時,系統中一定有由兩個或兩個以上的進程組成一條環路,該環路中的每個進程都在等待着下一個進程所佔有的資源。
四種死鎖處理策略
- 忽略該問題。
- 檢測死鎖並恢復。讓死鎖發生,檢測它們是否發生,一旦發生死鎖,採取行動解決問題。
- 仔細對資源進行分配,動態地避免死鎖。
- 通過破壞死鎖的四個必要條件之一,防止死鎖的產生。
死鎖檢測和恢復
死鎖檢測
每種類型一個資源的死鎖檢測
對於每種資源類型只有一個資源,可以對這樣的系統構造一張資源分配圖,如果這張圖包含了一個或一個以上的環,那麼死鎖就存在,在此環中的任何一個進程都是死鎖進程。
- 如下的資源分配圖:○表示進程,□表示資源,□→○表示進程佔用資源,○→□表示進程請求資源
死鎖檢測算法是依次將每個節點作爲一棵樹的根節點,並進行深度優先搜索,如果碰到了已經遇到過的節點,那麼就算找到了一個環。
每種類型多個資源的死鎖檢測
- 如下的資源矩陣:E 是現有資源向量,A 是可用資源向量,C 代表當前分配矩陣(第 n 行是進程 n 當前已分配到的資源),R 代表請求矩陣(第 n 行是進程 n 需要的資源)
死鎖檢測算法如下:
- 尋找一個沒有標記的進程 Pi,對它而言 R 矩陣的第 i 行向量小於等於 A。
- 如果找到了這樣一個進程,那麼將 C 矩陣的第 i 向量加到 A 中,標記該進程,並轉到第1步,表示該進程可以得到所需資源並運行完成。
- 如果沒有這樣的進程,那麼算法終止。
- 算法結束時,所有沒有標記過的進程都是死鎖進程。
死鎖恢復
- 利用搶佔恢復
- 利用回滾恢復
- 通過殺死進程恢復
死鎖避免
資源軌跡圖
- 資源軌跡圖:進程 A 在 I1 到 I3 使用打印機,I2 到 I4 使用繪圖儀;進程 B 在 I6 到 I8 使用打印機,I5 到 I7 使用繪圖儀
圖中的陰影部分表示兩個進程同時使用打印機或繪圖儀,而這是不可能實現的,所以不可能進入該區域。
如果系統一旦進入由 I1、I2、I5 和 I6 組成的矩形區域,那麼最後一定會到達 I2 和 I6的交叉點,此時產生死鎖。
安全狀態和不安全狀態
- 安全狀態:狀態 a 爲安全狀態
- 不安全狀態:狀態 b 爲不安全狀態
- 安全狀態,不安全狀態和死鎖
不安全狀態並不是死鎖,在不安全狀態系統還能運行一段時間,甚至還有一些進程可以運行完成。
安全狀態和不安全狀態的區別是:從安全狀態出發,系統能夠保證所有進程都能完成;而從不安全狀態出發,就沒有這樣的保證。
單個資源的銀行家算法
Dijkstra 提出了一種能夠避免死鎖的調度算法,稱爲銀行家算法,這是上面通過檢測死鎖算法的擴展。
- 單個資源的銀行家算法:Has 表示已有數量,Max 表示最大需求,下圖中 a,b安全,c 不安全
銀行家算法就是對每一個請求進行檢查,檢查如果滿足這一請求是否會達到安全狀態。若是,那麼就滿足該需求;否則,就推遲對這一請求的滿足。
爲了檢查狀態是否安全,銀行家需要考慮他是否有足夠的資源滿足某一客戶。如果可以,那麼這筆貸款是能夠收回的,並且接着檢查最接近最大限額的一個客戶,以此類推。如果所有投資最終都能被收回,那麼該狀態是安全的,最初的請求可以批准。
多個資源的銀行家算法
- 多個資源的銀行家算法:左矩陣表示已分配資源,右矩陣表示仍然需要的資源,E 表示現有資源,P 表示已分配資源,A 表示可用資源
同上面資源矩陣的檢測算法。
死鎖預防
破壞互斥條件
如果一個資源不被一個進程獨佔,那麼死鎖肯定不會產生。
通過採用假脫機(打印機)技術可以允許若干個進程同時產生輸出。該模型中唯一真正請求物理打印機的進程是打印機守護進程,由於守護進程不會請求別的資源,所以不會因打印機而產生死鎖。
可以考慮爲所有資源建立一個資源池。
破壞佔有並等待條件
禁止已持有資源的進程再等待其他資源。
- 實現方法一:規定所有進程在開始執行前請求所需的全部資源。
- 實現方法二:當一個進程請求資源時,先暫時釋放其當前佔用的所有資源,然後再嘗試一次獲得所需的全部資源。
破壞不可搶佔條件
搶佔正在打印的打印機可能會造成混亂,但是可以對這類資源可以採用虛擬化的方式來避免發生這類情況,假脫機打印機向磁盤輸出。
破壞環路等待條件
- 實現方法一:保證每個進程在任何時刻只能佔用一個資源,如果要請求另外一個資源,它必須先釋放第一個資源。
- 實現方法二:將所有資源統一編號,進程可以在任何時刻提出資源請求,但是所有請求必須按照資源編號順序(升序)提出。
有關死鎖的其他問題
兩階段加鎖
在第一階段,進程試圖對所有所需的記錄進行加鎖,一次鎖一個記錄。如果第一階段加鎖成功,就開始第二階段,完成更新然後釋放鎖。在第一階段並沒有做實際的工作。如果在第一階段某個進程需要的記錄已經被加鎖,那麼該進程釋放它所有加鎖的記錄,然後重新開始第一階段。
通信死鎖
資源死鎖是最普遍的一種類型,但不是唯一的一種。
通信死鎖發生在通信系統(e.g. 網絡)中,一個普遍的情形是:進程 A 向進程 B 發送請求信息,然後阻塞直至 B 回覆,但是請求信息在網絡中丟失,A 將阻塞以等待回覆,而 B 也會阻塞等待一個向其發送命令的請求,因此發送死鎖。
可以設置適當的超時機制來解決通信死鎖。
活鎖
在某些情況下,當進程意識到它不能獲取所需要的下一個鎖時,就會釋放已經得到的鎖,等待1ms,然後再嘗試一次,但是,如果另一個進程在相同的時刻做了相同的操作,那麼兩個進程就像兩個人在一條路相遇並同時給對方讓路一樣,相同的步調將導致雙方都無法前進。
保持飢餓:SJF
一些管理資源的策略可能使一些進程永遠得不到服務,比如最小作業優先策略,可以使用先進先出的分配策略來避免飢餓。
內存管理
存儲管理
存儲器管理的對象是主存,也稱內存。它的主要功能包括分配和回收主存空間、提高主存利用率、擴充主存、對主存信息實現有效保護。
存儲管理方案的主要目的是解決多個用戶使用主存的問題,其存儲管理方案主要包括:
- 分區存儲管理
- 靜態分區
- 可變分區
- 可重定位分區
- 分頁存儲管理
- 分段存儲管理
- 段頁式存儲管理
- 虛擬存儲管理。
無存儲器抽象
在沒有存儲器抽象的系統中實現並行的一種方法是使用多線程來編程。由於在引入線程時就假設一個進程中的所有線程對同一內存映像都可見,那麼實現並行也就不是問題,
在不使用存儲器抽象的情況下運行多個程序
操作系統只需要把當前內存中所有內容保存到磁盤文件中,然後把下一個程序讀入到內存中再運行即可。只要在某一個時間內存中只有一個程序,那麼就不會發生衝突。
在特殊硬件的幫助下(防止用戶進程之間相互干擾),即使沒有交換功能,併發地運行多個程序也是可能的。但這樣會有重定位問題,
- 保護:(IBM 360)給內存塊標記上一個保護鍵,並且比較執行進程的鍵和其訪問的每個內存字的保護鍵。
- 靜態重定位:當一個程序被裝載到地址16384時,常數16384被加到每一個程序地址上。裝載器還需要一定的方法來辨別地址和常數。
地址空間
把物理地址暴露給進程會帶來兩個嚴重問題:
- 如果用戶程序可以尋址內存的每個字節,那麼它們就可以很容易地破壞操作系統。即使在只有一個用戶進程運行的情況下,這個問題也是存在的。
- 在系統中沒有對物理內存的抽象的情況下,很難實現同時運行多個程序。
要使多個應用程序同時處於內存中並且不相互影響,需要解決兩個問題:保護的和重定位。
比無存儲器抽象時的保護和靜態重定位更好的辦法是創造一個新的存儲器抽象:地址空間。
就像進程的概念創造了一類抽象的 CPU 以運行程序一樣,地址空間爲程序創造了一種抽象的內存,是一個進程可用於尋址內存的一套地址集合。每個進程都有一個自己的地址空間,並且這個地址空間獨立於其他進程的地址空間。
- 動態重定位
- 基址寄存器
- 界限寄存器
- 當使用基址寄存器和界限寄存器時,程序裝載到內存中連續的空閒位置且裝載期間無須重定位,當一個程序運行時,程序的起始物理地址裝載到基址寄存器中,程序的長度裝載到界限寄存器中。
- 使用基址寄存器和界限寄存器重定位的缺點是:每次訪問內存都需要進行加法和比較運算。
交換技術
有兩種處理內存超載的通用方法。
- 交換技術:即把一個進程完整調入內存,使該進程運行一段時間,然後把它存回磁盤。
- 虛擬內存:能使程序在只有一部分被調入內存的情況下運行。
交換在內存中產生了多個空閒區(hole,也稱爲空洞),通過把所有的進程儘可能向下移動,有可能將這些小的空閒區合成一大塊。該技術稱爲內存緊縮(memory compaction)。
如果進程在運行時內存需要增長,爲了減少因內存不夠而引起的進程交換和移動所產生的開銷,當換入或移動進程時可以爲它分配一些額外的內存。
- 爲可能增長的數據段預留空間;
- 爲可能增長的數據段和堆棧段預留空間。
空閒內存管理
- 使用位圖的存儲管理
- 分配單元的大小是一個重要的設計因素。內存的大小和分配單元的大小決定了位圖的大小。
- 主要問題:查找位圖中指定長度的連續0串是耗時的操作。(爲了把一個佔 k 個分配單元的進程調入內存)
- 使用鏈表的存儲管理
- 維護一個記錄已分配內存段和空閒內存段的鏈表。其中鏈表的一個結點或者包含一個進程,或者是兩個進程間的一塊空閒區。
- 鏈表中的每一個結點都包含以下域:空閒區(H)或進程(P)的指示標識、起始地址、長度和指向下一結點的指針。
- 當按照地址順序在鏈表中存放進程和空閒區時,有以下幾種算法可以用來爲創建的進程,或從磁盤換入的已存在的進程分配內存(假設存儲管理器知道要爲進程分配多少內存):
- 首次適配
- 下次適配:每次從上次結束的地方開始搜索。
- 最佳適配:比首次適配算法慢,而且比首次適配和下次適配算法浪費更多的內存,因爲它會產生大量無用的小空閒區。
- 最差適配
- 快速適配:爲那些常用大小的空閒區維護單獨的鏈表。
- 優點:尋找一個指定大小的空閒區是十分快速的。
- 缺點:在一個進程終止或被換出時,尋找它的相鄰塊並查看是否可以合併的過程非常費時。
- 夥伴式的內存管理
- 拆分和合並涉及到較多的鏈表和位圖操作。
- Buddy算法的分配原理:
- 假如系統需要4(2*2)個頁面大小的內存塊,該算法就到free_area[2]中查找,如果鏈表中有空閒塊,就直接從中摘下並分配出去。如果沒有,算法將順着數組向上查找free_area[3],如果free_area[3]中有空閒塊,則將其從鏈表中摘下,分成等大小的兩部分,前四個頁面作爲一個塊插入free_area[2],後4個頁面分配出去,free_area[3]中也沒有,就再向上查找,如果free_area[4]中有,就將這16(2*2*2*2)個頁面等分成兩份,前一半掛如free_area[3]的鏈表頭部,後一半的8個頁等分成兩等分,前一半掛free_area[2]的鏈表中,後一半分配出去。假如free_area[4]也沒有,則重複上面的過程,直到到達free_area數組的最後,如果還沒有則放棄分配。
- Buddy算法的釋放原理:
- 內存的釋放是分配的逆過程,也可以看作是夥伴的合併過程。當釋放一個塊時,先在其對應的鏈表中考查是否有夥伴存在,如果沒有夥伴塊,就直接把要釋放的塊掛入鏈表頭;如果有,則從鏈表中摘下夥伴,合併成一個大塊,然後繼續考察合併後的塊在更大一級鏈表中是否有夥伴存在,直到不能合併或者已經合併到了最大的塊。
基礎內存管理:離散分配
- 分段
- 分頁
- 多級頁表
- 倒排頁表
爲什麼引入虛擬內存
需要運行的程序往往大到內存無法容納,而且必然需要系統能夠支持多個程序同時運行,即使內存可以滿足其中單獨一個程序的需要,總體來看它們仍然超出了內存大小。而交換技術並不一個具有吸引力的解決方案,因此引入了虛擬內存。
虛擬內存的基本思想是:每個程序擁有自己的地址空間,這個空間被分割成多個塊,每個塊稱作一頁或頁面。每一頁有連續的地址範圍。這些頁被映射到物理內存,但並不是所有的頁都必須在內存中才能運行程序。當程序引用到一部分在物理內存中的地址空間時,由硬件立刻執行必要的映射。當程序引用到一部分不在物理內存中的地址空間時,由操作系統負責將缺失的部分裝入物理內存並重新執行失敗的指令。
虛擬內存的特點
-
虛擬擴充 即不是物理上而是邏輯上擴充了內存容量。
-
部分裝入 即每個作業不是全部一次性地裝入內存,而是隻裝入一部分。
-
離散分配 即不必佔用連續的內存空間,而是“見縫插針”。
-
多次對換 即所需的全部程序和數據要分成多次調入內存。
-
多次性:是指無需在作業運行時一次性地全部裝入內存,而是允許被分成多次調入內存運行。
-
對換性(交換性):是指無需在作業運行時一直常駐內存,而是允許在作業的運行過程中,進行換進和換出。
-
虛擬性:是指從邏輯上擴充內存的容量,使用戶所看到的的內存容量,遠大於實際的內存容量。
局部性原理
局部性原理: CPU訪問存儲器時,無論是存取指令還是存取數據,所訪問的存儲單元都趨於聚集在一個較小的連續區域中。
- 時間局部性:如果一個信息項正在被訪問,那麼在近期它很可能還會被再次訪問。
- 空間局部性:在最近的將來將用到的信息很可能與現在正在使用的信息在空間地址上是臨近的。
頁面置換算法
- 最優頁面置換算法
- 替換最長時間不會被訪問的頁
- 先進先出頁面置換算法
- 第二次機會頁面置換算法
- 訪問位(R)位0則替換,否則減1
- 時鐘頁面置換算法
- 將第二次機會頁面置換算法中的鏈表改爲環形鏈表
- 最近最少使用頁面置換算法(LRU,Least Recently Used)
- 需要在內存中維護一個所有頁面的鏈表,最近最多使用的頁面在表頭,最近最少使用的頁面在表尾,每次訪問內存時都必須更新整個鏈表。(找到一個頁面,刪除它,然後把它移動到表頭)
- 最近未使用頁面置換算法(NRU,Not Recently Used)
- 檢查所有頁面的 R 和 M 位,分爲4類:(每次時鐘中斷清除 R 位)
- 0:沒有被訪問,沒有被修改。
- 1:沒有被訪問,被修改。
- 2:被訪問,沒有被修改。
- 3:被訪問,被修改。
- 隨機選擇編號最小的非空類中的一個頁面淘汰。
- 主要優點:易於理解和能夠有效地被實現。
- 檢查所有頁面的 R 和 M 位,分爲4類:(每次時鐘中斷清除 R 位)
- 老化算法(修改後的 NRU,可以模擬 LRU)
- 與 LRU 的區別:
- 在一個時鐘滴答內無法區分先後。
- 計數器只有有限位數。
- 與 LRU 的區別:
- 工作集頁面置換算法(解決抖動)
- 請求調頁:頁面是在需要時調入的,而不是預先裝入的。
- 工作集:一個進程當前正在使用的頁面的集合。
- 顛簸:每執行幾條指令程序就發生一次缺頁中斷。
- 工作集模型:不少分頁系統都會設法跟蹤進程的工作集,以確保在讓進程運行以前,它的工作集就已在內存中了。
- 預先調頁:在進程運行前就預先裝入其工作集頁面。
- 工作集是隨着時間變化的。
- 工作集時鐘頁面置換算法(WSClock)
- 工作集算法有合理的性能,但它的實現開銷較大。工作集時鐘算法是它的一種變體,不僅具有良好的性能,並且還能高效地實現。
Belady’s Anomaly: 所謂Belady現象是指:採用FIFO算法時,如果對—個進程未分配它所要求的全部頁面,有時就會出現分配的頁面數增多但缺頁率反而提高的現象。(FIFO 不是棧式算法)
文件系統
文件類型
文件至少包括兩部分:文件名字和文件內容。
- 字符特殊文件(UNIX):和輸入/輸出有關,用戶串行 I/O 類設備,如終端、打印機、網絡等。
- 塊特殊文件(UNIX):用於磁盤類設備。
- 目錄:管理文件系統結構的系統文件。是多級樹的結構。
- 普通文件:包含用戶信息的文件。
- ASCII 文件
- 由多行正文組成,以回車符、換行符或回車換行表示一行的結束。
- 最大的優勢是可以顯示和打印,還可以用任何文本編輯器進行編輯。
- 二進制文件
- 通常,二進制文件有一定的內部結構,使用該文件的程序才瞭解這種結構。
- 早期版本的 UNIX 的一個簡單的可執行二進制文件以魔數開始,表明該文件是一個可執行的文件。
- ASCII 文件
文件的實現
-
連續分配:把每個文件作爲一串連續數據塊存儲在磁盤上。
- 優勢
- 實現簡單:記錄每個文件只需記住第一塊的磁盤地址和文件的塊數。
- 讀寫性能好:單個操作就可以從磁盤上讀出整個文件。只需要一次需要(第一個塊),之後不再需要尋道和旋轉延遲。
- 不足:隨着時間的推移,磁盤會變得零碎。
- 適用於CD-ROM 這樣的文件系統,所有的文件大小都事先知道,而且在後續使用中不會再改變。
- 優勢
-
鏈表分配:爲每個文件構造磁盤塊鏈表。每個塊的第一個字作爲指向下一塊的指針,塊的其他部分存放數據。
- 可以充分利用每個磁盤塊。
- 儘管順序讀文件非常方便,但是隨機訪問卻相當緩慢。
- 由於指針佔用了一些字節,每個磁盤塊中存儲數據的字節數不再是2的整數次冪,降低了系統的運行效率。因爲許多程序以長度爲2的整數次冪來讀寫磁盤塊,所以要讀取一個完整的塊就需要從磁盤塊中獲得和拼接信息,這就因複製引發了額外的開銷。
-
採用內存中的表進行鏈表分配:取出每個磁盤塊的指針字,把它們放在內存的一個表中,就可以解決上述鏈表分配的兩個不足。
- 內存中這樣一個表格稱爲文件分配表(File Allocation Table,FAT)。
- 這種方法的主要缺點是必須把整個表都存放在內存中。
-
i 節點:給每個文件賦予一個 i 節點(index-node)的數據結構,其中列出了文件屬性和文件塊的磁盤地址。
- 相對於在內存中採用表的方式而言,這種機制具有很大的優勢,即只有在對應的文件打開時,其 i 節點纔在內存中。如果每個 i 節點佔有 n 個字節,最多允許 k 個文件同時打開,那麼只需在內存中提前保留 nk 個字節。
- 一個問題是,當一個文件中所含的磁盤塊數目超出了 i 節點所能容納的數目時,可以用兩個或更多包含額外磁盤塊地址的塊(一級間接塊),或指向其他存放地址的磁盤塊的磁盤塊(二級間接塊)。
- UNIX Inode:
目錄的實現
目錄中提供了查找文件磁盤塊所需要的信息:
- 整個文件的磁盤地址(連續分配方案)
- 第一個塊的編號(兩種鏈表分配方案)
- i 節點號
存放文件屬性:
- 把文件屬性直接存放在目錄項中。
- 文件屬性存放在 i 節點中而不是目錄項中。目錄項只有文件名和 i 節點號。
在目錄中處理長文件名的兩種方法:a)在行中;b)在堆中
目錄項的實現:
- 線性表
- 散列表:查找迅速,但是需要複雜的管理。
共享文件
- 硬鏈接:使用兩個文件名指向同一個內部數據結構來代表一個文件。
- 符號鏈接:是一類特殊的文件,這個文件包含了另一個文件的路徑名(絕對路徑或者相對路徑)。
- 在對符號文件進行讀或寫操作的時候,系統會自動把該操作轉換爲對源文件的操作,但刪除鏈接文件時,系統僅僅刪除鏈接文件,而不刪除源文件本身。符號鏈接的操作是透明的:對符號鏈接文件進行讀寫的程序會表現得直接對目標文件進行操作。某些需要特別處理符號鏈接的程序(如備份程序)可能會識別並直接對其進行操作。一個符號鏈接文件僅包含有一個文本字符串,其被操作系統解釋爲一條指向另一個文件或者目錄的路徑。它是一個獨立文件,其存在並不依賴於目標文件。如果刪除一個符號鏈接,它指向的目標文件不受影響。如果目標文件被移動、重命名或者刪除,任何指向它的符號鏈接仍然存在,但是它們將會指向一個不復存在的文件。這種情況被有時被稱爲被遺棄。
- 符號鏈接的優點在於它能夠跨越磁盤的界限,甚至可以命名在遠程計算機上的文件,不過符號鏈接的實現不如硬鏈接那樣有效率。
空閒塊管理
- 位圖
- 鏈表:每個空閒塊包含下一個空閒塊的指針
- 成組鏈表:每個空閒塊包含一些空閒塊號和一個指向下一個塊的指針
輸入/輸出
設備管理的功能
- 緩衝管理:爲達到緩解CPU和I/O設備速度不匹配的矛盾,達到提高CPU和I/O設備利用率,提高系統吞吐量的目的,許多操作系統通過設置緩衝區的辦法來實現。
- 設備分配:設備分配的基本任務是根據用戶的I/O請求,爲他們分配所需的設備。如果在I/O設備和CPU之間還存在設備控制器和通道,則還需爲分配出去的設備分配相應的控制器和通道。
- 設備處理:設備處理程序又稱設備驅動程序。其基本任務是實現CPU和設備控制器之間的通信。
- 設備獨立性和虛擬設備:用戶向系統申請和使用的設備與實際操作的設備無關
I/O 設備
I/O 設備大致可以分爲兩類:塊設備和字符設備。
塊設備把信息存儲在固定大小的塊中,每個塊有自己的地址。通常塊的大小在521字節至65536字節之間。所有傳輸以一個或多個完整的連續的塊爲單位。塊設備的基本特徵是每個塊都能獨立於其他塊而讀寫。硬盤、藍光光盤和 USB 盤是最常見的塊設備。
字符設備以字符爲單位發送或接收一個字符流,而不考慮任何塊結構。字符設備是不可尋址的,也沒有任何尋道操作。打印機、網絡接口、鼠標可看做字符設備。
設備控制器
I/O 設備一般由機械部件和電子部件兩部分組成。電子部件稱作設備控制器或適配器。
控制器的任務是把串行的位流轉換爲字節塊,並進行必要的錯誤校正工作。
內存映射 I/O
設備控制器中有控制寄存器和數據緩衝區用來與 CPU 進行通信。
- 單獨的 I/O 和內存空間
- 每個控制寄存器被分配一個 I/O 端口號,所有的 I/O 端口形成 I/O 端口空間,並且受到保護使得普通用戶程序不能對其進行訪問,只有操作系統可以訪問。
- 內存映射 I/O
- 將所有控制寄存器映射到內存空間中,每個控制寄存器被分配唯一的一個內存地址,並且不會有內存被分配這一地址。
- 在大多數系統中,分配給控制寄存器的地址位於或者靠近地址空間的頂端。
- 混合方案
內存映射 I/O 的優點:
- 對於內存映射 I/O,設備控制寄存器只是內存中的變量,在 C 語言中可以和任何其他變量一樣尋址,因此 I/O 設備驅動程序可以完全用 C 語言編寫。否則,就要用到某些彙編代碼。
- 對於內存映射 I/O,不需要特殊的保護機制來阻止用戶進程執行 I/O 操作,操作系統必須要做的全部事情只是避免把包含控制寄存器的那部分地址空間放入任何用戶的虛擬地址空間之中。
- 對於內存映射 I/O,可以引用內存的每一條指令也可以引用控制寄存器。
中斷
當一個 I/O 設備完成交給它的工作時,它就產生一箇中斷。
中斷信號導致 CPU 停止當前正在做的工作並且開始做其他的事情。地址線上的數字被用做指向一個稱爲中斷向量的表格的索引,以便讀取一個新的程序計數器。這一程序計數器指向相應的中斷服務過程的開始。一般情況下,陷阱和中斷從這一點上看使用相同的機制,並且常常共享相同的中斷向量。中斷向量的位置可以硬佈線到機器中,也可以在內存中的任何地方通過一個 CPU 寄存器(由操作系統裝載)指向其起點。
四種 I/O 訪問機制
- 程序控制 I/O
- 中斷驅動 I/O
- 使用 DMA 的 I/O
- 使用通道的 I/O
程序控制 I/O
I/O 可以採用三種根本上不同的方式來實現:程序控制 I/O、中斷驅動 I/O 和使用 DMA 的 I/O。
以打印一個字符串爲例:首先,數據被複制到內核空間。然後,操作系統進入一個密閉的循環,一次輸出一個字符。在輸出一個字符之後,CPU 要不斷地查詢設備以瞭解它是否就緒準備接收另一個字符。這一行爲通常稱爲輪詢或忙等待。
程序控制 I/O 十分簡單但是有缺點:直到全部 I/O 完成之前要佔用 CPU 的全部時間。
中斷驅動 I/O
這種允許 CPU 在等待打印機變爲就緒的同時做某些其他事情的方式就是使用中斷。
當打印字符串的系統調用發出時,字符串緩衝區被複制到內核空間,並且一旦打印機準備好接收一個字符時就將第一個字符複製到打印機中。這時,CPU 要調用調度程序,並且某個其他進程將運行,請求打印字符串的進程將被阻塞,直到整個字符串打印完。
當打印機將字符打印完並且準備好接收下一個字符時,它將產生一箇中斷。這一中斷將停止當前進程並且保存其狀態。然後,打印機中斷服務過程將運行。如果沒有更多的字符要打印,中斷處理程序就採取某個操作將用戶進程解除阻塞。否則,它將輸出下一個字符,應答中斷,並且返回到中斷之前正在運行的進程,該進程將從其停止的地方繼續運行。
使用 DMA 的 I/O
中斷驅動 I/O 的一個明顯缺點是中斷髮生在每個字符上。中斷要花費時間,所以這一方法將浪費一定數量的 CPU 時間。這一問題的一種解決方法是使用直接存儲器 DMA。
DMA 控制器能夠獨立於 CPU 而訪問系統總線。
讓 DMA 控制器一次給打印機提供一個字符,而不必打擾 CPU。本質上,DMA 是程序控制 I/O,只是由 DMA 控制器而不是主 CPU 做全部工作。這一策略需要特殊的硬件(DMA 控制器),但是使 CPU 獲得自由從而可以在 I/O 期間做其他工作。
DMA 的重大成功是將中斷的次數從打印每個字符一次減少到打印每個緩衝區一次。
I/O 系統的層次以及每一層的主要功能
I/O 請求 | 層次 | I/O 應答 | I/O 功能 |
---|---|---|---|
↓ | 用戶進程 | ↑ | 產生 I/O 請求;對 I/O 進行格式化;假脫機 |
↓ | 與設備無關的軟件 | ↑ | 命名、保護、分塊、緩衝、分配 |
↓ | 設備驅動程序 | ↑ | 設置設備寄存器;檢查狀態 |
↓ | 中斷處理程序 | ↑ | 當 I/O 完成時喚醒驅動程序 |
↓ | 硬件 | ↑ | 執行 I/O 操作 |
中斷處理程序
當中斷髮生時,中斷處理程序將做它必須要做的全部工作以便對中斷進行處理。然後,它可以將啓動中斷的驅動程序接觸阻塞。
設備驅動程序
每個連接到計算機上的 I/O 設備都需要某些設備特定的代碼來對其進行控制。這樣的代碼稱爲設備驅動程序,它一般由設備的製造商編寫並隨同設備一起交付。因爲每一個操作系統都需要自己的設備驅動程序,所以設備製造商通常要爲若干流行的操作系統提供驅動程序。
爲了訪問設備的硬件,意味着訪問設備控制器的寄存器,設備驅動程序通常必須是操作系統內核的一部分,至少對目前的體系結構是如此。
與設備無關的 I/O 軟件
雖然 I/O 軟件中有一些是設備特定的,但是其他部分 I/O 軟件都是與設備無關的。
與設備無關的軟件的基本功能是執行對所有設備公共的 I/O 功能,並且向用戶層軟件提供一個統一的接口。
與設備無關的 I/O 軟件的功能:
- 設備驅動程序的統一接口
- 設備驅動程序與操作系統其餘部分之間的接口。
- 如何給 I/O 設備命名,把符號化的設備名映射到適當的驅動程序上。
- 緩衝
- 緩衝可以提高應用的性能。
- 錯誤報告
- 錯誤在 I/O 上下文中比在其他上下文中要常見得多。當錯誤發生時,操作系統必須盡最大努力對它們進行處理。許多錯誤是設備特定的並且必須由適當的驅動程序來處理,但是錯誤處理的框架是設備無關的。
- 錯誤的類型:
- 編程錯誤
- 實際的 I/O 錯誤
- 分配與釋放專用設備
- 提供與設備無關的塊大小
- 不同的磁盤可能具有不用的扇區大小。應該由與設備無關的軟件來隱藏這一事實並且向高層提供一個統一的塊大小。
用戶空間的 I/O 軟件
儘管大部分 I/O 軟件都在操作系統內部,但是仍然有一小部分在用戶空間,包括與用戶程序連接在一起的庫,甚至完全運行於內核之外的程序。有一個重要的類別是假脫機系統,假脫機是多道程序設計系統中處理獨佔 I/O 設備的一種方法。
緩衝管理
- 無緩衝
- 用戶空間中的緩衝
- 內核空間中的緩衝接着複製到用戶空間
- 內核空間中的雙緩衝
磁盤臂調度算法
讀寫磁盤的時間由以下三個因素決定:
- 尋道時間:將磁盤臂移動到適當的柱面上所需的時間。
- 旋轉延遲:等待適當扇區旋轉到磁頭下所需的時間。
- 實際數據傳輸時間。
對大多數磁盤而言,尋道時間與另外兩個時間相比占主導地位,所以減少平均尋道時間可以充分改善系統性能。
許多磁盤驅動程序都維護着一張表,該表按柱面號索引。每一柱面的未完成的請求組成一個鏈表,鏈表頭存放在表的相應表目中。
- 先來先服務,FCFS
- 最短尋道優先,SSF
- 電梯算法
RAID
- Redundant Array of Inexpensive Disk(廉價磁盤冗餘陣列)
- Redundant Array of Independent Disk(獨立磁盤冗餘陣列)
改進磁盤的性能和可靠性。
將數據分佈在多個驅動器上稱爲劃分條帶。
- 0級 RAID:將連續的條帶以輪轉方式寫到全部驅動器上。
- 1級 RAID:複製了所有的磁盤。
虛擬化和雲
虛擬化的必要條件
虛擬機管理程序需要在以下三個維度上有良好的表現:
- 安全性:虛擬機管理程序應完全掌控虛擬資源。
- 保真性:程序在虛擬機上執行的行爲應與在裸機上相同。
- 高效性:虛擬機中運行的大部分代碼應不受虛擬機管理程序的干涉。
敏感指令:在內核態和用戶態執行的行爲不同。(進行 I/O 操作或修改 MMU 設置)
特權指令:在用戶態執行會導致陷入。
機器可虛擬化的一個必要條件是:敏感指令爲特權指令的子集。簡單來說,如果用戶態想要做不應該在用戶態做的事情,硬件必須陷入。
VT 技術的基本思想是創建可以運行虛擬機的容器。
第一類和第二類虛擬機管理程序
第一類虛擬機管理程序運行在裸機上;第二類虛擬機管理程序依賴於宿主操作系統的系統服務。
第一類虛擬機管理程序就像一個操作系統,因爲它是唯一一個運行在最高特權級的程序。它的工作是支持真實硬件的多個虛擬機拷貝,類似於不同操作系統支持的進程。
第二類虛擬機是一個依賴於 Windows、Linux 等操作系統分配和調度資源的程序,很像一個普通的進程。當然,第二類虛擬機管理程序仍僞裝成具有 CPU 和各種設備的完整計算機。
兩類虛擬機管理程序都必須以一種安全的方式執行機器指令。
運行在兩類虛擬機管理程序上的操作系統都稱作客戶操作系統。對於第二類虛擬機管理程序,運行在底層硬件上的操作系統稱作宿主操作系統。
- 第一類虛擬機管理程序:Xen、Hyper-V、vSphere
- 第二類虛擬機管理程序:VMare、Wine、Parallels
在不支持虛擬化的平臺上實現虛擬化
客戶機內核的敏感指令被替換爲對模擬這些指令的例程的調用。真實硬件不會直接執行客戶操作系統中的敏感指令。這些敏感指令被轉爲對虛擬機管理程序的調用,虛擬機管理程序模擬了這些指令的功能。
二進制翻譯:虛擬機管理程序在運行中改寫了部分代碼,將有問題的指令替換成了安全的指令序列,模擬原指令的功能。由於進行了改寫操作,因此可以替換掉不屬於特權指令的敏感指令。其他的指令可以直接執行。(例如,一條不安全的 I/O 指令會被替換成一個陷入操作,經過安全性檢查之後,執行等價的指令並返回結果)
雲
雲的五條必要特徵:
- 按需自助服務:無需人爲操作就能自動爲用戶提供資源。
- 普適的網絡訪問:所有資源都可以通過網絡用標準化的機制訪問,以支持各種異構設備。
- 資源池:雲提供商擁有的資源可以服務多個用戶並動態再分配,用戶通常不知道他們使用的資源的具體位置。
- 快速可伸縮:能根據用戶需求彈性甚至是自動地獲取和釋放資源。
- 服務可計量:雲提供商按服務類型計量用戶使用的資源。
雲即服務:雲的功能是提供一個用戶可以直接訪問並任意使用的虛擬機。因而,同一個雲中可能運行着不同的操作系統。這種雲稱作基礎設施即服務。
- 基礎設置即服務(IAAS)
- 平臺即服務(PAAS)
- 軟件即服務(SAAS)
- 網絡即服務(NAAS)