Uploaded by sherry225862

OS筆記

advertisement
作業系統概論
Chapter 1: Introduction
電腦系統架構
硬體
最基本的系統資源
CPU、記憶體、IO 裝置(螢幕、鍵盤、滑鼠...)
作業系統
協調硬體與軟體、使用者的互動
應用程式
決定系統資源如何被使用來解決使用者的問題
文字編輯器、編譯器、瀏覽器、資料庫系統、遊戲
使用者
人類
機器與其他電腦
什麼是作業系統?
在電腦硬體與使用者之間協調的程式
作業系統的目標:
執行使用者的程式解決使用者的問題
方便的用:使電腦系統方便使用,不必顧慮太多細節
有效率的用:能有效的使用電腦硬體資源
作業系統都做些甚麼? - 以使用者的觀點
方便易用:【ease of use】
不需要顧慮資源在硬體如何被運用(resource utilization)
多人工作環境(例如:mainframe、minicomputer)需要顧及所有使用者
其他多人環境
工作站(workstation):每位使用者皆有特定的資源
伺服器(server):常用的共用資源
手持裝置
資源較匱乏
作業系統必須要能夠最佳化資源使用率,包括電池續航力
某些電腦不太需要使用者介面
例如:汽車的嵌入式電腦(embedded computers)
作業系統的定義 - 以系統的觀點
作業系統為資源分配者【resource allocator】
管理所有資源
以效率/資源公平性為基礎,處理相互衝突的request
作業系統為控制程式【control program】
讓每支程式都能正常執行。避免拿不到資源or有程式惡意執行
作業系統並沒有統一的定義
電腦中永遠執行的那個程式稱為核心,又稱內核,【kernel】
其餘程式可為作業系統附帶的系統程式或者是其他應用程式
電腦系統組織
電腦系統運作
一個或多個CPU、裝置控制器(device controller)藉由共用的bus,來
存取共用的記憶體
裝置間都是共同執行的,增進效率
電腦啟動
當電源啟動時,【bootstrap program,又稱loader】被讀取並啟動
此類程式大多被放在ROM或EPROM等非揮發性(non-volatile,
斷電後資料仍保留) 儲存裝置,又稱為【firmware】
初始化系統的各個部分
讀取作業系統的kernel並開始執行
IO裝置與CPU可以同時執行
每一個device controller都掌控著某一個特殊的device(裝置)
每一個device controller都有自己的local buffer
所有資料都會先進到Buffer
CPU將資料從主記憶體搬進buffer或者從buffer中取出到主記憶體
IO則是從controller的local buffer搬到device進行
硬體的device controller利用【interrupt】告訴CPU:資料已經進來了,
好讓CPU知道已經有系統事件(event)發生
Interrupt是某一種硬體訊號,在bus上傳送
電腦系統組織:【Interrupt】
系統event的發生,通常都是由【interrupt】或【trap】所觸發
硬體:interrupt,通常透過bus來傳送
軟體:trap(可當作是軟體產生的interrupt),通常是因為錯誤
(error,例如除以0)或者是使用者要求(user request,例如system
call)
Interrupt發生後,會把CPU控制權從原本程式轉換到特定的interrupt
service routine
早期電腦:
固定跳到某一個通用的generic routine(通用的處理程序),然
後再進行判斷,最後在呼叫適當的處理程序
近來電腦:
使用【interrupt vector】
對於每一個device都有不同的routine,分別處理不同的
interrupt
Handler A
位址 XXX
(指令)
Handler B
(指令)
位址 YYY
從 table 的指標中
找出對應的
handler 指令位置
根據 interrupt 類
型,找到該有的
table 位置
Jump ZZZ
Jump YYY
Table
Jump ZZZ
Interrupt發生後,會把當前的指令位置儲存起來,待interrupt處理結束
後,再回到原本的指令執行
CPU可以視為有兩種執行模式:
處理應用程式
處理作業系統指令
通常,CPU會一直執行應用程式的指令。當有interrupt或trap發生
(亦即發生event),此時CPU控制權由應用程式改到OS,執行OS中
的某一些指令,並改變應用程式的狀態(例如blocked、ready),完
成後再跳回應用程式
電腦系統組織:【Storage Structure,儲存裝置結構】
主記憶體:
【Random access,隨機存取】
存取記憶體中任意位置所需要花的時間是一樣的
現在大部分的裝置都是隨機存取,而早期的磁帶機(tape)則是
sequential access
通常都是【volatile,揮發性】,斷電後資料會遺失
第二儲存裝置
提供non-volatile(非揮發性)的儲存能力
磁碟:Magnetic Disk
機械式,操作較慢
磁碟的表面可區分為tracks,又可在分為許多sectors
Disk controller決定了device與電腦間邏輯上的互動
track
固態硬碟
電子式,操作快
電腦系統組織:【Storage Hierarchy,儲存裝置架構】
Volatile
速度快的東西不能太多
怕拖慢存取速度
例如:register
通常CPU不會直接讀取記憶體的資料,會先把東西讀取到register,再
運算
上一層的都可以當作下一層的cache,cache只是一種概念
電腦系統組織:【Caching,快取】
極為重要的概念,在電腦的許多層面都有用到
包含硬體、作業系統、軟體
常用的資訊從較慢的儲存裝置暫時被複製到較快的儲存裝置
例如:從硬碟搬到主記憶體,因此主記憶體可以視為硬碟的cache
先檢查較快的儲存裝置(cache)中是否有想要的資料:
若有:則直接從cache中取用,速度快
若無:先將資料從較慢的裝置中複製到cache,然後再使用
Cache本身(例如主記憶體)的儲存空間大小通常都比要被cache的裝置
(例如硬碟)還要小
因此如何管理cache (cache management)很重要
Cache的大小以及replacement policy也很重要
Cache不能太大也不能太小
太小:放不下,需要常替換
太大:不必要的cost
多任務(multitasking)的環境
必須確保取用到的資料是最新的
亦即,無論該資料存在哪一個storage hierarchy,都要存取到最新
的
多核心(multiprocessor)的環境
必須注意cache的一致性 【cache coherency】
即,多個CPU各有cache,但卻共用主記憶體,導致主記憶體中的
某一筆資料會被讀取到各個cache中,當其中一個CPU cache資料遭
到修改時,其他CPU cache的內容就會和該被修改的內容不一致(也
就是其他CPU看到的值還是舊的)
必須想辦法維持一致
分散式(distributed)系統的環境
有不同台機器,同樣的資料會有多筆
如何保持資料一致性是重點(於第17章討論)
電腦系統架構
單處理器系統【Single Processor System】
大多數系統使用單一且多用途的處理器
single general-purpose processor
某些系統會擁有特殊需求的特製處理器
例如:顯示卡上的GPU,專門處理圖形運算
IO request
傳送或接收
裝置與記憶體直接溝
通的管道(不經 CPU)
多處理器系統【Multiprocessors systems】
此指單台機器擁有多個處理器
又稱parallel systems或tightly-coupled systems
【Increased throughput】
單位時間內可以完成的工作量增加,雖然不一定是線性成長
【Economy of scale】
一台三個core比起三台single core更便宜(某些資源可以共用,
例如顯示裝置)
【Increased reliability】
Fault tolerant
可容忍部分硬體錯誤
可以將無法運作core的task交給其他core
兩種類型
【Asymmetric Multiprocessing】非對稱式多核心
主從關係(master-slave relationship)
master決定哪些CPU做哪些事,統一控管
【Symmetric Multiprocessing】對稱式多核心
每個core行為都一樣、自己做自己的
多台機器系統【Clustered Systems】
類似於多處理器系統,但這次為使用多台機器而非單一機器
通常藉由【storage-area network (SAN)】來共用儲存裝置
補充:SAN與NAS(Network Attached Storage,網路儲存設備,
例如NFS、Samba)架構上有些許不同
SAN
應用程式→檔案系統→網路→儲存裝置
NAS
應用程式→網路→檔案系統→儲存裝置
可靠性更高【high-availability】
即使有一台機器毀損,整個系統仍能運作
【Asymmetric clustering】
非對稱式多機器系統
不是每台機器都一直在工作
存在某些stand-by的備用機器,當有運作中機器毀損時,備用
機器可以立即取代該舊機器角色(hot-standby)
【Symmetric clustering】
對稱式多機器系統
每台機器都在工做
每台電腦都可以自己處理request
機器間互相監控
作業系統結構
【Multiprogramming】
高效率
作業系統負責分配【job】 (由code與data組成),讓CPU一直有事
情做
將所有job中的一部份子集合取出,並放在記憶體內同時等待CPU
因此並非所有job都在記憶體
當某一個程序的狀態變成wait時(例如等待IO,不再需要CPU),作
業系統會自動換下一個job給CPU,稱為【job scheduling】
此方式由於會一直讓某程序占用CPU直到該job進入wait,如果該
job意外進入無窮迴圈等卡死的情況,會讓整個系統卡死
Multiprogrammed System的記憶體layout
【Timesharing (multitasking)】
系統執行多個程式,但CPU隨時切換處理各個程式
並非一直處理同一個程式直到wait
執行某一個程式一段時間後,就先去執行其他程式(即使都還
不必wait),目的是讓大家都有被執行
因此即使有某程式卡死,其他的仍會被執行
每一個程式都能在1秒內再次被執行(Response time小於1秒)
Process來源
使用者在記憶體內執行的program
【CPU scheduling】
有多個job準備同時執行時
【Virtual Memory】虛擬記憶體
由於主機同時執行多個程式,可能無法把所有程式放進記憶
體,因此需要swap(第二儲存裝置)(屬於某種Virtual Memory)
Process的一部分在記憶體,另一部分則是在硬碟
作業系統的運作
硬體:發出Interrupt
軟體:發出exception或trap
【Dual-mode】雙重模式
讓作業系統保護自己以及其他系統元件
User mode及Kernel mode
【User mode】
執行普通程式時
【Kernel mode】
執行作業系統、system call
設計目的:限制某些指令執行/某些資源(例如記憶體位置)的存
取,只有在kernel mode時才能存取
避免惡意應用程式攻擊系統(避免程式修改記憶體的任何位置)
【Mode bit】
由硬體提供
區別系統是在執行user code還是kernel code
【privileged instruction】
需要授權的指令
只能在kernel mode執行
通常是重要或可能造成危險的指令
例如:修改mode bit本身的指令
發生system call/interrupt/trap時,系統會把mode改為kernel
mode,執行結束return時,把mode改回user mode
程序管理(process management)
Process:為正在執行中的program,有自己的code和data,是系統工作
的單位
Program是passive entity(被動),process是active entity(主動)
Process需要系統資源來完成其任務
CPU、記憶體、IO、檔案
初始化用的資料(Initialization data)
當Process結束後,要把資源歸還給系統
Program counter
用來指示當前執行到哪一個指令
單執行緒(Single-threaded)程式
一次只執行程式中的一個指令
擁有一個program counter
多執行緒(Multi-threaded)程式
同時間多個指令
每一條執行緒(thread)都有一個program counter
通常系統會有多個程式/多個使用者在使用一個或多個CPU
在多個process/thread之間利用多工(multiplexing)的方式同時執行
Process Management Activities
作業系統必須處理許多和process有關的問題
建立/刪除使用者或系統的process
暫停(suspend)/復原(resume)某個process
提供可以讓process同步的手段
例如某一個process要等待另一個程式,另一個程式也要有通
知原來程式的機制
提供讓process間互相溝通
pass message:作業系統幫忙傳遞某訊息
shared memory:直接透過一塊共用的記憶體
提供deadlock處理的機制
記憶體管理
資料/指令都必須要存在記憶體才可以執行
記憶體管理行為:
需要追蹤記憶體的每一個部分正在被誰使用
由於無法把整個程式都放記憶體,因次要追蹤哪些正在用
決定哪些process或某process的某些部份需要從記憶體中移除並放
進硬碟(例如放進swap磁區)
因為記憶體空間可能不足,因此將不使用的放進硬碟
分配/釋放記憶體給process
儲存空間管理
作業系統提供統一且有邏輯的存放方式:【檔案 file】
應用程式只需要使用檔案作為儲存單位(只要管檔名就好)
不必知道硬碟的track/sector等硬體性質
不同的存放媒體各有自己的controller控制
磁帶、硬碟
不同的存放裝置其特性亦不同(存取速度、大小、random
access或sequential access)
檔案系統管理
檔案通常被組織在資料夾中
檔案系統要管理存取控制
誰可以存取哪些東西
作業系統行為包括:
建立/刪除/管理/操作檔案與資料夾
將檔案map到儲存裝置(secondary storage)
將檔案備份到其他非揮發性的儲存裝置
Mass-Storage Management 主要儲存裝置管理
通常使用硬碟來儲存需要長期存放的資料
整體電腦的速度與硬碟系統以及其演算法有很大的相關性
作業系統行為:
可用空間(free-space)管理
儲存空間分配(storage allocation)
硬碟排程:Disk Scheduling
因為硬碟是旋轉讀取的關係,要安排最好的旋轉方式
部分儲存裝置並不需要很快的速度
第三儲存裝置(Tertiary Storage)
光碟(optical storage)
磁帶(magnetic tape)
仍需要由作業系統或其他應用程式來管理
類型介於WORM和RW之間
WORM:write-one,read-many-time
RW:read-write
I/O 子系統(subsystem)
作業系統的其中一個目的就是隱藏硬體的特性
IO子系統負責:
管理
IO buffer的記憶體
把要給IO的東西暫時存放
Caching
IO將資料放進cache以便快速存取
Spooling
將Job間的輸入與輸入時間重疊
提供統一的device-driver介面
以便應用程式可以使用統一的API
Computing Environment 計算環境
傳統
Stand-alone的多用途電腦
不與其他電腦交流的電腦
網路連結 (Internet)
【Portal】
某些公司用來讓網頁可以存取內部系統的機制
【Network computers(thin client)】
類似web terminal
移動裝置藉由無線網路(wireless)連結
網路普及,許多系統都使用防火牆(firewall)來阻擋攻擊
移動裝置
智慧手機、平板
提供額外的作業系統功能
GPS、陀螺儀
擴增實境(augmented reality)
使用無線網路或者行動網路連接
分散式(Distributed)計算環境
將不同的電腦/裝置集合起來
透過網路連結,常用TCP/IP
網路作業系統 (Network Operating System)
有良好的網路溝通方式
可以提供類似單一系統的特性
Client-Server Computing
過去:終端裝置是笨拙的
現在:終端裝置是聰明的(例如PC)
Server:
用來回應來自client(客戶)的需求
【Compute-server system】
提供某些介面,提供client連線及服務
例如database
【File-server system】
提供介面讓client可以存放或取回檔案
Peer-to-peer (P2P)
另外一種分散式系統的模型
P2P沒有明顯的client和server區別
所有的node都被視為peer
亦是client,也是server
Node必須加入某一個P2P network
兩種方式:
與中央控管的機器註冊
Discovery protocol
發送廣播封包要求服務
收到回應後,自己也加入該service圈中
自己也成為server,接受來自其他人的request
例如:Skype
虛擬化:Virtualization
一台機器上執行多個OS
方便於不同平台的開發以及測試
雲端計算:cloud computing
將儲存、計算、應用程式作為網路上的一種服務
混合傳統作業系統與虛擬化技術
有許多類型:
Public cloud
只要願意付錢就可以使用
Private cloud
某個公司私下使用的
Hybrid cloud
擁有以上兩種類型的元素
SaaS
Software as a Service
雲端提供應用程式作為服務,例如文字處理器
PaaS
Platform as a Service
提供平台(伺服器)、software stack
例如:資料庫系統
IaaS
Infrastructure as a Service
提供資訊設備
例如提供某一台Server電腦(甚至可以自訂硬體規格)
或者是儲存空間可供使用
如何應用全由使用者自己決定
例如:雲端備份
連線能力與安全問題更加重要
Load balancer
在多個應用程式之間將負擔分散到各個機器
降低某一台機器的負擔
Real-Time Embedded System
最大宗的電腦系統,出現於相機/手機/汽車
嵌入式系統
擁有多種類型:
廣泛用途系統、特製用途系統、有限目的的作業系統、realtime作業系統
某些裝置可能不必使用作業系統就可以完成任務
Real-time 作業系統擁有完善定義的執行時間時間限制(fixed time
constraints)
所有的process【必須】在滿足該時間限制的情況下完成
只有在執行時間限制滿足時,操作才算正確
亦即,某些應用程式很要求時間限制(例如汽車控制系統等),
real-time作業系統在執行這些相關程式的時候,必須滿足其
高速運算的需求
作業系統概論
Chapter 2: System Structures
作業系統的服務
提供可執行程式的環境以及服務給程式和使用者
提供對使用者有幫助的服務
使用者介面【User Interface,UI】
Command-Line Interface(CLI)
使用指令輸入(例如 Windows 的 cmd)
Graphics User Interface(GUI)
圖形使用者介面(例如桌面/圖示等等)
程式執行【Program execution】
能夠將程式讀進記憶體並執行(無論正常啟動/不正常啟動)
能夠中止執行中的程式
I/O 操作【I/O Operation】
提供程式存取 IO 硬體的方式
檔案系統操作【File-system manipulation】
檔案的存取/搜尋/建立/刪除/權限管理等等
Process 間的溝通【Communications】
提供某種機制,讓不同的 process 間可以互相溝通
常見兩種方式:
shared memory
message passing
由 OS 替 process 傳送資料封包
如果是透過網路,則 OS 可以把底層網路隱藏,讓上層
process 看不到
錯誤偵測【Error detection】
OS 要能夠接收並處理各個硬體發生的可能錯誤,並採取適當
的動作
提供 debug 的工具,例如當程式當機的時候,OS 可以幫助存
取當前的程式資料(進行 dump,例如 stack、data、status 等
等)
提供與系統資源相關的服務,確保系統運作效率
資源分配【Resource allocation】
管理/分配系統資源
提供 API 得以存取資源
資源紀錄【Accounting】
紀錄/追蹤使用者使用資源的情形(例如用於收費)
保護與安全【Protection and Security】
保護:Protection
電腦內部的控制
控制某個應用程式可以存取哪些資源(例如控制檔案/目
錄存取)
避免存取/更動到不該更改的東西
安全:Security
保護來自外部的攻擊/存取
例如:使用者認證
作業系統服務示意圖
作業系統的使用者介面(UI)
Command-Line Interface(CLI)
擁有 command interpreter
允許使用者直接輸入指令
CLI 介面可能直接建立在 kernel 中,但大多都是利用一個額外程式
(即 shell)
指令來源:
Kernel 本身內建指令
如果要新增程式,就要更動 shell 等相關程式
某一支程式的名稱
新增程式時不需要動到 shell 或 kernel,直接寫新程式就
好
Graphics User Interface(GUI)
友善的桌面環境(desktop)
利用圖示【Icon】代表檔案/程式/行動等等
利用拖曳/點擊等等來操作
資料夾【Folder】:目錄【Directory】的另一個稱呼
大部分的系統通常同時提供 CLI 與 GUI
System Calls
是 OS 提供給程式的介面(相對於 UI 是 OS 給使用者的介面)
通常由高階語言寫成(C/C++)
但程式通常也不會直接呼叫 system call,而是使用高階的 API
API:Application Program Interface
比 system call 更高階一層
因為 system call 可能太過瑣碎,執行某一個動作需要太多 system
call,故使用 API
常見的 API:
Windows:Win32API
POSIX-based System:POSIX API
POSIX:Portable Operating System Interface
由 IEEE 定義,希望各個 OS 能夠通用,讓程式可以更容
易跨平台
JVM(Java virtual machine):Java API
不同系統底層 system call 不同,因此可以利用統一的 API 來讓程
式看到單一的 API 介面,而非直接看到系統的 system call,讓程式
更容易移植
System Call 實作 【System Call Implementation】
通常每一個 system call 都有自己的編號
System-call interface (即 API)
管理一個 table,並記錄著相關編號的索引(index)
作為仲介角色,替 process 呼叫 system call,並把執行結果 return
給 process
呼叫者並不知道 system call 到底是怎麼被實作的
只需要遵守 API 規則且了解 OS 會做些甚麼就好
API 會將大部分的 OS interface 隱藏,不讓程式設計師看到
API、System Call、作業系統間的關係
例子:Standard C Library
System Call 參數傳遞 (Parameter Passing)
System Call 可能需要更多的資訊,來執行某些動作
例如:寫入檔案的內容是甚麼、顯示到螢幕的東西是哪些
三種傳遞參數給作業系統的方式:
最簡單的方式:直接放在 Register
直接把參數放在特定的 register 中,讓 system call 直接去看
register
通常 register 都太少,不能夠傳遞太多訊息
將參數放在記憶體的某一塊區域中
稱為 block 或 table
利用 register 將該記憶體的位址傳給作業系統
使用 stack
將參數 push 到程式中的某一塊記憶體中
作業系統藉由 pop 出該 stack 裡的東西,來取得參數
一般來說都使用第二種和第三種,因為可以傳遞的資料量較大
System call 的種類
Process Control:程序控制
結束程序(end,正常結束);中止程序(abort,不正常結束)
讀取(load)/執行(execute)程式
建立(create)/中止(terminate)程序(process)
取得/設定 process 的屬性,例如執行優先權(例如 niceness)
讓 process 等待一段時間(例如 sleep)
等待 event、對 event 發出 signal
註:event 可以是系統發生的事件,例如使用者對鍵盤輸入
分配/釋放記憶體
Dump Memory:發生錯誤時,把 process 的 register、stack 等狀態
存到檔案中,以便除錯
提供除錯器(Debugger)、單步執行(Single Step Execution)
提供 Lock 來控制共享資料的存取
不讓某一筆資料被兩個 process 存取而衝突(至少有一個人要
寫入的時候)
File Management:檔案管理
建立(create)/刪除(delete)檔案
打開(open)/關閉(close)檔案
檔案讀取(read)/寫入(write)/重新定位(reposition)
重新定位(reposition):重新設定當前讀到的 bytes,例如
移動到最前/最後面等等
例如:C 語言的 Seek
取得/設定檔案的屬性
例如:存取模式(例如僅有擁有者能讀取等等,unix 的
chmod)
Device Management:裝置管理(例如:硬碟、USB 等等)
操作類似於檔案處理(例如 Unix 把 Device 視為檔案來管理)
要求(request)裝置:需要使用的時候
釋放(release)裝置:用完的時候
讀取/寫入/重新定位
取得/設定裝置的屬性
Logically attach(附加) or detach(移除)裝置
Information Maintenance:資訊維護
取得/設定系統時間
取得/設定系統資料
例如:系統資源的使用情形
取得/設定 process/檔案/裝置的屬性
Protection:保護
控制資源的存取
取得/設定權限(permission)
允許(allow)或拒絕(deny)使用的存取
Communication:溝通
建立/刪除溝通用的連線 communication connection (或稱溝通用的
通道:communication channel)
傳送(Send)/接收(Receive)訊息
Message Passing Model
由 OS 來交換訊息
先把訊息傳給 OS 核心,OS 再傳給目標
當資料量小時很有用
但如果資料太大,由 OS 來傳遞算是浪費時間
因為還要複製到 kernel 然後再複製出去
比 Shared-Memory 容易實作
Shared-Memory Model
利用存取某一塊共享的記憶體空間來交換資料
快速且方便
較難實作
在共享的空間中,需要有保護(protection)、同步
(synchronization)的機制,也要處理不同 process 間 cache
資料的新舊問題
例如 lock 機制,避免兩個 process 都在寫入
傳遞狀態資訊(status information)
附加(attach)/移除(detach)遠端裝置(remote device)
作業系統範例:MS-DOS
Single-tasking
同一時間,一次只能執行一個程式
當系統啟動(booted)後,執行 shell
執行程式的方式很簡單
沒有產生新的 process
直接把 shell 的空間用掉,變成 process
只有單一個記憶體空間
讀取程式到記憶體執行時,為了讓程式有更多的記憶體,會覆蓋
掉一部分 interpreter(即 shell)的記憶體
程式結束後,重新載入 shell
作業系統範例:FreeBSD
FreeBSD 為 Unix 作業系統的變形
Multitasking
可以同時執行多個程式
Shell 和其他 process 可以共存
當使用者登入(login)後,會執行使用者所選擇的 shell
當要執行程式時
Shell 使用 fork()函數,在記憶體中產生一個和自己一模一樣的
shell
新產生的 shell 使用 exec()函數,將程式讀取到自己的記憶體並執
行(蓋掉那一個新產生的 shell)
原本的 shell 有兩種行為:
若該 process 不是背景執行,則 shell 會等待該 process 中止
後,繼續讓使用者輸入指令
若該 process 是背景執行,則 shell 不會等待而是繼續讓使用
者輸入指定
Process 中止後,回傳 0 代表正常,或者回傳大於 0 的 error code
System Programs:系統程式
系統程式提供了 process 方便的開發與執行環境
大部分使用者看到的 OS 操作,都是由系統程式所提供的,並非真實
的 system call
系統程式中有些只是用來呼叫 system call 的簡單介面,有些則是更加
複雜的程式
【File Management:檔案管理】
建立、刪除、複製、重新命名、列印檔案內容(print)、傾印
(dump)、列出目錄內容(list)
其他泛用的檔案或資料夾操作
【Status Information:狀態資訊】
取得系統資訊:日期、時間、可用記憶體、硬碟空間、使用者數
量等
提供詳細的效能(performance)、紀錄(logging)、除錯(debugging)資
訊
某些作業系統(例如 Windows)使用【註冊表(Registry)】來存放/取
得設定資訊(configuration information)
【File Modification:檔案修改】
文字編輯器(text editor):建立/修改檔案
搜尋檔案內容的指令、文字轉換指令(transformation of the text,
例如 Unix 的 awk 與 sed)
【Programming Language Support:程式語言相關支援】
提供:編譯器(Compiler)、組譯器(Assembler)、除錯器
(Debugger)、直譯器(Interpreter)
【Program loading and execution:載入與執行程式】
把程式讀取到記憶體並分配資源讓其執行
absolute loader、relocatable loader、linkage editors
提供除錯的功能
【Communication:溝通】
使用者可以傳訊息到其他人的畫面(screen)、瀏覽網頁、傳送
email、遠端登入電腦、在電腦間傳輸檔案等等
【Background Service:背景服務】
在系統啟動時(boot time)執行
有些程式在開機執行之後就中止
有些則常駐在系統中直到關機
提供磁碟檢查(disk checking)、程序排程(process scheduling)、錯誤
紀錄(error logging)、列印(printing)等服務
在 user context 中執行,而非 kernel context
通常稱之為 Service、Subsystem、Daemon
作業系統設計(Design)與實作(Implementation)
並沒有一個標準的設計與實作方式
但有一些技巧是被證實是成功的設計/實作方式
隨著作業系統不同,內部結構也有很大的差異
受到硬體以及系統類別的影響
最重要的:定義這個 OS 的最主要目的是甚麼?
User Goal 和 System Goal:以使用者/系統的觀點來看
User Goals:使用者的觀點
OS 必須要能夠方便使用、易於學習
OS 要可靠(reliable)、安全(safe)、快速(fast)
System Goals:系統的觀點
OS 要易於設計、實作與維護
要有彈性(flexible)、可靠性(reliable)、無錯誤(error-free)、有
效率(efficient)
重要的原則
要區分以下兩個東西
Policy:What to be done? 需要做甚麼事情?
Mechanism:How to do it? 要用甚麼方法?
必須要把這兩者分開,不要綁死,例如:
policy 希望系統 IO 優先高,於是在硬體直接寫死 IO 設定
事實上應該使用 configure(設定檔)或者傳參數的方式,加上
general 的系統(有彈性(flexible)的作業系統),避免 policy 改變
時造成麻煩
如何設計 OS 是個極需創意的軟體工程(software engineering)
作業系統的實作(Implementation)
演進
早期的作業系統是用組合語言編寫
後來改用 Algol、PL/1 等語言
現在使用 C/C++
事實上是各種語言的混合
最底層(lowest level)的功能用組合語言
系統主體使用 C
System Program 則是使用 C/C++或者是 Scripting Language,例如
Perl、Python、Shell Script 等
越高階(high-level)的語言所撰寫的程式越容易攜帶(port)到其他硬體平
台上(使用不同 CPU、Device)
但越高階語言所寫的程式執行效能會較差
多用途(General-purpose)的作業系統是個大型程式,有很多不同的結構
組成方式
作業系統的結構(Structure)
簡單結構:Simple Structure (Monolithic kernel,單核心、整合式核心)
舉例:MS-DOS
用最少的空間提供最大的功能
沒有清楚區分為許多模組(module),整個系統是一體的
雖然 MS-DOS 有某種程度上的結構,但功能性上仍沒有良好
的切分
舉例:UNIX
受限於硬體,原始的 UNIX 系統仍沒有良好的結構性
UNIX 的作業系統主要由兩個部分組成:
System Program:系統程式
Kernel:核心
但 kernel 仍沒有區分的很明顯,意即沒有細分
module
分層架構:Layered Approach
OS 被切割為許多層(layer、level)
每一層都是建立在低一級的層之上(lower layer)
每一層只能使用低一層 layer 的資源來呼叫,不能跨層
最底層(layer 0):硬體
最高層(layer N):使用者介面
效率較差,因為需要一層一層的呼叫
微核心架構:Microkernel
Kernel 越小越好,只保留最重要的功能
將大部分的功能移動到 user space,變成 user program
例如:File System、Network
例子:Mach
User module 之間的 communication 的方式採用 Message Passing
好處:
Microkernel 容易延伸(extend),因為比較不複雜
容易將 OS 移植到新的硬體架構
因為與硬體有關的 kernel 大小變小了,需要改的地方也
變少
更加穩定可靠(reliable)、更加安全(secure)
以 kernel mode 執行的程式碼變少了
Kernel 被更動的機率較小,較安全、可靠
缺點:
User space 與 kernel space 之間的 Communication 會造成
overhead
模組化架構:Modules
現在多數的作業系統都有實作可載入的核心模組,
即【loadable kernel module】
使用物件導向(object-oriented)的概念
每一個 module 的核心(core)元件都是分離的
Module 之間藉由良好定義的介面(interface)來溝通
當有需要的時候,OS 可以將之讀取進 kernel
整體來說,類似於 Layered Approach,但更加有彈性
例如:Linux、Solaris
混合式系統:Hybrid System
現在多數系統並沒有採用單純的一種 model
混合式系統採用了多種增進效能、安全、使用姓的方法
Linux 和 Solaris 的 kernel 是在 kernel address space 中,所以算
是 monolithic(整個寫在一起,較無分層)
但是加上了動態載入的模組化功能
Windows 則是主要是 monolithic,加上不同的 microkernel
subsystem personalities(個人化設置)
作業系統除錯(Debugging)
尋找並修正錯誤
OS 會產生記錄檔(log files)來記錄錯誤相關資訊
當應用程式失敗(Failure)時,會產生 core dump(核心傾印)檔案,該檔案
紀錄著失敗時,該 process 的記憶體狀態
作業系統失敗時,會產生 crash dump,包含了 kernel 相關的記憶體
log file 不只可以找出錯誤,也可以拿來增進系統效能
Trace listing:
紀錄系統活動
Profilling:
週期性取樣指令的指標(instruction pointer),可以了解系統花
比較多的時間在執行哪個部分的指令
DTrace
幫助了解系統程式的行為,例如何時被執行 or 被執行的時候
stack 狀態/參數等等
產生作業系統 (Operating System Generation)
由於 OS 必須要能夠在各種機器上執行,所以必須要能夠讓使用者根
據需求/硬體來產生 OS
SYSGEN 程式
能夠產生比 general kernel 更好效能的 OS
系統啟動:System Boot
當電源初始化系統後,指令執行會從一塊固定的記憶體開始
Firmware 使用 ROM 來存放最初(initial)的 boot code
作業系統必須要能夠讓硬體存取並使用,才能夠讓硬體去啟動它
使用小區塊程式碼:Bootstrap loader
儲存在 ROM 或 EEPROM 之中
用於鎖定 kernel 的位置,讀取之進記憶體並執行之
有時候採用兩步【Two-Step process】的方式
Boot block 固定在某個位置,由 ROM code 來載入
之後 boot block 再去硬碟中把 bootstrap loader 載入到記憶體
Bootstrap loader 再去進行後續的 kernel 載入動作
GRUB
為一常見的 bootstrap loader
允許從多個硬碟選取不同的 kernel 來執行
當 Kernel 被載入後,系統便開始運作(running)
作業系統概論
Chapter 3: Process Concept
Process Concept
OS 會執行許多程式,稱為 process、job 或 task
Process:
正在執行中的 program 稱為 process
在記憶體中程式碼的部分(program code)
又稱為 text section
擁有 program counter 與 processor register
Stack:
用於存放暫時的資料
例如:
函式的參數、return address、區域變數
Data section:
存放全域變數(global variables)
Heap:程式執行時期動態分配的記憶體
Program 是被動(passive)存放在硬碟中(executable file,可執行檔)
Process 則是主動的(active),存在於記憶體中
一個 program 可能有許多個 process
例如許多使用者執行同一個 program,而產生許多 process
Process State:Process 的狀態
當 process 正在執行時,狀態(state)會改變
New:程式剛被執行時(Process 剛被建立時)
此時等待系統資源允許(例如 process 數量限制等等)
如果沒有特別限制,通常 new 會馬上變成 ready
Ready:Process 已經準備好了,正在等待可用的 CPU 來處理它
Running:Process 的指令正在被執行
Waiting:Process 正在等待某些 event 發生,而非只有 CPU 而已
例如:等待 IO
相較於 Ready 只缺 CPU 而已了
Terminated:程式的執行已經結束
Process Control Block (PCB)
又稱為 task control block (TCB)
用來記錄與 process 有關的資訊
Process state:程式的狀態
Program counter:紀錄下一個要執行的指令的位置
CPU register:紀錄之前在 CPU 中執行時,此 Process 使用的
Register 的狀態
Context switch
換別的 process 執行時,原本 process 在 CPU 的 register 和
program counter 等等資訊需要轉存出來,存在 PCB,以便下
次回到 CPU 執行時可以使用
CPU Scheduling information:CPU 排程資訊
例如:優先權(priority)、scheduling queue pointer(排程佇列的
指標)
Memory-management information:記憶體管理資訊
紀錄此程式是在記憶體中的哪個位置
Accounting information:統計資訊
CPU 使用量、使用的 CPU clock time、自從執行後過了多少時
間、時間限制(time limit)
這些紀錄可以方便進行 scheduling,例如:避免 starvation(某
個程式很久沒被執行)
I/O status information:IO 狀態資訊
已經分配給此 process 的 I/O 裝置
已開啟的檔案清單
CPU 在 process 之間切換(switch)
Theads:執行緒
在此之前討論的 process 都是單一執行緒的執行
多執行緒:multi-thread
一個 process 擁有多個 program counter
可以一次執行多個位置的指令
PCB 必須要儲存每一個 thread 的細節以及各自的 program counter
將在下一個章節詳述
Process Scheduling:Process 排程
最大化 CPU 使用量
迅速的切換 process 給 CPU 執行:Time sharing(每個 process 都被 CPU
執行一下子之後換別的 process 執行)
Process Scheduler:從可用的 Process 中選擇下一個要給 CPU 執行的
process
維護 Scheduling queues of process (process 排程佇列)
【Job queue】:系統中所有 process 的集合
【Ready queue】:所有在主記憶體中且已經等待 CPU 執行的
process 集合
【Device queue】:正在等待 IO 裝置的 process 集合
Process 會在這些 queue 之間移動
Queuing diagram:表示 queue、資源與流程
Time Slice Expire:
OS 規定每一個 process 只能執行一段時間(避免其他 process 沒得
執行),時間用完就必須停止執行並回到 ready
Schedulers:排程器
Long-term scheduler
又稱為 job scheduler
根據系統最大能夠執行多少 process 來決定誰可以到 ready state
控制 degree of multiprogramming (multiprogramming 的程度)
控制同時有多少 process 一起被執行
比較不常被呼叫,時間單位通常是秒鐘、分鐘
用於加強 Process Mix
Short-term scheduler
又稱為 CPU scheduler
決定哪些 process 接下來要被執行,然後分配 CPU
OS 一定會擁有此 scheduler
常常被呼叫,因此反應必須要很快,時間等級是
millisecond(1/1000 秒)
有些系統可能沒有限制最大 process 數量,就只會有 short-term
scheduler
Process 可以被區分為:
I/O-bound process
花大量時間在 I/O
多次短時間的 CPU 運算(burst)
並非一直需要 CPU
CPU-bound process
花大量時間在 CPU
次數少但是長時間的 CPU 運算(burst)
Medium Term Scheduling
若想增加 degree of multiple programming,可增加此種 scheduler
執行 Swapping
把記憶體中的 process 移到硬碟存放(不執行)
把硬碟中需要繼續執行的程式拿回記憶體
移動式裝置中的 Multitasking (Multitasking in Mobile Systems)
某些早期的系統受限於資源
同一時間只允許一個 process 執行
其他 process 是暫停狀態(suspended)
現行的系統:受限於使用者介面(觸控螢幕)只能給一個 process 使用
單一個前景(foreground) process
使用了使用者介面
受到使用者介面控制、與使用者互動
多個背景(background) processes
仍存在於記憶體中
process 仍運行中(running),但受到限制
限制包含:
短期工作(不會浪費太多時間的 process)
更新某些資訊(更新股市/天氣等等)
收發通知或 mail 等的 process
某些特別設定的長期工作
例如音樂播放
Andriod 系統的背景 process 限制較少
背景 process 使用 service 來完成任務
即使背景 process 被停整,系統中的 service 仍持續運行
沒有使用者介面,占用少量記憶體
Context Switch
Context:Process 執行的狀態,其內容會用 PCB 紀錄
Context-switch
CPU 切換執行其他 process
將當前 process 的狀態/資訊等等儲存起來
將要執行的那個 process 的 context 讀取回來
Context-switch 消耗時間造成的 overhead
context-switch 沒有任何效能上的增益(亦即,常使用會造成
overhead)
越複雜的 OS,其 PCB 越複雜,會讓 context-switch 的時間變長
Context-switch 所花的時間會根據硬體支援而不同
多組 Register 的 CPU
每一組 Register 可以給一個 process 使用
同一時間可以讀取多組 context 進 CPU
切換 process 時,CPU 只要切換 Register 組就可,不必重新把 PCB
從記憶體讀取回 CPU
Process Creation:Process 的建立
Parent Process(父程序) 與 Child Process(子程序)
父程序會產生子程序,之後會繼續產生其他程序,形成程序樹狀
結構(tree of processes)
通常每一個 process 都有一個獨立的識別 ID,稱為 PID (process
identifier)
父程序與子程序之間資源分享(Resource sharing)的方式
分享全部資源
子程序分享父程序的一部分資源
不分享任何資源
父程序與子程序之間執行(Execution)的方式
父程序與子程序兩者的指令都同時在執行(concurrently)
父程序暫停執行,等待(wait)子程序的結束(terminate),之後才繼
續執行父程序
Tree of Process
記憶體空間(Address Space)處理
子程序複製一份父程序的記憶體
例如:UNIX 的 fork()指令
子程序將硬碟中某一支程式讀取到自己的記憶體中,覆蓋掉自己
原本程式的記憶體
例如:UNIX 的 exec()指令
UNIX 範例:
wait 也是 system call 的一種等待子程序結束(exit)
C 語言程式使用 fork 與 wait 的範例:
Process Termination:Process 的終止
當 Process 執行完自己最後一個指令後,會要求 OS 將之刪除(exit())
子程序將某些資料回傳給自己的父程序
父程序利用 wait()指令等待子程序終止
子程序要終止時,父程序中必須要清除部分與子程序相關的資
源,例如父程序使用了 PID Table 記錄了子程序的 PID,在該子程
序結束後,要清除該 Table 的 entry
其他大部分的資源則由 OS 來釋放
父程序在某些情況下,可以中止子程序的執行
使用 abort() : 用於不正常結束時,父程序中止子程序
比較:exit() :子程序自己呼叫來正常結束自己
可能發生的情況:
子程序使用超出預定的資源(例如記憶體過量)
指派給子程序的任務不再需要(也就是子程序接下來的執行都
是白跑了,與其等到子程序慢慢跑到 exit,不如用父程序直
接 abort 掉子程序,節省資源)
父程序自己正在結束
由於某些 OS 不允許子程序在父程序中止後還繼續執行
故在父程序中止時,必須要強制中止其所有子程序
Cascading Termination:連帶性的終止
如果某個子程序在 exit 時沒有正在等待的父程序
該子程序稱為 Zombie(殭屍)
發生原因:子程序執行到 exit 的時候,父程序可能還沒執行
到 wait
當父程序執行到 wait 時,zombie 狀態才結束
如果某個子程序沒有自己的父程序
該子程序稱為 Orphan(孤兒)
OS 會重新指派 init 程序(PID = 1)為該子程序的新父程序
Multiprocess 架構的例子:Chrome 瀏覽器
多個分頁(tab):多個 process
process 三大分類:
【Browser】
主要處理使用者介面、硬碟及網路的 IO,通常只會有一個
browser process(即使有很多分頁)
【Renderer】
隨著每一個分頁/網站,都會產生 renderer process,用來處理
網頁原始碼
Sandbox:沙盒模式
每個 renderer 玩自己的沙子,不影響別人
硬碟、網路 IO 皆受到限制
降低安全風險
【Plug-in】
隨著每一個 plug-in(附加元件),就會有一個 process
每一個分頁都有自己的 renderer process(負責解讀網頁原始碼並把內容
呈現)
由於 renderer 可能因為網頁寫得不好而當機,但因為不同分頁間是不
同 process,所以並不會影響到其他分頁
Interprocess Communication (IPC):程序間的溝通
Process 間可能是合作(cooperating)或者是獨立(independent)
獨立(independent)的 process 不能影響到其他 process
合作(cooperating)的 process 可能影響其他 process 或者是受到其他
process 的影響,包括分享的資料(sharing data)
Process 進行合作的原因
分享資訊【Information Sharing】
計算加速【Computation Speedup】
模組化【Modularity】
把單一程式拆分為許多 module 並由許多不同 process 執行
方便性【Convenience】
溝通的兩種方式(於 Chapter 2 中有簡介過,這裡不贅述)
Shared Memory 與 Message Passing
Producer-Consumer Problem
為合作程序(Cooperating process)的一種例子
某一個 process 產生資料,而另一個 process 把資料消耗掉
Unbounded-buffer
沒有明確定義 buffer 的最大 size
但通常都是使用 bounded-buffer
Bounded-buffer
假定 buffer 有一個固定的大小
例如:Shared-Memory 就採用 bounded-buffer
下列為定義一個 bounded-buffer 的程式碼
採用 circular queue 的方式,滿了就從頭開始覆蓋
請注意事實上只有 BUF_SIZE -1 個位置可用
buffer 滿的定義:((in + 1)% BUF_SIZE) == out
滿的時候,還是浪費一個空間
定義只剩下一個空間代表 buffer 為滿的用意是,避免空
間全用完時,in == out,與 buffer 為空的時候定義重複,
造成分辨不出來到底是空的還是滿的
buffer 空的定義:in == out
#define BUF_SIZE 10
typedef struct {
. . . //定義每一個資料的內容
} item;
item buffer[BUF_SIZE];
int in = 0;//定義下一個可以放資料的位置
int out = 0;//定義下一個可以取資料的位置
下列為一個簡單的 Producer 的程式碼
item next_produced; // 存放新產生的資料
while (true) {
.../*將產生的資料放進 next_produced 變數 /
// 下面的 while 迴圈用來檢查 buffer 是不是滿了
while (((in + 1)% BUF_SIZE) == out){
// 由於空間滿了,所以什麼都不做
}
}
// 還有可用空間,於是將新資料放進 buffer
buffer[in] = next_produced;
in =(in + 1)% BUF_SIZE; //指向下一個空位
下列為一個簡單的 Consumer 的程式碼
item next_consumed; // 存放下一個要被消耗掉的
while (true) {
while (in == out){ // 檢查 buffer 是不是空
// buffer 是空的,甚麼都不做
}
// 讀取下一筆資料
next_consumed = buffer[out];
// out 增加,將 buffer 中的該筆資料捨棄
out =(out + 1)% BUF_SIZE;
.../* 處理剛才獲得的資料 */
}
Interprocess Communication(IPC):Message Passing
Process 間溝通與同步動作時使用(synchronize the actions)
Message System
Process 間互相溝通,而不必倚賴共用的變數
提供兩個操作:
Send(message):傳送訊息,訊息大小可能是固定或浮動
Receive(message):接收訊息(將訊息放在 message 變數中)
若兩個 process:P 與 Q 需要溝通,則必須
在兩者之間建立溝通連線【Communication Link】
然後藉由上述的 send/receive 來交換訊息
Communication Link 的實作方式
物理上(Physical):使用 shared memory 或硬體 bus 等等
數位邏輯上(Logically):
1. 直接(direct)vs 間接(indirect)
是否使用 Mailboxes(或稱 ports)(下述)
2. 同步(synchronous)vs 非同步(asynchronous)
傳送者與接收者是否等待訊息傳送/接收完成(下述)
3. 自動(automatic)vs 明確使用 buffer(explicit buffering)
process 是否得明確處理訊息傳輸的過程
例如:process 是不是需要自己管理 message buffer 的空
間狀況,或者是直接丟給 OS,讓 OS 替 process 傳送(故
此訊息如何在 OS 中儲存對 process 來說,是『自動』的)
實作上的一些問題
兩個 process 之間如何建立連線(link)?
一個連線可以連接超過兩個 process 嗎?
每一對溝通中的 process 最多可以建立多少連線?
單一個連線最大的通訊容量(Capacity)是多少?
連線可以接受的訊息大小是固定(fixed)的還是可變(variable)?
連線屬於單向(unidirectional,只能從某一個 process 傳到另一個,
無法反向傳遞)還是雙向(bi-directional)?
Direct Communication:直接連線
Process 必須明確指明另一個 process,然後:
1. Send(P,message):將訊息傳送給 Process P
2. Receiove(Q,message):從 Process Q 接收訊息並放在 message
變數中
此種連線的特性(properties)
1. 連線是自動建立(由 OS 管理),兩個 process 只需要互相知道
對方的身分(例如 PID)即可,不必管理訊息如何在 OS 中傳送
2. 一個連線必定只能連接一對 processes 來溝通(一對一)
3. 每一對 processes 之間只能存在一個連線
4. 連線可能是單向的,但通常都是雙向(因為只能建立一條連線)
Indirect Communication:非直接連線
訊息被丟到 Mailboxes(或稱 ports),然後另一個 process 再到
mailbox 中取得訊息
1.
2.
每一個 mailbox 都有唯一的 ID
兩個 process 可以溝通的情況是,該兩個 process 共用同一個
mailbox
此連線的特性
1. 只有在兩個 process 共用同一個 mailbox 時,連線才建立
2. 一個連線可以連結許多 process(只要它們都共用同一個
mailbox 即可)
3. 每一對 processes 之間可以有許多連線(共用多個 mailbox)
4. 連線可能是單向也可能是雙向
此連線的操作(operations)
1. 建立新的 mailbox
2. 刪除舊有的 mailbox
3. 傳訊息到某一個 mailbox
4. 從某一個 mailbox 接收訊息
函數原型(Primitives)
1. Send(A,message):把訊息送到 mailbox A 之中
2. Receive(A,message):從 mailbox A 中取出一個訊息並放到
message 變數中
Mailbox 共用
1.
2.
情境:
假設:P1、P2、P3 共用 Mailbox A
P1 送出訊息時,P2 和 P3 想接收
到底誰取得該訊息?
三種解決方案
1. 限制每一個連線至多兩個 process,故可以避免超過兩個
process 共用 mailbox 的問題
2. 同一時間只允許一個 process 可以執行 receive 的操作
亦即,在訊息送出之後,一定只會看到一個 process
要接收訊息,故不會發生不知道送給誰的情況
3. 允許系統隨機挑選一個接收者(例如:系統採用 roundrobin 機制(輪流制,每個人輪流收一次)),然後系統會通
知 sender:最終這個訊息被誰收到了。
因此,sender 可以知道是不是傳到正確的 process
如果錯誤,就重新傳。如果系統是採用輪流制,則
最終一定會有一次送到正確的 process 中
Synchronization:同步議題
兩種類別:blocking 與 non-blocking
Blocking:被視為是同步的【synchronous】
1.
Blocking send:
Sender 將訊息送出之後,會一直等待,直到 receiver 將
該訊息收下來之後,才繼續執行
2.
Blocking receive:
Receiver 需要收訊息的時候,會一直在原地等待,直到
sender 將訊息送出且自己收下之後,才會繼續執行
Non-blocking:被視為是非同步的【asynchronous】
1. Non-blocking send:
Sender 將訊息送出後,就繼續執行(不會理會 receiver 是
否收到)
2. Non-blocking receive:
當 receiver 執行到 receive 的指令時,會看看有沒有訊息
可以接收,如果有就收下。若沒有,就直接跳過接收的
指令(此時稱收到的訊息稱為空值(null))
以上四種傳送與接收的方式可以混搭使用
如果傳送端與接收端都是 blocking,則稱為 rendezvous(交會)
1. 形成 Producer-consumer 的模式
message next_produced;
while (true) {
/* 產生要傳送的訊息 */
send(next_produced);
}
message next_consumed;
while (true) {
receive(next_consumed);
/* 處理收到的訊息 */
}
Buffering
在一條連線中,一個儲存訊息的佇列(Queue of Messages)被附加在
其上,該佇列由以下三種方法之一來實作:
1. Zero Capacity(沒有使用 buffering):沒有使用暫存的佇列(只能
儲存 0 個訊息),因此 sender 必定得等待 receiver 來接收訊息
(就像是 sender 必須親手把訊息交給 receiver,沒有地方可以
借放),此情況稱為 rendezvous
2. Bounded Capacity:至多只能儲存 n 個訊息,在佇列還沒滿之
前,sender 可以不必等待 receiver 接收。但如果佇列已滿,
則 sender 必須要等待(等待 receiver 把訊息消耗掉之後,有新
的空間才能繼續放新訊息)
Unbounded Capacity:可以存放無限個訊息,sender 永遠不必
等待
IPC 系統範例:POSIX 的 Shared Memory
Process 一開始必須建立一塊共享的記憶體片段(Shared Memory
3.
Segment)
shm_fd = shm_open(name, O_CREAT | O_RDRW, 0666);
此指令也可以用來打開一個已經存在的記憶體片段,並且分
享之
設定要分享的物件的大小
ftruncate(shm_fd, 4096);
將要分享的物件對應到記憶體,並取得其指標
ptr = mmap(0, SIZE, PROT_WRITE, MAP_SHARED,
shm_fd, 0) ;
此後,就可以直接利用該指標來寫入共享的記憶體片段(如同使用
自己記憶體的指標一樣),而不必再通過 system call
IPC 系統範例:Mach
Mach 系統的 communication 為 message based
甚至連 system call 也是 message
每一個 task 在建立的時候,都會取得兩個 mailbox (在 Mach 稱為
port):Kernel 與 Notify
Message 的傳輸只需要三個 system call:
msg_send(), msg_receive(), msg_rpc()
Mailbox 為溝通的必備之物,可藉由 port_allocate()來建立
Message 的傳送與接收是有彈性的(flexible)
例如:當 mailbox 已滿的時候,sender 有以下四種處理方案
1. 一直等待,直到 mailbox 有空位
2. 等待至多 n 個 milliseconds,超時仍沒有空間時就忽略
3. Process 立刻 return
4. 將 message 給 OS 暫存
IPC 系統範例:Windows XP
訊息藉由 advanced local procedure call (LPC) 機制來管理
只能替在【同一個系統】中的 process 傳遞訊息(不能跨機器)
使用【ports】(類似 mailbox)來建立和維護 communication channel
溝通流程如下:
Client 開啟 handle(handle:用來控制/存取 port 的物件)到
subsystem 的【connection port】物件
當作是存取信箱(mailbox,在此稱為 port)
Client 送出 connection request(連線需求)到 connection port
Server 從剛才的 connection port 中收到需求後,另外在開啟
兩個私用的 communication ports,並且把其中一個 port 的
handle 交給 client,好讓 client 能夠存取新開啟的 port (而另
一個 port 則由 server 保留自己用)
Server 與 client 分別使用自己的 port 來傳送訊息與
callbacks,並接收回傳的結果
Shared Section Object
如果 server 與 client 需要分享大於 256bytes 的資料,就
會建立此物件
此物件可以讓兩邊直接存取,避免複製資料而浪費資源
在 client-server 系統中的溝通方式
分為:Sockets、Remote Procedure Calls(RPC)、Pipes、Remote Method
Invocation (Java)
Socket:
Socket 定義為溝通用的端點(endpoint)
沒有明確規定訊息的內容格式
由一組 IP 位址和一個 port(連接埠,記錄在訊息封包的開頭,用來
識別不同的網路服務)所組成
例如:socket 140.113.1.1:53
代表電腦 140.113.1.1 上的 port 53 號
Socket 的類型:
Connection-oriented (TCP)
Connectionless (UDP)
MulticastSocket class:Java 特有
可以把資料傳送給多個接收者
溝通由一對的 socket 所組成
低於 1024 的 port 號為常見服務所使用
特殊 IP 位址:127.0.0.1(loopback)代表本機(localhost)
使用 127.0.0.1 會透過機器上特殊的 loopback 網路介面卡,與
呼叫自己的 public IP(例如 140.113.1.1)不同。使用自己的
public IP 事實上是把資料丟出去,在收回來
Remote Procedure Calls (RPC)
將網路化系統(networked system)的 process 間的 procedure call 抽
象化
亦即,讓不同機器上的 process 不必顧慮到背後網路溝通的細
節跳
同樣使用 port(連接埠)來區分不同服務
不同於 socket 可以任意定義傳輸的內容格式,RPC 的內容傳輸定
義必須符合規定
Stub:
存在於 client 端
作為 server 端真實程序的 proxy(代理)
因此 Client 不是直接控制 server 端的程序,而是透過其他程
式
Stub 會處理網路溝通的部分,讓 process 不必自己處理
Client 端的 stub 鎖定 server 的位置並且把 RPC 參數等等進行
marshall(將訊息包裝起來)
Server 端的 stub 接收訊息,並把資料解開(unpack),然後根據參
數,執行所要求的程序(procedure)
資料的表達方式(data representation)由另外的 External Data
Representation (XDR)來處理
原因:不同機器硬體架構,儲存資料時的方式不同
例如:Big-endian 與 little-endian
Big-endian:儲存數值資料時,將 MSB 的 byte 放在記憶體位
址較低的部分
Little-endian:將 LSB 放在記憶體位址較低的位置
遠端的溝通會比本地端的溝通還要多許多發生錯誤的情況
例如:網路問題
OS 通常會提供 rendezvous (又稱為 matchmaker),來建立 client 與
server 之間的連線
Pipes
作為兩個 process 間溝通的管道(conduit)
相關議題
此中溝通方式是單向還是雙向?
如果是雙向,則該管道為 half-duplex 還是 full-duplex?
half-duplex:傳送與接收不能同時執行(同一時間只允許
單一方向傳輸)
full-duplex:可同時傳送與接收(可同時信行雙向傳輸)
使用 pipe 溝通的兩個 process 一定要存在某種關係(例如:
parent-child:父子程序關係)嗎?
Pipe 可以跨網路使用嗎?(連接不同機器上的 process)
Ordinary Pipes:原生的 Pipe
允許標準的 producer-consumer 方式溝通
Producer 在某一個端點(end)寫入資料
此端點又稱為 pipe 的 write-end
Consumer 從另一個端點讀取資料
該端點又稱為 pipe 的 read-end
也因此,Ordinary Pipes 是單向的
要求兩個 process 必須要是 parent-child 的父子程序關係
Windows 上,稱為 anonymous pipes
同樣限制必須要是 parent-child 關係
讀寫的方式就像檔案一樣
範例:parent-child 的 pipe 程式碼
假設 parent 要送資料給 child
Named Pipes:具名的 Pipe
須注意與 Ordinary pipe 的差異
比 Ordinary pipe 更加強大
溝通可以是雙向的
沒有要求 process 間必須要是 parent-child 關係
可以數個 process 一起使用 named pipe 來溝通
UNIX 系統上的 Named pipe
又稱為 FIFO(First In First Out,先進先出)
利用 mkfifo()來建立,相關操作為 read(), write(), open(),
close()
只支援 Half-Duplex(傳送與接收無法同時)
因此常需要創立兩個 pipe 來進行雙向溝通
要求使用的所有 process 都要在同一台機器上
Windows 系統上的 Named pipe
相關函式:CreateNamePipe(), ConnectNamePipe(),
ReadFile(), WriteFile().
支援 Full-Duplex
Process 可以在相同或不同機器上
作業系統概論
Chapter 4: Multithreaded Programming
Thread Overview:什麼是 Thread(執行緒)
Thread 為 CPU utilization 的最小單位,用有自己的 program counter、
stack、registers
傳統程式:heavy-weight、只有單一個 thread
Multi-threaded 程式:
單一 process 中有許多 thread,擁有部分自己的資料(program
counter、stack、registers)
Thread 間共用某些資源,例如 code、data、已開啟的檔案
使用 Thread 的動機
現今大多程式都是 multi-threaded
程式內的許多不同工作都可以指派給不同 thread 執行
例如:更新畫面顯示、取得資料、語法檢查、回應網路需求
太重
等等
nf
輕
相較於整個 process 本身是 heavy-weight,Thread 是 light-weight
想要簡化程式、提升效率
有 不同 ⼯作
事實上,Kernel 大多都是 multithreaded
每個 thread 各有各的工作,例如管理裝置(device)、管理記憶體、
處理 interrupt 等等
範例:伺服器使用 thread 的方式,把網路需求交給其他 thread 來處理
_
-
-
- 5 i n
※
kemel ⼤多 是 multithreaded
t-n.is
,
好處
Responsiveness:反應性
將 process 工作切細為許多 thread 後,某些 thread 可能因為
system call 而 blocking,但其他 thread 仍可進行其他的工作,而不
用整個 blocking
程式不會因為某些 thread block 而整個卡死,反應能力提升
對使用者介面(UI)的程式特別重要
Resource Sharing:資源分享
許多 Thread 之間可以直接共享資源,比起使用 shared memory 或
-
ttnnrn兩
✓
message passing 還要簡單
✓
Economy:經濟實惠
比起使用多個 process 來處理工作,多個 thread 的方式無論是產
生或 context-switch 的花費都比多個 process 還要低
因為 thread 間有共用資源,不像 fork 多個程式時,資源無法共享
(例如 code、data 等等資源)
Scalability:可擴大性
比起 single thread 的 process 來說,更加可以進行平行運算
Multicore Programming:多核心程式設計
程式設計師必須要注意:
Dividing activities:工作切割
Balance:切割平衡性(盡量平均負擔)
Data Splitting:資料切割,將資料分成許多份
Data Dependency:資料相依性
相依的盡量放一起不要分開,否則可能 processor 之間需要等
multithreudcreateandantextǜlg
待(要改 A 資料前需要先等 B 資料改完)
Concurrency
單一 processor 也可達成
每個 process 都碰一點,切換著執行
感覺似乎是許多程式同時被執行
但單一時間內其實還是只有一個 process 在執行
Parallelism
使用多個 processor 來處理工作
與 Concurrency 不同的是,同一時間真的有多個 task 在執行
Data Parallelism
把要處理的資料拆成很多分給不同的 processor 處理
譬喻:三個助教每人改 1/3 的考卷
舉例:將一張圖片切割成許多小塊,同時執行像素處理
n
_
呂 呂 呂
n
Task parallelism
每個 processor 執行某一部分的小工作
譬喻:助教 ABC 分別改 1~3 題、4~6 題、7~9 題,但每個助
教都會改到所有的考卷
隨著 thread 的數量增加,硬體架構也使用了 thread 的概念
CPU core 中擁有 hardware thread
I.FI
Amdahl’s Law
能夠利用平行加速的部分只能針對"可平行化"的部分
只能使用 serial 執行程式碼的無法加速,故有再多的 CPU 也無法改善
serial 程式碼的效能
計算加速量的公式:
S 為 serial 程式的比率
+0.75
N 為 processor 的數量
當 N 趨近無限大時,speedup 至多為 1/S
舉例:某程式 75%是可平行化,25%是只能 serial,則從單核心轉為雙
核心,其 speedup 為 1.6 倍
User thread 與 Kernel thread
User thread
由 user-level 的 thread library 實作
建立 thread 不是 system call
kernel 並不知道 user thread 間的建立與切換
對 kernel 來說,就只是一個普通的 process,並不知道其使用
thread 的狀況
飇
Kernel thread
建立 thread 是 system call,由 OS 支援提供
不同的 thread 可以分到不同 CPU 上執行
現行的作業系統大多都有支援
Thread Library
提供建立、管理 Thread 的 API
兩種主要的實作方式:
Library 整個都在 user space,與 kernel 無關(user thread)
由 OS 提供,為 Kernel-level library(提供 kernel thread)
三大主要 thread library
POSIX Pthreads
Win32 Threads
Java Threads
Multithreading Models
大致分為:
Many-to-one:多對一
One-to-one:一對一
Many-to-many:多對多
two-level model:分為 user 與 kernel 兩個 Level
Many-to-one:多對一
多個 user thread 對應到單一個 kernel thread
Thread 的管理是由 user-level 的 thread library
每個 user thread 和 kernel thread 各有各個狀態(state)
User thread 有狀態為:ready、running、blocking 等
Kernel 亦同,但其狀態的意義與 user thread 不同
例如:如果只是 thread 之間的互等而造成某些 user
thread 發生 blocking
但整個 kernel thread 的狀態仍是 running
當其中一個 user thread 發生 block 時,會帶動 kernel thread 也
block,導致所有 user thread 都被 block
CPU 進行 context switch 的時候,都是以 kernel thread 為單位
之前提到 user thread 與 kernel thread 的狀態是不同的,當有
一個 user thread 需要 I/O 而等待時,只有該 thread 是
blocking,而其他 user thread 仍是 running 等等
iz
但對於那一個需要 I/O 的 user thread,OS 必須將之移動到
waiting queue,而 CPU 進行 context switch 的時候,都是以
kernel thread 為單位
⼀
因為只有一個 kernel thread,故整個 process 都被從 CPU 中移
出,導致所有 user thread 都被 block (即是不是所有的 user
thread 都需要 block)
ETE
⼀
無法運用多處理器來加速
因為每一個 kernel thread 只能放在單一 CPU
One-to-one:一對一
每一個 user thread 都恰巧有指定的 kernel thread 來對應
kerel
OS 會介入 Thread 的管理
不會因為一個 user thread 的 block 而造成其他 user thread 跟著被
block
可以運用多處理器進行加速(因為有許多個 kernel thread 可以分別
到不同 CPU 執行)
缺陷:使用太多的 user thread 時,會產生大量的 kernel thread,
使用較多系統資源,產生的 overhead 高
Many-to-many:多對多
允許多個 user thread 對應到多個 kernel thread
但是沒有指定哪一個 user thread 對應到哪一個 kernel thread,且
kernel thread 的數量也不一定要和 user thread 一樣多
不會因為 user thread 太多而開了太多 kernel thread 造成系統
rnn
✓
r t n n
負擔
同樣不會因為某一個 user thread 的 block 造成整個 process 被
block(OS 可以選擇那些需要執行的 thread 執行)
Two-Level Model
與多對多類似,但又允許某一個 user thread 綁定到特定的 kernel
thread
各大 Thread Library 簡介(由於講義中大多為範例程式碼,此處將略過,考
試不太會考)
Pthread
沒有定義一定是 user-level 或 kernel-level 的 thread
只有 Specification(訂標準),而沒有 Implementation(實作)
因此可能是有 user-level 的實作或者是 kernel-level 的實作,根據
平台而異
Java Thread
由 JVM 所管理
實際上的實作是 user thread 或 kernel thread 仍是由 OS 決定
Implicit Threading:隱性的 Thread
使用者不用顯性的指定 thread 的操作
因為使用顯性的 thread (explicit thread,例如 Pthead)會讓程式變的複
雜
建立、管理 thread 的工作變成由 compiler 以及 run-time library,而不
是由使用者自己來
三種方案
IFork-J.in/7araHe1im-en- -n-IK
Thread Pools
OpenMP
Grand Central Dispatch (GCD)
Thread Pools
當建立 process 時,就把 thread 建起來放。需要時就拿去用,不
是需要時才建立,當該 thread 被用完時,放回 pool
當 pool 中沒有更多可用的 thread 時,該 process 就需要等到至少
有可用的 thread 為止
優點:
由於不用一直建立、移除 thread,所以效率稍好
可以將應用程式的 thread 總數控制在 pool 的大小之內(避免
系統過度負載)
OpenMP
由一群的編譯器指令(compiler directives,以#開頭)和 API library 所
組成,由編譯器來產生 thread 所需的程式碼
提供於 C/C++與 Fortran
提供 shared-memory 架構下的平行執行能力
一般來說產生的 thread 數量會與 CPU core 數相同
使用者只需要指定哪些區域可以平行執行(稱為 parallel regions,
以{ }形成的 blocks 為單位)
例如:指定一個平行區塊:
#pragma omp parallel
{
/*可平行執行的程式碼 */
}
或者是平行執行的 for 迴圈
#pragma omp parallel for
for(i=0;i<N;i++) {
c[i] = a[i] + b[i];
}
Grand Central Dispatch (GCD)
使用於 Mac OS X 和 iOS
C/C++的延伸 API 與 Library
允許使用者指定平行執行的區塊,而由 OS 來管理 thread 的部分
平行區塊:^{}
例如:
^{ printf("I am a block"); }
管理方式:會將所有的 block 放置在 dispatch queue 中
當有可用的 thread 時,將之從
queue 中移除並給予 thread 來
⼀
執行
兩種類別的 dispatch queue
⼀
Serial
稱為 main queue
Queue 內的 block 是以 FIFO 的方式循序移除並執行
下一個 block 必須等待前一個 block 被執行結束,才可以
開始執行
使用者可以在程式內建立多個 serial queue
Concurrent
與 serial 最大的差異:指派 thread 給 block 時,雖然也是
FIFO 的方式取出,但是下一個 block 不必等待前一個
block 執行結束
只要 thread pool 中有可用的 thread,就可以將 block 從
queue 中移除並執行
三個 system wide 的 queue:low、default、high
Threading Issue:使用 thread 的相關議題
Semantics of fork() and exec():fork 與 exec 的語意問題
Fork()
Single thread 時,fork 是將整個 process 複製
但有多個 thread 時,是否完整複製(連所有 thread 都複製)?
還是只複製當前 thread?
不同 OS 可能有不同做法,甚至為兩者提供不同的 fork
Exec()
與 single thread 相同,將原始程式整個蓋掉(不管 thread)
Signal Handling
在 UNIX 系統中,會使用 Signal 來通知 process 哪些特定的 event
已經發生了(例如使用者按下 Ctrl + C 或者發生 segmentation fault)
-.-
Signal Handler
當某個特定的 event 發生後,signal 會產生並且送到某一個
process 中
送達後,由 signal handler 來處理該 signal
Signal Handler 可以是預設或者使用者自行定義
每一個 signal 都有預設的 handler,稱為 default handler
可以利用 user-defined signal handler(使用者自行定義的
handler)來取代預設的行為
舉例:鍵盤按下 Ctrl + C 會對當前 process 發生 Interrupt 的
signal
預設的行為:中止程式執行
但也可以是使用者自行定義(例如對某些程式使用了 Ctrl
+ C 卻無法中止,就是因為該程式自行定義的 Ctrl + C 的
handler 不是中止程式)
在 single-threaded 的環境下,該 signal 就是單純送到該 process
但是在 multi-thread 的環境下該如何處理?
Signal 可以有以下幾種送達方式
1. 根據 signal 的類別送到能接受該 signal 的 thread
2. 將 signal 送往所有的 thread
3. 將 signal 送到某些特定的 thread
4. 將所有 signal 送到一個單一個 thread 來處理
在 UNIX 系統中,允許指定每一個 thread 所接受、忽略的
signal
Thread Cancellation:中止 thread 的執行
此處將要被終止的 thread 稱作 target thread
當 target thread 不再被需要而要被終止時,有兩種方式:
Asynchronous Cancellation:非同步模式
⼝ target thread 終止,但可能造成某些資源沒
立即主動將
有釋放的問題
-_-
例如:target thread 在自己的指令中使用 malloc 建立動
態空間,但遭到強制終止時,並沒有機會執行到 free 來
釋放空間,導致資源沒有釋放的問題
成
Deferred Cancellation:延遲模式
沒有立即中斷 target thread,而是設立一個 flag 變數,要
求 target thread 自行終止
Target thread 要週期的看有沒有 flag 變數要求自己要終
止
target thread 看到 flag 後,由於仍有執行能力,因此可以
進行資源釋放的相關動作(例如執行 free 來釋放記憶
體),等到相關動作都完成後,target thread 自行主動終
止
這樣的中止不是立即的,而是有『延遲』。意思就是,被
告知要停止(建立 flag 後)到得知要停止(目標看到 flag 後)
之間有一段延遲時間
⼀發
garbge
Thread-Local Storage
Thread 雖有自己的 local stack 儲存空間,但其內的變數只限於該
函式執行而已
⼀個 thread 內 可
Thread-local storage (TLS) → 只有 在
thread不⾏
又稱 thread-specific data 函式 間 亦可 ⽤ , 其他
可視為 thread 自己使用的 global data,可以在函式之間共
通,卻又和其他 thread 不通用
不同於一般的 global data 是所有 thread 共用的
不同於一般 thread 上 local variable 只能在該次函式內使用
使⽤
Scheduler Activations
Many-to-many 和 Two-level model 需要控制分配到該程式的 kernel
thread 數量
一般來說會使用介於 user-thread 和 kernel-thread 的資料結構:
⼝
light weight process(LWP)
類似一個虛擬的處理器,可以讓 process 將 user thread 分派
到其上執行,就好比 schedule 到真實 CPU 那樣
LWP 與 kernel thread 是 one-to-one(一對一)對應
給某 process 的 LWP 數量可以根據不同原因(例如系統負載)調
整
OS 會將 kernel thread 分派到真實的處理器上執行
。
Scheduler Activations 提供 upcall
為一種讓 kernel 與 thread library 中的 upcall handler 溝通的方
式
可以允許應用程式控制正確的 kernel thread 數量
Operating System Examples(簡述,考試不太會考)
採用指標結構
為了讓 thread 間可以某種程度共用資料,故每個 thread 內部紀錄
資料記憶體可能會用指標紀錄
多個 thread 可以擁有指向同一個記憶體位置的指標,達到資料記
憶體共用
Windows Threads
實作於 Windows API,採用 one-to-one 對應的方式
每一個 thread 擁有的資訊:
Thread ID
表示 CPU 狀態的 register set
thread 自己的 stack
由 run-time library 和 DLL 提供的私有資料空間
上述後三者稱為 thread 的 context
Linux Threads
將 thread 稱為 task
傳統的 fork()是將整個 process 複製
使用 clone()來建立 thread,並且可以透過指定 flag 參數來決定哪
些資料要共用
每一種資料的共用與否可以由特定的 flag 指定
若無指定 flag,其行為就跟 fork 相同(都不共用)
作業系統概論
Chapter 5: Process Scheduling
基本概念
最大化 CPU 使用量,就如同 multiprogramming 那樣(CPU 一直都不會閒
著)
CPU-I/O Burst 循環(如右圖)
由一個 CPU burst 和一個 I/O burst 組成
CPU Burst:
process 需要 CPU 進行運算
I/O Burst
Process 需要 I/O 裝置處理
例如:檔案讀寫
CPU burst 是接在 I/O burst 之後
在 IO burst 時,CPU 沒在使用,
希望讓 IO burst 發生時,
讓 CPU 去處理別的 process
Burst 的長度、CPU burst 的分布都會
會影響 scheduling 的方式
CPU-burst 示意圖
大部分的 CPU burst 其實都不長(短而多的 burst)
I/O-bounded program:短而多的 CPU burst
CPU-bounded program:數量少但持續時間長的 CPU burst
多為需要長時間運算的程式,例如圖形處理(rendering)
CPU Scheduler (Short-term scheduler) 在 Chapter 3 筆記第 4 頁也有簡述過
選定某一個 ready queue 中的 process,讓 CPU 去執行
Ready queue 可以由很多種方式排序(不一定是 FIFO(先進先出))
CPU Scheduling Decision(決定哪個 process 可以使用 CPU)的發生時機:
某個 process 從 running 變成 waiting state
例如:開始等待 I/O 處理,因此 CPU 便去執行其他 process
屬於 non-preemptive (下述)
某個 process 從 running 變成 ready state
例如:被分配的執行時間用完了(time slice expire),因此從
CPU 中退出,把 CPU 讓給其他 process
屬於 preemptive
某個 process 從 waiting state 變成 ready state
例如某 process 的 I/O 執行結束之後,進入 ready queue
某些高優先權的 prcess 可能會在 IO 結束進到 ready 後,取代
正在執行的程式來使用 CPU
屬於 preemptive
某個 process 終止執行(Terminate)
將自己占用的 CPU 資源釋出給別的 process
屬於 non-preemptive
Non-preemptive 與 Preemptive (先發制人)
Non-preemptive
又稱 Cooperative
此種 scheduling 是執行中的 process 自願放棄 CPU 資源,而
非 OS 強制要求該 process 終止
例如等待 IO、process 終止
Preemptive
執行中的 process 是被 OS 強制終止的,必須考量以下幾點
共用資料的存取(shared data access)
發生在 user data (而非 kernel data)
假設情境:某一個 process P 正在 CPU 中執行
1.
2.
P 從記憶體 shared data 中取出一值,並在 CPU 中計
算
因為某些原因,導致 P 被強制暫停執行,但之前 P
從 shared data 取出計算的值仍留在 Register 中,未
3.
存回記憶體
與 P 共享資料的 process Q 被執行,同樣從 shared
data 中取出該值並計算(但是取出來的值是 P 更新前
的舊值)
4. Process Q 執行結束,將該值寫回 shared data
5. Process P 繼續執行,最後將自己 Register 中的值寫
回記憶體中的 shared data,將 Q 所寫入的值覆蓋掉
了,導致資料錯亂
Kernel mode 中的 Preemption
關係到 kernel data (OS 內部的資料結構等等)
執行中的 process 正在 kernel mode 中(例如正在執行
system call,可能在修改 kernel 的資料結構)
另一個 process 若強制終止該 process 並進入 kernel
mode,就會產生互相干擾的問題
在關鍵的 OS 操作時發生 interrupt 時的問題
關係到 kernel data
同上述,此時 process 可能在 kernel mode,但發生
interrupt 會讓 process 離開 running
使得另一個 process 可能進入 kernel mode 造成干擾
解決方法:不要強制終止在 kernel mode 的
process,
例如:進入 kernel mode 後就禁止 interrupt,直
到離開 kernel mode 為止
Dispatcher(調度員)
屬於 scheduler(排程器)的一部分
當 scheduler 決定好下一個 process 要進入 CPU 之後,由
dispatcher 來進行切換的工作
進行 context switch
切換到 user mode
跳到新 process 適當的位置上(例如:上次被停止的位置),並
重新執行該 process
Dispatch latency(延遲)
停止執行中的 process 並執行另一個 process 所需的時間
(即:切換所需的時間)
Scheduling Criteria (量測標準)
CPU utilization (CPU 使用率):盡量讓 CPU 越忙越好
Throughput:單位時間內的完成的工作量越多越好
Turnaround time:某 process 進到系統開始,直到它執行結束之後所需
的時間越短越好
Waiting time:在 ready queue(而非 waiting queue)時等待 CPU 的時間越
少越好
Response time:程式送出計算需求後,等待 CPU 回應的時間不能太長
(在 time-sharing 系統中,CPU 可能正在執行別的 process,但因為不
能讓 process 等太久(亦即,CPU 很久才回應),所以 scheduler 要適當安
排,不能太久沒執行該程式)
從 process 進入 ready queue 中等待,一直到其進入 CPU 為止的這
段時間
Scheduling 最佳化的指標
最大化:CPU utilization、Throughput
最小化:Turnaround time、Waiting time、Response time
Scheduling 方式:先來先處理(First-Come, First-Serve,FCFS)
依照 process 進入 ready queue 的時間點,先到的先處理
情境假設:有三個 Process P1、P2、P3
抵達 ready 順序
Process Burst time(需要 CPU 計算的時間)
1
P1
24
2
P2
3
P3
3
3
執行時間軸:
等待時間(在 ready queue 等待,但仍沒被 CPU 執行的時間)
P1 = 0
P2 = 24
P3 = 27
平均等待時間:(0 + 24 + 27) / 3 = 17
但如果抵達順序換了…
抵達 ready 順序
Process Burst time(需要 CPU 計算的時間)
1
P2
3
2
P3
3
3
P1
24
執行時間軸:
等待時間
P1 = 6
P2 = 0
P3 = 3
平均等待時間:(6 + 0 + 3) / 3 = 3,比前一個例子好很多
【Convey effect】
執行短的 process 卡在執行長的 process 後方
會讓平均等待時間變長
例如:一個 CPU-bounded process 和許多 I/O-bounded process
Scheduling 方式:執行短的先處理(Shortest-Job-First,SJF)
對每一個 process 綁定一個 next CPU burst time(以後進入 CPU 之後,需
要多少時間計算)
讓需要最少時間計算的 process 優先使用 CPU
至於如何算出 next CPU burst time 會在後面詳述
SJF 是 optimal(最佳的),可以讓平均等待時間最小
困難點:如何計算/預測 next CPU burst time
另外的方式:詢問使用者
SJF 範例:
Process 預估的 Burst time
P1
6
P2
8
P3
7
P4
3
執行時間軸:
平均等待時間(P1+ P2+ P3+ P4) / 4:(3+16+9+0) = 7
如何決定 next CPU burst time
利用之前的 CPU burst 長短,來預測下次使用 CPU 的時間長短
將過去的 CPU burst 長短,利用指數平均的方式預測
令 tn = 第 n 次 CPU burst 的長度
令 τn = 第 n 次 CPU burst 長度的預測值
常數值α, 0 ≤α≤ 1
定義下一次的預測值:τn+1 = αtn + (1-α) τn
亦即,下一次的預測值,是由該次真實的 CPU burst time 與原
本的預測值以某個比例合併而成
α值的影響:
越低:浮動比率越小(過去的值影響比重更高)
越高:近期的值影響越大
當α = 0 時,τn+1 = τn
最近發生的 CPU burst 長度都不會影響預測值
當α = 1 時,τn+1 = tn
預測值只受到上一次 CPU burst 時間的影響
通常α值設為 1/2
τn+1 的展開式:
由於 1-α是小於 1,所以越久遠的值影響力越小
預測值變化的範例
藍色:預估值;黑色:實際值
當 SJF 牽涉到 preemptive 時,稱為 shortest-remaining-time-first
亦即:剩餘 CPU 處理時間最短的優先處理
當有些 process 剛從 IO 回到 ready 時,可能其需要的處理時間很
短,比當前執行 process 剩餘所需時間還短,所以取代當前
process 來執行
範例:有 4 個 Process
Process 抵達 ready queue 時間
Burst time
P1
0
8
P2
1
4
P3
2
9
P4
3
5
執行時間軸:
執行分析:
需要判斷的時間點:有 process arrive 時、某 process 結束時
時間點 0:P1 進入 ready queue
P1 是唯一在 ready queue 的 process,進入 CPU 執行
時間點 1:P2 進入 ready queue,執行時間為 4
此時 P1 剩餘執行時間為 7 (與上次相比減少了 1)
P2 取代 P1 執行,P1 回到 ready queue
時間點 2:P3 進入 ready queue,執行時間為 9
P1 剩餘執行時間為 7
P2 剩餘執行時間為 3(與上次相比減少了 1)
P2 繼續執行
時間點 3:P4 進入 ready queue,執行時間為 5
P1 剩餘執行時間為 7
P2 剩餘執行時間為 2(與上次相比減少了 1)
P3 剩餘執行時間為 9
P2 繼續執行
時間點 5,P2 結束執行
P1 剩餘執行時間為 7
P3 剩餘執行時間為 9
P4 剩餘執行時間為 5
P4 進入 CPU 執行
時間點 10,P4 結束執行
P1 剩餘執行時間為 7
P3 剩餘執行時間為 9
P1 進入 CPU 執行
時間點 17,P1 結束執行
P3 是唯一在 ready queue 的 process,進入 CPU 執行
平均等待時間(僅計算在 ready queue 中等待的時間)
[(10-1)+(1-1)+(17-2)+(5-3)]/4 = 26/4 = 6.5
Scheduling 方式:根據優先權處理(Priority Scheduling)
每個 process 都有相對應的優先權,優先權高的 process 優先處理
優先權以整數(integer)表示,數值越低代表優先權越高
CPU 將被分配到優先權最高(數值越低)的 process
Preemptive
高優先權 process 進入 ready 時,馬上把執行中的低優先權
process 取代(如果自己已經是 ready queue 裡面最高優先的話)
Non-preemptive
高優先權在 ready 後,還是等當前 process 結束,再進入執行
(只是在 ready queue 裡面插隊,讓下次變成該 process 優先)
事實上,SJF(最短 process 優先)也是一種 Priority Scheduling,其中優先
權就是 next CPU burst time 的倒數(因此,預估時間越短的,優先權就
會越大(注意!這裡不是討論優先權的”數值”))
依據優先權處理的問題:Starvation (飢餓)
低優先權的 process 可能長時間無法被執行
解決方法:Aging(老化)
每一段時間之後,漸漸提高 ready queue 裡面的 process 的優先度
讓一直等待的 process 最後擁有高優先權來執行
Priority Scheduling 的範例
Process Burst Time
優先度數值(越低越優先)
P1
10
3
P2
1
1
P3
2
4
P4
1
5
P5
5
執行時間軸:(優先高→低)
2
平均等待時間:(6+0+16+18+1)/5=8.2
Scheduling 方式:輪流制(Round Robin ,RR)
每一個 process 都會被分派到一小單位的 CPU 執行時間
稱為 time quantum q
通常為 10~100ms
當此時間用完之後,該 process 會被暫停並且移到 ready queue 的
最後方
當某一時間點,有新 process 進入且同時有某 process 從 CPU 中結
束執行
這兩個 process 都要進入 queue
順序:新的 process 排在結束 CPU 執行的 process 前面
因為舊的 process 剛結束 CPU 使用,優先應該較低
算出來的平均等待時間可能不會很好,但可以確保每一個 process 的
response time 可以很短
若 ready queue 有 n 個 process,time quantum 皆為 q。則最差情
況下 process 的等待時間不會超過(n-1)q 個時間單位
使用 Timer
Timer 在每一個 quantum 時間到時,向 OS 發出 interrupt
OS 因此知道此時需要更換 process
效能
q 太大:形成 FIFO
q 太大,但因為所有 process 的 CPU burst 時間都沒那麼長,
導致 process 們都可以完成自己的工作
就如同 FIFO 一樣,先到先做
q 太小:overhead 過高
由於 Context Switch 會有 overhead,q 太小造成頻繁的
Context Switch,會讓 overhead 過於顯著
q 要比 Context Switch 的時間大
q 通常為 10~100ms,context switch 時間則是小於 10us
RR 範例:設定 Time Quantum = 4
Process Burst Time
P1
24
P2
3
P3
3
執行時間軸
P1 執行 4 單位時間後停止,剩餘時間 20 單位
P2 執行 3 個時間單位後結束
P3 執行 3 個時間單位後結束
只剩下 P1,不斷執行直到結束
平均等待時間:(6 + 4 + 7 ) / 3 = 5.7
Time Quantum 與 Context Switch 次數的關係
假設執行時間(process time)是 10
則根據不同的 quantum 值 q:
q = 12,需要 Context Switch 次數 = 0
q = 6,需要 Context Switch 次數 = 1
q = 1,需要 Context Switch 次數 = 9
算法:⌈ (𝑝𝑟𝑜𝑐𝑒𝑠𝑠 𝑡𝑖𝑚𝑒) / 𝑞𝑢𝑎𝑛𝑡𝑢𝑚 ⌉ − 1
Turnaround Time 與 Time Quantum 之間的變化
Turnaround Time 為某一個 process 從開始執行(進入到 ready queue
就開始算)到結束這段期間有多長
情境假設:
有 3 個 process
每一個 process 需要 10 單位的時間來完成任務
所有 process 一開始就存在於 ready queue
都開始計算 Turnaround Time
假設 time quantum 為 1
執行時間軸:
處理中的 process A
B
C
A
開始處理的時間
1
2
3
0
…
C
A
B
C
26
27
28
29
當 A 處理完之後,從其進入 ready queue 開始一直到 A
被處理結束,總共花了 28 單位時間(27 單位時間開始執
行 A 的最後一輪,28 單位時間後結束 A 的全部執行)
同理,B 的 turnaround time 為 29,C 為 30
平均 turnaround time 為 29
假設 time quantum 為 10
執行時間軸
處理中的 process A
B
C
開始處理的時間
10
20
0
Turnaround time
A:10 單位
B:20 單位
C:30 單位
平均:20 單位
範例:當 time quantum 變化時 turnaround time 的改變
80%的 CPU Burst 應該要比 q 還要短
計算:當 time quantum = 2
處理中的 process P1
P2
P3
P4
P1
P2
P4
P1
P4
P4
開始處理的時間
2
4
5
7
9
10
12
14
16
0
Turnaround time
P1:14
P2:10
P3:5
P4:17
平均:11.5
Multilevel Queue:多種 level 的 queue
Ready queue 被分為許多類型的 queue,有各自的 scheduling rule
Foreground(前景):互動程式(interactive)
Background(背景):服務、批次處理程式(Batch)
Process 只能待在自己所屬的 queue 中,無法在 queue 之間切換
在各自的 queue 中,有自己的 scheduling rule
Foreground:RR
因為希望前景的互動 process 的 response time 低
Background:FCFS
多個 queue 之間的 schedule
因為 CPU 只能處理來自某一個 queue 的某一個 process,所以
queue 之 間也得有優先度
Fixed priority scheduling(固定優先權)
前景程式優於背景程式
先將所有 foreground queue 中的 process 處理完之後才輪到
background queue 中的 process
可能發生 starvation(飢餓)的問題:背景程式可能都無法被執
行
Time Slice
每一個 queue 都有被分配的 CPU 時間,然後再依照各個
queue 內的分配原則來執行 process
例如:前景有 80%的 CPU 時間,背景有 20%。
而前景的 80%時間中,就使用自己的 RR 方式來排程
(scheduling)
某一種 Multilevel queue 的示意圖
Multilevel Feedback Queue
【與之前的 Multilevel queue 不同之處】
此種 queue 中的 process 可以在某些情況下切換自己所屬的 queue
可用來調整 process 的優先權,例如實作 Aging(老化,process 優先權
會隨著等待的時間而增高)
Multilevel feedback queue 的 scheduler 必須定義以下的參數:
Queue 的數量
每一個 queue 的 scheduling 演算法
如何決定某一個 process 是否需要提高(upgrade)/降低(demote)優
先權
如何決定某一個 process 需要執行時要進入哪一個 queue
Multilevel feedback queue 範例:
使用 3 個 Queue (優先由高到低)
Q0:使用 RR,Quantum = 8ms
Q1:使用 RR,Quantum = 16ms
Q2:使用 FCFS
排程(Scheduling)方式:
高優先的 queue 必須空了,才會執行低優先的 queue
Q1 只會在 Q0 空之後執行,Q2 只會在 Q0、Q1 空之後執行
當新 process 抵達時,進入 Q0,以 FCFS 的方式排隊
註:RR 就是 FCFS 加上時間限制
排到的 process 取得 8ms 的執行時間
若 8ms 內並沒有執行結束,則該 process 會被移動到 Q1
的尾端排隊
在 Q1 中,繼續以 FCFS 的方式排隊
排到的 process 取得另外 16ms 的執行時間
若時間內仍是沒有完成,則該 process 可能是需要大量
CPU 計算的程式,於是移動到最低優先的 Q2 尾端
Thread Scheduling
User-level 與 kernel-level 的 scheduling 規則不同
當該 OS 有支援 thread scheduling 時,進行 scheduling 的單位就變成
thread 了,而非 process
對於 Many-to-one 與 Many-to-many
Thread library 將會把 user-level thread 分配到 LWP(Light Weight
Process,相當於虛擬的 processor)上執行
稱為 process-contention scope (PCS),只有發生在 process 內部的競
爭
因為事實上只是 user thread 內自己競爭,無關乎系統中的其他
process 和 thread
將 kernel thread 進行 scheduling 到真實的可用 CPU
稱為 system-contention scope (SCS)
需要和整個系統中的 process/thread 搶資源
Pthread 的 Scheduling:根據使用 PCS 或 SCS,而有不同的 API
PCS:PTHREAD_SCOPE_PROCESS
SCS:PTHREAD_SCOPE_SYSTEM
目前 Linux 和 Mac OS X 只允許使用 PTHREAD_SCOPE_SYSTEM
Multiple-Processor Scheduling
多 CPU 的排程更加複雜
Homogeneous Processors
每個 process 都長得一樣,都可以存取系統資料
Asymmetric multiprocessing:非對稱式
有主僕關係,Master processor 可以存取 kernel 資料,來分配
process 給其他處理器
比較沒有 data sharing 造成的問題
Symmetric multiprocessing (SMP):對稱式
每一個 processor 都可以自己存取 kernel 資料來找自己的 process
來執行
兩種 queue 的方式:
每個 processor 有自己的私有 ready queue
但可能會有某些 CPU 特別忙或者是特別閒,因為 process
特性/處理難度不同(例如等待 IO 的時間不同)
所有的 process 都在共用的 ready queue 中
Processor affinity(處理器親和性、處理器相關性)
某一個 process 可能偏好某一個 processor
亦即,某 process 之前在某 processor 執行,則下一次執行時也會
偏向給同一個 processor (尤其是 processor 有自己的 cache 或
private memory 時,可以利用過去留下來的資料,減低資料傳輸
到其他 processor 或從主記憶體讀取的時間)
【Soft Affinity】:OS 會盡量找到同一個 processor,但不保證一定
會找到同一個的
【Hard Affinity】:可以讓程式指定哪些 CPU,之後一定只會
scheduling 到特定 CPU
Linux 平常以 soft affinity 為主,但也提供某些 system call 來達成
hard affinity
NUMA (non-uniform memory access) 與 CPU Scheduling
NUMA:每個 CPU 都有離自己比較近的 memory,所以 CPU 存取
不同記憶體的時間不一致(即使從使用者的角度只能看到一塊統一
的記憶體空間)
Multiple-Processor Scheduling – Load Balancing:負載平衡
概念:將負擔分擔到多個 processor 上,而非集中在某幾個 processor
但此概念與 affinity(希望某 process 一直用同一個 processor)某種程
度上衝突
折衷:先希望有 affinity,但如果有負擔過高,則進行 balance
使用 SMP 時,為了效率,盡量讓每一個 CPU 都負擔一些工作
當系統只有一個 ready queue 時,不一定需要特別去進行 load
balance
但現代的系統都讓 processor 有自己私有的 private queue,因為希
望 process 可以一直用某一個 processor(即是 affinity 的概念)
Load Balance 用來讓工作量平均分配的方法
Push Migration
有一個監控的 task 定期檢查每個 CPU 的負載,把負擔過高的
CPU 的 task 給其他 CPU
Pull Migration
沒有特別監控的 process,由空閒的的 CPU 自己去忙碌 CPU
的 queue 中找等待中的 task 來做
某些系統會同時擁有 push 和 pull 兩種方法可供選擇
例如 FreeBSD 系統有 Linux scheduler 與 ULE scheduler
Multicore Processors:thread 相關,跳過
Real-Time CPU Scheduling
每一個 task 都有其必須完成的時間 deadline,系統必須要確保 process
在 deadline 之前完成
【Soft real-time system】
系統盡量讓 task 在時限內完成,但不保證
無法稱為真正的 real-time system
【Hard real-time system】
某個 task 一定要在某個時間點前完成
兩種影響 real-time system 效能的延遲因素:
【Interrupt latency】
從發生 interrupt 到啟動處理 interrupt 的 routine(處理程序)所
需的時間(該 routine 稱為 interrupt service routine,ISR)
由於該 routine 也得到 CPU 中執行,所以也需要進行 Context
Switch
偵測 interrupt 的時間、進行 Context Switch 的時間,會根據
OS 以及硬體(例如單組 register v.s 多組 register)不同而有差異
因此,real-time system 必須要確保當 interrupt 出現時,在某
個時間內一定會被處理到(而不會拖太久)
【Dispatch(調度) latency】
當有新的 process 需要切換執行時,多少時間內就可以切換過
去執行
包含進行 context switch 的時間
進行 context switch
Conflicts 階段
處理衝突(造成高優先權的 process 無法執行的問題)
1. 當低優先權的 process 正在 kernel mode 執行時(例如執行
system call),高優先權的 process 仍是得進行 preempt,
把低優先權的 process 從 CPU 中停止
2.
亦即,OS 必須要支援在 kernel mode 中也能夠被中
斷,而不必等到 system call 結束
但要考量到原本的 process 留在 kernel 中的資料必
須要被保留,確保下次重新執行時不會遭成干擾
當高優先權的 process 所需資源被低優先權的 process 佔
去,OS 必須要把該資源從低優先權的 process 中釋放出
來
Real-Time CPU Scheduling : Priority-based
Real-time 的 scheduler 必須支援 preemptive(可中斷其他 process),
priority-based(適當的指派優先權)的 scheduling
某些系統僅給予 real-time process 較高的優先權等級
例如:Windows 有 32 個 priority level,其中較高的 16~31 等
級是留給 real-time process 使用
但此種方式只能確保 soft real-time
對於 hard real-time 系統,必須要有足夠能力去滿足 process 的
deadline
利用合適的排程演算法(scheduling algorithms),依照 deadline 去排
程
兩種類別:
Rate-monotonic scheduling (RMS)
Earliest-deadline-first scheduling (EDF)
Rate-monotonic scheduling (RMS)
根據 rate(頻率,為週期的倒數)來排程
條件:
Process 必須要是 Periodic process 且每次 CPU burst 的時間都是固
定的常數
亦即滿足:
processing time(處理時間):t
deadline:d
period(週期):p,其中 0 ≤ t ≤ d ≤ p
process 的優先權是固定的(static period),週期越短的 process 會有越高
的優先權
範例:兩個 process
P1:週期 p = 50,處理時間 t = 20
因此在每 50 個單位時間內,都要完成 20 單位時間的處理
P2:週期 p = 100,處理時間 t = 35
P1 擁有較高的優先權(因為週期比 P2 短)
Plih
因為
-_-
在 50 單位時間時:
P1 的 deadline 已到,而此時 P2 仍在執行中
由於 P1 有高優先權,所以搶掉 P2
在使用 static period(固定優先權)的模式中,RMS 是最佳的(optimal)
亦即,在所有固定優先權的演算法中,沒有比 RMS 更好的
Missed Deadlines 的情況(亦即無法在時間內滿足某些 process 的要求)
假設:
P1:週期 p = 50,處理時間 t = 25
CPU
utilization (叁⼗號 )
:
=
0.4 ⼗ 0:35
750
5 0
2 5
t.it
P2:週期 p = 80,處理時間 t = 35
P1 擁有較高的優先權
0
25
5
0
應該 在
完成 task
p
i
75
周
⼀
時候 就
.
P2 的 deadline 已
到,卻仍未完成
)超 过 8
o_o
-
805 的
.
此種情況下的 CPU utilization 為(25/50)+(35/80)=0.94 < 1
CPU utilization 低於 1(沒有超出實際的 CPU 使用量最大限
制),代表這兩個 process 事實上存在某種排程的方式可以同
時滿足 deadline
RMS 的缺陷:無法充分利用 CPU
最差情況下(worst-case),RMS 的 CPU utilization 為:
uǜ
-1
N(21/N-1)
當 N 為 1 時,CPU utilization 為 100%;
但當 N 趨近無限大時 CPU utilization 僅約 69%
Earliest Deadline First Scheduling (EDF):deadline 最早的優先
相較於 RMS,EDF 的優先權是浮動的
離 Deadline 越早的 process,其優先權越高
當有 process deadline 週期到時,檢查當前所有 process 距離其最
)
回
,
近 deadline 的剩餘時間,剩餘時間越少的,優先權越高
沒有要求一定要是 periodic process,也沒要求每次使用 CPU 時間固定
需要知道在下一次 deadline 之前需要執行多少時間(還需要執行多久,
才可以滿足此次該 process 的需求)
以上述 RMS 的例子來看
第 50 時間單位時:
P1 的 deadline 到了,因此 P1 需要下一輪的 CPU 執行時間
但是就 deadline 上來看,P1 只需要在第 100 時間單位前完成
5010015
執行就好,而 P2 此時的 deadline 則是在第 80 時間單位
故 EDF 此時選擇讓 P2 繼續執行,而不像 RMS 改為 P1 執行
第 80 時間單位時:
.int/len-
P2 的 deadline 到了,同理 P2 只需要在第 160 時間單位前完成
執行就好,相較於 P1 的第 100 時間單位比較早,故繼續執行
P1
第 100 時間單位時:
兄 蕊 嚴 Rprioriyt
.
?
!
爾
典葡
厲爾
P1 下一次的 deadline:150
P2 下一次的 deadline:160
故 P1 繼續執行
Proportional Share Scheduling
依照所持的股份(shares)進行時間分配
系統將 T 個 shares 分給所有的 process
每個 process 取得的 shares 數量 N,其中 N < T
每一個 process 可以使用 N / T 的 CPU 時間
此種排程方式必須要確保分配出去的 shares 數量不能超過 T,當要分
配 N 個 shares 給 process 時,必須要確保剩餘的 shares 足夠
POSIX Real-time scheduling:跳過
OS 的 scheduling 範例:Linux 【參考就好,並不會考 or 考太細】
Linux 版本:version 2.5 與更早
在 kernel version 2.5 以前,使用標準 UNIX scheduler 的變形
根本沒考慮到 multiprocessor 的架構
版本 2.5 時使用了 constant order O(1)的 scheduling time 的技術
Preemptive, priority based
兩種優先權等級:time-sharing 與 real-time
Real-time 範圍:0~99
一般使用範圍:100~140
數值越低,優先度越高
優先度越高的 process 會取得越長的 time quantum q
如果 task 能在 time slice 內完成
稱為 active(活躍)
可以不斷執行(run-able),直到無法在 time slice 內完成為
止
CPU-Burst 短,優先權高
若無法在 time slice 內完成的 task
稱為 expired(過期)
必須等待其他 task 用完他們的 time slice 才能繼續執行
(才能夠進入 run-able 狀態)
所有的 run-able task 都記錄在每一個 CPU 的 runqueue 結構中
兩個陣列(active 與 expired)
以優先權為索引(indexed by priority)
當已經沒有 active 的 task 時(大家都在 expired 時),兩個
陣列互換(所有 expired 的 task 都進入 active 陣列)
運作良好,但對於互動式 process 來說 response time 太差
會優先處理 I/O bound(每次 CPU 使用時間短)的 process
Linux 版本 2.6.23 以後
使用 Completely Fair Scheduler (CFS)
【Scheduling classes】
每一種 class 都有特定的優先權
Scheduler 會從最高優先的 class 中挑出最高優先的 task
包含 2 種 scheduling class:default 與 real-time
Time quantum 不是固定的,會依照占用 CPU 時間的比率而改
變
Quantum 是由 nice value 計算出來
Nice 值範圍是-20~+19
Nice 值越低,優先度越高
Nice 越高,好人值越高,就是好人,優先越低
先計算 target latency
在多少時間內,CPU 可以讓所有的 process 都執行過
一次
因此,每一個 process 的 response time 不會大於這
段時間
當 active task 數量變多,target latency 就會增加(因為所
有需要執行一次的 process 變多了)
Time quantum 由 target latency 與 nice 值共同決定
CFS Scheduler 替每一個 task 維護一個 virtual run time,儲存在
vruntime 變數中
當要決定下一個可執行的 task 時,會看 virtual run time 最低
的(也就是 virtual run time 越低,優先越高)
Virtual run time 的衰減率(decay factor)
優先越低,衰減率越高
若某低優先的 task(nice 值大於 0)需要 200ms 的執行時
間,則其 vruntime 將會大於 200 ms
相反的,若某高優先的 task(nice 值小 0)需要 200ms 的執
行時間,則其 vruntime 將會小 200ms
一般優先的 task(nice 值等於 0),其 vruntime 就是真實
的執行時間
CFS 由於需要快速知道哪一個 process 的 vruntime 最小,因
此需要良好的資料結構『Red Black Tree(紅黑樹)』來幫助
否則尋找花太多時間,就沒意義了
紅黑樹是二元搜尋樹(Binary Search Tree)的一種,最左方
的 leaf node 就存放著有最小 vruntime 的 process
Real-time task 有固定的優先權,不受 nice 值影響
一般的 task 利用 nice 值決定其最終的優先權
Nice 值-20 代表全域(global)優先值 100
Nice 值+19 代表全域(global)優先值 139
Real-Time task 的優先值都在 99 以下
OS 的 scheduling 範例:Windows
同樣使用 priority-based 與 preemptive
執行單位是 thread 而不是 process
Dispatcher 與 Scheduler 是同一者
一般來說功能是分開的
Scheduler:安排誰是下一個進入 CPU
Dispatcher:進行 Context Switch 的動作
Thread 會執行直到碰到以下任一情況後停止:
遭到 block(例如需要 I/O 時)
Time slice 用盡
遭到高優先權的 thread 插隊(preempt)
Real-time thread 可以插隊(preempt)non-real-time 的 thread
使用 32 個 priority level
Variable class(一般 thread 使用):1~15
Real-time class:16~31
不同於 Linux,此處優先值越高,越優先
優先值 0 保留給記憶體管理的 thread
(memory-management thread)
每一個 priority 都有自己的 queue,每次從該 queue 中找 task 來做
CPU 如果沒有可以執行的 thread,則會執行 idle(閒置的) thread
Windows Priority Classes
Win32 API 有提供每一個 process 都可以選擇一個 priority class
REALTIME_PRIORITY_CLASS,
HIGH_PRIORITY_CLASS,
ABOVE_NORMAL_PRIORITY_CLASS,
NORMAL_PRIORITY_CLASS,
BELOW_NORMAL_PRIORITY_CLASS,
IDLE_PRIORITY_CLASS
而在某一個 priority class 的 process 中的每一個 thread 又可以細分
自己的 relative priority(相對優先權)
TIME_CRITICAL,
HIGHEST,
ABOVE_NORMAL,
NORMAL,
BELOW_NORMAL,
LOWEST, IDLE
利用 Priority class 和 relative priority 可以組合出優先權的數值
在某一個 priority class 中,最基本的優先權(Base priority)是
NORMAL
如果某 thread 的 quantum 過期(超過沒執行完),則其優先權會降
低,但都不會低於 Base priority
如果發生等待(wait),則會根據等待的類別,提高優先權
例如如果是等待滑鼠等的互動程序,就會提高較多優先權
前景視窗(foreground window)通常都是給予 30 以上的優先權
在 Windows 7 中新增了【user-mode scheduling (UMS)】
應用程式可以在 user mode 的情況下處理 thread 的優先權,
而不必用到 kernel mode
對於 thread 數量大時,效率好(不用一直進入 kernel mode)
UMS 來源是因為 C++等程式語言提供了 Concurrent Runtime
等架構(允許使用多個 thread 的 library)
OS 的 scheduling 範例:Solaris
為 Priority-based scheduling
有六種 class 可用:
Time sharing (default) (TS)
Interactive (IA)
Real time (RT)
System (SYS),主要是給 kernel 的程序使用
例如 scheduler 或 paging daemon
Fair Share (FSS)
Fixed priority (FP)
每一個 thread 一次只能在某一個 class 之中
每一個 class 都有自己的 scheduling 演算法
利用 multi-level feedback queue 的方式進行 Time-sharing
可動態調整優先權以及 time slice 的長度
優先權越高,time slice 越小(剛好與 Linux 相反,Linux 是優先越
高,time slice 越長)
Solaris Dispatch Table
Time quantum expired
當發生 quantum 發生過期後,下一次新的優先權數值
下一次的優先權會依照這一次的優先權去算,如果這次 time
quantum 用完卻還沒執行完,則下一次優先權會降低
Return from sleep
當該 thread 從 sleep 狀態(例如等待 I/O)回到執行時,其優先
權會變成什麼(通常會變高)
Solaris Scheduling
fixed-priority class 和 fair-share class 是在 Solaris 9 之後才有的
Scheduler 會將 class-specific priority(每一個 class 內自己的優先數
值)轉換為 per-thread global priority(所有 class 都看的全域數值)
越高優先數值的優先執行
直到碰到以下狀況才停止
遭到 block(例如需要 I/O 時)
Time slice 用盡
遭到高優先權的 thread 插隊(preempt)
如果有多個 thread 有同樣的優先權,則使用 RR(輪流制)決定
Algorithm Evaluation:評價演算法的好壞
有許多種排程演算法,到底哪一種好?
Deterministic modeling:利用分析的方式
給定一些簡單的 workload,然後進行效能預估
情境:給定 5 個 process,皆在時間點 0 時抵達
Process Burst Time
P1
10
P2
29
P3
3
P4
7
P5
12
計算各種不同 scheduling 方式的平均等待時間
單一 process 等待時間計算:
最後結束時間 – 自己的 burst time – 自己的 arrive time
FCFS (先來先做)
表示法:[(P1)+(P2)+(P3) +(P4) +(P5)] / 5
[(10-10)+(39-29)+(42-3)+(49-7)+(61-12)] /5 = 28 時間單位
Non-preemptive SFJ (先做短的)
[(20-10)+(61-29)+(3-3)+(10-7)+(32-12)] /5 = 13 時間單位
RR (輪流做),time quantum = 10
[ (10-10)+(61-29)+(23-3)+(30-7)+(52-12)] / 5 = 23 時間單位
Queueing Models:排隊理論分析
利用機率分布函數的方式,描述 process 抵達的時間、CPU 及 I/O
burst 發生的機率
通常都是用 exponential 的函數,以平均值(mean)來表示
計算平均 throughput、utilization、wait time 等等
也可以用來描述多個電腦的網路服務
由已知的 arrival rate 和 service rate
Little’s Formula
n = 平均的 queue 的長度
W = 在 queue 中的平均等待時間
λ = 進入 queue 的平均頻率
在穩定狀態(steady state)下,process 離開 queue 的頻率和進
入 queue 的頻率應該是相同的
故應滿足: n = λ x W
此規則應該對所有 scheduling 演算法和 arrival 分布都有
效
Simulations:模擬
使用分析或排隊理論的方式都有其限制
直接模擬實境更加精確
使用 Programmed model of computer system
Clock 是可變的(Variable)
收集相關的統計數據後作為演算法的效能分析
用來驅動模擬的資料可以由以下方式取得:
隨機數產生器
數學上(mathematically)或經驗上(empirically)的分布模式
追蹤實際系統發生的事件
示意圖:藉由模擬,來分析各種演算法的效能
Scheduler 的實作(implementation)
仍是有精確度的限制
如果直接實作新的 scheduler,並且在真實系統上測試的話
花費太高、風險太大
到時候使用的環境變化不一,不一定和測試時一樣
大部分有彈性的 scheduler 都可以被修改為 per-site 或 per-system 的特
製版本
或者是使用 API 來修改 priority
但同樣的仍受不同的環境影響
結論:環境因素影響很大
作業系統概論
Chapter 6: Process Synchronization
背景
Process 可以同時(concurrently)被執行
可能隨時遭到 interrupt,而其計算只進行到一部分
同一時間存取共用的資料,可能造成不一致的問題(inconsistency)
維持資料的一致性必須要確保互相依賴合作的程式(cooperation
process)的執行順序
舉例:Producer-Consumer 問題(請參考第三章筆記的第 10~11 頁)
Producer 在把資料產生出來後,必須把 counter 增加 1,讓 consumer
知道有資料產生
Consumer 把資料用掉之後,要把 counter 減去 1,釋放被用過的空間
Race Condition:process 間的互相競爭執行
兩個 process 因為執行順序不同,每次都有不同的結果,稱為 race
condition
以剛才的例子,由於把 counter 增加或減去 1 不是一個 atomic(單
一指令)的操作指令,也就是說:
counter++的某一種實作方式
register1 = counter
register1 = register1 + 1
counter = register1
而 counter—則是
register2 counter
register2= register2 - 1
counter = register2
當非 atomic 的指令被中斷執行時,就會有問題
舉例:counter 初始值為 5,producer 與 consumer 各別執行一次
自己的 counter++與 counter-正常預期結果下,counter 的數值在兩者執行結束後會保值原
來的值
但當碰到 race condition 時會出錯,以下為某一種發生 race
condition 的指令執行順序 (以時間點依序列出,P 代表
producer,C 代表 consumer)
1. P 執行:register1 = counter
Register1 = 5
2.
P 執行:register1 = register1 + 1
3.
Register1 = 6
切換到 C 執行(P 被 context switch 掉)
C 執行:register2 = counter
4.
Register2 = 5
C 執行 register2 = register2 - 1
5.
Register2 = 4
切換回 P 執行
Counter = register1(此時為 6)
6.
Counter = 6
切換到 C 執行
Counter = register2(此時為 4)
Counter = 4
結果為 counter 因為 race condition 而發生資訊錯誤
Critical Section Problem
每個 process 都有自己的 critical section
Process 可能更改某些共用變數或寫入檔案等等
當有一個 process 進入 critical section 時,就不該有其他 process 也
進入 critical section
否則可能發生 race condition 等問題
當 process 需要執行 critical section 時,會向 OS 要求權限
若允許,則會進入 entry section,之後執行 critical section,最後
執行 exit section,離開 critical section 模式
該 process 可以繼續執行剩餘的部分(remainder section),在此同
時,其他 process 也可以開始進入 critical section
處理 Critical section
必須要滿足某一些限制
Mutual Exclusion:互斥
同一時間內,只能有一個 process 在執行 critical section
Progress:進展
必須要確保每個 process 都有機會執行自己的 critical
section (公平問題)
Bounded Waiting:有限的等待
當 process 要求進入 critical section 時,不能讓 process 無
限制地等待(不能讓該 process 等太久)
根據 kernel 是否是 preemptive,有兩種處理方式
Preemptive:process 可能在 kernel mode 中執行時遭到
preempt
Non-preemptive:直到 process 離開 kernel mode 或者是自願
讓出 CPU 時才會被 preempt
避免了在 kernel mode 中發生的 race condition
Peterson’s Solution
簡單且良好解決 critical section 的方法,滿足互斥、進展、有限等待
缺點:只限於兩個 process
假設:load 與 store 都是 atomic 的指令(亦即無法被 interrupt)
兩個 process 會共用以下兩個變數:
int turn
儲存可以進入 critical section 的 process ID (假設為 i 與 j)
boolean flag[2]
每一個位置指示該 process 是否已經準備進入 critical section
例如:flag[i] = true 代表 process i 已經準備好進入
對於 process i 的演算法(process j 亦同,只是 i、j 互換)
do {
// Process i 已經準備好要進入 critical
flag[i] = true;
// 但怕 process j 已經在 critical 中執行了
// 所以先把 turn 設為 j,讓 j 先把 critical 完成
turn = j;
// 若 j 確實在 critical 中,則以下迴圈會成立
// 會使 process i 開始等待,直到 process j 自行把 flag[j]關閉
while (flag[j] && turn == j);
// 會 process j 已離開 critical,process i 開始執行 critical
/*process i 的 critical section*/
// 關閉 flag[i],結束 critical section 的執行
flag[i] = false;
/*process i 其餘的 section (remainder section) */
} while (true);
Synchronization Hardware
由硬體支援來解決一致性的問題
支援兩個以上的 process
利用 locking 的概念:只有取得 lock 的人才能進入 critical section
do {
acquire lock
//取得lock,此後其他process不可以再取得lock
/*critical section*/
release lock
//釋放lock,讓其他process可以取得並進入critical
/*remainder section*/
} while (TRUE);
對於單一處理器(uniprocessor):利用關閉 interrupt
關閉 interrupt 就相當於取得 lock
關閉 interrupt 後進入 critical section,其他 process 就無法藉由
interrupt 的方式中斷該 process 的執行(因為已遭關閉)
達到『有 lock 就不會被中斷』的目的
離開 critical 後,重新開啟 interrupt 的功能,相當於釋放 lock
對於多處理器的系統
關閉 interrupt 會讓所有 CPU 的 interrupt 都關掉。導致不必要的影
響 (降低效能)
某些 CPU 正在執行沒有 critical section 的 process,也會受到
關閉 interrupt 的效果,影響 concurrency 執行的能力
替代方案:由硬體提供特殊的 atomic 硬體指令
Atomic = 不可被 interrupt
這些指令看似由許多小指令組成,但卻無法被 interrupt
例如:test_and_set、compare_and_swap
test_and_set 指令
作用:將目標設為 TRUE,然後將原本的值回傳
定義:
boolean test_and_set (boolean *target)
{
boolean rv = *target; //取得舊的值
*target = TRUE; //把值設為 TRUE
return rv: // 回傳舊值
}
利用 test_and_set 來實作 lock
利用共用的 boolean 變數 lock,初始值為 FALSE
TRUE:lock 已被取得;FALSE:lock 沒人用
do {
//嘗試把 lock 設為 TRUE,並取得原本的 lock 值
//若原本的 lock 值已為 TRUE,就代表 lock 有人用了,故等待
while(test_and_set(&lock)); /* do nothing */
// 上述迴圈離開就代表原本的 lock 值變成 FALSE,
// 此外,通過上述指令也會讓 lock 值回到 TRUE
// 相當於當前 process 已經取得 lock
/* 執行 critical section */
// 釋放 lock,將之設為 FALSE,讓其他 process 可以取用
lock = false;
/* 執行 remainder section */
} while (true);
為何不直接看 lock 就好?而需要使用 test_and_set 去看?
因為可能兩個人都同時看到 false,導致兩者同時都進
去執行 critical section
test_and_set 是把檢查與設置一體化
原本怕看完之後在寫入之前被 interrupt 掉,然後沒改
到的值被別人看到
以剛才的例子,看到 lock 沒人用時,想去把它鎖起來
變成 true,但在這之前卻因為自己被 interrupt 掉,讓被
別人看到還沒鎖起來的 lock,導致兩個 process 都執行
但是用 test_and_set 之後,就不怕看後且寫入前被偷看(因為
在那段時間不怕有人 interrupt 自己)
compare_and_swap 指令
作用:指定一個期望的值,如果目標值與期望值相等,則把目標
值設為想要的值,並把原來的目標值回傳
定義:
Value:想要檢查的變數
Expected:期望的數值
New_value:想要的值
int compare_and_swap(int *value, int expected, int new_value)
{
int temp = *value; // 把原本的值先存起來,用於回傳用
if(*value == expected) // 檢查舊的值是不是和期望的值相同
*value = new_value; // 若相同,則會把舊的值用想要的值取代
return temp; // 無論如何都會回傳舊值(無論是否滿足期望的值)
}
利用 compare_and_swap 來實作 lock
與上一個部分使用 TRUE/FALSE 類似,這裡用 0 代表 FALSE,1
代表 TRUE
初始的 lock 值為 0 (即 FALSE)
do {
// 檢查 lock 是否為 0
// 若 lock 原本不為 0,則回傳值會是 1,會不停執行等待迴圈
// 若 lock 為 0 則把其值設為 1,代表取得 lock
while(compare_and_swap(&lock, 0, 1)!= 0); /*do nothing*/
/* critical section */
// 釋放 lock
lock = 0;
/* remainder section */
} while (true);
但無論是 test_and_set 或者是 compare_and_swap,都無法保證
bounded wait(有限的等待時間)
因為到底誰可以在 lock 是 FALSE 時執行 compare_and_wait 來取得
lock 是隨機的(OS 並沒有特別安排每一個 process 輪流取得 lock)
利用 test_and_set 來實作滿足有限等待(bounded-wait)的 lock
共用的變數:假設有 n 個 process
boolean waiting[n];
指示某一個 process 是否在等待中了,若在等待中,
則無法進行往下的程式
boolean lock;
全都初始化為 false
do {
waiting[i] = true; // process i 需要 lock,開始等待
key = true; // 用來存放取得的 lock 用的暫時變數
// 不斷的取得 lock 的值,當 lock 值是 false 時,此迴圈就會結束
// 至於檢查 waiting[i]的用意是,如果之前有 process 結束 critical
// 發現 process i 仍在等待中,就會直接把 lock 交給 process i
// 然後把 waiting[i]設為 false,讓 i 直接進入 critical
while (waiting[i] && key){
key = test_and_set(&lock);
}
waiting[i] = false; // 可以執行 critical 了,故已經不是等待狀態
/* critical section */
// 藍色部分是處理 bounded wait 的主要指令
// 當 process i 完成自己的執行後,會繼續檢查之後有沒有人需要 lock
j = (i + 1) % n;
// 不斷往後找,找到某一個正在等待中的 j 為止
while ((j != i) && !waiting[j])
j = (j + 1) % n;
if (j == i) // 如果發現 j==i,就是代表繞了一圈還沒找到人需要 lock
lock = false; // 將 lock 釋放掉
else // 找到 process j 需要 lock,直接將 j 的等待設為 false
waiting[j] = false;
// 等待關閉後,process j 在之前『while (waiting[i] && key)』
// 的地方等待的 while 迴圈就會結束,process j 於是取的 lock
/* remainder section */
} while (true);
為何有辦法達到有限等待?
因為 lock 的讓予是繞圈循環檢查的,而非隨機由 OS 安
排
每個 process 最終都會被讓予到取得 lock 的機會
Mutex Locks
提供一個更簡單的 API 給使用者使用
提供兩個 atomic 的函式 (通常都是用硬體的 atomic 指令來實作)
acquire():取得 lcok
release():釋放 lock
定義分別為:
acquire() {
while (!available)
; /* busy wait */
available = false;;
}
release() {
available = true;
}
用法:在 critical section 開始前 acquire lock,結束後 release lock
缺點:此種方式使用 busy waiting,會浪費 CPU
因此此種 lock 又稱為【spinlock】
Semaphore(信號燈)
就像停車場入口指示當前剩餘車位的數字,控制當前還剩下多少資源
(停車位)可以使用
當停車位有剩時,才可以進入停車場
否則就只能等在入口,直到有空位
Semaphore S 是一個整數變數值,代表當前剩餘資源數量
有兩個主要的 atomic 操作:
Wait():進入停車場,在入口等待資源,直到 S 大於 0 之後,才會
繼續執行剩下的程式,並把 S 的值減去 1(相當於用掉一個停車位)
Signal():離開停車場,把 S 的值增加 1,以便其他人使用停車位
定義分別為:
wait (S) {
while (S <= 0)
; // busy wait
S--;
}
signal (S) {
S++;
}
Semaphore 的用處:
Binary semaphore:
整數變數 S 的值只有 0 或 1 (就如同 true 和 false)
效果就如同 mutex lock
Counting semaphore:
整數值不只 0 或 1
可以用來控制 process 存取有限數量的資源(例如同一時間最
多只有固定數量的 process 可以存取某些資料或硬體)
Semaphore 可以用來處理同步問題,控制指令執行的順序
情境:process P1 需要執行指令 S1,P2 需要執行指令 S2,但
是希望 S1 在 S2 之前執行
使用 Semaphore,假設其變數名稱為 synch,初始數值為 0
用法:
讓 S1 結束後用 signal()把 synch 增加 1
此後 S2 的 wait()看到 synch 大於 0,就把 synch 減去 1,
並執行 S2 內容
由於 synch 大於 0 的時機發生在 S1 執行結束後,所以可
以確保 S1 在 S2 之前執行
P1:
S1;
signal(synch);
P2:
wait(synch);
S2;
不使用 busy waiting 的 semaphore
避免 CPU 浪費時間在 while 迴圈中等待不做事
避免使用 busy waiting 的方法:
讓每一個 semaphore 擁有一個 waiting queue (就如停車
場門口的排隊隊伍)
Queue 中的每一個 entry 都是由兩個資料組成:
Value(整數值,類似於原始 semaphore 的整數值 S)
Pointer(指向 queue 中的下一個 entry)
typedef struct{
int value;
struct process *list;
} semaphore;
兩種新操作:
Block:把新進來要取得 semaphore 的 process 放進
waiting queue
Wakeup:將 process 從 waiting queue 中移除並放入
ready queue
Wait 與 signal 的定義
由於要確保 bounded-waiting,所以 list 取出的方式是用
FIFO
wait(semaphore *S) {
S->value--;
if (S->value < 0) {
// 如果新來 process 造成了 S 的 value 變成負的,就代表原來是 0
// 也就是沒有資源可用,所以放進 waiting queue
add this process to S->list;
block();
}
}
// 此外,當有很多 process 都進來時,S 的 value 會變得更負,該值指示了當前
// 有多少 process 正在等待(負越多,越多 process 在等)
signal(semaphore *S) {
//釋放資源
S->value++;
if (S->value <= 0) {
//
//
//
//
value <= 0 代表 queue 內還有人在等,所以把 process P 拿出來
如果 value++之後(釋放資源後),value 大於 0,就代表沒人在等
假設原本 value 是 1,wait 後變成 0,signal 裡++變成 1,就代表沒人等
故如果大於 0 就不必把去 list 拿 process 出來
remove a process P from S->list;
wakeup(P);
}
}
Deadlock, Starvation, Priority Inversion
Deadlock:兩個 process 互相等待
舉例:process P0 與 P1,使用兩個 semaphore S 與 Q
下圖:
紅色箭頭:兩個 process 的執行順序
此後 P0 與 P1 互相都在 wait 中等待自己需要的 semaphore,
而沒有人可以執行到 signal 來釋放 semaphore,導致永遠的
互相等待(deadlock)
Starvation
在 no-busy-waiting 的 semaphore 中,如果 list 的移除順序是使用
LIFO(最後進去的最早出來),就可能會導致某些 process 一直在
queue 裡等待
Priority Inversion
優先權低的 process 取走了高優先 process 所需的 lock/semaphore
導致高優先 process 的 critical section 會比低優先 process 的
critical section 晚執行
範例:有三個 Task 1~3,優先由高到低
(1). 一開始最低優先的 Task 3 最早抵達並執行
(2). Task 3 然後取得 semaphore 並進入 critical section
(3). Task 1 抵達,由於高優先,即使 Task 3 在 critical section,仍
然遭到 preempt
但仍是不能違反只能有一個 process 進入 critical section
的規則,故 Task 1 即使取得執行權也只能直接非 critical
section 的部分
(4). Task 1 可以執行自己的非 critical section
(5). Task 1 想要進入 critical section,想取得 semaphore,但已經
用盡,遭到 block
(6). Task 3 得以繼續執行 critical section
(7). Task 2 preempt 掉 Task 3
(8). Task 2 執行
(9). Task 2 結束,Task 3 恢復執行
(10). Task 3 繼續執行 critical section
(11). Task 3 結束 critical section,釋放 semaphore
(12). Task 1 取得 Semaphore,進入 critical section
Task 1 的 critical section 明明早該執行,卻因為 Task 3 提早取得
semaphore,導致 Task 1 無法執行,要等 Task 3。但此時 Task 2 卻
能藉由中斷 Task 3 的方式搶得優先執行權(只要 Task 2 不使用
critical section)
最後看到的結果是,優先較低的 Task 2 卻能在大多數時間比 Task
1 更能優先執行,造成長時間的 Priority Inversion,讓 Task 1 等很
久
解決 Priority Inversion 的兩種方法
方法一:Priority Inheritance:優先權繼承
若高優先的人需要低優先的 lock,則把低優先的 lock 的優先
提高跟高優先一樣
前一個例子:Task 3 因為拿了 Task 1 需要的 lock,所以 Task 3
繼承了 Task 1 的優先權,讓 Task 3 不會被 Task 2 給中斷。直
到 Task 3 釋放其 lock 後,其優先權才恢復原本值
缺點:會產生 run-time 的 overhead
原因:系統需要即時檢查每一個存取該 lock 的 process
的優先權
優先權調整的時間點:
直到當場碰到有需要該 lock 的,才調整
需要當場即時偵測
方法二:Priority ceiling:直接把優先變成最高
當有人取得某個 lock 時,就提升該 process 的優先權
所有可以使用此 lock 的 process 中誰最高,就調成那麼
高
該優先權等級稱為 ceiling priority
以前一個例子來看:Task 3 會直接取得最高的優先權,所以
不會被 Task 2 中斷 (即使 Task 1 當下沒有出現取用該 lock)
只會產生 compile-time 的 overhead
因為在程式碼中,發現有取得 lock 的片段,就插入能夠
調整優先的 system call 了,不必等 run-time 調整,當然
也【不必理會】是不是有人要跟他搶 lock
缺陷:由於取得 lock 之後直接取得最高優先,導致其他
原本高優先卻不需要 critical section 的 process 也無法執
行 (因為 lock 的優先被調到最高了)
優先權調整的時間點:在程式碼中看到用 lock,就直接調整
到最高了,直到 critical section 程式碼結束後就加入恢復優先
權
解決 Priority Inversion 的兩種方法的範例:(同之前的例子)
方法一:使用 Priority Inheritance:優先權繼承
在(3)時,Task 3 取得 Semaphore,此時優先權仍未改變
在(6)時,Task 1 嘗試取得 Semaphore,但已被 Task 3 取走,
故 Task 3 繼承了 Task 1 的優先權,優先權提高
於(7)時段內,Task 3 不會被其他優先低於 Task 1 的 process(例
如 Task 2)中斷,直到 Task 3 的 semaphore 釋放為止
(8)時段,Task 3 釋放 semaphore,優先權回歸原本值,使得
Task 1 取得 Semaphore 並中斷掉 Task 3 執行
整體來看雖然仍有 Priority Inversion
在 Task 3 繼承 Task 1 高優先權,直到 Task 3 釋放
semaphore 的這段時間,也就是第(7)時段
無法避免,因為即使 Task 1 把 Task 3 中斷,仍無法取得
Semaphore 來執行
但與之前沒使用任何解決方法的例子來看,相對的
Inversion 的時間沒那麼長
方法二:使用 Priority Ceiling
時段(2),Task 3 取得 semaphore,此時優先權立刻變成與
Task 1 一樣高
時段(4),Task 1 無法中斷 Task 3 的執行(因為優先一樣高)
時段(6),Task 3 釋放 semaphore,優先權降回原來的值。此
時就被 Task 1 給中斷,Task 1 繼續執行
整體仍有 Priority Inversion
Inversion 時間確實有比較短
發生 inversion 的時間與使用 Priority Inheritance 一樣長
但與 Priority Inheritance 相比,Task 1 前面那一段沒有要
critical section 的部分仍無法執行
此次例子中的(7)時段應該要像上個例子那樣在(5)時段執
行才對
導致整體上 Task 1 整個被延遲的程度比使用 Priority
Inheritance 還要大(整體往後移動較多 Task 1 感覺更晚被執行)
Bounded Buffer Problem
Producer 與 Consumer 的問題,但是 Buffer 大小有限制
共用的資料:假設 buffer 有 n 個變數
長度為 n 的 buffer
semaphore mutex = 1 ;
作用:防止有多個人同時寫入 buffer 內容相關的變數
例如:buffer index
semaphore full = 0 ;
指示當前有多少個 buffer item 已經可供取用
semaphore empty = n ;
指示當前 buffer 空間還剩多少
Producer:
放 貨物
do {
...
* 將新產生的 item 放到 next_produced */
...
wait(empty); // 等待:需要至少有一個以上的空間來存放
wait(mutex); // 等待修改 buffer 相關變數的 mutex
/* 將 next_produced 的資料放入 buffer */
signal(mutex); // 寫入 buffer 完成,釋放 mutex
signal(full); // 將 full 增加 1,表示多一筆可用資料
...
} while (true);
Consumer:
清 貨物
do {
...
wait(full); // 等待:至少要有一個以上的資料才可繼續進行
wait(mutex); // 等待修改 buffer 相關變數的 mutex
/* 將下一個 item 從 buffer 中取出放到 next_consumed */
signal(mutex); // 修改 buffer 完成,釋放 mutex
signal(empty); // empty 增加 1,代表多出一個可用空間
...
/* 將資料 next_consumed 消耗掉 */
} while (true);
Readers-Writers Problem
有兩種 process 類別:reader 與 writer
Reader:只能讀取資料,不能進行任何修改
Writer:可以讀寫資料
同一時間內:
可以有多個 reader 讀取資料
但若有 writer 正在存取資料時,其他 process 不管是 reader 還是
writer 都不能存取(包括讀取和寫入)
相當於資料庫 Transaction 中,reader 為 S-lock,writer 為 X-lock
有許多種變形,writer 與 reader 有不同的對待方式,都與有先權有關
變形一:除非 writer 已取得權限要使用該 shared-object,否則
reader 就不必等待,可直接存取
變形二:當 writer 準備好時,盡可能讓 writer 越早執行越好
都可能產生 starvation 而有更多的變形
某些系統有提供 reader-writer lock 來解決此問題
共用的資料:
data set
semaphore rw_mutex = 1;
只允許一個 process 進行讀寫
semaphore mutex = 1;
reader 改變 read_count 變數時用來保護該變數不被多個
reader 同時寫入
integer read_count = 0;
計算 reader 的數量
Writer 程式碼:
do {
wait(rw mutex);//取得讀寫用的 lock
...
/* 進行寫入
...
*/
signal(rw mutex); //釋放讀寫 lock
} while (true);
Reader 程式碼:
do {
wait( mutex ); // 需要修改 read_count 變數
read count++;
if ( read count == 1 )
wait( rw mutex );
//
//
//
//
只有第一個要求 read 的人,要去等待 rw_mutex 被釋放
且同時此 reader 也取走了 mutex 並等待
讓其他 reader 也因為取不到 mutex,同樣也會進行等待。
直到領頭的(也就是第一個)reader 取得 rw_mutex 後
// 順便釋放 mutex,讓大家一起往下執行來讀取
signal( mutex );
...
/* 進行讀取 */
...
wait( mutex ); // 要寫入 read_count 所以需要 mutex
read count--;
if ( read count == 0 )
signal( rw mutex );
// 最後一個離開的 reader,
// count 剛好變為 0,就把 rw_mutex 加回去,
// 讓可能等待中的 writer 可以開始寫入
signal( mutex );
} while (true);
哲學家吃飯問題:Dining-Philosophers Problem
哲學家只有兩種動作:Eating(吃飯)與 Thinking(思考)
在一圓桌上,每位哲學家間都放有一支筷子,中間放有一鍋飯
當某個哲學家想要吃飯時,必須從他左右兩邊鄰近的位置各取一支筷
子(一次取一支,先左後右或先右後左)
必須要兩支筷子都拿到了才可以開始吃,吃完後兩支都放回去
代表含意:
哲學家:代表 process
筷子:代表 lock
中央一鍋飯:代表共用的資料,需要兩個 lock 才能存取
範例:有 5 位哲學家時
共用的資料為:
中央一鍋飯(data set)
Semaphore chopstick [5] //代表 5 支筷子,初始值皆為 1
不能用單一 semaphore,因為使用單一 semaphore 的話
相當於筷子可以任意從別的地方拿,而不用從隔壁拿
每一個哲學家最直覺的演算法:
do {
wait ( chopstick[i] ); // 等左邊筷子
wait ( chopStick[ (i + 1) % 5] ); //等右邊筷子
// eat 吃飯
signal ( chopstick[i] ); //釋放左邊
signal (chopstick[ (i + 1) % 5] ); //釋放右邊
// think 思考
} while (TRUE);
但剛才的演算法是有問題的
大家同時都拿走左手邊那支筷子時就會造成 deadlock
數種可能的解決辦法
1.
2.
3.
在 n 個位置(n 支筷子)的情況下,最多只允許 n-1 為哲學家
等到兩邊筷子都可以拿時,才一起拿走。而不是拿一支等一支
使用不對稱的方式
奇數編號的哲學家從左邊開始拿第一支筷子,偶數編號則從
右邊開始拿第一支筷子
使用 Semaphore 的問題
錯誤的使用方式
Signal 與 wait 的順序顛倒,導致無法滿足互斥
signal (mutex) → wait (mutex)
忘了寫 signal:造成 deadlock
wait (mutex) → wait (mutex)
Deadlock 與 Starvation 問題
Monitors
高階的 API,讓使用者不必自己明確管理 semaphore 等同步問題
一次只允許一個 process 進入 monitor 執行
但因為較方便且抽象,能做到的事情較少,能力較低
基本結構:
monitor monitor-name
{
// 共用變數的宣告,由於只會有一個 process 進入
// 所以這些共用變數的存取是安全的
Data…
// 函數宣告
procedure P1 (…) { … }
procedure Pn (…) {…}
// monitor 一開始的初始化程式碼
Initialization code (…) { … }
}
共用的資料
排隊中的 process
Monitor 中進行的操作
初始化的程式碼
條件變數:Condition Variables
宣告方式:
condition x,y;
相關操作:
x.wait():process 進入等待狀態,直到有其他 process 進入
monitor 並執行 x.signal()
x.signal():將某一個過去因為呼叫 x.wait()的 process 喚醒回來
執行,若此時沒有 process 在 wait,則 signal 不會造成任何影
響
與 semaphore 類似
但 semaphore 的 signal 無論如何都會增加 1
而 condition variable 在沒有人 wait 時,就不會增加其值
由於當某 process 因為 condition 的關係進入等待狀態時,該
process 已經在 monitor 之中
會在 monitor 中替每一個 condition 建立一個 queue
在 monitor 內,如果發生了有 process 在等待某一些條件(condition
variable),此時為了效率,會讓下一個 process 進來執行
不怕資料共用資料被同時修改,因為原本那一個 process 已經進入
waiting,同時執行的還是只有一個(也就是新進來的那一個)
如果新進來的 process 使用了 x.signal(),讓 x 中可以有一個人恢復
執行,但要滿足同一時間只有一個人能執行,有兩種處理方式:
1. 新來的繼續,舊的等待新來的 process 執行結束
2. 舊的恢復執行,新的 process 進入等待
各有優缺點,由實際的實作者決定
使用 Monitor 來解決哲學家吃飯問題
monitor DiningPhilosophers
{
enum { THINKING; HUNGRY, EATING) state [5] ;
condition self [5]; // 指示哲學家自己是不是在等待中
void pickup (int i) { // 哲學家 i 想拿筷子
state[i] = HUNGRY; // 自己設為飢餓狀態
test(i); // 檢查並嘗試取得筷子
// 若檢查後仍沒有進入 EATING,就代表沒拿到筷子
// 故自己進入等待狀態
if (state[i] != EATING) self [i].wait;
}
void putdown (int i) { // 哲學家 i 放掉筷子
state[i] = THINKING;
// 檢查左右兩邊鄰居是不是需要筷子且正在等待
// 若是,則嘗試讓他們再拿一次筷子
test((i + 4) % 5); // 左邊
test((i + 1) % 5); // 右邊
}
// 檢查哲學家 i 是不是需要拿筷子,若是,則嘗試拿筷子
void test (int i) {
// if 條件:左右邊都不能正在 EATING,且自己需要是飢餓狀態
if ( (state[(i + 4) % 5] != EATING) &&
(state[i] == HUNGRY) &&
(state[(i + 1) % 5] != EATING) )
{
// 取得筷子,進入 EATING 狀態,並讓自己取消等待
state[i] = EATING ;
self[i].signal () ;
}
}
// 一開始大家都在 THINKING
initialization_code() {
for (int i = 0; i < 5; i++)
state[i] = THINKING;
}
}
定義好 monitor 後,哲學家部分的程式碼就變得簡單:
// 由 monitor 來管理同時只有一個人能執行 monitor
DiningPhilosophers.pickup (i);
/* EAT */
DiningPhilosophers.putdown (i);
此種方法可以避免 deadlock,但仍可能有 starvation
不會有 deadlock 的原因:每位哲學家只在兩邊都有筷子可以拿的
時候,才會去拿筷子
使用 Semaphore 來實作 Monitor
一般變數:
semaphore mutex;
初始值為 1
控制一次只能有一個 process 能存取
semaphore next;
初始值為 0
用於 condition variable,藉由 signal 此變數,來告知等待中的
process 可以離開等待繼續執行了
int next_count = 0;
用於 condition variable,大於 0 代表有人因為使用 wait()的關
係而進入等待,數值為正在等待的 process 數量
用途:紀錄 semaphore 的數量,以便當沒有人 wait 時可以不
要呼叫 signal
在 Monitor 中的每一個函數 F 會被改寫為:
wait( mutex ); // 等待 mutex 確保只能有一個 process
…
body of F; // 執行函數內容
…
if ( next_count > 0 ) //若還有人在等待
signal( next ) // 把 next 增加一,讓等待的人去執行
else
signal(mutex); // 沒人等待,則直接釋放 mutex
對於每一個 condition variable x,新增以下變數
semaphore x_sem;
初始值為 0
為 1 時才可以讓被暫停的 process 執行
故一開始,沒人 signal 前,碰到等待 x 的 process 都不能執行
int x_count = 0;
記錄當下有多少 process 正在等待 x
類似於 next_count 變數,可以讓沒有人使用 wait(x_sem)時,
不要使用 signal(x_sem)來增加 semaphore 的值
x.wait() 的實作:
x-count++; // 等待 x 的數量增加
if ( next_count > 0 ) // 如果自己之後還有別人在等
signal( next ); // 把執行權交給下一個 process
else // 沒人在等,就直接釋放 mutex
signal( mutex );
wait( x_sem ); // 等待有人呼叫 x.signal
x-count--; // 到此處代表有人呼叫 signal 了,故計數減少 1
x.signal() 的實作:
if ( x-count > 0 ) { // 有人在等待 x 時才要處理
next_count++;
// 此演算法採用舊的 process 優先執行
// 所以增加 next_count 標示自己需要等待
signal( x_sem ); // 讓等待 x 的 process 可以執行
wait( next ); // 自己則等待執行中 process 給予 next 訊號
next_count--; // 收到 next 訊號,繼續執行,故計數減 1
}
當有多個 process 同時在 monitor 中需要恢復執行時
情境:有多個 process 因為 x.wait()而等待
現在收到一個 x.signal(),則哪一個 process 該被恢復執行?
FCFS 並不是個適當的方法
使用 condition-wait:
使用方法:x.wait(c)
C 為優先權數值(priority number)
C 值最低的 process,優先權最高,會優先被恢復執行
A Monitor to Allocate Single Resource
利用 monitor 實作單一資源分配器
某一個資源一次只能有一個 process 能存取
存取時需要指定存取的時間長度
多個人正在等待中時,不使用 FCFS 方式
指定存取時間長度越短的,越優先執行
實作方式:
monitor ResourceAllocator
{
boolean busy; //指示該資源是否正在被使用
condition x;
void acquire( int time ) {
if ( busy ) // 若該資源有人在用,就根據使用時間等待
x.wait(time); //用越短,優先越高
busy = TRUE;
}
void release() {
busy = FALSE;
x.signal();
}
initialization code() {
busy = FALSE;
}
}
使用方式:
R.acquire( t );
. . .
/* 存取該資源 */
. . .
R.release( );
Synchronization 的範例(概述)
Solaris 作業系統
使用多種 lock 來支援 multitasking, multithreading, multiprocessing
使用【adaptive mutexes】:會根據情況調整的 spin lock
適用於短小的程式碼區塊(code section)
行為類似於原始的 spin-lock 的 semaphore
若拿 lock 的人正在執行,可能很快就結束 lock 了,所以直接
等就好
若拿 lock 的人沒在執行(非 running state),就不用 spin 了,直
接 sleep (因為可能要等很久)
對於較長的程式碼區塊,則會使用 condition variables 與 readerswriters
使用 turnstiles 來調整到時候 thread 要醒來的順序
包含 adaptive mutexes 與 readers-writers
使用 priority inheritance 來解決 priority inversion 的問題
Windows XP
在單一處理器的系統:使用 Interrupt Mask 來保護共用資料
在多處理器的系統則使用 spinlocks
Spinlock 通常等一些比較短的 process
故設計為 spin-locking 的 thread 不會被中斷
提供 dispatcher objects
行為類似 lock
Events:如同 condition variables
Timer:當 thread 把時間用完時通知該 thread
dispatcher objects 有兩種狀態:
signaled state
代表 object 是可以使用的
相當於可以取用的 lock
Non-signaled state
代表 object 是不可使用的
相當於被拿走的 lock
Linux 作業系統
2.6 版以前:kernel 為 non-preemptive,使用關閉 interrupt 來實作
短期的 critical section
2.6 版以後:kernel 為 fully preemptive
可以把 kernel mode 的 process 中斷掉
中斷後別的 process 也可以進入 kernel mode
有提供 semaphore、spinlock、reader-writer
在 SMP 架構(對稱式 multiprocessors)下,多使用 spinlock 來處理較
短的程式碼區段
較長的程式碼區段則使用 mutex 或 semaphore
在單一處理器的版本,會使用『關閉 kernel interrupt』來取代
spinlock
Pthread API
為可在 user-level 使用的 API,為 OS-independent
提供:mutex、condition variable、read-write lock、spinlock
Atomic Transactions
某一段區塊的程式碼,不是全部做完就是都沒做
與資料庫領域相關
一系列的操作最後以 commit 指令作為結束,只有在 commit 之
後,該系列的指令才算成功
若中途發生錯誤或者是執行到 abort 指令,則該系列對資料所造
成的變更都要全部復原
使用 atomic 區塊不需要牽涉到 lock,故不會產生 deadlock
Atomic 區塊可以由軟體或硬體來實作
Log-Based Recovery
最常見的是 write-ahead logging
為了要能夠復原資料,故在進行變更時,會記錄原本是甚
麼、改成了甚麼等等的資訊
當 transaction 開始時,把資料寫到 log
為了要在系統突然崩潰時能夠保留紀錄,log 檔案通常都得寫
入到硬碟陣列等 stable storage (而不是記憶體或 cache)
作業系統概論
Chapter 7: Deadlocks
System Model
系統中存在著一系列的資源(resource,例如 CPU cycle、記憶體空間、
I/O 裝置等),分別為 R1R2…Rm
每一個種資源 Ri 只會有 Wi 個實體(instance)可以使用,也就是同時間最
多 Wi 個 process 可以取用 Ri 資源
每一個 process 使用資源的流程:
Request:要求資源,等待可用的資源被配給到該 process
Use:使用資源
Release:釋放資源以供其他 process 使用
Mutex Lock 產生 deadlock 的例子:假設有 2 個 mutex lock
Process 1 (P1)
Process2 (P2)
Lock(mutex1) (1)
Lock(mutex2) (3)
…
Lock(mutex2) (2)
Lock(mutex1) (4)
…
Unlock(mutex2)
Unlock(mutex1)
Unlock(mutex1)
Unlock(mutex2)
執行流程:
(1). P1 取得 mutex1
(2). P2 取得 mutex2
(3). P1 想取得 mutex2,但已被 P2 取走,故等待
(4). P2 想取得 mutex1,但已被 P1 取走,故等待
造成 P1 與 P2 無止境的互相等待
形成 deadlock 的四大條件 (如果同時滿足,則會造成 deadlock)
1. Mutual exclusion:互斥
某一個資源的 instance 同一時間只能有一個 process 持有
2. Hold and wait:持有並等待
某一個 process 持有某一個資源,但卻也在等待取得另一個被其他
process 佔去的資源
3. No preemption:不可奪取
4.
已經被某 process 佔去的資源是無法被奪取的,除非該 process 自
願釋放該資源
Circular wait:循環等待
存在一群 process P0,P1,…,Pn 互相等待對方釋放資源
P0 等待 P1,P1 等待 P2,…,Pn 等待 P0
Resource-Allocation Graph:資源分配圖
由一群頂點 V 與邊(edge)E 組成
頂點 V 有兩種類別
P = {P0,P1,…,Pn},代表系統中 process 的集合
R = {R0,R1,…,Rm},代表系統中各類別資源的集合,且每種資源都有
自己的 instance 數量
E 的兩種類別
Request edge:由 Pi→Rj 的邊,代表 process Pi 需要資源 Rj
Assignment edge:由 Rj→Pi 的邊,代表已經把資源 Rj 分配給 Pi
圖例:
範例:
P = {P1, P2, P3}
R = {R1, R2, R3, R4}
R1: 1 instance, R2: 2 instances
R3: 1 instance, R4: 3 instances
E = {P1→R1, P2→R3 , R1→P2 ,
R2→P2, R2→P1, R3→P3}
此例子中不會產生 deadlock,因為沒有形成 cycle
但以上範例如果加上一條 edge:P3→R2
𧃍
產生以下 Graph
典 !⻍!!!!!:
簬
會形成 deadlock,每一個 process 確實都在等待某個資源而無法執
行
要 回到 ⾃⼰
圖中的兩個 cycle: 最後
P1 → R1 → P2 → R3 → P3 → R2 → P1
P2 → R3 → P3 → R2 → P2
並非有 cycle 形成就會產生 deadlock,以下例子:
Brnns
圖中確實有 cycle,但並沒有形成 deadlock
原因:並非所有 process 都在等待。P4 並沒有等待其他資源,所
以可以繼續執行。當 P4 執行結束後就會釋放資源 R2,使得其他
process 可以繼續執行
基本概念
Graph 沒有 cycle:不會有 deadlock
有 cycle 時:
若每個資源只有一個 instance,則必定 deadlock
若資源有多個 instance,則『可能』產生 deadlock
處理 deadlock 的幾種方法
1. 確保系統不會進入 deadlock 狀態
Deadlock prevention:預防 deadlock,事先制定好某些規則,使得
deadlock 不會發生
Deadlock avoidance:避免 deadlock,要執行某 process/分配資源
之前事先偵測是否會在未來造成 deadlock,若有,則不執行該
process 或分配資源
2. 允許系統進入 deadlock 狀態,但可以偵測並復原
Deadlock detection:偵測 deadlock
Deadlock recovery:從 deadlock 狀態中恢復
3. 假裝 deadlock 不會在系統中發生,忽略 deadlock 問題而不處理
事實上此模式被大多數作業系統採用,包含 UNIX
Deadlock prevention:預防 deadlock
設法使 deadlock 四大條件中的某一個無法滿足
以下將逐條分析避免四大條件發生的可行性
1. Mutual Exclusion:互斥
由於很多實際上的資源都是真的必須互斥(例如:印表機只能同時
印一份資料),所以難以讓資源沒有互斥的特性,故不可行
2. Hold and Wait:持有並等待
必須保證當有 process 要求取得其他資源時,該 process 必不可以
持有任何其他的資源
方法一:
規定該 process 必須在開始執行前就取得所有資源(例如哲學
家吃飯一次就要拿完兩支筷子)
方法二:
規定每一個 process 要取得新資源時,必須釋放原本持有的所
有資源
缺陷:
資源使用率(utilization)低,因為可能某些資源只需要在某時
段使用一小段時間,卻在整個執行期一開始就被該 process 拿
走,導致大部分時間此資源其實都沒有被用到
造成飢餓(starvation),因為資源一開始就被某個 process 全部
拿走了(即使該 process 沒有馬上要用),使得其他 process 必
須等待該 process 整個執行結束
3.
No preemption of resource:不可奪取資源
允取資源被奪走:如果某個 process 想拿別的資源卻無法立刻拿
到,就要把自己所有的資源都釋放掉(相當於被 preempt 掉)
被奪走資源將會加入原本該 process 的 waiting list,代表被奪走的
資源也是該 process 需要等待的資源之一
當新需求的資源與被奪走的資源都同時可以使用,該 process 才可
以繼續執行
4.
Circular Wait:循環等待
將所有資源利用函數 F 依類別編號來避免循環等待
把所有資源編號(例如 1、2、3),拿到某一個資源以後,以後要拿
的其他資源的編號都要比原本資源的編號還大
例如:第一次拿了編號 3,之後要拿的資源只能是 4、5、6 等
等,而不能拿 1 和 2。如果真的要拿編號小的資源,就必須要把
編號大的資源釋放掉(釋放掉資源 3,之後才可以去拿編號 1 或 2)
如果同一種 type (編號相同)的資源有多個,那麼這些資源必須要
一次全部被指派出去,不能有留一部分給其他 process 的情形
換句話說:編號 X 的資源類別只有兩種狀態:
被拿走 vs 還沒被拿走
最一開始 mutex lock 的 deadlock 例子
令 F(mutex1) = 1,F(mutex2) = 5
則 Process 2 就必須依照規定的順序先取得 mutex1 再取得
mutex2
函數 F 的決定:必須要依照實際資源的相依性來決定
例如:再使用印表機 printer 之前,會先使用到磁帶 tape,所
以定義 F(tape) < F(printer)
Witness:FreeBSD 中使用的 lock-order verifier
主要特色:動態決定資源的編號
舉例:原本 mutex1 與 mutex2 沒有編號。只有當有 process
真的取到資源時,根據使用情形並給資源編號
當 Process 1 使用 mutex 之後,才給予 mutex 編號。未來
Process 2 要使用時才會得知編號順序有問題。
解除編號設定的可能時機:沒有任何人在使用那些資源時
但使用 lock ordering 仍是有可能產生 deadlock
例子(銀行交易問題)
規定:必須要先 lock 來源帳戶,才可以 lock 目標帳戶
Lock(來源帳戶)
Lock(目標帳戶)
…(交易處理)…
Unlock(目標帳戶)
Unlock(來源帳號)
當有兩個 process 都要執行此類交易行為時:(假設有帳戶 A 與 B)
對 Process 1 來說,來源帳戶是 A,目標帳戶是 B
對 Process 2 來說,來源帳戶是 B,目標帳戶是 A
執行順序:
(1). Process 1 鎖定來源帳戶 A
(2). Process 2 鎖定來源帳戶 B
(3). Process 1 想要鎖定目標帳戶 B,但無法,必須等待
(4). Process 2 想要鎖定目標帳戶 A,但無法,必須等待
造成 deadlock
即使都有滿足『先鎖定來源帳戶,再鎖定目標帳戶』,仍會有
deadlock
Deadlock Avoidance
OS 在有 Process 發出資源需求時,檢查與評估當時的情況,若可能在
配發資源後造成 deadlock,就不允許配發資源給該 process
最簡單概念:確保維持在”safe state” (安全的狀態)
需要事先知道:某一個 process 最多需要用到哪一些資源
(maximum number of resource)
如果某一個 process 的資源要求會造成系統離開 safe state(或者
說,進入”unsafe state”),就不配發資源給那一個 process
如果系統在 Safe state 中,就保證絕對不會有 deadlock 發生
但不代表,『不在』safe state 就一定會有 deadlock,只是有機率而
已
Safe State
在 safe state 的條件:
系統中的所有 process 存在某一種執行順序,假設有 n 個
process,則這些 process 分別從 P1、P2…Pn 依序執行
這 n 個 process 中其中任何一個 process Pi 都要滿足
『只要把 P1…Pi-1 這些 process 執行結束後,將所佔有的資源
都釋放掉,Pi 就可以有足夠的資源執行』
也就是說,P1…Pi-1 所占有的資源加上當前系統剩下沒人用的
資源,足以讓 Pi 執行
試圖找出一個能夠滿足所有 process 的『最大資源需求』的執行順
序
範例:假設系統有 12 單位的資源,三個 process:P1,P2,P3
最大需求
當前需求
最大-當前
P0
10
5
5
P1
4
2
2
P2
9
2
7
在尋找執行順序前,先把所有 process 的『當前需求』先滿足
因此,系統所剩的可用資源為 12-5-2-2 = 3 單位
剩下 3 單位的資源,可以先分配給 P1 執行,因為 P1 的『最大
-當前』 < 3,所以首先確定了 P1 一定可以執行完畢,故第一
個執行的 process 是 P1 (分配了 2 個給 P1,還剩下 1 單位的資
源)
因為已知 P1 可以執行完畢,所以當 P1 執行完畢後,其所持的
最大資源(共 4 個)可以被釋放出,加上當前剩餘的 1 個資
源,總共變成 5 個
這 5 個資源可以用來滿足 P0 的所有需求,所以將 P0 當作第二
個執行的,給了 P0 資源後,剩下 0 個
目前的執行順序:P1→P0
當 P0 執行完後,釋放所持的所有資源共 10 個,就可以拿來
執行 P2
故可以找出執行順序為:P1→P0→P2,所以系統處在”safe
state”
不滿足 safe state 的例子:基本假設與上面相同,但 P2 的
『當前需求』有些微變更
最大需求
當前需求
最大-當前
P0
10
5
5
P1
4
2
2
P2
9
3
6
最初的剩餘資源:12-5-2-3 = 2 單位
在此情況下,第一個可以執行的 process 仍是 P1。分配資源
給 P1 後,剩餘 0 個
當 P1 執行完後,P1 佔有的資源 4 個釋放。但此時發現,剩餘
的 4 個資源並沒有辦法滿足 P0 或 P2 的『最大-當前』,也就是
沒辦法滿足他們的最大需求
因此,萬一哪天 P0 和 P2 都需要最大量的資源的時候,就會發
生 deadlock
故此狀態為 unsafe state
在 Safe state 中:必定不會 deadlock
在 unsafe state 中:有機率發生 deadlock
不一定發生的原因:
不是每個 process 都一直需要『最大需求』
Avoidance 原則:永遠不要進入『unsafe state』
Avoidance 的演算法:
resource-allocation graph
處理每一種類的資源只有一個實體的時候
banker’s algorithm
處理每一種類的資源有多個實體的時候
Resource-Allocation Graph
以 Pi 代表 process,Rj 代表某一種資源
3 種 edge:
Claim edge:
Pi
Rj
代表 Pi 未來可能需要使用到資源 Rj
Request edge:
Pi
Rj
由 claim edge 轉變而來,代表 Pi 向 OS 要求使用資源 Rj
Assignment edge:
Rj
Pi
由 request edge 轉變而來,代表 OS 將資源 Rj 指派給 Pi。
當 Pi 釋放此資源後,這個 edge 會再度變回 claim edge(代表以後可
能還會再使用到)
在系統中,必須要先把所有的 claim edge 建立起來,也就是說,需要
事先知道哪些 process 未來需要哪些資源
Safe state 的例子:沒有形成 cycle
Unsafe state 的例子:將上個例子的 R2 指派給 P2,造成 cycle,導致潛
在發生 deadlock (萬一以後 P1 也需要 R2 時,就 deadlock)
因此系統必須要再指派資源給 process 前,檢測給資源之後會不會在
graph 上形成 cycle
Banker’s Algorithm
類同之前舉的那個 3 個 process 使用 12 個資源的例子
需要事先知道每一個 process 對於每一種資源的最大需求
當 process 取得它需要的所有資源後,在有限的時間內必須要釋放資源
所使用用到的資料結構:假設有 n 個 process,m 種資源
Available:長度為 m 的陣列,Available[j] = k 代表資源 Rj 還剩下 k
個實體可以使用
Max:n × m 的二維表格,Max[i,j] = k 代表 process Pi 對資源 Rj 的
最大需求是 k 個。此資訊很重要,因為是未來用來預測系統使用
狀況的資訊。
Allocation:n × m 的二維表格,Allocation[i,j] = k 代表 process Pi 當
前已經取得 k 個資源 Rj
Need:n × m 的二維表格,Need[i,j] = k 代表 process Pi 可能還需要
k 個資源 Rj 來完成執行
Need[i,j] = Max[i,j] - Allocation[i,j]
Safety Algorithm:『找出 safe sequence』(可以安全避免 deadlock 的執
行順序)的方法,或稱為『檢測是不是在 safe state』的方法
1. 令 Work 與 Finish 分別為長度為 m 與 n 的陣列,初始化為:
Work = Available // Work 代表當下的可用資源
Finish[i] = false , for all i = 0,1,…,n-1 // Finish[i]指示 Pi 是否已經完成
2. 找出某一個 Process i,滿足以下:
3.
(1). Finish[i] == false
(2). Need[i] ≤ Work
若找不到此種 i,直接進入步驟 4
在步驟 2 找到 i 後,執行:
Work += Allocation[i] // Pi 釋放資源
Finish[i] = true // Pi 標示為已完成
4. 若所有的 i 都滿足 Finish[i] == true,就代表系統是 safe state
Process Pi 的 Resource-Request Algorithm
當 Process Pi 需要使用資源時的演算法
1. 初始化:
令 Requesti 為 Pi 的 request vector,當 Requesti [j] = k 時,代表 Pi 當
前需要 k 個資源 Rj。
若 Requesti ≤ Need[i],則進入步驟 2。若不滿足,就代表 Pi 要求
了超過當初規定上限的資源量,故回報錯誤
2. 若 Requesti ≤ Available,則進入步驟 3。若不滿足,就代表 Pi 需
要等待,因為其當下需求的資源還沒準備好
3. 代表當前資源足以讓 Pi 執行,故『假裝』把資源給了 Pi,故進行
以下操作:
Available -= Requesti // 系統中可用資源減少
Allocation[i] += Requesti // 已分配給 Pi 資源增加
Need[i] -= Requesti // Pi 還需要的資源減少
4. 利用 Safety Algorithm 檢查當下狀態還是不是 safe state
若是 safe state,就代表可以真的把資源給 Pi
若是 unsafe state,則 Pi 就必須等待,且剛才修改的那一些資
料結構(Available、Allocation、Need)都必須被還原
Banker-Algorithm 範例
假設有 5 個 process:P0~P4
三種資源 A(有 10 個)、B(有 5 個)、C(有 7 個)
在一開始的時候:
Allocation
Max
Need
Available
A
B
C
A
B
C
A
B
C
A
B
C
1
0
7
5
3
7
4
3
P0 0
0
0
3
2
2
1
2
2
P1 2
0
2
9
0
2
6
0
0
3
3
2
P2 3
1
1
2
2
2
0
1
1
P3 2
0
2
4
3
3
4
3
1
P4 0
其中 Available(當前可用資源)是由所有資源 10、5、7 減去所有一開始就分
配的 Allocation 而得
此狀態是為 safe state
第一個執行:P1,執行後可用資源為:(3,3,2) + (2,0,0) = (5,3,2)
執行後可用資源 = 當前資源 + 該 process 的 Allocation
第二個執行:P3,執行後可用資源為:(5,3,2) + (2,1,1) = (7,4,3)
第三個執行:P4,執行後可用資源為:(7,4,3) + (0,0,2) = (7,4,5)
第四個執行:P2,執行後可用資源為:(7,4,5) + (3,0,2) = (10,4,7)
第五個執行:P0,執行後可用資源為:(10,4,7) + (0,1,0) = (10,5,7)
如果 P1 現在又需要(1,0,2)的資源,是否允許?
檢查 Request1 <= Available,故當前資源足夠
先假設分配給 P1,然後檢查是否會造成潛在的 deadlock
Allocation
Max
Need
Available
A
0
3
3
2
0
B
C
A
B
C
A
B
C
A
B
C
1
0
7
5
3
7
4
3
P0
0
2
3
2
2
0
2
0
P1
0
2
9
0
2
6
0
0
2
3
0
P2
1
1
2
2
2
0
1
1
P3
0
2
4
3
3
4
3
1
P4
第一個執行仍是 P1(因為 Need1 ≤ Available),即使當前需求變
高,由於受到 Max 的限制,會使得 Need 變小。因為第一個仍是
P1 執行,後續的順序不受影響,
整體執行順序仍是: P1→P3→P4→P0→P2
仍在 Safe state,故 P1 的資源需求可以被批准
如果現在 P4 需求了(3,3,0)的資源,是否允許?
不行,因為現在剩下的 Available (2,3,0) < Request4 (3,3,0)
那如果現在 P0 需求了(0,2,0)的資源,是否允許?
檢查 Request0 (0,2,0) <= Available(2,3,0),故當前資源足夠
假設分配給 P0,檢查潛在 deadlock
Allocation
Max
Need
Available
A
0
3
3
2
0
B
C
A
B
C
A
B
C
A
B
C
3
0
7
5
3
7
2
3
P0
0
2
3
2
2
0
2
0
P1
0
2
9
0
2
6
0
0
2
1
0
P2
1
1
2
2
2
0
1
1
P3
0
2
4
3
3
4
3
1
P4
剩下的資源無法在滿足任何一個 process 的 Need,
故新的狀態為 unsafe state
因此 P0 的需求不能被批准
Deadlock Detection:偵測 Deadlock 的存在
允許系統進入 deadlock 狀態,但有演算法能夠偵測 deadlock 的存在並
使用 recovery scheme 離開 deadlock 狀態
對於每一種資源只有一個實體的情況:使用 wait-for graph
由 Pi→Pj 的 edge,代表 Process Pi 正在等待 Process Pj 釋放資源
定期呼叫此檢算法檢查 graph 中是否有 cycle
若有,就代表存在 deadlock
演算法複雜度:O(n2)
左圖資源等待圖,右圖為 wait-for graph (由左圖簡化而來)
若每一種資源有多個實體的時候:演算法類似於 Banker’s Algorithm
使用的資料結構同樣是 Available、Allocation、Request、Work、Finish
不同於 banker’s,此處『不再需要得知每種資源的最大需求 Max』,因
為遇到需求時是直接先把資源給 process,事後再檢查是否為 deadlock
演算法的意義變為:從當前剩餘的資源以及各個 Process 的需求
(Request),來判斷是不是已經進入 deadlock
檢查是否為 deadlock 的演算法與 safety algorithm 類同,只是最後的
Finish 陣列的解釋變為:
若存在某一個 i 使得 Finish[i] == false,則系統是在 deadlock state
且 Process Pi 是被 deadlock 鎖死的 process
反之,如果 Finish 都是 true,就代表存在 safe sequence,使得系
統為 safe state
演算法複雜度:O(m × n2)
偵測 deadlock 演算法的範例:
假設有 5 個 process:P0~P4
三種資源 A、B、C 分別有 7、2、6 個
剛開始的時候(只把資源扣掉 Allocation)
Allocation
P0
P1
P2
P3
P4
Request
Available
A
0
B
1
C
0
A
0
B
0
C
0
2
3
2
0
0
0
1
0
0
3
1
2
2
0
1
0
0
0
0
0
2
0
0
2
A
B
C
0
0
0
Request 為當前所有 process 額外對資源的需求
執行順序
當前資源(0,0,0)滿足 P0 的(0,0,0),執行後剩餘資源為(0,1,0)
當前資源(0,1,0)滿足 P2 的(0,0,0),執行後剩餘資源為(3,1,3)
當前資源(3,1,3)滿足 P3 的(0,1,0),執行後剩餘資源為(5,2,4)
當前資源(5,2,4)滿足 P1 的(2,0,2),執行後剩餘資源為(7,2,4)
當前資源(7,2,4)滿足 P4 的(0,0,2),執行後剩餘資源為(7,2,6)
故存在 safe sequence 使得 Finish[i] = true, for all i,代表
deadlock 不存在
假設現在 P2 多要求了 1 個資源 C,使得 Request 變為(0,0,1)
Allocation
P0
P1
P2
P3
P4
A
0
2
3
2
0
B
1
0
0
1
0
C
0
0
3
1
2
Request
A
0
2
0
1
0
B
0
0
0
0
0
Available
C
0
2
1
0
2
A
B
C
0
0
0
注意,不同於 Banker’s,此處不必再更新 Allocation 欄位
只需要更新 Request,並再配合當下的 Available 狀態去計算是不是進
入 deadlock state
此例子中,一開始只能夠滿足 P0 的需求(0,0,0),結束 P0 後得到的
資源是(0,1,0),不足以滿足剩餘其他 process 的 Request,故存在
deadlock,且牽涉到 P1、P2、P3、P4 (因為 Finish[1]~ Finish[4]都是
false)
使用 Detection-Algorithm
使用此演算法的時機(多久使用一次?):與 deadlock 發生率有關
當發生 deadlock 時,需要 rollback(還原)多少個 process?每一個
disjoint 的 deadlock cycle 都需要還原一個 process
從 Deadlock 中復原:必須要終止某些 Process
最簡單的方法:終止所有與 deadlock 相關的 process,但會導致效能低
落
較有效率的方法:逐個 process 依序,看看甚麼時候可以解除 deadlock
要選擇哪一個 process 終止?有以下考量因素:
Process 的優先權
某個 Process 已經執行多久了?它還需要多少時間來完成計算?
不希望把快要計算完成的 Process 終止掉
Process 的已經使用的資源狀況
Process 對資源額外需求(非已使用的資源)數量
例如:把要求太多資源的 Process 終止掉
需要被終止的 Process 總數
Process 的類別是互動的(interactive)或者是背景程式(batch)
Resource Preemption:將執行中的 Process 資源搶奪
選擇一個受害者(victim)來進行還原:希望讓損失最小
例如還原後,希望 deadlocked process 數量最少、希望做白工的時
間(被停掉的 process 被還原後,原本那些有算卻被丟棄的部分)越
少越好
進行還原(Rollback):漸漸 rollback,逐漸釋放資源,直到 deadlock 消
除。最簡單就是整個重來,但是代價可能太高 (不必要的 rollback)
飢餓問題(Starvation):別讓固定某幾個 process 一直當作受害者。因此
在挑受害者的時候,還要注意這個 process 已經被犧牲多少次了。
作業系統概論
Chapter 8: Memory-Management Strategies
Base and Limit Registers
在多個使用者的環境下,必須保護每一個 process 自己的記憶體不受其
他 process 干擾
某一種可行的方案:定義 base register 與 limit register,定義了某一個
process 在記憶體中可以使用的位址範圍
CPU 會在 process 想要存取記憶體的時候,利用硬體檢查是不是滿足範
圍限制,若違反規定就產生 error (或者是發出 trap)
Base 與 limit register 的數值只會由 OS 使用 privileged instruction 來設定
Base & limit register 示意圖:
檢查記憶體範圍的示意圖
Address Binding
另一種管理記憶體的方式:將 CPU 中呼叫執行的記憶體位址對應到真
實記憶體中另一個位址 (從某一個 address space 對應到另一個 space)
用意:程式編譯時統一把起始位址當作是 0,由於真實執行時沒辦法
把每個 process 都放在位址 0,所以需要進行 mapping(CPU 內記憶體位
址與實體記憶體位址不同)
程式記憶體空間演變流程
原始碼階段:使用變數名稱
編譯後的程式碼:利用相對位址,bind(綁定)到某一個 relocatable
address (可以重新定位的位址)
例如:某變數的位址是此 module 的起始位置加上 14 bytes,
但沒有定義該 module 的記憶體位址是多少
好處:整個程式可以搬移道不同起始位址執行
經過 Linker 或 Loader 之後:將 relocatable address 進行 bind 到某
個真實記憶體的位址
例如上個例子:設定該 module 位置是 74000,則某變數的最
終位址是 74014
不同的 Address Binding 階段
以下將由『最不彈性』排列到『彈性最佳』的 binding 方式
Compile time:編譯時期
直接在編譯時直接指定最後要使用的
實體記憶體的位址
如果因為某些原因,使用的位址改變,
就需要重新編譯
用於早期系統,例如 MS-DOS,
因為本來就只能執行一個 program,
所以起始位置固定
Load time:讀取到記憶體的時候
在編譯時期必須使用 relocatable address
,而非直接的實體記憶體位址
Execution time:執行時期
在執行時期才動態決定某些函式呼叫
的位址(例如 DLL 動態函式庫)
需要硬體支援 address mapping
當前大多數的 OS 都採用此方式
例子:process 呼叫某些動態的系統 library(例如 DLL)
如果該系統 library 還沒讀取,則該 process 無論在編譯或者
是 load 的時期,都不會知道該 library 到底在哪裡。
只有在執行期『呼叫到相關 library 時』,把 library 的 DLL 讀
取進來後的真實位址告訴該 process,進行執行期的 binding
好處:可以在執行期動態更換 process 的記憶體空間,例如用於
swap-in 時,所在的位址與 swap-out 不一定是一樣的,所以需要
能夠執行期重新 bind
Logical vs. Physical Address Space
Logical address:在 CPU 運算時想要取用的記憶體位址,可視為程式碼
指令中使用到的記憶體位址,又稱為 virtual address
Physical address:記憶體控制器最後看到的位址,是為真實記憶體位址
對於 compile-time 或 load-time 的 binding 來說,logical address 和
physical 相同
因為 loader 可以幫助改寫程式中取用的記憶體位址,所以
relocatable 處理之後的記憶體和真實記憶體是一樣的
Logical address space:某一個程式所產生的所有 Logical address 的集合
Physical address space:所有那些 Logical address 對應到的 Physical
address 的集合
Memory-Management Unit(MMU):記憶體管理單元
對一般程式來說,它只知道自己的 Logical address,範圍從 0 到 max,
不會看到真實記憶體位址
硬體利用 MMU 來對應 Logical address 到真實位址,至於 MMU 使用的
方法有很多種(本章節接下來會說明)
之前提到的最簡單方法”Base register”
在此稱為 relocation register,將真正的起始位址記錄到此
讓 logical 位址可以簡單地算出真實位址
缺陷:必須要保證有連續的記憶體才可以執行該 process(無
法分段)
Dynamic Loading
不會把整個 program 內容讀取到記憶體,一開始可能只是讀取 entry
point(程式進入點)部分
需要執行某一個 routine 時,如果那個 routine 還沒被讀取到記憶體,
才去硬碟中讀取
記憶體空間利用度較好,不使用的 routine 就不會被讀取
例如 error routine,如果程式執行沒 error,就不會需要那些 code
允許同時讀取更多程式來執行。因為如果一次都讀全部而用到更多記
憶體,會導致記憶體內能夠執行的 process 變少
Dynamic Linking:動態連結
Static Linking:靜態連結
最後的執行檔會把整個 library 包在一起,這樣在編譯時期就知道
相對位址在哪(知道要跳過去使用 library 的時候,知道要跳多遠)
Dynamic Linking:動態連結
不會把 library 的內容包含在執行檔中,節省記憶體空間,而是由
多個 process 在記憶體中共享同一份 library
在 process 程式碼中 library 執行的位置會置放一段 Stub 程式碼
當執行到需要的 library 時,stub 會檢查該 library 是否已經讀取
若沒有讀取,則讀取該 library
讀取之後(或本來就被讀取過了),stub 會把自己的指令位址取代
為最終 library 的執行位址,讓 process 能夠執行該 library
需要 OS 協助檢查 library 是否已讀取
通常用於 shared libraries (Windows 稱為 DLL)
使用時需要注意版本問題,process 需要知道自己想使用的 library
版本,被讀取的 library 本身也要記錄自己的版本,避免版本衝突
Swapping
暫時將某個 process 的內容從記憶體移動到硬碟中儲存,暫停執行
因此所有 process 總計的記憶體使用量可能會超出實體記憶體大小
Backing Store:在硬碟中,那一塊用來儲存 process 的區域,通常存取
速度較快,空間也比記憶體大
Roll out、Roll in
為 swapping 的變形,主要概念是把低優先的 process 移出記憶
體,並將高優先的 process 拿回記憶體執行
Swap 占用的時間很長(牽涉到硬碟存取),盡量要降低 swap 次數
在 ready queue 的 process 可能在記憶體或硬碟,OS 必須要知道
Swap 前後,process 是否要在同一個記憶體位址?
比較有彈性的方式是:不一定要同一個位置
大部分 OS 都存在經過修改的 swapping 機制 (UNIX、Linux、Windows)
平常是沒有使用 swap 機制 (因為 swap 代價太高,不希望常做)
當記憶體使用量超過某個門檻,就會啟動 swapping 機制
記憶體使用回到某個門檻以下之後,就關掉 swap 的功能
CPU Scheduler 會呼叫 dispatcher 來處理 context switch,此時 dispatcher
就得檢查 process 是在記憶體或硬碟,然後可能會需要進行 swapping
牽涉到 swapping 的 context switch
如上圖,如果想要執行的程式不在記憶體,就必須要將舊程式 swap
out,把新程式 swap in,導致需要兩次的硬碟存取,效能低
改進的方法:減少被 swapping 的記憶體數量,不是把整個 process
swap out/in,指定確實需要的記憶體數量來 swap
不要對等待 IO 的 process 進行 swap
因為通常等待 IO 時,會指定某一個 IO buffer (例如指定一塊自己
的記憶體空間來存 IO 的結果)
由於不知道 IO 何時會來,所以萬一該 process 被 swap 掉,當此後
IO 來時,IO 再把結果填入 buffer 時,就會發生 IO 把資料填到別
的 process 的問題(因為原本那個 process 的位址給別人用了)
如果非得把等待 IO 的 process 進行 swap,就得用 double buffering 的方
式來解決:先把 IO 放到 OS 的另一個 buffer,在轉交給 process
缺點:更多的 overhead
行動裝置上的 swapping
通常少用,因為其儲存空間是 flash memory,空間不夠大、也不能夠
經常寫入,且 CPU 與 flash memory 之間的 throughout 也不佳
會利用其他的方式來解決記憶體低下的問題
iOS:詢問哪些 process 願意釋放記憶體
通常是釋放那些 read-only 的資料,因為 read-only 的資料不
用在寫回 flash(因為也不會被改),到時候重讀就好
Android:直接將程式終止,但是會將該 app 的執行狀態記錄到
flash 中,以便下次快速恢復執行
無論如何,原則上就是盡量減少寫入 flash 的次數
Contiguous Allocation
為早期的方法
Process 各自擁有一塊連續的記憶體空間,Kernel 也是
通常 kernel 都在較低的記憶體位址
利用 base 與 limit register 控制記憶體的使用
MMU 會『動態的』對 logical address 進行 map
可以有 transient 效果
程式不會因為其他程式/kernel 記憶體更動而讓自己的記憶體空間
不同
因為它所用的記憶體空間已經被規定好了,不會受到其他因素影
響
例如某些 code 不需要的時候就釋放掉,並讀取新的進來
Variable-Partition Size
分派給每一個 process 的記憶體空間都不同 (根據 process 而異),
每一個 partition 的大小就是該 process 占用的記憶體大小
Hole:許多零碎且大小不一的可用記憶體空間,散佈在整個記憶體中
當有新 process 要執行時,找一塊足夠大的 hole,在上面執行
作業系統必須要知道『被用掉的 partition』與『Free partition』
Dynamic Storage-Allocation 的問題
如何從一系列的 hole list 中,替大小為 n 的 process 找出一塊可用的記
憶體?
三種原則:
First-fit:依序在 hole list 中尋找,找出第一個大小大於 n 的 hole
Best-fit:從 hole list 中找出與需要記憶體大小最接近的且也可以
放得進去的 hole
此方法必須搜尋整個 list,除非 list 有照大小排序
Worst-fit:搜尋整個 list,找出空間最大的 hole 並將 process 放進
去
此方法也是需要搜尋整個 list,且可能導致把大空間都切碎,
讓其他大程式放不進去
通常來說,First-fit 與 Best-fit 的空間使用效率較好
Fragmentation
External Fragmentation:外部的碎片
由 Variable-sized allocation 所造成
浪費掉的空間是在自己程式的 partition 之『外』
OS
Process 1
浪費的 hole
Process 2
浪費的 hole
導致雖然整體記憶體空間足夠,卻無法放入較大的 process
Internal Fragmentation:內部的碎片
由 Fix-sized allocation 所造成
將記憶體區分為許多固定大小的分區,每個 process 依照自己
所需的大小,取得一個或數個分區
OS
Process 1
浪費的 hole
Process 2 分區 1
三個分區的大小一致
Process 1 使用一個分區
Process 2 使用兩個分區
Process 2 分區 2
分區 2 浪費的 hole
可以避免造成太多細碎的 hole(因為最小釋放單位就是一個分
區,也就是一次釋放出的空間最少就是一個分區,不會太小)
解決 external fragmentation 的方法:Compaction
希望把 hole 合併
移動 process 佔有的記憶體,來把 hole 合併
前提是需要支援可在執行期動態調整記憶體的 OS
代價高:因為需要動態的搬移程式記憶體的位置,可能造成不少
的 overhead。而且也不是每一個程式都適合被搬移
同樣有 IO 問題:鎖定該 process 不被移動,或使用 double buffer
另外的解決方法:noncontiguous allocation (非連續記憶體空間配置)
使用 Segmentation 或 Paging
Segmentation
依照某些規則,將程式拆成許多小碎塊(segment),分別放在記憶體的
hole 中
由於不需要大塊的連續空間,所以記憶體使用效率較佳
每一個拆成的碎塊仍是 variable size
碎塊(segment)可以是下列所舉的 logical unit
Main program
Procedure
Function
Method
Object
Local variable、Global variable
Common block
Stack
Symbol table
Arrays
User space 中的碎塊與實際記憶體
為了要知道各碎塊位置,logical address 必須由以下的 two-tuple 組成
<segment-number , offset>
segment-number:該碎塊的編號
offset:所需要的指令/資料在該碎塊內的起始位置
這個 two-tuple 用以記錄每一個碎塊的起始位置
當程式被編譯的時候,編譯器會將程式拆成碎塊並給予 segmentnumber
例如 C 編譯器會把程式拆成:
code、global variable、heap、stack、standard C library
Segment Table
將二維的 logical address (即<segment-number,offset>)對應到一維的
實體記憶體位址
每個 table entry 有:
Base:紀錄該 segment 本身在實體記憶體的開始位置
Limit:紀錄該 segment 的長度
範例:執行到某指令發現是<2,100>,就會知道真實位址是 segment 2
的位址(查詢 segment table,得知是 1500)加上 100,故最後的指令/資
料位址會是 1600
Segment-table base register (STBR):紀錄 segment table 在記憶體內的位
置
Segment-table limit register (STLR):紀錄某個 program 有多少個
segment,故 segment number 必須要小於 STLR 才算合法
segment 保護措施:
每一個 segment table 中的 entry 都包含了:
Validation bit:若此 bit 為 0 代表這是非法的 segment
Read/write/execute 的權限規定
由於 segment 長度不一,所以屬於 dynamic storage allocation,仍
會有 fragment 的問題產生,只是利用 segment 之後,每一個碎塊
的大小比較小,記憶體利用率比較高
每個 segment 內仍是連續的記憶體空間
Segmentation hardware
Paging
整個程式在實體記憶體內不連續,只要記憶體空間夠多(不必連續),就
可以分派記憶體給該程式執行
可避免 external fragmentation
避免有不同大小的記憶體碎塊散佈各處的問題(就像 segmentation
那樣,每一個連續的小碎塊大小都不同)
將實體記憶體切成固定大小的 blocks,稱為 frames
大小為 2 的次方,從 512byes~16Mbytes
將 logical 記憶體使用和 frame 一樣大小的 block 切塊,稱為 pages
每一個 page 會對應到一個 frame,故需要知道有哪些 frame 是可用的
(還沒被其他 process 使用掉)
對程式來說,看到的是一連續的 VM(virtual memory),事實上對到
的 frame 可能是分散的
可使用硬碟(backing store)來存放部分 page
由於每一個 frame 大小固定,所以仍會有 internal fragmentation 問題
Address Translation Scheme:如何從 VM 轉換為實體記憶體
對於編譯器來說,只要把每一個 process 的空間從 0 開始編就好,
到時候會動態 mapping
由 CPU 所產生的 VM,可以區分為兩大部分:
Page number (p):當作是 index,可以在 page table 中,找到
對應的 entry
Page offset (d):由於一個 page 和 frame 可儲存的東西是一樣
大的,所以 page offset 可以用來當作找到目標的 frame 之
後,在那個 frame 裡面(以 0 開始),要往後找多少位址就可以
找到資料。(類似 segment 的 offset)
當每一個 page 越大,可用的 page 總數就越少(因為總空間是固定的)
假設 logical address 有 2m bytes,每一個 page 大小為 2n
Paging Hardware
每個 Process 有自己的 VM space 和 page table
將 VM 記憶體的 page number 欄位用 frame number(來自 page
table)取代掉,就得到實際位址
Page table
存放每一個 page 對應的 frame number
Frame number:相當於 page number,只是 frame num. 是在實體
記憶體中以 frame 切割後的編號。Frame num.的 bit 數可能與 page
num. 不同
Page table 有 p 個 entry,將所有 Page num.對應到一個 frame
num.,故 page table 不用另外的欄位存 page num.,因為可以直接
把 page num.當作是陣列的索引
範例:計算 page table 的大小
zln
10 0 0
假設:
VM space 共有 232 bytes
✓
-5
Page(frame)的大小為 1 Kbytes,即 210 bytes
實體記憶體有 248 bytes
Page number 總數:232 ÷ 210 = 222
Frame number 總數:248 ÷ 210 = 238
每一個 entry 都需要表達每一個 frame number,所以每一個 entry
需要 38 bit
Page table 大小:222 × 38 bits
要減少 page table 大小,就要讓 page size 大一點,減少 entry 數量
Paging Model
完全將 programmer 看到的記憶體空間與實際記憶體空間分開
只能看到自己 process 整塊的 VM,其中只有一個 program
不會干涉到其他 process 記憶體(因為根本不知道也看不到)
Paging 計算範例
假設:
Page number = 4
Page size = 4 bytes
VM:16 bytes
實體記憶體:32 bytes
由 logical addr.推算實體 addr.
Logical:0
所在 page:0 / 4 = 0
實體的起始點:5 (查 table)
Offset:0 % 4 = 0
實體位址:5 × 4 + 0 = 20
Logical:3
所在 page:3 / 4 = 0
實體的起始點:5 (查 table)
Offset:3 % 4 = 3
實體位址:5 × 4 + 3 = 23
Logical:4
所在 page:4 / 4 = 1
實體的起始點:6 (查 table)
Offset:4 % 4 = 0
實體位址:6 × 4 + 0 = 24
Logical:13
所在 page:13 / 4 = 3
實體的起始點:2 (查 table)
Offset:13 % 4 = 1
實體位址:2 × 4 + 1 = 9
Internal Fragmentation 的問題
一般來說,平均的 fragment 大小為 frame size 的 1/2
要減少內部碎片大小,就得降低 page size(即 frame size)大小,但此舉
會造成 page number 變大,使得 page table 變大
Free Frames
Free-frame list 會記錄當前有哪些 frame 是可用的(即灰色部分)
當有新的 process 建立時,會分派空間它並建立 page table,將所需的
page 分配給它 (範例中,從 free-frame list 取出 4 個 page 給他並記錄
在 page table)
左圖:分配前;右邊:分配後
Page Table 的實作
Page table 是存在記憶體裡的,每個 process 都需要用 register 來記錄
它的位置
Page-table base register (PTBR):記錄 page table 在記憶體的位置
Page-table length register (PTLR):page table 的大小
由於 page table 是在記憶體裡面的關係,導致程式每一次要存取記憶
體時(包含指令與資料),事實上都需要兩輪的記憶體存取
先存取 page table,算出真實的記憶體位置
在去真實的記憶體位址存取指令/資料
避免兩次記憶體存取的機制:使用 TLB (translation look-aside buffers)
利用硬體實作,又稱為 associative memory,可以儲存一部分的
page table 的 entry,以供快速查詢/轉換 (類似 cache 的概念)
TLB 欄位的示意圖:(此處只有列出必要的兩個欄位,事實上會有
其他的欄位,例如 ASID)
TLB 由於 page number 並不是連續的,所以沒辦法用 page
number 當作 index 直接用
從 CPU 產生的 logical address,先查查看 page number 有沒有符合
的。若還是沒找到,才到記憶體中存取
某些 TLB 中會有 ASID (address-space identifiers)欄位:
用來識別某一個 page table entry 是來自哪一個 process
不像 page table,硬體的 TLB 是讓所有 process 共用,若 TLB 無法
辨紀錄來源的 process,就只好在 context switch 時,清空舊的 TLB
內容(即使 TLB 根本還沒滿),來避免存取到別的 process 的記憶
體。切換後,第一次的 TLB access 都是 miss
若有 ASID:則 context switch 就不必清掉 TLB,因為可以辨認是不
是自己的 process,不怕取到其他 process 的記憶體
也就是:process 1 留在 TLB 的 entry,可以在下一次又輪到
process 1 執行時,直接取用 (如果這段時間內 TLB 還沒滿的話)
TLB 大小通常都不大
當發生 TLB miss 時,會把 miss 掉的 value 存到 TLB,以便下次快速存
取
至於 TLB 中的 replacement policy (如何置換掉舊的 entry)必須要良
好設計
某些比較常用的 entry 可以寫死(wired down)在 TLB 中,讓每次存
取都很快
使用 TLB 之後的 Paging Hardware
TLB 的搜尋是平行執行的,速度很快
事實上,page
table 是在記
憶體內
Effective Access Time (EAT)
對於程式來說,實際存取記憶體所需的時間
假設:TLB hit rate 為α
代表 page number 可以在 TLB 中找到的比率
若α = 80%,每次記憶體存取需要 100ns
EAT = 0.80 x 100 + 0.20 x 200 = 120ns
80%的機率只要存取 1 次,20%的機率要存取 2 次
不必考慮到 TLB 存取的時間,因為可以和 CPU 指令 pipeline 平行
執行,所以忽略
一般來說,α = 99%
EAT = 0.99 x 100 + 0.01 x 200 = 101ns
Memory Protection:記憶體保護
可以替每一個 frame 設定存取模式,例如 read-only 或是 read-write 等
等
在 page table 中增加 Valid-invalid bit
因為不是大部分的程式都會把整個 VM space 用滿,所以使用
valid bit 表示用了哪些
此 bit 指示該 page 是不是可用的 page(例如該程式寫錯,使得
page number 超過這個 process 可以使用的範圍等等)
也可以使用 page-table length register (PTLR),指示最大的 page
number 數量
任何違規存取都會發出 trap 到 kernel,讓使用者知道記憶體出錯
Shared Pages
某些一記憶體位置可以在多個 process 間共享(例如:read-only 的資
料、文字編輯器等)
Read-write page:可以讀寫的記憶體位置,可利用於 process 間的溝通
(如同 shared memory)
方法:把不同的 virtual page mapping 到同樣的 frame
範例:以下為三個文字編輯器 process (text editor, ed),共享文字編輯
器本身的資料(ed1~3),但卻開啟不同檔案(data1~3)
☆
Page Table 的結構議題
原始的 page table 問題:entry 太多
缺陷:
需要在實體記憶體中占用大量的空間
這些空間需要連續(因為需要用 page number 做 index)
解決方案:
Hierarchical Paging
Hashed Page Table
Inverted Page Table
Hierarchical Page Table
使用多層的 page table,將連續的大塊 page table 拆散成許多小單元,
然後用另一個更上層的 page table 來指向這些 table
範例:兩層的(2-level) page table
Bit 的分配方式:(假設記憶體是 32 bit,page size 為 1K)
單層的 page table:
22 bit 的 page number
10 bit 的 page offset (因為 page size 是 1K)
雙層的 page table
Page offset 仍是 10 bit
中間的 page table 使用 10bit (亦即,每一個中間小 table 都有
1K 個 entry)
外層的 table (outer page table)使用 12 bit,故總共有 212 個小
table
優點:解決原本需要連續大空間的問題,因為中間那些小 table 可
以分散在各處
缺點:越多層,需要的記憶體總量越多,因為需要額外的外層
table 來存,『無法』降低使用量,反而還需要更多空間
單層 page table:222 個 page entry
雙層 page table:每一個小 table 有 210 個 page entry,總共有
212 個小 table,總計仍有 222 個 page entry,而寫還需要額外
空間去存外層的 page table
Address-Translation Scheme:記憶體轉換的方式
事實上 logical address 每一塊分割(P1,P2),都可以拿來當作 index
P1 是 outer page table 的 index,P2 則是中間較小 table 的 index
d 是 page offset,可以當作在目標 page 中與起始點的距離
此種方式又稱為 forward-mapped page table
缺點:需要進行多次的記憶體存取(查 table)才可以找到真正的位址
在 64-bit 系統種,2-level 可能不夠
若 page size 為 4K(212),則單層 table 就會需要 252 個 entry
使用更多層:例如 3-level
需要進行更多次記憶體存取
Hashed Page Tables
通常用於 address space 比較大(例如大於 32bit)
單層 table,但是每一個 entry 不再只儲存一筆 page/frame number,而
是一個類似 linked-list 結構,儲存多個 frame number
Page number 不再直接拿來當作 page table 的 index,而是經過一個
hash function (假設為 f(x))
因為有經過 hash,事實上 page table entry 數量變少了,所以導致有兩
以上的 page number(例如 p 與 q)對應到同一個 page table entry 的情
況,而這個 entry 存著兩組 frame number
也就是 f(p) = f(q) = 同一個 entry
此例中,f(p) = f(q),所以這兩個 page/frame number 放在同一個
entry
利用 hash 後可以減少 table entry 的數量,但是找到該 entry 後,
仍需要用原本的 page number 到 linked-list 中依序尋找正確的
frame number(例如圖中必須要在(q,s)、(p,r)中找到正確的(p,r))
需要更多空間:因為每一個 linked-list 的 element ,也就是一組
(page #,frame #),都需要額外的指標欄位
Hash 的變形:clustered page tables:用於 64-bit 環境
原本一個 chain 的 element 上只有一個(page #,frame #) pair,現在
可以放一堆(但是必須要是連續的,這一堆 pair 共用一個指向下一
個 element 的指標,省下指標空間)
Inverted Page Table
所有的 process 都會共用一個 inverted page table
存放所有 process 的 virtual memory
Page entry 結構:<Process-id, page-number>
這一次,是用 frame number 當作 index,利用 PID 和 P 搜出 entry 後,
直接看該 entry 的 index,就可以得到 frame number
需要追蹤所有可用的 logical page 與所有實體記憶體,每一個 page
entry 與實體記憶體是『1-1』關係,故不能有 shared page
實體有多少的 page,這 table 就有多少 entry
記憶體存取效能較低,因為每次都需要用<pid,page #>去搜尋整張
table,但 table 只需要一張,用時間換空間
加速方法:可用 hash table 來降低 entry 量、利用 TLB
剩下的內容是一些硬體平台簡介,此處略過
作業系統概論
Chapter 9: Virtual-Memory Management
Review:Virtual Memory (VM)
只有程式需要執行的部分要放在記憶體(其餘可能放在硬碟)
Logical Address Space 可以比實際記憶體空間大
可以放心的將不同用處的記憶體放在整個 VM Space 的各處,供各自成
長使用(不怕互相蓋掉),稱為 Sparse Address Space
允許某些記憶體位址被多個 process 共享
建立 process 更有效率
Fork 時可以將某些 read-only 的 page 分享給多個 process 使用
Shared Library 可以共用
可同時執行多個 process
降低 load / swap process 的 I/O
VM 實作方式:
Demand Paging
Demand Segmentation
Demand Paging
需要哪些 page,就拿哪些,而不是一開始就把整個 process 讀取
降低 IO 與記憶體需求,加快反應速度與允許同時更多程式
當某個 page 被需求時,先檢查是不是合法的位址(是不是超過記憶體
範圍?),然後在看是不是已經在記憶體。若沒有,則搬到記憶體中
Lazy Swapper:沒有用到的 page 就不會被搬進去。每次搬的單位都是
page
Valid-Invalid Bit
此處用來表達"在記憶體(以 v 表示 valid)"與"不在記憶體(以 i 表示
invalid)",沒有表達是否超過範圍
一開始:全部都是 i
需要使用記憶體時,若發現為 i,就是發生 page fault。將需求的
page 搬進記憶體並把 bit 設為 v
Page Fault 時的處理
亦即,所需要的 page 不在記憶體中
Paging HW(硬體,hardware)會發出 trap 給 OS,告知 page fault 發
生
OS 會檢查另一個 table:是否是無效的存取(超過範圍)?若
是,會直接終止程式。若否,就代表只是單純不在記憶體
尋找一個空的 frame 來放新拿進來的 page
利用硬碟操作將 page 搬進記憶體
把 valid bit 設為 v
重新繼續因為 page fault 而暫停的程式
圖解:
Demand Paging 的其他議題
極端的 demand paging:Pure demand paging
一般的 demand paging 在程式開始時會搬一部分的 page 進來
但是 pure demand paging 是不會搬任何 page 進來的,因此一
開始一定會發生 page fault
事實上有些指令會造成多個 page fault
但影響不大,因為有時間以及空間上的 locality
Temporal locality:最近用到的 page 會常用到
Spatial locality:用到的 page 附近的 page 會常用到
需要的 HW 支援
具有 valid/invalid bit 的 page table
Secondary memory (可以放 swap 出去的 page 的地方,backing
store)
支援重新開始某一個指令(instrument restart)
instrument restart:指令重新開始
執行一個指令的許多步驟(例如 C = A+B),只要其中一個步驟發生
page fault,整個指令都會作廢。下次開始時要整個重新來
但如果指令重來時,資料有變更時如何處理?
整個指令重來是否會造成問題?
例如:block move:將 block A 的資料移動到 block B
其中這兩個 block 在某個區域上有重疊
複製時,將紅色區塊搬到藍色區塊(相對位置)
紅色區塊搬移完成後,繼續搬移時,發生 page fault,整個指
令重來
由於紅色區塊”已經”複製到藍色區塊(事實上是 block A 自己的
另一個部分)。當新開始指令的時候,整個 block A 與先前的
block A 已經不同了
(因為自己的一部分資料被自己的另一部分資料蓋掉)
導致重新 move 的結果錯誤
兩種解決方案:
1. 在開始移動前,先檢查整塊 block A 與 B 是否會發生 page
fault,若有就當下處理。如此一來,就不會在搬移的時候發
生 page fault,也因此避免了需要整個指令重來
2. 事先把被寫入(範例中的 block B,包含會被蓋掉的 block A 的
部分)的資料備份到暫時的 Register 中。如果發生需要指令重
來,就利用之前存下來的值把 block B 的資料還原。這麼一來
那一個重疊的 block A 部分也會被還原
Demand Paging 的流程
1. 對 OS 發出 trap
2. 將 CPU 狀態(包含 register)存起來,因為接下來 CPU 要去處理 trap 和相
3.
4.
5.
關的 handler
檢查 interrupt 是不是因為 page fault 引起
檢查該存取是否合法(沒超過範圍),且該 page 存在於硬碟上
發出硬碟讀取指令,等待硬碟有空時將該 page 讀取到空的 frame 中
6.
7.
8.
等待時,將 CPU 讓給其他 process
收到由硬碟發出的 IO interrupt(即,讀取完成了)
將當前其他 process 的 CPU 狀態存起來,準備 context switch
9. 確定該 interrupt 是來自硬碟
10. 更新 page table(例如把 reference bit 設為 v),標示該 page 已經在記憶
體
11. 等待 CPU 重新將此 process 拿去執行
12. 恢復 register 內容與 CPU 狀態,並重新執行被中斷的指令
Demand Paging 的效能
Page fault rate 0 ≤ 𝑝 ≤ 1
p = 0:沒有 page fault
p = 1:每次都 page fault
Effective Access Time (EAT)
EAT = (1 − p) × 𝑚𝑒𝑚𝑜𝑟𝑦 𝑎𝑐𝑐𝑒𝑠𝑠 𝑡𝑖𝑚𝑒 + 𝑝 × 𝑝𝑎𝑔𝑒 𝑓𝑎𝑢𝑙𝑡 𝑡𝑖𝑚𝑒
假設:page fault time 包含了從 page fault 發生到指令重新開始這
段時間(也就是包含了放進 table 後再讀取的 memory access time)
範例:
memory access time = 200 ns
平均的 page fault time = 8 ms = 8,000,000 ns
EAT = ( 1 –p ) x 200 + p x 8,000,000
若每 1000 次存取有一次 fault,p = 0.001
EAT = 8200 ns ,慢了將近 40 倍
若想要讓效能延遲低於 10%,也就是讓 EAT 小於 220 ns
220 > ( 1 –p ) x 200 + p x 8,000,000
p < 0.0000025
每 400,000 次記憶體存取只能有一次 page fault
Demand Paging 最佳化
過去的 BSD:程式一開始執行時,把整個程式內容搬到 swap space 中
存放,從此以後 swap in 與 out 都在 swap space 中操作
硬碟對 swap space 的存取速度通常比 File System(簡稱 FS)還要快
改良的方法:第一次 demand page 時還是從 FS 中取出來,但是當它被
swap out 時,是把資料寫回 swap space 而不是 FS,之後的 swap 操作
仍是在 swap space 進行
如果某些 page 本身是 read-only,則當需要取得 free frame 時,直接將
這類的 page 丟棄(不必寫回 FS 或 swap space),為現行 BSD 使用。
但如果是可讀可寫的檔案,就還是得放在 swap space
行動裝置通常不支援 swap。例如 iOS 會把可讀寫的資料留在記憶體(不
會 swap 到儲存裝置)。當記憶體不足時,會將 read-only 的 page 丟棄
Copy-on-Write (COW)
剛 fork 出新 process 時,當 child 還沒有修改 page 內容時,實際上的
page 都是共享的
當 child 或 parent 修改 page 時,那一個 page 才被複製兩份並做各自的
修改
使用 COW,會讓 fork 的效率更高
vfork()
當 child 被 fork 出來後,就要馬上 exec(),而沒有要執行原本的程
式時,根本不需要複製 parent 的記憶體或監視是否有修改
使用 vfork 可以在 fork 出 child 之後,讓 child 沒辦法修改 parent
的記憶體(只能讀)
與 fork + COW 不同 (會監視 child 是否有修改到共用的 page)
因此效率極高
沒有 Free Frame 的處理?
每一個 process 仍需要分配一定量的實體 frame,避免一直 swaping
Page Replacement:
尋找記憶體中某一個沒再用的 page,將之移除
至於是要終止該程式或者是單純 swap out 等等,有不同的評估方
式
modify bit(dirty bit):表示這個 page 已經有被修改過了
當 page 需要從記憶體中移除時,檢查是否為 dirty
若是:需要寫回硬碟
若否:代表從硬碟拿出來後還沒被修改,可以直接丟棄
Page replacement 示意圖
基本的 Page Replacement 流程
1. 尋找所需要的 page 在硬碟上的位置
2. 尋找 free frame
3.
4.
若沒有,尋找一個 victim frame (受害者)
如果受害者 page 是 dirty,則將之寫回硬碟
將新的 frame 從記憶體中讀取到記憶體並更新 page table
繼續執行被中斷的那個程式
贏
劃劃
Page and Frame Replacement Algorithm
Frame-allocation algorithm
決定了該給每一個 process 多少實體 frame
給越多,page entry 就越多,理論上 page fault 的機率就越低
Page-replacement algorithm
如何選擇受害者 page
目標:最小化 page-fault,無論是新的 page 還是舊的 page 被重新
存取時
reference string:一連串 page 的存取編號,接下來的例子都用這個來
衡量 replace algorithm 的效能
7,0,1,2,0,3,0,4,2,3,0,3,0,3,2,1,2,0,1,7,0,1
First-In-First-Out (FIFO)
發生需要取代 page 時,當初最早進來的 page 會優先被取代掉
若有三個可用 frame,則被取代的順序是:1,2,3,1,2,3,… (依序取代)
1
2
3
紅框:發生取代的 page,框內左邊是取代前,右邊是取代後
可以發現取代的順序是 1,2,3,1,2…依序取代
此例中,共有 15 個 page fault
實際上 page fault 的次數會隨著不同的 reference string 而有很大差異
甚至會發生 Belady’s Anomaly
Belady’s Anomaly
對於某些特殊的 reference string,會發生的情況(並非每次都會發生)
當增加可用 frame 總數(剛才的例子是 3),反而會增加 page fault 次數
下圖中,可以發現當 frame #為 4 的時候,page fault 的次數居然比
frame #為 3 的時候還要多
Optimal Algorithm
此方法不可能實作,但可以用來找出某一種 reference string 中的最佳
解,供未來量測新的 replacement 方法的準則
要取代 page 時,看看『未來』哪一個 page 最少被使用,就把誰換掉
因為是看未來,所以實際上不可能實作
圖中三條線分別是 7、0、1 下一次出現的位置,此演算法會選擇
下一次最久才出現的(也就是 7)將之取代掉
共有 9 次 page fault
Least Recently Used (LRU) Algorithm
常用的演算法
取『最久沒被使用』的 page 來取代掉
需要紀錄每個 page 的使用時間
圖中三條線是當下各個 page 的最後使用時間,箭頭越長就代表越
久之前使用(越久之前沒用到)
可以看出 page 3 最久沒被用到,所以下一次取代 page 的時候,
是 page 3 被換掉
共有 12 個 page fault
LRU:如何記錄最後使用時間?
使用 Counter:
替每個 page table entry 多增加一個 counter 欄位
當 page 被取用時,將當前的時間複製到 counter 欄位中,表示取
用時間
需要取代 page 時,搜尋整個 table 並找出 counter 值最小的取代
掉
缺點:需要掃過整個 page table,效率較差
使用 Stack:
使用 double linked-list 實作堆疊
被 access 就拿到 stack 頂端,越頂端就代表越常被存取
取代時,只要把最底端的(也就是最不常用的)取代掉
優點:取代時,不用看完整個 list
缺點:page 被 access 而需要更新 list 順序時,需要更新 6 個指
標,overhead 比使用 counter 時還高
LRU 與 Optimal Algorithm 皆是 stack algorithms 中不會發生 Belady’s Anomaly
因為這兩種演算法中,frame 多的情況是 frame 少的情況的 super set
也就是,frame 少(但大於 3)時,前三名存取是 721,如果 frame 變多
時,一樣的存取 pattern,前三名還是 721
不像 FIFO 時,如果 frame 數變多,留在記憶體中的 frame 可能不是最
常存取的那幾個(以剛才的例子來說,可能 721 不會都出現的意思)
舉例:reference string:1,2,3,4,5,3,1,3,2
FIFO + 3 frame:最後的 page 狀態:3,2,1
FIFO + 4 frame:最後的 page 狀態:5,1,2,4
可發現,理當最常出現 page 1,2,3 應該出現在最後的 page 的狀態
中,但是 FIFO + 4 frame 卻沒有出現 3
LRU 的效能議題與改進:使用估計法
LRU 需要硬體支援,但速度仍慢
改進方法:『估計』誰是可能是最久沒用的,而不是真的詳細去紀錄
採用 Reference Bit
一開始每個 page 的 reference bit 被設為 0
當某個 page 被取用,它所屬的 reference bit 設為 1
固定時間後,把所有 page 的 reference bit 設回 0
要 replace 時,找那個 reference bit 為 0 的
原理:如果是常用的 page,就比較不會受到定期把 bit 設為 0 的
影響 (很快會被設回 1)
Second Chance Algorithm
基於 FIFO 加上 HW 提供的 reference bit
概念:在尋找受害者時,每個 page 有兩條命,被抓到第一次只會
損失一條命,並不會被取代。當某一個受害者已經用掉一條命了
還被抓到,就會被取代
原理:
每個 page 設有 reference bit,OS 會循環檢查(FIFO)找受害
者。
若某個 page 的 reference bit 為 0:選為受害者並取代
若某個 page 的 reference bit 為 1:
將 reference bit 設為 0,但不取代之,而是繼續往下找
但如果系統內的 reference bit 都是 1,就會把全部都變成 0 然後把
最早碰到的取代掉,退化為原始的 FIFO
被設為 0
被換掉
Second Change Algorithm 的改良
被 write 過的 page,因為取代掉時需要寫回 disk,代價高
增加 modify bit (dirty bit),指示是否需要寫回硬碟
可能的情況:
Ref bit
Dirty bit
描述
0
0
最適合被取代的
0
1
次之,雖然取代前要寫回硬碟
有修改卻 ref = 0 原因:之前用掉一條命了
1
0
不適合,可能很快又被用到
1
1
最不適合,可能很快又被用到且要寫回硬碟
Counting Algorithm
計算使用次數,但此方法不常見
每 reference 到一次就把 counter 增加 1
LFU (least frequently used) Algorithm
挑最少被使用到的取代掉
精神:認為常被取用的 page,counter 應該很高
缺陷:
一開始狂用(counter 高),但後來不用的 page 就會浪費掉
解法:定期降低大家的 counter
MFU (most frequently used) Algorithm
挑最多(counter 最高)的取代掉
精神:因為次數少的可能是剛被搬上來,以後需要用的,所以不
該換掉
Page-Buffering Algorithms
想要解決的問題:當 page 需要取代的時候,當下操作會被延遲(因為
需要做 page swap in/out 的動作)
先保留一些 free page,當 page fault 時,直接把 free page 給它用,降
低當下的 delay(但是當下先找好犧牲者),有空時,再把犧牲者換掉,
產生 free frame
額外:建立一個 modified page list
紀錄哪些 page 已經被修改過(dirty),然後趁硬碟等 backing store
有空的時候,把 page 寫回硬碟,保持 page 為 clean
降低以後因為 page 為 dirty 而需要寫回硬碟的機率
額外:將受害者在標記為 free-frame 的時候,不要把內容清掉
如果恰巧在該 page 被蓋掉之前又用到該 page 的內容,就可以直
接從 free pool 中有標記的拿來用(取消標記),而不需要重新從硬
碟讀取
多發生在預估錯誤:發生在這個 page 其實很常用
應用程式與 page replacement 間的議題
應用程式本身比 OS 更了解應用程式中 data 的使用狀況
例如資料庫系統(database)
不希望 OS 進行 buffering 等操作延誤效能
OS 提供直接操作硬碟的方法(不透過任何 file system(檔案系統))
Raw disk:可以直接讓應用程式存取一部分沒有被檔案系統格式化的磁
碟分割 (省去 OS 去做 buffering 或 locking 等動作)
Frame 的分配議題
每個 process 至少需要一定量的實體 frame,以避免不斷的 page fault
Fix Allocation:固定分配
等分模式(Equal Allocation):
每個 process 可用的 frame 數量
= 總 frame 數扣掉 OS 使用後 ÷ process 總數
比例分配(Proportional allocation)
根據程式本身的 size 而定,size 越大,分到的 frame 越多
計算方式:
si = process Pi 的 size
S =Σsi,即所有 process 的 size 總和
m = 扣到 OS 之後的 frame 總數
ai = 分配給 pi 的 frame 數 =
𝑠𝑖
𝑆
× 𝑚 (即比率 x 總數)
優先權分配(Priority allocation)
類同比例分配,但不依照 process 的 size,而是依照程式本身
的執行優先權
分配 frame 之後,當發生 page fault 時,該如何處理?
將自己的 frame 中的某一個 page 取代掉
若 OS 允許,則可以把優先權比較低的 process 的 frame 給取代掉
Global replacement 與 Local replacement
Global replacement
選擇 frame 取代時,可以選擇其他 process 的 frame (包含自己的)
好處:OS 可以衡量整個系統,增加整體效能
缺點:程式執行時間變化大
因為可能剛好有機會都偷別人的,就比較快(fault 的機率低)
但也可能都把自己的換掉,導致自己 fault 次數高,執行變慢
Local replacement
對 process 來說,相當於 free frame 只限制於自己原本被分配的
frame 數 (不能把別人的換掉)
缺點:某些記憶體空間浪費掉(明明有 free frame 卻沒用)
Non-Uniform Memory Access (NUMA)
過去提到的:存取每一個記憶體位置所需時間是一樣的
NUMA:可能有多塊記憶體由 system bus 連結,因此存取每一個位址
的時間可能不同。
當需要取代 Page 時,盡量先存取離 CPU 比較近的那些 page,避免存
取的 delay 太長
Thrashing
當某 process 可以使用的實體 page 太少時,會一直發生 page fault
需要花很長的時間在讀取/寫入 page,而非 CPU 計算
導致 CPU 使用率低,可能會讓 OS 以為系統很閒,讀取更多程式,導
致系統更加惡化(每個 process 可以用的 page 更少)
Thrashing:表示某一個 process 不斷在進行 page 的 swap in/swap out
當下需要常常存取的 page 數量,比給定可用的 page 還高,就會
一直 page fault
例如:某程式跑迴圈內容需要 5 page,但可用只有 4 page,就會
發生跑迴圈時一直 page fault
希望 Thrashing 不要影響其他 process:使用 local replacement,使
得 thrashing 的 process 不會偷取其他 process 的 page 來用
但仍沒有完全不影響他人:因為當某 process thrashing 時,該
process 的 paging 操作會塞滿 paging queue (OS 都忙著幫這個
process 進行 paging),導致其他人要 paging 的時候變慢
Locality Model
Locality:
在程式中某一段中,常常需要被一直存取的那些 page,通常都是
一起被使用
例如:迴圈內需要用的 5 個 page,在迴圈不斷執行的過程
中,這些 page 會一直被重複存取,這些 Page 稱為同一個
locality
隨著程式執行,locality 可能會改變(例如進入另一個迴圈,使用另
一群不同的 page)。
程式由許多 Locality 組成,而 locality 之間可能會重疊(某些 page
存在於多組 locality 之中)
示意圖:記憶體存取 Pattern
圖中密集的區塊就是 locality
Thrashing 發生的原因:
當 locality 中的 Page 總數少於總共可用的實體 page 總數時,
就會不斷的 page fault ,導致 thrashing
Working-Set Model
Δ:working-set window
某一個固定的 page reference(參考)數目
例如:10,000 次
WSSi (working set of Process Pi)
Process Pi 在最近 Δ 次 page 參考中,總共存取了多少個 page
若兩次相鄰存取到同一個 page,則不會再多算一次 page
可以用來預估 locality 大小
雖著程式執行到不同階段,即是給予一樣的 Δ,WSSi 也不一定相
同(例如執行到迴圈時,WSSi 可能會變少,因為多次 page
reference 都是迴圈內的那幾個 page)
若 Δ 太小:則 WSS 沒辦法覆蓋整個 locality (假設 Δ= 3,但是
locality 有 5 個 page,則用 WSS 算出來也只有 3,無法預估到
locality 的大小)
若 Δ 太大:預估出來的 page 可能涵蓋多個 locality (因為取樣的範
圍太廣),而無法正確描述出當下 locality 的大小
當 Δ 趨近於無限大,則含括範圍為整個程式的執行
WSS 的範例:設 Δ 為 10
WS 大
涵蓋許多 page
WS 小
涵蓋少許 page
D:total demand page = Σ WSSi
所有 process 要求的 page 總和
m:所有可用的 page 數
若 D > m,則代表發生 Thrashing
若發生此狀況,則代表需要暫停或 swap 掉某些 process
如何估計 working set?
使用 fixed-interval timer interrupt + reference bits
每固定時間,檢查每一個 page 被存取的數量(被存取的次數用
reference bit 來記錄),高於某個數量,就假設該 page 在 WS 之中
每個 page 都有固定數量的 reference bit,如果被存取,某個 bit 就
會被設為 1
固定時間後,檢查那些至少存在 bit 為 1 的,就是在 WS 中
檢查後,把所有的 reference bit 都設回 0
可能不準確,改進方法:縮短量測的時間間隔 + 增加 reference bit 的
數量
Page-Fault Frequency (PFF)
比 WSS 更加直接的方式
紀錄每一個 process 的 page fault 的頻率
使用 local replacement
制定合適的下限與上限
PFF 越低,代表持有的實體 frame 太多,故將之釋放,未來可分配
給其他 process 使用
PFF 越高:代表持有的 frame 可能太少,分配更多 frame 給此
process
若個個 process 的 PFF 都持續增加且沒有可釋放的 frame,就得進
行 swap 來釋放 frame
WS 與 PFF
波峰與波峰之間其實就是一個 WS (locality)
變高:因為換到別的 WS (例如
換到別組不相干的迴圈,進到
令一個 locality),需要重新讀取
WS 的 page 都
拿完了,fault
降低
page
Memory-Mapped Files
把檔案存取改用記憶體存取的方式來存取,而非額外的檔案 IO 指令
不需要檔案的 read/write 等等
方法:將某一個 disk block 對應到記憶體裡的一個 page
當此類檔案剛被存取時,會類似 demand page,會將此檔案中取出一
個與記憶體 page 一樣大的區塊放到實體記憶體中
之後對此檔案區塊的讀取就利用對這塊記憶體的存取就行
效能較佳:
因為讀寫檔案時一次是以 page 為單位,而非 bytes
允許多個 process 開啟同一個檔案可以直接在記憶體裡分享該 page
何時將被修改的 dirty data 寫回硬碟?有許多可能時機
週期性的寫回(例如週期性掃描是否有 dirty page)
當對檔案執行 close()時寫回
將所有 IO 都用 Memory-Mapped file 取代
有些 OS 利用 Memory-Mapped files 取代標準的 IO
Process 可以明確指定使用 memory mapped file (例如透過 mmap
這類的 system call)
對於標準 IO 的 API (read、write、open、close 等等)仍可使用
只不過,背後仍是透過 mmap 實作,還是 memory mapped
file,並將之放在 kernel address space
根據需求,可能會搬到 user space (例如 process 使用了
read 與 write 時)
COW (copy-on-write):可用來處理對 non-shared page 進行
read/write 的情況
當對此類 page(事實上可能是檔案)進行修改時,就複製一
份新的 page 然後再修改
Memory-Mapped file 也可用在 shared memory 上
Memory-Mapped I/O
把對 IO 的存取視同對記憶體的存取
將 device register(IO 裝置中儲存資料的地方)對應到記憶體空間中
適合用於反應速度快的裝置(例如顯示卡)
Allocating Kernel Memory:分配給 kernel 的記憶體
與一般 user 的記憶體分配方式不同,因為 kernel 的許多特性不同
通常都是用 free-memory pool 的方式分配
Kernel 所需的記憶體有與多不同的特性與大小
有些可能不到單一個 page,不能以 page 為單位分配(否則會造
成 internal fragment 的問題)
有些則需要連續的記憶體區塊(例如 IO 裝置使用的記憶體)
兩種處理 kernel process 記憶體的方式:
Buddy System
Slab allocation
Buddy System
核心概念:讓 kernel 使用的記憶體是連續的
由許多固定大小的 segment 為單位來分配記憶體
每一個 segment 事實上是實體上的連續記憶體組成
需求/釋放記憶體的時候,都是以二的指數給予/釋放
例如:最小單位是 32KB
若 kernel 需求 50KB,則會在其旁邊再找另一個 32KB 的空間合
併為 64KB 給 kernel 使用 (超過 32KB 但未超過 64KB)
若再超過 64KB,則會擴展成 128KB 的連續記憶體
若當前已經有了 256KB 的 chunk,而 kernel 只需求 21KB
先分裂成兩塊 128KB
其中一塊 128KB 再切割成兩塊 64KB
其中一塊 64KB 再切割成兩塊 32KB
把其中一塊 32KB 的空間給 kernel 使用
Buddy 優點:可以快速將沒用到的小 chunk 合併為大 chunk
Buddy 缺點:會有 fragmentation 的問題(例如剛才 kernel 需求
21KB,卻給了 32KB,造成 fragmentation)
Slab Allocation
核心精神:事先建立好許多與 kernel 資料結構大小相近的記憶體空
間,當需要時就 assign 給 kernel 使用
Slab:一個或多個連續的實體 page
Cache:由一個或多個 slab 組成
對於每一種不同的 kernel 資料結構,都會有一個 cache
這個 cache 專門存這種類型的資料結構,因此 cache 內可以視
為切割為許多該資料結構的空間
當 cache 剛建立的時候,其內的每一個資料結構都標示為未使
用(free)
當 kernel 需求此類資料結構的時候,就將未使用的資料結構給
kernel 使用,並將之標為 used
當某一個 cache 內已經沒有未使用的資料結構的時候,kernel 又要
求該類型的資料結構,就拿新的 slab 來使用並將新的資料結構物件
放到該 slab 中
好處:
不會有 fragmentation:因為已經對每種不同的資料結構創立大
小相符的記憶體空間了,不會浪費
Kernel 需要取用記憶體的速度很快
Other Consideration:Prepaging
Pre-paging:避免 process 剛執行的時候因為記憶體內都還沒有 page
導致一連串的 page fault
先把某些比較可能用到的 page 先搬進來,而非用到的時候才從記憶
體拿
例如程式進入的 page 與其附近幾個 page 都拿進記憶體
但如果拿進來的 page 沒用到,就會造成 IO 浪費
α:prepaing 中,實際有被用到的 page 的比率。
α 接近 1:代表幾乎都有用到,pre-paging 是有用的
Other Issues – Page Size
Page 的大小會影響許多方面:
Fragmentation 影響的程度
Page table 大小
Resolution (若 page 越小,resolution 越好)
I/O overhead (讀寫某一個 page 所需要的時間)
如果 page 太大,而只有小部分 page 內容需要,就會造成
整體 IO 的 over head 高
Page fault 的次數
Locality 的情況
TLB 的大小與效用程度
Page 大小為 2 的指數倍 bytes
現在的情況:page 大小越來越大
Other Issues – TLB Reach
TLB Reach:表示 TLB 總共可以存多少大小的記憶體
TLB Reach = (TLB 的 entry 數) × (Page Size)
理想的情況:使得當前的存取(working set)都可以被 TLB 給涵蓋,避
免一直發生 page fault
增加 TLB Reach 的方法:
增加每個 page 的大小,但會增加 fragmentation 的影響程度
解法:TLB 提供不同大小的 page size,讓應用程式可以將較大
的資料放在較大的 page 中,避免整體提升 page size 造成的
fragmentation
Other Issues – Program Structure
陣列結構問題:例如二維陣列的 row-major 方式存放
在記憶體中,每一個 row 都是放在連續的記憶體中
假設每一個 row 都存在一個 page 中
存取方式:
如果是逐個 row 存取,則只有存取該 row 的第一個元素時會發
生 page fault,因為其餘的元素都會與第一個元素同一個 page
若是逐個 column 存取(存取每一個 row 的第一個元素之後,再
去存取每一個 row 的第二個元素),則會不斷發生 page fault
因為沒有應用到 page 一次把整個 row 存起來的優點
Other Issues – I/O interlock
當 IO 進行時,記憶體中的某些 page 必須讓給 IO 裝置讀寫,而不能
將之 replace 掉
可能的實作方法:
每個 page 都增加 lock bit,
當被用來當作 buffer 時,
可以把該 bit 設為 1 來鎖定該 page
作業系統概論
Chapter 10: File System:檔案系統
File Concept:有關於檔案的基本概念
在 logical address space 上是連續的空間
型態多元(文字檔、執行檔等等)
File Attribute (檔案屬性)
Name:檔案名稱,給人辨識用的
Identifier:在檔案系統中唯一識別此檔案的 unique tag(數字)
Type:檔案型態
Location:指向此檔案在裝置上(例如硬碟)的實際位置
Size:檔案大小
Protection:控制誰可以對此檔案讀寫/執行
其他資訊:例如 time ,date ,user identification 等等,根據不同系統
有不同附加資訊
File Operation:檔案操作
Create:建立
Write:在 write pointer 所指的地方寫入此檔案
Read:在 read pointer 所指的地方讀取此檔案內容
Seek:在檔案中重新定位 pointer
Delete:刪除檔案
Truncate:砍掉檔案的一部份
Open (Fi):開啟檔案,在 directory structure 中尋找 entry Fi,並將
其內容讀取到記憶體以供讀寫
Close (Fi):關閉檔案,與 Open 相反,是把 directory structure 中 Fi
的 entry 寫回硬碟
Open Files:開啟檔案所需要的一些資料
Open-file table:記錄了哪些檔案已經被開啟
File Pointer:指向最後讀/寫的位置,每一個開啟該檔案的 process
都有自己的 pointer
File-open count:記錄該檔案被開啟了幾次。如果 count 從 1 變回
0,就代表沒人在用了,可以將之從 open-file table 中移除
Disk Location of the file:記錄存取檔案資料的位置資訊等等
Access right:每一個 process 對某個檔案的存取權限
Open File Locking
類似 reader-writer lock
Shared lock:類似 reader lock,可多個人用 Shared lock 來讀
取檔案
Exclusive lock:類似 writer lock:避免其他人讀取或寫入此檔
案
Lock 的類別:Mandatory 與 Advisory
Mandatory:由 kernel 自行檢查每一個 open、read、write 是
否可已存取(例如檢查有沒有他人拿走了 lock)
Advisory:
這些 lock 只是由某一群特定的 process 間建立的(稱為
cooperating processes),只有這些 process 互相用這些
lock 來處理某些檔案間的存取
也就是,這些 lock 只在特定 process 中生效
其他沒有參與其中的 process (稱為 uncooperative
processes) 則可以忽略那些 lock,任意的存取檔案,
Kernel 不會干涉
File Structure:檔案結構
無結構:僅是一連串的 words 或 bytes
簡單結構:
由許多 Line 組成
Line 的大小可以是固定或可變
複雜結構:
Formatted document (有特定結構的文件)
Relocatable load file
處理複雜結構:可以在檔案開始的地方插入一些固定長度的
控制字元
Access Method:存取檔案的方法
Sequential Access:循序存取,讀完這一個換下一個
可支援的操作:
Read next:讀取指標指向的下一個資料
Write next:將資料寫入在指標所指的下一個位置
Reset:重置檔案指標的位置
Direct Access:可以直接存取檔案中的任意位置
檔案由許多固定長度的 logical record 組成
使用 relative block number 來決定位置
不是硬碟上的實際 block number,而是相對於檔案起始點的
block number
用意:不用讓該檔案固定在 disk 的某一個 block 上,較有彈
性
可支援的操作:(n:代表 relative block number)
Read n:讀取 relative block number 為 n 的 block
Write n
Position to n:將檔案指標指向 block n
Read next:對檔案指標所指的位置讀取
Write next
Direct Access 模擬 Sequential 的方式
其他的存取方式:使用 Index
用意:建立 index file,如果搜尋欄位是利用被 index 過的東西,
在搜尋的時候就不用讀取整個檔案內容,只要從 index file 中找出
record 的位置,直接過去存取就好了
若 index 檔案本身過大,就使用多層的 index
Disk Structure
硬碟可以被切割成許多磁碟分割(partition),又稱為 minidisks,slices
磁碟或磁碟分割可以利用 RAID 的方式來保護資料
磁碟或磁碟分割可以是 raw (沒有使用檔案系統)或者是利用某種檔案系
統格式化
裝有檔案系統的磁碟或磁碟分割稱為 Volume(例如 C 槽、D 槽)
每個 Volume 中會包含該檔案系統中所有檔案的資訊,儲存在 device
directory 或者 volume table of contents 的 entry 之中(這些資料結構又被
稱為 directory,用來儲存檔案的 meta data)
範例:磁碟組織
檔案系統的類別
一般來說都是指 general-purpose (一般多用途)的檔案系統
但偶爾會有比較特殊的檔案系統,以下舉例:
tmpfs:temp. file system
以記憶體為主的檔案系統,用於快速存取以及暫時的 IO
斷電後檔案會遺失
objfs:虛擬的檔案系統,提供 file API 讓 debugger 等程式可以存
取 OS 的資料結構
ctfs:”contract” file system
包含系統啟動時哪些 process 需要被啟動與持續在系統中執行
的資訊
lofs:loopback file system
把某一個 file system mount 到另一個 FS 上,透過 mount 別人
的那一個 FS,也可以存取被 mount 的 FS
procfs:透過 file API 取得 proces 的一些資訊
ufs、zfs:一般的 general purpose file systems
對 Directory(目錄、資料夾)的操作
搜尋檔案、建立/刪除檔案、列出此目錄下的所有內容、重新命名檔
案、traverse 整個檔案系統
Directory 的組織
Efficiency:尋找定位檔案的速度要夠快
Naming:對使用者來說要方便
不同使用者可以有相同名稱卻不同內容的檔案
某一個檔案可以有不同名稱
Grouping:可以依據檔案的特性將檔案分類
例如:提供資料夾的架構,可以分類檔案
例如:把所有 Java 有關的程式都放到同一個目錄底下
Single-Level Directory
利用單一個 directory 把所有使用者的 file 都放在一起
Naming Problem:同一個目錄,同名稱會造成搜尋時無法區分
Grouping Problem:沒有對檔案進行子目錄/資料夾的分類
Two-Level Directory
替每一個使用者建立他們自己的 directory
Naming:不同使用者間可以擁有同名稱的檔案
搜尋較有效率:對於某一個使用者來說,只需要搜尋自己的 directory
就好,不用管其他使用者的
依然沒有 Grouping 的能力
Tree-Structured Directories
擁有良好的搜尋效率與 Grouping 能力
當前所在的目錄稱為 Working Directory(工作目錄)
絕對路徑(Absolute Path Name):完整的目錄,例如/var/mail/my
相對路徑(Relative Path Name):從當前工作目錄開始找
剛才的例子:如果工作目錄為/var,則相對路徑為 mail/my
Acyclic-Graph Directories
允許某一些檔案或子目錄(subdirectory)可以共享,也就是雖然看到兩
個同樣的名字,但實際上的檔案/目錄只有一個
如何實作檔案/子目錄的共享?
利用 Link:類似指標/捷徑,指向當前存在的檔案/目錄
OS 有時候會忽略這些 Link:當進行整個檔案系統的 traverse 或者
是將所有檔案複製到備份儲存區的時候
因為實際上的檔案只需要存一份,利用 Link 共用的檔案不必
要複製兩次
尋找檔案時也不用同一個 link 找兩次
可能遇到的問題:
aliasing:某一個檔案擁有多個檔案名稱、多個絕對路徑
dangling pointer 問題:當 Link 所指的目標檔案本體被刪除時
不同的可能解決方案:
告知使用者該檔案已遺失(例如 Windows 或 UNIX)
利用 reference list 記錄有哪些 Link 連結到這個檔案,只
有在所有的 Link 都被移除的時候,才把檔案本體刪除
Entry-hold-count solution:檔案本身記錄有多少 Link 指向
它
General Graph Directory
可能存在有 cycle 的 Link,進行 traverse 時需要注意不要無窮迴圈
因為有 cycle,所以有可能發生 reference bit 是 0,但卻無法存取的檔
案或目錄(因為 cycle 互相 link)
例如:一群 file 內部有 loop 互相指,使得 count 非 0,但卻無法
從 root 走去存取
使用 Garbage Collection
用來移除無法存取的檔案或目錄
方法:Traverse 整個檔案系統,把碰到的檔案或目錄標記起
來。當走完以後,把沒有被標記的檔案或目錄給移除(因為他
們不可能被存取到了)
利用移除 cycle 的演算法:當每一次建立 link 的時候就執行 cycle
檢查,但此方法昂貴
File System Mounting
檔案系統在使用前被需要被 mount 到某一個目錄,方可使用
主要的檔案系統 mount 的位置:根目錄(整個系統最底層的目錄)
Mount point:新的檔案系統掛載的位置。對於新的檔案系統來說,
mount point 即為根目錄(但 mount point 對於原本的檔案系統來說,
mount point 只是一個普通的目錄,不一定要是根目錄)
至於 mount point 目錄是否必須為空,不同 OS 有不同規定
若可以不為空,mount 後原資料夾內容將暫時被隱藏,不可存取
Mount 前:
左邊的 users 目錄未來將成為 mount point,原本 users 底下的資料會被
隱藏
Mount 後:
File Sharing
在多個使用者的系統共用檔案
若是分散式系統,可能透過網路共享(例如 NFS,Network File System)
必須要處理權限與保護的問題
User ID:辨別檔案擁有者,通常擁有最大的存取權限
Group ID:允許多個使用者在同一個群組中,可透過群組權限存
取該檔案(權限可能比擁有者低一些)
Remote File System
分成手動、自動、半自動
手動:例如 FTP
自動:例如 NFS 等,看起來就像是 local 的 FS
半自動:透過 world wide web (WWW)
Client-Server 架構
Server 可以同時服務多個 client,一個 server 的檔案系統可以
分享給多個 client 看到
但 client 之間的身分驗證問題必須要好好處理
NFS:UNIX 系統使用的 Client-Server 檔案分享機制
CIFS:由 Windows 使用
Distributed Information Systems (distributed naming services)
分散式資訊系統
允許在多台電腦間共享某些資訊(例如使用者資訊)
例如:LDAP、DNS、NIS 等等
Failure Modes
當檔案系統發生錯誤時:進入 failure mode
例如:directory 的結構或者是 metadata 損壞
Remote file system 可能會因為網路、伺服器等等眾多因素而發生
失敗
可能採取的方法:終止所有操作 or 延遲操作(稍後再處理)
當從錯誤狀態中恢復時:必須要恢復過去的狀態資訊
(state information)
例如:網路斷線恢復後,要恢復過去的登入資訊
某些檔案系統(例如 NFS V3)是 Stateless,也就是從 client 傳給
server 的每一個 request 都必須包含完整的狀態資訊,server 不會
記住 client 本身的資訊
Consistency Semantics
當多個使用者修改檔案時的處理
Unix File System(UFS)的做法:
整個檔案只有一份 image
當有任何人修改該檔案,所有的 user 都會馬上看到被修改的
內容
一個檔案的 image 視為一個 exclusive 的資源
一個檔案 image 內只有一個 pointer 指向檔案內的位置,
以供讀寫,所有開啟此檔案的 process 必須共用此
pointer
換句話說,無法同一瞬間有兩個 process 在寫入檔案,因
為只有一個共用的 pointer
因此如果發生同時存取的情況,其中一個 process 的操作
就會被 delay
Andrew File System (AFS)的做法:
使用多個 session 與 image
只有在 session 完成並寫入時,檔案才會改變
若當前 user 沒有把 session 寫入,那麼被修改的 image 就會
暫時留著,不會蓋掉原本的檔案
不同於 UFS,檔案被修改並不會馬上看到差異,除非該檔案
被寫入之後,重新開啟該檔案
Protection
控制檔案可以被誰存取(access)
存取的類別:
Read:讀
Write:寫
Execute:執行/進入目錄
Append:附加內容在檔案後方
Delete:刪除
List:顯示目錄內容
UNIX 的權限範例:
使用者、群組、其他所有人(universe、other)
作業系統概論
Chapter 11: Implementing File Systems:檔案系統實作
File-System Structure:檔案系統的結構
一般的檔案是由 logical blocks 組成的
檔案系統提供了使用者介面,將 logical 的檔案對應到實體的 block
在硬碟中,I/O 操作的單位是 blocks 與 sectors,一般使用者不易維護
檔案系統會幫忙進行翻譯的動作
File Control Block:記錄了檔案有關的資訊的資料結構
Device Driver:控制實體的儲存裝置(例如硬碟)
檔案系統根據不同功能分為許多階層(layer)
Layered File System:檔案系統分層
Device drivers:
管理 IO 裝置本身 (IO Control Layer)
給予低階的 device command,例如:
read drive1, cylinder 72, track 2, sector 10, into memory location 1060
詳細描述了硬體結構中的哪一塊資料需要被操作
Basic file system
將 physical block 資訊轉換為 disk structure info(例如 cylinder 或
block 等等)
接受的指令例如:
retrieve block 123
然後將此 block number 依照硬碟結構轉換成對應的 device
command
其他功能:管理 memory buffer 與 cache
包含分配(allocation)、釋放(freeing)與取代(replacement)
Buffer:儲存傳輸中的資料
Cache:儲存常取用的資料
File organization module
可以認出檔案、Logical block number 與 Physical block number
可以將檔案中的 Logical block number 轉換為實體的 Physical block
number
Logical block number:以某一個檔案的起始 block 為 number 0,往
後遞增。轉換為實體後需要轉為真正在硬碟裡的 block 位置(可能
不連續)
管理 free space 與 disk allocation
Logical File System
將應用程式指定的檔案名稱轉換為實際的檔案資訊,包含 file
number、file handle、file control block 等資訊
例如 UNIX 中的 inodes
管理 Directory 結構
檔案保護:例如檔案所有權、檔案存取權限等等
檔案系統分層的特色
彈性大:在不同 FS 上,可能只要改變表面幾層,底層硬體部分可能都
相同或類似,那麼時做這些 FS 就可以共用底層 layer,只需要修改上
層的 layer 就行
但是效率會降低(比起 raw FS:沒有分層處理的檔案系統)
On-Disk File System Structures:儲存在硬碟中的一些重要的 FS 資料結構
Boot Control Block
在那些包含 OS 的 volume 中使用,指示了開機時要去哪裡讀取 OS
一般來說都是該 volume 的第一個 block
Volume Control Block
每一個 volume 都會有,又稱 Superblock
包含了該 volume 的一些基本資訊,例如:block 總數、可使用的
block 總數、block 大小、可使用的 block 的指標或陣列等等
在 NTFS (Windows 使用)中,這個 block 的資訊會存在 master file
table
Directory Structure
每個 FS 都會有,儲存了每個檔案名稱以及其相對應的 inodes
File Control Block (FCB)
每個檔案都會有,記錄了檔案的各種資訊
例如:inode number、存取權限、檔案大小、修改/存取日期、檔
案擁有者/檔案所屬群組、指向 data blocks 的指標等等
In-Memory File System Structures
Mount Table
記錄了 FS 被 mount 的狀態,例如哪一個 FS 被 mount 到哪一個
mount point,以及該 FS 的類型是甚麼(NFS、UFS 等等)
檔案相關的結構,OS 會記錄檔案被開啟的狀態
OS 有時會保留檔案的目錄結構(可用於加快檔案的搜尋速度)
Open-file table:記錄了哪些檔案被開啟,許多程式都開啟同一個
檔案的時候,可以利用 index (相當於 FD 或 file handler),來記錄
每一個檔案
要讀檔案之前,要先找到 FCB,然後才會知道 data blocks 位置
process 開檔前,會先去 system-wide table 看看有沒有開過,可以
不用每個 process 開同一個檔案都要存檔案的 FCB
Partitions(磁碟分割) and Mounting(掛載)
磁碟分割:可以包含有檔案系統(稱為 cooked,『煮過的』磁碟分割)或
者是沒有包含檔案系統(就只是一連串的 blocks,稱為 raw,生的)
Root Partition:包含當前 OS 的那一個磁碟分割
在開機時就會被 mount 到根目錄
其他磁碟分割則是手動會自動 mount 到其他 mount point
進行 Mount 時,會檢查檔案系統的 consistency(一致性)
亦即檢查 metadata 是否有錯誤
若有錯,則會試圖修復並重試
若一切正常,就會將之加入到 mount table 並允許存取
Virtual File System(VFS)
基本概念:將許多不同的檔案系統包裝起來,讓使用者用起來感覺就
像一個單一的檔案系統
第一層:file-system interface,最上層與使用者接觸的 API,基本上與
一般的檔案系統 API 一樣(例如 open、read、write 等等)
第二層:VFS interface,允許利用相同的 system call 對不同的檔案系統
操作
有特殊的資料結構來確保可以存取到不同檔案系統中的檔案
例如:Vnode,在整個 VFS(跨網路)中是唯一的,用來識別某一個
檔案
VFS 會根據實際上檔案系統的不同,呼叫不同的 API 來處理檔案
對使用者來說,只需要處理 VFS 的操作就好,對於不同 FS 間
的處理一律交給 VFS 來做,使用者不必擔心
第三層:不同檔案系統本身,包含跨網路的遠端網路系統
VFS 的實作:類似定義一個統一的規格
一般的 Linux 檔案系統有以下四種 object type
Inode:代表 individual file
File:代表一個被打開的 file
Superblock:代表整個檔案系統
Dentry:代表一個 individual directory entry
對於 VFS 來說,剛才提到的每一種 object,VFS 都定義了對於該 object
來說需要的 function,這些 function
每一種 object 都要有一個指向 function table 的指標
該 function table 記錄著該 object 需要用到的 function
例如:file object 的 function 有:open、close、read、write、
mmap
不同的檔案系統要以自己的方法去實作每一個 object 的 function
table 中的 function
類似定義一個統一的規格。由各種不同的 FS 來實作
Directory Implementation
在 directory 中,如何將檔案記錄起來?
使用者給予檔案名稱,找出相對應的檔案 metadata
Linear List:
將所有檔案名稱與其資料 block 的指標當作 node
把所有 node 用 list 串起來
實作、搜尋方便,但是搜尋效率差
增加效率的方法:利用 tree 等結構把 node 依照檔案名稱排序,
但會導致新增檔案時 overhead 更大
Hash Table
由 linear list 改良而來,加上 hash 結構
將檔案名稱 hash,之後存到不同 entry。除非有兩個檔案名稱
hash 之後的值是一樣的,才用 linked-list 串起來。但是 list 的長度
會比原來的 linear list 還要短很多
缺點:調整 entry 的 overhead 比 linear list 大,因此盡量避免 entry
變動
Allocation Methods:如何在硬碟中儲存檔案?
Contiguous Allocation:連續擺放
每個檔案都是占用一塊連續的空間
效能最佳(因為沒有額外的硬碟 seek time,檔案位置一旦找到之
後,硬碟就可以連續讀取)
實作簡單,只需要起始 block 位置與 block 數量就可以描述一個檔
案的位置
缺陷:
如何在硬碟中尋找該檔案要存放的位置?
First fit?Best fit?Worse fit?
檔案大小無法成長,因為這個檔案的下一個位置緊鄰著其他
檔案,除非整個檔案搬到新的位置才能成長
造成 external fragment 問題
需要 compaction (重新搬移檔案位置消除 fragment),無
論是 on-line 或 off-line 進行
Extend-based system
為改版後的 Contiguous Allocation
不同處:當檔案需要成長而空間不足時,會在硬碟空閒處找另一
塊連續 block,並將多出來的部分存在該處(該部分稱為 extent)。
最後使用指標將兩個部分連接
因此檔案的存放『不一定是連續的 block』
如果檔案還是太大,就會繼續分配更多的 extent
造成的問題:
Internal Fragment:如果新分配的 extent 超過所需大小 (也就
是 extent 太大,導致沒用完)
External Fragment:各個大小不一的 extent 散佈在各處,在不
斷的 allocation 與 deallocation 之後就會造成 fragment
Linked Allocation
每個檔案都由散佈在硬碟各處的 block 以 linked-list 的方式串起來
在 Directory entry 中(檔案的 metadata),指向檔案在硬碟中的第一
個 block
之後其內的每一個 block 的最後都會有指標,指向下一個 block,
最後一個 block 的指標為 NULL (Nil)
檔案大小不需事先知道,且大小可以成長
通常會由某一個 Free Space Management System 管理與維護空間
(例如紀錄有哪些 block 是可用的,以後會詳述)
效率較差,因為每個 block 位置不同,硬碟 seek 比較久。
尋找特定某一個 block 是很麻煩的,必須要從頭不斷 traverse
指標,造成很大的記憶體與硬碟 IO overhead
增加效率的方法:將 block 分群(clustering)
許多個 block 放在一起,只用一個指標指向下一群 block
降低硬碟 seek 的數量,缺點是會造成 internal fragment
可靠度問題:當其中一個 block 的指標損毀,整個檔案就不完整
了(因為無法往下找到其他 block)
FAT (File Allocation Table)
為 Linked-list 的變形,為了解決搜尋時硬碟 seek 過多的問題
在 volume 的起始處,建立一個 table,利用所有可用的 block 當作
index
結構類似 linked-list,在這個 table 中會記錄每一個 block 的下一個
block 是誰
但與之前 block 的 list 不同,這只是一個 table,可以存在記
憶體,供快速存取
也因為在記憶體,存取某一個 block 的位置只需要在記憶體
內進行 linked-list 的搜尋,不需要讓硬碟一直 seek
在 table 的 entry 中填的就是下一個 block 的編號
尋找可用的 block 很簡單:找那些編號是 0 的 block
在 directory entry 中一樣存放著該檔案的第一個 block #
但搜尋時是去找 table 而不是 seek 硬碟
Indexed Allocation
不使用 linked-list 的概念
每個檔案使用一個 index block,這個 block 會記錄這個檔案使用的
block #,該 block 內使用陣列的方式存放 block #
隨機存取(random access),因為 index block 內是以陣列存
放 block #,要取得第 i 個 block 不用進行 linked-list 的 traverse
上圖左邊是 index block 內的陣列,每個 entry 都記錄著 block #
缺陷:每個檔案都至少要有 index block,所以對於小檔案來說(實
際使用的 block 沒幾個,index block 內大部分都是空的),空間消
耗程度比 linked-list 還高 (即 Space Overhead 高)
由於單一個 index block 能存的 entry 數量有限,導致了限制最大
的檔案大小
解決方法:
Linked scheme:將許多的 index block 用 linked list 串起來
Multilevel index scheme:多層 index table,外層 index table
entry 指向許多的內層的 index table,最內層的 index table
entry 才指向檔案內容的 block
Combined scheme
由於小檔案使用 index table 反而浪費空間,因此希望根
據檔案大小不同,調整使用的 index table 的數量與層數
根據不同檔案大小不同,可能不使用 index table,或者
是混合使用二層、三層等等
使用於 UNIX
index blocks 可以被放在記憶體,但 data block 則是散佈
在硬碟中(不連續存放)
檔案超過一定大小之後,再
更多的 block,就會用多一層
的 index 來存
效能議題
Contiguous:sequential 與 random access 都很快
Linked:只有 sequential 效能好
Indexed:即使存取單一個 block 都需要先去存取 index block,
overhead 較高
某些 FS 允許建立檔案的時候根據存取特性,決定不同類別的儲存方式
甚至是當檔案成長時,動態更改儲存方式
由於 IO 存取的速度非常慢,因此增加記憶體存取或者是 CPU 指令來降
低 IO 時間是合情合理的
Free-Space Management
如何管理硬碟中的可用空間(亦即,哪些 block 是空的)
使用 Bit vector (或稱 bit map)
使用一個陣列,每個元素長度都是 word (例如,4 bytes)
對於這整個陣列的第 i 個 bit,代表著 block i 是不是空的
1:空的;0:已經被用掉
範例:假設總共有 n 個 block,將這個 vector 切成 n 個 bit 來看:
由於 vector 是由許多 word 組成,每個 word 如果是 4 bytes(32
bits),則一個 word 代表了 32 個 block 的使用狀態
那如何在這個以 word 為 index 的陣列中取得第一個空的
block 的 block number?(也就是,這個陣列中第一個是 1 的
bit 在哪?)
換句話說:如何在 int 的陣列中,找出整個陣列中第一個非 0
的 bit 在哪?
計算方式:
先找出陣列中,第一個數值不是 0 的 word 在哪
(因為如果 word 數值是 0,代表這個 word 內所有 bit 都
是 0,也就是 block 都被用掉了)
之後找出那一個數值非 0 的 word 之後,再利 用某些特
殊的 CPU 指令,找出該 word 中第一個 bit 1 的 offset
最終的 block number =
(每個 word 有多少 bit) × (有多少數值是 0 的 word)
+ 在非零 word 中,第一個 1 bit 的 offset
可以很容易取出連續的 block
Bit vector 需要占用額外的空間:
block size = 4 KB = 212 bytes
硬碟大小 = 1 TB = 240 bytes
Block 總數 = 240/212 = 228 個
需要用 228 個 bit = 256 MB 的記憶體來存 bit vector
減少空間消耗的方式:利用 cluster
若每 4 個 block 形成一個 cluster,只用一個 bit 表示
則記憶體空間消耗變為 1/4,即 64MB
使用 Linked-list (free list)
類似之前用 linked-list 紀錄使用的 block 一樣,這裡是使用 linkedlist 紀錄哪些 block 是空的
無法輕易取出連續的 block (因為下一個 block 不一定是隔壁的)
不需要額外空間儲存資訊(因為直接存在那個 block 內,不像 bit
vector 需要額外花記憶體去存)
尋找空間時需要 traverse 整個 list
使用 Grouping
類似 linked-list,但是將許多 free block 組成一群 (例如 n 個)
不要一個一個串
在第一個 free block 中,會儲存接下來 n-1 個 free block 的位址(但
不一定是連續的 block),以及下一個儲存 free block 位址的那一個
block
可以快速找到大量的 free blocks
使用 Counting
不像 Grouping 中,第一個 free block 內紀錄的 free block address
不一定是連續的
Counting 中,第一個 free block 記錄著『往後有多少的連續的 free
block』,以及下一個有紀錄的 free block 的位置
可以方便取得連續的空間
記錄 free block 的 entry 可以使用 balance tree 來儲存 (效率高)
Efficiency and Performance
Efficiency:效率議題
檔案的 metadata 是否要事先分配空間來節省到時候使用時的時
間,或者是在需要時才分配?
在 UNIX 中,用來儲存檔案 metadata 的 inode 會事先分配一
部份在硬碟(也就是,即使是空的硬碟,也有一部分空間被用
掉)
此種方式可以在未來存取的時候提升效率
inode 分布在硬碟各處,存檔案的時候,會讓檔案存在距離
inode 比較近的地方,存取會比較快
使用 Cluster 將 block 聚集分配可以減少 disk seek 的數量,但會造
成 internal fragment
儲存在 directory entry 的資料類別會影響存取速度
例如儲存『last access time,上次存取時間』,則會在每次讀
取檔案時更新,overhead 高
資料結構使用固定大小或者是動態大小?
早期 Solaris 系統中,process table 或 open-file table 都是固定
大小的,簡單且效率高,但是限制了最大 processes/files 的數
量
近期 Solaris 系統使用了動態調整大小的 table,但是因為變複
雜而會降低存取速度
Performance:效能議題
metadata((例如 Inode)放近一點,會讓存取快一點
是否使用 Buffer Cache 來存放常用的 blocks,加快存取效率
Synchronous write
亦即,資料在記憶體中被更改後,需要直接寫回硬碟,寫入
完成後才能被讀取 (不准 Buffering 或 Caching)
造成效率變低
非同步寫入(Asynchronous writes):由於不需要等待硬碟寫入
後才讀取資料,可以更平凡寫入,且使用 buffer 來加入
Free-behind 與 read-ahead
在 sequential 存取,例如影片播放中,可以把已經讀取播放
完的部分從記憶體移除,然後將未來需要播放的 block 事先
讀取到記憶體
Page Cache
用 VM 的技術來 cache file,而非透過原本的 FS 的那些結構
因為用 Mem-mapped file 已經類似記憶體 IO 了,當然可以用類似
VM 那樣的技巧來 cache,加快存取
但事實上,FS 在處理 I/O 時會使用另外的 buffer cache
導致造成 double-cache 的問題
原本 FS 為了效率,就會在 IO 讀取時使用 buffer cache
但現在又在上面用一層 page cache,導致資料出現在兩個 cache
中,可能造成效率差或不同步的問題
Unified Buffer Cache
解決 Page cache 中的 double caching 的問題
只用單一個 cache 來當作 memory-mapped 的 cache 以及原本 IO 使用
的 cache
問題:哪一個 cache 優先比較高?replacement 的方式該用何種?
Recovery
當檔案系統發生錯誤(例如 metadata 損毀或遺失)時,需要進行檢查與
復原
Consistency checking:檢查 directory entry 中的資料與實際硬碟中的
data block 是否一致。若否,則嘗試比較並修復
執行速度慢且不一定會成功
使用備份機制比較保險,當檔案遺失或損毀時就從備份中取出
Log Structured File Systems
希望增加 write 的效率,先把對 FS 的更新內容先記錄起來,之後在一
次更新
且更新時,不會覆蓋舊的資料,會直接把修改後的資料寫入新的且連
續的空的 block
寫入效率較佳,因為整個新資料是寫在後面的連續 block
不必在硬碟中到處找原本的資料,然後改完後跑到別的地方改(需
要多次 seek)
復原快:即使中途斷電使得寫入新資料失敗,舊的檔案內容也還存
在,沒有被改 (亦即,不會出現檔案部分是新的,部分是舊的)
作業系統概論
Chapter 12: Mass-Storage Systems
Magnetic Disks:傳統磁碟
機械手臂會帶動讀寫頭前後移動,加上磁盤旋轉,即可找到指定的
sector
機械手臂上的讀寫頭是一起前進與後退的,不能一前一後(因此有了
cylinder 的概念)
Seek time:機械手臂前後移動所花的時間(最占時間),通常 3ms~12ms
Rotational latency:磁盤旋轉使得所需要的 sector 轉到讀寫頭下的所需
時間
與磁碟的每分鐘旋轉數(RPM)有關
計算轉一圈的時間:每秒圈數的倒數
每秒圈數:(RPM/60)
轉一圈時間:1 / (RPM/60)
計算所花時間時,都是以平均轉 1/2 圈來算
範例:4200 rpm
轉一圈所需時間:1 / (4200/60) = 0.01428 秒 = 14.28ms
半圈時間:7.14 ms
Positioning time: seek time + rotational latency
Transfer Rate:資料從硬碟傳到電腦的傳輸速率
理論上:6Gb/每秒,實際上約 1Gb/每秒
硬碟藉由 IO Bus 連接到電腦,然後電腦用 Host controller 與硬碟的 Disk
controller 進行溝通
Performance
範例:傳輸 4KB 的資料,7200RPM,average seek time 5ms,傳輸速率
1Gb/sec,controller overhead 0.1ms
0.1ms + 5ms + 1/2 × 1 / (7200/60) sec + 4KB / 1Gb/sec = 9.39ms
Disk Structure
Logical blocks:將硬碟中所有的 block 視為一個連續位址(block 1,2,3…)
將 logical block 對應到真正的硬碟結構的方式:
Sector 0:最外圈 cylinder 的第一個 track 的第一個 sector
依照以下順序逐漸編數字:先將該 track 都編完,在來是同一個 cylinder
的其他 track,最後是由外往內編其他的 cylinder
好處:logical 位址越近,理論上 physical 就會越近
除了那些損壞的 sector,否則將 logical mapping 到 physical 很容易
Disk Scheduling
由於硬碟 IO 緩慢,OS 需要盡量降低硬碟 IO 中的 overhead
降低 seek time (盡量讓機械手臂移動距離越短越好)
最大化 bandwidth (單位時間內傳輸的資料輛)
方法:重新調整 IO request 的順序 (好比之前 CPU Scheduling 那樣調整
process 的順序)
IO request 包含:input/output mode、硬碟資料位址、記憶體資料位
址、需要傳輸的 sector 數量
如果硬碟很閒(Idle),IO request 可以馬上被處理。但當有許多 request 時,就
必須使用 queue 把這些 request 存起來
也只有使用 queue 時才可以讓 OS 進行最佳化(因為沒有 queue 就不知道
下一個指令是什麼,就沒辦法進行最佳化)
優化的方式:重新調整 IO queue 內的 request 順序
以下將簡單介紹幾種演算法,模擬情境:
當前讀寫頭的位置:53
Queue 內的 request:98, 183, 37, 122, 14, 124, 65, 67
這些數字用來描述機械手臂前後的位置,範圍是從 0~199
0 與 199 代表最外圈與最內圈
FCFS (First come, first serve)
直接根據當前 queue 內的順序,不重新調整
處理順序:53→98→183→37→122→14→124→65
效率差,seek time 很長
機械手臂需要前後移動的距離(藍色線)總長度很長
總共移動了 640 個 cylinder
SSTF (Shortest Seek Time First)
從當前位置為基準,找出下一個與當前 seek 距離最近的 request 優先處理
類次 shortest job first,移動最少的優先
一開始位置是 53,距離他最近的是 65 (差距只有 12 個 cylinder),所以優先處
理 65。接下來找出與 65 距離最近的(67),依此類推
處理順序:53→65→67→37→14→98→122→124
總共移動了 236 個 cylinder
缺陷:starvation 問題
如果新的 request 一直都在當前 cylinder 附近,那麼比較遠的就一直沒
辦法被處理到
SCAN:掃描法,又稱電梯演算法(elevator algorithm)
機械手臂不斷從最外走到最內,然後又從最內走到最外,沿路上處理碰到的
request
處理順序(假設一開始往編號 0 移動):
53→37→14→0→65→67→98→122→124→183,總共移動 236 個 cylinder
注意,有走到『0』,即使第 0 個 cylinder 根本沒有 request
缺點:服務 request 頻率不平衡問題
靠近 0 與 199 的 cylinder 由於來回的關係,被處理到的頻率比在中間的
cylinder 還要頻繁
C-SCAN
為了解決 SCAN 演算法中兩端與中間的服務不頻繁問題
與 SCAN 的不同處:機械手臂只往單一方向服務,走到底之後,直接回到另
一邊的端點,重頭開始掃描
避免在兩端的服務機率太頻繁
LOOK 與 C-LOOK
改良 SCAN(成為 LOOK)與 C-SCAN(成為 C-LOOK)
不必每次都走到最兩端的 cylinder,當檢查發現該方向往後已經沒有更多
request 需要處理,就不再繼續往前
與 SCAN 比較:相較於 SCAN 的 236 個 cylinder,LOOK 只需要 208 個 cylinder
各種 Scheduling 演算法的選擇
對於一般的系統:SSTF 較常見
對於硬碟負擔大的系統,使用 SCAN 與 C-SCAN 效率較佳,也降低 starvation
硬碟效能會受到不同型態的 IO Request 影響,而 IO Request 的類別又受到檔
案 allocation 的方法影響
若檔案使用 contiguous allocation,就會產生一大串連續的 block (在硬碟
上的位置相近,seek 時間較短)
若使用 linked 或 indexed,則資料 block 是散佈整個硬碟
將 directory entry 放在中間的 cylinder:因為平均來說,中間的 cylinder
最常被接觸
處理 disk-scheduling 演算法的 module 應該要和 OS 分開,讓使用者可以依照
情況取代不同的演算法
OS 只能處理 seek time,對於 rotational latency,OS 難以估計
事實上,OS 不會只單純依照 IO request 的順序來寫,有時候會照資料特性不
同而改變順序
例如:要寫入檔案的時候,必須先確保檔案的 metadata 有寫入成功,
成功後才能繼續寫入該檔案的 data block
因為如果 metadata 寫入失敗,其他 data block 就算也成功了,metadata
也沒有記錄 data block 的位置,導致 data block 的寫入沒有意義
RAID 結構
利用多個硬碟,來提升整體的效能與可靠性(reliability)
Striping:將同一個檔案內的資料分散到不同硬碟,讓這些硬碟同時讀取這個
檔案的不同部分,提升整體的讀取效能
Bit-level striping (RAID 2,3):將檔案的每個 byte 分散到不同硬碟去存放,
此做法使得不管檔案大或小,都需要同時讀取所有硬碟
好處:無論讀取的檔案多大多少,效率都很好
缺點:由於每個 byte 都被拆散,所有的 IO request 都必須排隊處
理
Block-level striping(RAID 0,4):只把檔案的不同 block 分散到不同硬碟儲
存
好處:大檔案時,效能依然好。當檔案小時,可以同一時間內讀取
多個小檔案(如果這些小檔案的 block 各別都在不同硬碟時)
增加可靠性的方法:儲存 redundant data (額外的資料)
Mirroring(鏡射,或稱 shadowing):RAID 1
將另一顆硬碟的所有資料複製一份保存
好處:可靠性最高
缺點:需要用掉很多硬碟空間
Block interleaved parity:RAID 4,5,6
使用 parity 的技術記錄修復資訊,當損壞一顆硬碟時,其資料可以
由其他硬碟的 parity 來算出來
好處:只需要用少部分的空間來記錄 parity
壞處:當有兩顆以上的硬碟損壞就無法復原
由於 RAID 磁碟陣列仍有可能發生損壞(例如 RAID 4,5,6 發生兩顆硬碟損壞),
所以通常在不同的磁碟陣列之間可能也會存重複的資料
Hot-spare:在磁碟陣列中可能有一些備用的空硬碟,當主要磁碟陣列中的一
顆硬碟損壞時,備用硬碟可以立刻上線,並且重新建立資料(例如把遺失的資
料算出來放在新硬碟)
RAID Levels
常用的 level:0,1,5
C:代表完整複製的資料
P:Parity (用來算出資料的 error-correcting data)
RAID 0:完全利用空間,增加存取速度,並沒有增加可靠度
RAID 1:只有增加可靠度/存取速度,用掉了大量(一半)的空間當作備份
RAID 2~5:在增加可靠度(利用 Parity)、存取速度時,減少了用來當作備份的
空間量(比起 RAID 1)
RAID 0
使用 Block-level striping,增加存取速度
所有硬碟都拿來增加可用空間,沒有任何額外資料,故沒有增加可靠性
RAID 1:Mirroring
使用 N+N 個硬碟,但只有 N 個硬碟的儲存空間
寫入時是同時對兩個硬碟做寫入
當其中一個硬碟發生錯誤時,可以從相對應的備份硬碟讀取資料
RAID 2:bit-interleaved Hamming code
N+E 的硬碟(例如 4+3)
以 bit-level 的方式把資料存在 N 個硬碟,然後產生 E-bit ECC 的復原資料
由於太複雜,並沒有實際使用
RAID 3:bit-Interleaved Parity
需要 N+1 顆硬碟
將資料以 bit-level 的方式散佈在 N 個硬碟中
剩下的一顆硬碟儲存修復用的 Parity
讀取:同時從所有硬碟中讀取資料,速度快,但是每秒能夠處理的 IO 數量
較少
寫入:把資料分散寫入。至於產生新的 parity 之後,需要等待 parity disk 的
更新,沒辦法加速
不常用
RAID 4:block-Interleaved Parity
類同 RAID 3,使用 N+1 個硬碟
不同處:以 block-level 散佈資料
讀取時,只需要讀取資料所在的 block 即可,故對於較小的檔案可以平行處
理
寫入:仍需要等待 parity disk 的更新,沒辦法平行寫入(即使是小檔案)
不常用
RAID 5:Distributed Parity
由 RAID 4 改良,將 Parity 的資料分別拆散在所有硬碟中,而不是存放在單一
個 parity disk
改良了 RAID 4 在寫入時,需要等待 parity disk 的問題
由於 parity 已經散佈在各個硬碟,只要需要寫入的 parity 資料不是在同
一個硬碟,就可以平行寫入
parity 仍有機率不小心掉在同一個 disk,但至少機率不高
廣泛使用
Download