You are on page 1of 94

系統程式 – 理論與實務

第 10 章、作業系統

作者:陳鍾誠

旗標出版社
第 10 章、作業系統
10.1 簡介
10.2 行程管理
10.3 記憶體管理
10.4 輸出入系統
10.5 檔案系統
10.6 實務案例
10.6.1 Linux 作業系統
 10.6.1.1 Linux 的行程管理
 10.6.1.2 Linux 的記憶體管理
 10.6.1.3 Linux 的 案系統
 10.6.1.4 Linux 的輸出入系統
10.1 簡介
圖 10.1 今日常見的作業系統家族圖
RedHat Fedora
Linux
Debian Ubuntu
Minix
UNIX
Apple
FreeBSD
OS X

作業系統 Win3.1
Microsoft
Windows
Win95

WinNT Win 2000 Win XP/Vista


DOS
WinCE
作業系統的五大功能模組
行程管理

記憶體管理

輸出入系統

檔案系統

使用者介面
五大模組的功能
行程管理系統
讓任何程式都能輕易的執行
記憶體管理系統
打造一個記憶體配置環境
輸出入系統
將硬體的控制包裝成簡單易用的函數
檔案系統
輸出入系統的延伸,讓程式透過作業系統所提供的
『檔案輸出入函數』
使用者介面
提供程式師與使用者一個方便的操作環境
10.2 行程管理
程式、執行 與行程
程式
程式設計師用文字編輯器撰寫出來的文字

執行檔
程式師用組譯器或編譯器將程式檔轉換成可執行檔

行程
程式師通常透過命令列介面輸入檔名以啟動該執行檔
此時,作業系統會將執行檔載入到記憶體後開始執行
這個執行中的程式就稱為行程
多工 (Multtasking)
現今的電腦與作業系統當中,通常具備同時執行多
個程式的能力,這種能力稱為『多工』

多工能力是行程管理系統的重點,我們現今的電腦
使用經驗當中,非常仰賴行程管理系統當中的多工
機制。如果作業系統沒有了行程管理系統,將會使
電腦難用許多

多工作業系統: Linux, Windows, …


單工作業系統: DOS, …
圖 10.2 單行程系統與多工系統之比

中斷向量
作業系統
作業系統

行程 1
行程
單一行程 行程 2

行程 3

(a) 無作業系統的情況 (b) 單一行程的作業系統 (c) 多工作業系統

例如:簡單型嵌入式系統 例如: DOS 作業系統 例如: Linux 作業系統


圖 10.3 程式的行為模式 – CPU 與
I/O 交替運行
int wc(const char *fname) {
int words=0;
FILE *fp=fopen(fname, "r");
while((ch=getc(fp))!=EOF) {
if(isspace(ch)) sp=1;
else if(sp) {
words++;
sp=0;
}
if(ch=='\n') ++lines;
}
printf(" 共有 %d 個英文詞彙 \n", words);
fclose(fp);
return words;
}
圖 10.4 作業系統利用輸出入的空
切換行程

行程 3 CPU I/O

行程 2 CPU I/O
CP CP
行程 1 I/O I/O
U U
系統 行程 系統 行程 系統 行程
作業 呼叫 切換 呼叫 切換 呼叫 切換
系統
範例 10.1 不正常的行程 – 無窮迴圈
導致當機

int sum=0, i=0;


while (i < 100) {
sum += i;
}
行程的概念
執行中的程式

在多工系統當中,同時允許多個行程被載入到記憶
體當中,而且會交替的將 CPU 分配給每一個行
程,以讓每個行程都能公平的被執行到,就好像整
台電腦只為了該行程而存在一般。
圖 10.5 行程的狀態轉換圖

新建 載入 離開 結束
中斷

就緒 執行
排程器
分派
I/O 或事件結束 I/O 或等待事件
等待
排程問題
當『執行』狀態的行程因輸出入或被強制中斷而暫
停時,如果系統中有許多『就緒』的行程等待被執
行。那麼,到底哪一個行程應該被挑選出來執行呢
?這個問題,就是行程管理當中著名的排程問題。
常見的排程方法
 先到先做排程 FCFS (First-Come, First Served)

 最短工作優先排程 SJF (Shortest Job First)

 最短剩餘優先排程 SRF (Shortest Remaining First)

 優先權排程 PS (Priority Scheduling)

 循環分時排程 RR (Round-Robin Scheduling)

 多層佇列排程 (Multilevel Queue Scheduling)

 多層反饋佇列排程 (Multilevel Feedback Queue

Scheduling) 等。
循環分時排程 (Round-Robin
Scheduling)
循環分時排程 (Round-Robin Scheduling) ,該方
法會為行程事先分配一個時間片段 T (Time
Slice) ,然後才切換到該行程中。在切換到某行程
之前,排程系統先設定 T 時間後應發生時間中斷
,然後才將控制權切換給該行程。一但 T 時間到
達後,排程系統就可以透過時間中斷取回控制權,
然後切換到下一個行程,以防止某行程霸佔 CPU
過久而導至其它行程無法進行。
執行緒
執行緒又被稱為輕量級的行程 (Light Weight
Process)

執行緒與行程的不同點
執行緒會完全共用記憶體空間與相關資源。這使得執
行緒在進行切換時不需要儲存這些資源,並且不需要
進行記憶體重新映射等動作。所以執行緒的切換相當
快速,因為只需要保存暫存器即可。
執行緒的切換
傳統的兩個行程通常擁有不同的記憶空間,在具有
記憶體管理單元 (Memory Management Unit:
MMU) 的作業系統中,兩個行程的記憶空間是完
全獨立的,各自擁有自己的記憶體映射表。所以在
行程切換的同時必須同時更換映射表,這樣的動作
會消耗許多時間。但是執行緒就沒有這個問題,因
此可以進行快速的切換。
執行緒的用途
執行緒由於共用記憶體空間,因此很容易進行變數
共享的動作,這使得執行緒之間很容易互相合作,
以便完成某一件共同的任務。舉例而言,假如我們
希望撰寫一個網頁伺服器 (Web Server) 時,就可
以建立為每個連線建立一個執行緒以便分別服務,
但這些執行緒卻可以共享某些變數,像是『目前連
線人數』、『歷史記錄檔』等資源,這是在設計網
路程式時常用的一種程式設計手法。
行程同步機制
競爭狀況
當多個行程或執行緒共用某些變數時,如果兩個執行
緒 P1, P2 同時修改某個變數 V1 的 為 X1, X2 ,
那麼在修改完畢之後 V1 的 應該是多少 ?這個
問題的答案有可能是 X1 、也有可能是 X2 、甚至還
有可能是其他 。

臨界區間
這些修改共用變數的程式區段被稱為『臨界區間』
競爭情況的解決
 避免競爭情況
 排除兩個執行緒同時進入臨界區間的可能性,以便防
止競爭狀況的發生。

臨界區間的保護方法 ( 鎖定機制 )
 禁止中斷
 支援同步的硬體
 號誌
死結
鎖定機制
可以防止競爭情況
卻會造成一個執行緒 ( 例如 P1) 鎖住時把持某些資
源,讓另一個執行緒 (P2) 無法取得的情況。
更糟糕的是,若此時 P2 也把持了 P1 所需的某些
資源,就會導致兩者互相等待,卻有永遠都無法完成
的窘境。
這種情況就被稱為『死結』。
死結的處理
在作業系統的設計中,死結是相當難以處理的,於
是有很多作業系統根本就不處理死結問題,將問題
留給應用程式自行處理。所以能夠理解死結問題,
並且在設計程式時能防止死結的發生,也是程式設
計師的責任,雖然大部分的程式並不會遭遇到這樣
的問題,但是使用多執行緒模式的程式就可能會遭
遇死結問題,這是使用執行緒時必須特別小心的部
分。
10.3 記憶體管理
記憶體管理式作業系統的責任之一,有效的管理記
憶體除了能提高電腦的效率之外,還可以保護電腦
不受到駭客或惡意程式的入侵。

兩種記憶體配置情況
行程中的記憶體配置方法
作業系統中的記憶體管理方法
行程中的記憶體配置方法
在行程執行的過程中,經常會需要取得某些記憶空
間,以便儲存電腦運算過程中所產生的資料。

程式中的資料通常被放在兩個記憶體區塊中,一個
稱為堆疊區 (Stack) ,一個稱為堆積區 (Heap) 。
堆疊與堆積
 堆疊的用途
副程式參數、區域變數與返回點等資訊會被推入堆疊中,
然後利用框架暫存器 (Frame Pointer) 進行變數的存取。
在這個過程當中,記憶體的配置並不困難,當需要記憶體
時,使用的一定是堆疊最上層的區域,編譯器只要根據變
數的型態與數量,決定配置空間的大小即可。

 堆積的用途
在 C 語言當中, malloc() 函數是主要的記憶體請求指令
,這種指令通常被稱為動態記憶體 (Dynamic Allocation)
配置請求,因為 malloc() 函數會在執行的過程當中,動
態的取得足 的記憶體空間,以便分配給程式使用。
圖 10.6 行程的記憶體分配圖
程式段 .text 程式段 .text

資料段 .data 資料段 .data

未初始化資料 .bss 未初始化資料 .bss

使用中堆積區塊
使用中堆積區塊
堆積段 (heap)
已釋放區塊

使用中堆積區塊

已釋放區塊
使用中堆積區塊

堆積段 (heap)

堆疊段 (stack)
堆疊段 (stack)
使用中的堆疊

(a) 程式開始時的記憶體分配情況 (b) 程式執行中的記憶體分配情況


記憶體管理系統需處理的問題
記憶體漏洞
當記憶體被歸還時 ( 例如使用 free() 函數 ) ,記憶
體管理系統可以將該歸還記憶體與緊鄰的上下方空洞
合併,以變形成更大的可用區塊,這將有助於增加記
憶體使用率,避免形成太多的小區塊,導致分配演算
法的失敗情況。

記憶體不足
堆疊溢出
堆積空間不足
記憶體分配策略
First Fit ( 最先符合法 ) :從串列開頭開始尋找,然
後將所找到的第一個足夠大的區塊分配給該程式。
Next-Fit ( 下一個符合法 ) :使用環狀串列的結構
,每次都從上一次搜尋停止的點開始搜尋,然後將
所找到的第一個足夠大的區塊分配給該程式。
Best-Fit ( 最佳符合法 ) :從頭到尾搜尋整個串列一
遍,然後將大小最接近的可用區塊分配給該程式。
Worst-Fit ( 最差符合法 ) :則是將大小最大的區塊
分配給程式 ( 以便留下較大的洞 ) 。
堆積空間不足時的處理方式
記憶體聚集法
將記憶體重新搬動,以便將分散的小型可用區塊聚集
為大型可用區塊,然後再試圖分配給使用程式的方法

但是記憶體聚集所附出的代價非常的高,需要大量的
時間搬移記憶體,因此在現代的系統中很少被使用到

垃圾蒐集法
利用程式自動回收記憶體。
垃圾蒐集法
 蒐集方法
在使用 收集法的程式中,通常不需要由程式主動釋放
記憶體,因為 蒐集系統會在記憶體不足時被 動,以
蒐集記憶體中已經沒有被任何程式變數指到的記憶區塊,
然後再將這些區塊標示為可用區塊,以便回收使用。

 使用案例
C 語言當中通常沒有支援 蒐集演算法,但是在 Java,
C# 等語言中則內建了 蒐集機制,該機制會在發現記
憶體不足的情況時,自動回收記憶體。因此 Java 與 C#
的程式設計人員通常不需要釋放記憶體,這對程式設計人
員而言是很好用的一項功能,可以減輕不少負擔。
作業系統的記憶體管理方法
記憶體除了可以用來儲存資料之外,還可以用來儲
存程式。

對於作業系統而言,記憶體管理系統是支援行程管
理系統的重要元件之一,因為當某程式被啟動之前
,必須先被載入到記憶體當中。

作業系統必須決定要將程式載入到哪裡?特別是針
對多工系統而言,作業系統必須有效的分配記憶體
給各個行程,才能更多的行程同時被放入記憶體當
中執行,提升多工的能力。
記憶體管理單元 (MMU)
許多高階的處理器可能會以硬體的方式,支援記憶
體管理機制,這種硬體被稱為記憶體管理單元
(Memory Management Unit: MMU) 。 MMU 可以
增進存取的效率並且加強保護機制。
常見的 MMU 單元
重定位暫存器

基底界限暫存器

分段機制

分頁機制
重定位暫存器
重定位暫存器
0x0000
R2=0x0212 Base = 0x1030

邏輯位址 真實位址 主記憶體


IR=00200100 + +
0x0312 0x1342
LD R1, [R2+0x0100] ALU

CPU0

圖 10.7 最簡單的 MMU - 使用『重定位暫存器』


基底界限暫存器
界限暫存器 基底暫存器
Limit=0x3000 Base=0x1030
R2=0x0212

邏輯位址 是 是 真實位址
ALU
< >=0 + 主記憶體
(+) 0x0312 0x0312 0x0312 0x1342
否 否
IR=00120100
中斷:定址錯誤

CPU0

圖 10.8 使用『基底 - 界限暫存器』提供硬體保護措施


分段機制
STR 界限 基底
0
..
B[d]=0x1030
7 0x3000 0x1030
指令:
LD R1, [data1+100] 分段表

邏輯位址 是 真實位址
IR=00170100 < + 主記憶體
d=100 0x1130
(s=7,d=100)

中斷:定址錯誤

CPU
圖 10.9 使用分段表進行記憶體位址轉換的一個範例
分頁機制
TLB
邏輯頁 實體頁
IR … p d 29 321
… TLB 命中
7 439

分頁表 f d

f
TLB 失誤 記憶體

圖 10.10 使用分頁表搭配 TLB 進行位址轉換的過程


圖 10.11 各種分段與分頁機制的組

TLB 命中
TLB

分段機制 s d TLB 失誤 B[s]+d

單層分頁機制 p d f d

雙層分頁機制 p1 p2 d f d

反轉分頁機制 pid p d f d

分段式分頁 s p d f d
分頁表
( 分段表
)
10.4 輸出入系統
 撰寫輸出入程式的方式
專有輸出入指令
記憶體映射

 等待輸出入訊息的方式
輪詢
中斷

 作業系統中的輸出入系統
將複雜且多樣的輸出入裝置,透過函數包裝後,提供給程
式設計師使用。
順利銜接檔案系統
驅動程式
定義
負責控制並存取特定裝置的程式

運作方式
驅動程式會將一些函數指標傳遞給作業系統,讓作業
系統記住這些函數,作業系統會在有該裝置的輸出入
需求時,呼叫這些函數。這種『註冊 - 呼叫』機制是
驅動程式常用的方式。而這些函數通常被稱為反向呼
叫函數 (Call Back Function) 。
Linux 的驅動程式
register_chrdev()
註冊字元裝置的處理函數
unregister_chrdev()
清除該註冊的函數。

…
區塊裝置
光碟與硬碟等,這些裝置通常以磁區 (Sector) 為
儲存單元,因此又被稱為『區塊裝置』。

『區塊裝置』在讀取時通常必須必須移動讀寫頭與
轉動碟片,並且指定磁區編號與磁軌以讀取特定磁
區。

在作業系統中,如何有效安排磁碟讀取動作的問題
稱為『磁碟排程問題』。這個問題所考慮的主要因
素有搜尋時間 (Seek Time) 、旋轉延遲
(Rotational Latency) 與傳輸時間 (Transfer Time)
等三項。
磁碟排程演算法
先到先做法 (First-Come, First-Served : FCFS)

最短搜尋時間優先 (Shortest Seek Time First :

SSTF)
掃描法 (SCAN)

循環掃描法 (C-SCAN)

查找法 (LOOK)

循環查找法 (C-LOOK)

SSTF 與 LOOK 是兩種經常被使用的方法。


輸出入系統的功能
 封裝輸出入函數

 提升設備使用效率

 防止設備損壞,延長設備壽命

Ex: 快閃記憶體

 形成檔案系統的基礎
快閃記憶體 (Flash Memory)
 快閃記憶體的同一區塊在數十萬或數百萬次的寫入後就

會自然損壞,因此必須避免長期的寫入某個磁區。
 於是人們開始利用『日誌式檔案系統』 (Journaling

File System:JFS) 以避免 NAND Flash 過早損壞,因


為日誌式檔案系統會將更動部分以日誌的方式儲存,除
了可以防是因停電而導致的資料毀損之外,還能平衡區
塊的寫入次數,延長記憶 的壽命
 微軟的 NTFS 與 Linux 的 ext3 、 reiserFS 與 JFS,

JFS2 等都是日誌式檔案系統的範例。
10.5 案系統
檔案系統
是輸出入系統的一個優秀的組織呈現方式。

基本概念
檔案
 案 (File) 是一個資料的儲存單元
目錄
 資料夾 (Folder) ( 或稱為目錄 Directory) ,則是可用來儲存
許多檔案的容器
 使用者也可以在其中建立子資料夾,以形成樹狀檔案結構。
案系統的功能
組織資料,形成檔案與目錄結構

由於檔案的實際儲存體通常是像硬碟、光碟這樣的區

塊儲存裝置,所以實際儲存時是以區塊為單位的。
但是邏輯上的檔案系統卻是以檔案與資料夾的方式呈

現的,這導致檔案系統的兩端有相當大的落差
作業系統必須試圖彌補這個落差,將一個變動大小的

檔案儲存到許多固定大小的區塊當中。
案系統結構
MS. Windows 的 NTFS 檔案系統
使用了一種稱為 B+ 樹的著名資料結構,以便有效
的組織檔案描述器。

UNIX/Linux
使用了一種稱為 i-node 的樹狀結構,以便組織檔案
描述器並儲存在磁區中。
可用區塊管理
 檔案空間的分配必須以磁區為單位。

 可用空間管理的兩種資料結構
鏈結串列
 將所有可用磁區儲存在可用區塊檔中。可用區塊檔由一個一個串
接起來的磁區組成,每個磁區都會記錄被連接的下一個磁區位置
,以便將這些磁區串接起來。

位元映射 (Bit mapped)


 將整個磁碟的映射位元儲存在數個磁區中
 舉例而言,如果該磁碟的大小為 1G ,而每個磁區大小為
1K ,那麼總共就會有 1G/1K = 1M 個磁區,於是我們可以用
1M/8=0.125MB 的磁碟空間,儲存整個磁碟的位元映射地圖。
舉例而言,這個映射圖可以放在第 0-124 個磁區中,於是我們
就可以用 125 個磁區記錄所有一百萬個磁區的使用狀況
 其效用非常高,是相當經濟且快速的一種實作方式。
分散式 案系統
檔案系統甚至可以將遠端電腦上的裝置視為目錄結
構的一部分,讓使用者感覺到檔案資料就在手邊,
像是 MS. Windows 上的網路芳鄰機制,就可以
讓同一區域的電腦互相分享檔案,感覺就好像是自
己電腦中的檔案或資料夾一樣。

如果更進一步將資料分散儲存在網路上的各個電腦
中,還可以提升檔案的安全性,利用特殊的備份機
制,可以避免單一儲存裝置的損毀造成的資料損失
,像是 Google 的雲端運算 (Cloud Computing)
就是分散式檔案系統的一個大型範例。
10.6 實務案例
10.6.1 Linux 作業系統
Linux 作業系統 是 Linus Torvalds 於芬蘭赫爾辛基
大學當學生時,希望在 IBM PC 個人電腦上實作
出類似 UNIX 系統的一個專案。

在 Linux 剛發展時主要參考的對象是荷蘭阿姆斯
特丹大學教授 Andrew S. Tanenbaum 的 Minix
系統,後來 Torvalds 決定利用 GNU 工具全面
改寫,於是發展出一個全新的作業系統,後來該作
業系統被稱為 Linux 。
圖 10.12 Linux 的基本架構
系統管理 使用者 使用者 GNU 視窗
使用者程式層
界面 行程 函式庫 開發工具 環境

System Shared Libraries ( 共享函式庫 )


函式庫層
系統呼叫界面
行程系統 記憶體系統 VFS 輸出入系統 網路系統

核心層 檔案系統
CPU 相關 MMU 相關 區塊裝置 字元裝置 網路裝置
程式碼 程式碼 驅動模組 驅動模組 驅動模組
硬體模組界面
硬體層 Loadable Kernel Module ( 驅動程式 )
Linux 2.6 版
 包含『行程』、『記憶體』、『檔案』、『輸出入』、『網
路』等五大子系統。
 行程系統是支援行程與執行緒等功能,實作了排程、行程切
換等程式。
 記憶體系統可利用硬體的 MMU 單元支援虛擬記憶體等機
制。
 檔案系統的最上層稱為虛擬檔案系統 (Virtual File System:
VFS) , VFS 是一組檔案操作的抽象介面,我們可以將任
何的真實檔案系統,像是 FAT32, EXT2, JFS 等,透過
VFS 掛載到 Linux 中。真實檔案系統是在檔案結構的組織
下,利用區塊裝置驅動模組所建構出來的。
 網路系統也是透過網路裝置驅動模組與 TCP/IP 相關程式
碼所堆疊出來的。而輸出入系統則統合了『區塊、字元、網
路』等三類裝置,以支援檔案、網路與虛擬記憶體等子系統

Linux 的各種版本
Red Hat
Ubuntu
Fedora
Debian
…

這些版本幾乎都利用 Tovarlds 所維護的核心,整


合其他開放原始碼軟體後所形成的,因此雖然版本
眾多卻有統一的特性。
Linux 與 POSIX 標準
Linux 原本是以 GNU 工具所開發的,因此也被
稱為 GNU/Linux 。

由於 GNU 工具支援 IEEE 所制定的 POSIX 標


準,該標準對 UNIX 平台的函式庫進行了基本的
統一動作,因此 Linux 自然也就屬於 POSIX 標準
的成員之一。
Linux 所支援的處理器
雖然 Tovarlds 最早是利用 IBM PC 開發 Linux
作業系統的,但是目前 Linux 已經被移值到各種
平台上。因此 Linux 所支援的處理器非常眾多,
包含 IA32 、 MIPS 、 ARM 、 Power PC 等。當
您想要將 Linux 移植到新的處理器上時,必須重
新編譯 Linux 核心,您可以利用 GNU 的 gcc,
make 等工具編譯 Linux 核心與大部分的 Linux
程式。
10.6.1.1 Linux 的行程管理
Linux 中的行程 (Process) 被稱為任務 (Task) ,
其資料結構是一個稱為 task_struct 的 C 語言結

在 Linux 2.6.29.4 版當中光是該結構的宣告就占


了 306 行的程式碼,
任務 (Task) 的資料結構
行號 Linux 2.6.29.4 版核心原始碼 include/linux/sched.h 檔案
… …
1115 struct task_struct {
1116 volatile long state; /* -1 unrunnable, 0 runnable, >0
1117 stopped */
1118 void *stack;
1119 atomic_t usage;
1120 unsigned int flags; /* per process flags, defined
… below */
1263 unsigned int ptrace;
1264 …
1265 /* CPU-specific state of this task */
1266 struct thread_struct thread;
1267 /* filesystem information */
1268 struct fs_struct *fs;
1269 /* open file information */
1270 struct files_struct *files;
1271 /* namespaces */
1272 struct nsproxy *nsproxy;
1273 /* signal handlers */
… struct signal_struct *signal;
struct sighand_struct *sighand;
執行緒結構
Linux 的行程結構中包含了一個 thread 欄位
(1264 行 ) ,該欄位用來儲存與 CPU 相關的暫存
器、分段表等資訊

由於暫存器資訊是與處理器密切相關的,所以每一
種 CPU 都會擁有不同的執行緒結構, IA32 (x86)
的 thread_struct 之程式片段如範例 10.3 所示。
執行緒 (Thread) 的資料結構
行 Linux 2.6.29.4 版核心原始碼
號 arch/x86/include/asm/processor.h 檔案
… …
39 struct thread_struct {
1 /* Cached TLS descriptors: */
39 struct desc_struct
2 tls_array[GDT_ENTRY_TLS_ENTRIES];
39 unsigned long sp0;
3 unsigned long sp;
39 …
4
39
5

Linux 行程的狀態
 原有 行 Linux 2.6.29.4 版核心原始碼
號 include/linux/sched.h 檔案
Running … …
Interruptible 17 #define TASK_RUNNING 0
Uninterruptible 4 #define TASK_INTERRUPTIBLE 1
17 #define TASK_UNINTERRUPTIBLE 2
Zombie 5 #define __TASK_STOPPED 4
Stopped 17 #define __TASK_TRACED 8
6 /* in tsk->exit_state */
17 #define EXIT_ZOMBIE 16
 後來又增加了 7 #define EXIT_DEAD 32
17 /* in tsk->state again */
Traced 8 #define TASK_DEAD 64
EXIT_DEAD 17 #define TASK_WAKEKILL 128
9 …
TASK_DEAD 18
TASK_WAKEKILL 0
18
1
18
2
範例 10.4 Linux 中的行程狀態
18
Linux 的行程切換
特性
行程切換 ( 內文切換 ) 是與處理器密切相關的程式
碼,每個處理器的實作方式均有相當大的差異

基本原理
將上一個行程 (prev, 以下稱為舊行程 ) 的暫存器保
存後,再將程式計數器設定給下一個行程 (next, 以
下稱為新行程 ) 。
Linux 的行程切換程式碼 (IA32 處
理器 )
Linux 2.6.29.4 檔案 arch/x86/include/asm/system.h

#define switch_to(prev, next, last) \
do {\

unsigned long ebx, ecx, edx, esi, edi;
asm volatile(“pushfl\n\t” /* save flags */ \ 將新行程的程式計數
“pushl %%ebp\n\t” /* save EBP器*/ \
“movl %%esp,%[prev_sp]\n\t” /* save ESP */ \ 推入堆疊中
“movl %[next_sp],%%esp\n\t” /* restore ESP */ \
“movl $1f,%[prev_ip]\n\t” /* save EIP */ \
“pushl %[next_ip]\n\t” /* restore EIP */ \
“jmp __switch_to\n” /* regparm call */ \
“1:\t” \
“popl %%ebp\n\t” /* restore EBP
跳入 */ \C 語言的
“popfl\n” /* restore flags */ \ switch_to() 函數中
\
….
while (0) 由於 C 語言函數在返回前會從堆疊中取出返回點,以返回
… 上一層函數繼續執行。因此, switch 返回時會跳入新行程
中。
Linux 中的排程
 Linux 採用的排程機制較為複雜,該排程式建構在一個稱為
goodness 的行程 ( 或執行緒 ) 評估值上, goodness 是一
個從 0 到 1999 之間的整數。一般執行緒的 goodness
小於 1000 ,但即時執行緒的 goodness 則會從 1000 開
始,因此保證了任何即時執行緒的 goodness 都會高於一
般執行緒。

 Linux 的即時執行緒又進一步分成 FIFO (First in-First Out)


執行緒與 RR (Round-Robin) 執行緒,分別採用先到先做
(FIFO) 與大輪迴式 (RR) 的排程策略。

 一般執行緒的 goodness 值會隨著『剩餘時間量』的大小


而變動,剩餘時間量是一個類似倒數的機制。 Linux 藉由
時間量控制執行緒的 goodness ,然後讓 goodness 較高
者得到較多的 CPU 執行時間。
Linux 中的多行程範例程式
檔案 ch11/fork.c 說明
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int spawn(char *prog, char **arg_list) { Spawn 為生育的意思
pid_t child;
child = fork(); 用 fork() 函數分枝出子行程
if (child != 0) { 如果不成功
return child; 傳回失敗的行程代碼
} else { 否則
execvp(prog, arg_list); 將 prog 參數所指定的
fprintf(stderr, "spawn error\n"); 程式載入到子行程中
return -1;
}
}
int main() { 主程式開始
char *arg_list[] = { "ls", "-l", "/etc", NULL }; 設定分支行程的指令
spawn("ls", arg_list); 開始分支
printf("The end of program.\n"); 印出主程式結束訊息
return 0;
}
執行過程與結果
執行過程與結果
$ gcc fork.c -o fork
$ ./fork
The end of program.
$ total 94
-rwxr-x--- 1 ccc Users 2810 Jun 13 2008 DIR_COLORS
drwxrwx---+ 2 ccc Users 0 Oct 7 2008 alternatives
-rwxr-x--- 1 ccc Users 28 Jun 13 2008 bash.bashrc
drwxrwx---+ 4 ccc Users 0 Oct 7 2008 defaults
-rw-rw-rw- 1 ccc Users 716 Oct 7 2008 group
….
Linux 中的執行緒
POSIX 標準中支援的執行緒函式庫稱為
pthread ,我們可以透過 pthread 結構與
pthread_create() 函數執行某個函數指標,以建立
新的執行緒。

範例 10.7 顯示了一個利用 pthread 建立兩個執


行緒,分別不斷印出 George 與 Mary 的程式。
該程式中總共有三個執行主體,第一個是
print_george() 、第二個是 print_mary() 、第三個
是主程式本身。由於每隔 1 秒印一次 George ,
但每隔 0.5 秒就印一次 Mary ,因此執行結果會
以 George, Mary, George, George,Mary 的形式
印出。
範例 10.7
#include <pthread.h> 引用 pthread 函式庫
#include <stdio.h>
void *print_george(void *argu) {
while (1) { 每隔一秒鐘印出一次
printf("George\n"); George 的函數
sleep(1);
}
return NULL;
}
void *print_mary(void *argu) {
while (1) { 每隔一秒鐘印出一次
printf("Mary\n"); Mary 的函數
sleep(2);
}
return NULL;
}
int main() { 主程式開始
pthread_t thread1, thread2; 宣告兩個執行緒
pthread_create(&thread1, NULL, &print_george, 建立執行緒 1
NULL); 建立執行緒 2
pthread_create(&thread2, NULL, &print_mary, 主程式每隔一秒鐘
NULL); 就印出分隔行
while (1) {
printf("-------------------------\n");
sleep(1);
}
return 0;
執行過程與結果
執行過程與結果
$ gcc thread.c -o thread
$ ./thread
George
Mary
-------------------------
George
-------------------------
George
Mary
-------------------------
George
-------------------------

10.6.1.2 Linux 的記憶體管理
 Linux 作業系統原本是在 IA32 (x86) 處理器上設計的,由於

IA32 具有 MMU 單元,因此大部分的 Linux 都預設支援虛擬記


憶體機制。

 然而,在許多的嵌入式處理器中,並沒有 MMU 單元,於是有 Jeff

Dionne 等人於 1998 年開始將 Linux 中的 MMU 機制去除並


改寫,後來釋出了不具有 MMU 的 µClinux 版本。

 曾經有一段時間,嵌入式的系統開發者必須決定應使用具有 MMU

的 Linux 或不具 MMU 的 µClinux

 但是後來在 2.5.46 版的 Linux 中,決定將 µClinux 納入核心中,

所以後來的 Linux 核心已經包含了 µClinux 的功能,可以選擇是


IA32 (x86) 的記憶體管理單

Intel 的 IA32 (Pentium 處理器 ) 採用了 GDT 與
LDT 兩種表格,其中的 LDT 分段表 (Local
Descriptor Table) 是給一般行程使用的,而 GDT
分段表 (Global Descriptor Table) 則包含各行程
共享的分段,通常由作業系統使用。

IA32 的分段與分頁機制
 IA32 同時具有分段與分頁單元,可以支援『純粹分
段』、『單層分頁式分段』與『雙層分頁式分段』等三
種組合。

 『邏輯位址』 (Logical Address) 經過分段單位轉換後


,稱為『線性位址』 (Linear Address) ,再經過分頁
單位轉換後,稱為真實位址 (Physical Address) 。

邏輯位址 線性位址 真實位址


(se,d) 分段單位 分頁單位 m

圖 10.13 IA32 的兩階段位址轉換過程


圖 10.14 IA32 分段模式
選擇器 (S) 位移 (D)
分段
13 1 2 32 bits
s g pr d

d
分段表 (GDT or
LDT)
s
32 bits
aux limit base
圖 10.15 IA32 的分頁模式
目錄代號 分頁代號 頁內位移

31 p1 22,21 p2 12,11 d 0

PT P2 4-KB
分頁表 page
PD
P1
分頁
目錄 (p2, d)

4-MB
page
圖 10.16 IA32 的分頁式分段模

線性位址
邏輯位址 (A) 真實位址 (M)
(L)
指令 分段單位 分頁單位 匯流排

cr3 M2=(P2, d)
PT P2 4-KB
+ 分段表 分頁表 page
A=(S,D) PD
P1
L 分頁
目錄 延伸分頁模式
4-MB
L=B[S]+D page
M1=(P1, p2, d)

邏輯位址 A= (S, D) 選擇器部分 S=(s, g, pr) 位移部分 D=(p1, p2, d)


Linux 的記憶體管理機制
 X86 版本 Linux 利用 GDT 指向核心的分頁,然後用
LDT 指向使用者行程的分頁。 LDT 中所記載的是各
個行程的分段表,以及行程的狀態段 (Task State
Segment : TSS) 。而 GDT 中則會記載這些分段表的
起始點, TSS 起始點,以及核心的各分段起點。圖
10.17 顯示了 x86 版的 Linux 的分段記憶體管理機制

 透過圖 10.17 的分段機制, Linux 可為每一個行程的


段落分配一塊大小不等的區段。其中每一個區段又可能
佔據許多個分頁, x86 版本的 Linux 採用 IA32 的延
伸分頁模式,利用 IA32 『分段 + 雙層分頁』的延伸記
憶體管理模式,因此每個頁框的大小為 4KB 。
圖 10.17 Linux 的記憶體管理機制
行程 k
LDT
GDT Task1 .data
0 NULL
LDT k 1 Task k .text Task1 .text
TSS k 2 Task k .data …
kernel …

.data
LDT 0 Task k .tss …
TSS 0 … 行程 0
kernel
.text sys
NULL Task1 .data
Kernel Data Task 0 .data Task1 .text
Kernel Text Task 0 .text

核心位址空間 Task 0 .tss 使用者位址空間


Linux 的記憶體分配方法
作業系統的頁框配置
使用 Buddy 頁框分配系統

行程中的小量記憶體分配
使用 Slab 記憶體配置器
Buddy 頁框分配系統
 當需要進行分段配置 ( 例如載入行程 ) 時, Linux 會使用對偶式記憶體
管理演算法 (Buddy System Algorithm) 配置分頁,在 Buddy 系統中,
一個頁框代表一段連續的分頁,該演算法將的頁框區分為十種區塊大小,
分別包含了 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 個連續的頁框,其中每
個區塊的第一的頁框位置一定是區塊大小的倍數。舉例而言,一個包含
32 個頁框的區塊之起始位址一定是 32 * 4KB 的倍數。
 Buddy 系統的運作方法,乃是利用一個名為 free_area[10] 的陣列,該
陣列中儲存了對應大小的位元映像圖 (bitmap) ,以記錄區塊的配置狀況。
當有分頁配置需求時, Linux 會尋找大小足 的最小區塊,舉例而言,
如果需要 13 個頁框,則 Linux 會從大小為 16 的頁框區中取出一個可
用頁框,分配給需求者。但是如果大小為 16 的頁框區沒有可用頁框,
則會從大小為 32 的頁框區取得,然後分成兩半,一半分配給需求者,
另一半則放入大小為 16 的可用頁框區中。
 當某頁框被釋放時, Buddy 系統會試圖檢查其兄弟頁框是否也處於可用
狀態,若是則將兩個頁框合併以形成一個更大的可用頁框,放入可用頁框
串列中。
Slab 記憶體配置器
當 Linux 需要配置的是小量的記憶體 ( 像是
malloc 所需的記憶體 ) 時,採用的是一種稱為
Slab Allocator 的配置器,其中被配置的資料稱為
物件 (Object) 。 Slab 中的物件會被儲存在
Buddy 系統所分配的頁框中,假如要分配一個大
小為 30 bytes 的物件時, Slab 會先向 Buddy
系統要求取得一個最小的分頁 ( 大小為 4KB) ,然
後分配個 Slab 配置器。然後 Slab 配置器會保留
一些位元以記錄配置資訊,然後將剩下的空間均分
為大小 30 的物件。於是當未來再有類似的配置請
求時,就可以直接將這些空的物件配置出去。
10.6.1.3 Linux 的 案系統
檔案系統包含『檔案』、『目錄』、『路徑』、
『檔案屬性』等邏輯物件

概念 ( 物件 ) 範例 說明

路徑 /home/ccc/hello.txt 檔案在目錄結構中的位置

目錄 /home/ccc/ 資料夾中所容納的項目索引
( 包含子目錄或檔案之屬性與連結 )

檔案 Hello World !\n ….. 檔案的內容

屬性 -rwxr-xr-- 1 ccc None 61 檔案的名稱、權限、擁有者、修改日期


Jun 25 12:17 README.txt 等資訊

表格 10.1 檔案系統中的基本邏輯概念
表格 10.2Linux 檔案系統的第一層目錄
目錄 全名 說明
/bin Binary 存放二進位的可執行檔案
/dev Device 代表設備,存放裝置相關檔案
/etc Etc… 存放系統管理與配置檔案,像是服務程式 httpd 與
host.conf 等檔案。
/home Home 用戶的主目錄,每個使用者在其中都會有一個子資料夾,
例如用戶 ccc 的資料夾為 /home/ccc/
/lib Library 包含系統函式庫與動態連結函式庫
/sbin System binary 系統管理程式,通常由系統管理員使用
/tmp Temp 暫存檔案
/root Root directory 系統的根目錄,通常由系統管理員使用
/mnt Mount 用戶所掛載上去的檔案系統,通常放在此目錄下
/proc Process 一個虛擬的目錄,代表整個記憶體空間的映射區,可以透
過存取此目錄取得系統資訊。
/var Variable 存放各種服務的日誌等檔案
/usr User 龐大的目錄,所有的使用者程式與檔案都放在底下,像是
/usr/src 中就存放了 Linux 核心的原始碼,而 /usr/bin
則存放所有的開發工具環境,像是 javac, java, gcc,
perl 等。 ( 若類比到 MS. Windows ,此資料夾就像是
C:\Program Files)
案系統 - Windows v.s. Linux
 在 MS. Windows 中,分割是以 A: B: C: D: 這樣的概
念形式呈現的。一個分割概念再 Windows 中通常稱
為『槽』。由於歷史的因素,通常 A: B: 槽代表軟碟
機,而 C: 槽代表第一顆硬碟, D: E: …. 槽則可能是
光碟、硬碟、或隨身碟等等。

 在 Linux 中並沒有槽的概念,而是直接將裝置映射到
/dev 資料夾的某個檔案路徑中。舉例而言,第一顆硬
碟的第一個分割通常稱為 /dev/hda1 ,第二個分割則
為 /dev/hda2, … 。而第二顆硬碟的第一個分割區則為
/dev/hdb1 ,…。軟碟則被映射到 /dev/sda1,
/dev/sda2, …./dev/sdb1, …. ,以此類推。
案系統的掛載
 在 Linux 中,我們可以利用 mount 這個指令,將某
個分割 ( 槽 ) 掛載到檔案系統的某個節點中,這樣就
不需要知道某個資料夾 ( 像是 /mnt) 到底是何種檔案
系統,整個檔案系統形成一顆與硬體無關的樹狀結構。
舉例而言, mount -t ext2 /dev/hda3 /mnt 這個指令可
以將 Ext2 格式的硬碟分割區 /dev/hda3 掛載到 /mnt
目錄下。而 mount -t iso9600 -o ro /dev/cdrom
/mnt/cdrom 這樣的指令則可將 iso9600 格式的光碟
/dev/cdrom 以唯讀的方式掛載到 /mnt/cdrom 路徑當
中。當然,我們也可以用 unmount 指令將這些掛載上
去的裝置移除。 在 UNIX/Linux 中,硬碟的分割為
hda1, hda2, hda3, … 這樣的順序,
圖 10.18 UNIX/Linux 中的檔案與目錄
概念
ccc@ccc-kmit2 ~/book
目前路徑
$ pwd
/home/ccc/book

ccc@ccc-kmit2 ~/book

$ ls -all 連結數量 群組 名稱

total 1
drwxrwxrwx+ 3 ccc None 0 Jun 25 12:15 .
drwxrwxrwx+ 13 ccc None 0 Jun 25 12:15 ..
-rwxr-xr-- 1 ccc None 61 Jun 25 12:17 README.txt
drwxr--r--+ 2 ccc None 0 Jun 25 12:15 ch01

權限 擁有者 修改時間
10.6.1.4 Linux 的輸出入系統
新建 載入 離開 結束
中斷

就緒 執行
排程器
分派
I/O 或事件結束 I/O 或等待事件
等待

You might also like