作業系統之程序管理
參考連結:
https://github.com/CyC2018/CS-Notes
https://blog.csdn.net/kuangsonghan/article/details/80674777
https://blog.csdn.net/chenhfm/article/details/26587367
https://www.cnblogs.com/alantu2018/p/8526916.html
目錄
程序和執行緒
- 區別
- 根本:程序是資源分配基本單位,執行緒是任務排程執行基本單位
- 開銷:每個程序有獨立的程式碼和資料空間(程式上下文),在進行程序切換時,涉及當前執行程序 CPU 環境的儲存及新排程程序 CPU 環境的設定,開銷大。同一類執行緒共享程式碼和資料空間,每個執行緒都有自己獨立的執行棧和程式計數器(PC)
- 執行:在作業系統中能同時執行多個程序(程式);而在同一個程序(程式)中有多個執行緒同時執行(通過CPU排程,在每個時間片中只有一個執行緒執行)。從一個程序的執行緒切換到另一個程序的執行緒時,會引起程序切換。
- 記憶體分配:系統給每個程序分配不同的記憶體,但不會給執行緒分配,執行緒之間共享程序的資源
- 舉例:不同程序:qq和瀏覽器;不同執行緒:瀏覽器中的HTTP請求執行緒、事件響應執行緒、渲染執行緒
- 通訊:執行緒間可以通過直接讀寫同一程序中的資料進行通訊,但是程序通訊需要藉助IPC(InterProcess Communication)
- 多執行緒共享和獨享的資源
共享:
a. 堆 由於堆是在程序空間中開闢出來的,所以它是理所當然地被共享的;因此new出來的都是共享的(16位平臺上分全域性堆和區域性堆,區域性堆是獨享的)
b. 全域性變數 它是與具體某一函式無關的,所以也與特定執行緒無關;因此也是共享的
c. 靜態變數 雖然對於區域性變數來說,它在程式碼中是“放”在某一函式中的,但是其存放位置和全域性變數一樣,存於堆中開闢的.bss和.data段,是共享的
d. 檔案等公用資源 這個是共享的,使用這些公共資源的執行緒必須同步。Win32 提供了幾種同步資源的方式,包括訊號、臨界區、事件和互斥體。
獨享:
a. 棧 棧是獨享的
b. 暫存器 這個可能會誤解,因為電腦的暫存器是物理的,每個執行緒去取值難道不一樣嗎?其實執行緒裡存放的是副本,包括程式計數器PC - 程序控制塊(Process Control Block, PCB)
描述程序的基本資訊和執行狀態,所謂的建立程序和撤銷程序,都是指對PCB的操作。 - 核心執行緒(kernel thread)
Linux核心可以看作一個服務程序(管理軟硬體資源,響應使用者程序的種種合理以及不合理的請求)。
核心要多個執行流並行,為了防止可能的阻塞,支援多執行緒是必要的。
核心執行緒就是核心的分身,一個分身可以處理一件特定事情。核心執行緒的排程由核心負責,一個核心執行緒處於阻塞狀態時不影響其他的核心執行緒,因為其是排程的基本單位。
這與使用者執行緒是不一樣的。因為核心執行緒只執行在核心態
因此,它只能使用大於PAGE_OFFSET(傳統的x86_32上是3G)的地址空間。
程序狀態切換
- 三種狀態:就緒、執行、等待
- 狀態切換
就緒->執行:通過CPU排程獲得時間片
執行->就緒:時間片用完
執行->等待:缺少資源(不包括CPU時間)或I/O等待
等待->就緒:有資源或等待完成
程序排程
針對不同系統有不同策略:
- 批處理系統:沒有太多的使用者操作,目標是保證吞吐量和週轉時間(從提交到終止的時間)。
- 先來先服務,first come first served (FCFS)
按照請求順序服務,不利於短作業 - 短作業優先, shortest job first (SJF)
不利於長作業 - 最短剩餘時間優先, shortest remaining time next (SRTN)
是搶佔式的演算法,排程程式總是選擇剩餘執行時間最短的那個程序執行。當一個新的作業到達時,其整個時間同當前程序的剩餘時間做比較。如果新的程序比當前執行程序需要更少的時間,當前程序就被掛起,而執行新的程序。
- 互動式系統:有大量的使用者互動操作,目標是快速地進行響應
- 時間片輪轉
就緒程序按FCFS排隊,每次排程時,把一個時間片分配給首程序。該時間片用完後,由計時器發出時鐘中斷,將當前程序送到就緒佇列的末尾,而後把時間片再分配給隊首。
時間片大小關係:太小,程序切換過於頻繁;太大,實時性無法保證。 - 優先順序排程
給每個程序分配程序,按優先順序高低排程。為了防止低優先順序餓死,可在過程中不斷調整優先順序。 - 多級反饋佇列(可看成時間片輪轉+優先順序排程)
提出的原因:在時間片輪轉演算法中,有些程序需要輪轉很多輪才能執行完。
多級反饋佇列設定了多個佇列,並且每個佇列分配的時間片大小不同,按照從小到大(比如1,2,4,8...)時間片的順序設定。
在每個佇列,採用FCFS模式,當在該佇列的時間片分配用完後,即轉到下一佇列的隊尾等待下一次排程。
注意:只有當上一個佇列都沒有程序等待時,才能程序下一個佇列的時間片分配(即最上面的優先順序最高)。
- 實時系統:要求在一定時間內響應
分為硬實時(嚴格限制完成時間)和軟實時(可以有一定的延時)
具體演算法以後再補
程序同步
- 臨界區:對臨界資源進行訪問的那段程式碼
- 同步與互斥:同步是程序按一定順序執行;互斥是同個時刻只能有一個程序進入臨界區
- 訊號量(Semaphore)
- P操作(down)。如果down之前訊號量=0,程序睡眠
- V操作(up)。喚醒睡眠程序。
- P和V操作要設計成原語,常見做法是遮蔽中斷
- 如果訊號量只能是0或1,則變成互斥量(mutex)
typedef int semaphore;
semaphore mutex = 1;
void P1() {
down(&mutex);
// 臨界區
up(&mutex);
}
void P2() {
down(&mutex);
// 臨界區
up(&mutex);
}
- 管程
使用訊號量機制實現的生產者消費者問題需要客戶端程式碼做很多控制,而管程把控制的程式碼獨立出來,不僅不容易出錯,也使得客戶端程式碼呼叫更容易。提供了** insert()** 和** remove() 方法。管程有一個重要特性:在一個時刻只能有一個程序使用管程**。程序在無法繼續執行的時候不能一直佔用管程,否者其它程序永遠不能使用管程。 - 經典同步問題
- 生產者和消費者問題
https://blog.csdn.net/liushall/article/details/81569609
https://www.cnblogs.com/wkfvawl/p/11529681.html
該問題的關鍵就是要保證生產者不會在緩衝區滿時加入資料,消費者也不會在緩衝區中空時消耗資料
(1)單生產者單消費者:
semaphore mutex = 1; //互斥訊號量
semaphore empty = n; //同步訊號量。空閒緩衝區的數量
semaphore full = 0; //同步訊號量。產品的數量,非空緩衝區的數量
producer(){
while(1){
生成一個產品;
P(empty); //消耗一個空閒緩衝區
P(mutex);
把產品放入緩衝區;
V(mutex);
V(full) //增加一個產品
}
}
consumer(){
while(1){
P(full); //消耗一個產品
P(mutex);
從緩衝區取出一個產品;
V(mutex);
V(empty); //增加一個空閒緩衝區
使用產品;
}
}
注意:P操作的(empty和mutex)順序不可以顛倒,而V可以
(2)多生產者多消費者
https://www.cnblogs.com/wkfvawl/p/11531382.html
2. 讀者和寫者問題
https://www.cnblogs.com/wkfvawl/p/11538431.html
要求:1.可以多個讀者同時閱讀;2.只能由一個寫者在寫;3.寫者寫的時候不能由其他讀者或寫者在
互斥關係:寫程序-寫程序;寫程序-讀程序
最核心的問題:是如何處理多個讀者可以同時對檔案的讀操作,解決方法是設定count值來記錄讀程序數
(1)讀優先
semaphore rw = 1; //實現對檔案的互斥訪問
int count = 0; //當前讀者數
semaphore mutex = 1;//實現對count變數的互斥訪問
int i = 0;
writer(){
while(1){
P(rw); //寫之前“加鎖”
寫檔案
V(rw); //寫之後“解鎖”
}
}
reader (){
while(1){
P(mutex); //各讀程序互斥訪問count
if(count==0) //第一個讀程序負責“加鎖”
{
P(rw);
}
count++; //訪問檔案的程序數+1
V(mutex);
**讀檔案**
P(mutex); //各讀程序互斥訪問count
count--; //訪問檔案的程序數-1
if(count==0) //最後一個讀程序負責“解鎖”
{
V(rw);
}
V(mutex);
}
}
(2)寫優先
上面的演算法有個問題:如果讀者源源不斷進入,則寫者就會一直飢餓!解決此問題的方法是再設定一個互斥量w=1。
分析:這裡我們來分析一下讀者1->寫者1->讀者2這種情況。第一個讀者1在進行到讀檔案操作的時候,有一個寫者1操作,由於第一個讀者1執行了V(w),所以寫者1不會阻塞在P(w),但由於第一個讀者1執行了P(rw)但沒有執行V(rw),寫者1將會被阻塞在P(rw)上,這時候再有一個讀者2,由於前面的寫者1程序執行了P(w)但沒有執行V(w),所以讀者2將會被阻塞在P(w)上,這樣寫者1和讀者2都將阻塞,只有當讀者1結束時執行V(rw),此時寫者1才能夠繼續執行直到執行V(w),讀者2也將能夠執行下去。即先到先服務,對讀寫操作公平。如果目前沒有寫者,那讀者可以源源不斷進去;若出現寫者的需求,則後面的讀者就被堵塞,等到當前在裡面的讀者都讀完後,輪到寫者開始寫。
3. 哲學家用餐問題
https://blog.csdn.net/qq_28602957/article/details/53538329
問題描述:
注意:若五位哲學家同時飢餓而各自拿起了左邊的筷子,這使五個訊號量 chopstick 均為 0,當他們試圖去拿起右邊的筷子時,都將因無筷子而無限期地等待下去,即可能會引起死鎖。
方法:
(1)至多隻允許四位哲學家同時去拿左筷子,最終能保證至少有一位哲學家能進餐,並在用完後釋放兩隻筷子供他人使用。
(2)僅當哲學家的左右手筷子都拿起時才允許進餐。
(3)規定奇數號哲學家先拿左筷子再拿右筷子,而偶數號哲學家相反。
方法1:
方法2:
或是
方法3:
程序通訊Inter Process Communication(IPC)
目的:程序之間傳輸資訊。也可以說,為了能夠達到程序同步的目的,需要讓程序進行通訊,傳輸一些程序同步所需要的資訊。
- 管道
int pipe(int fd[2]);
,fd[0] 用於讀,fd[1] 用於寫
特點:1.半雙工通訊(單向交替傳輸);2.只能在父子程序之間 - FIFO(命名管道)
int mkfifo(const char *path, mode_t mode);int mkfifoat(int fd, const char *path, mode_t mode);
特點:1.去除父子程序之間通訊的限制,常用於客戶-伺服器應用程式中,FIFO 用作匯聚點,在客戶程序和伺服器程序之間傳遞資料 - 訊息佇列
https://blog.csdn.net/qq_22863733/article/details/80385765
整個訊息佇列有兩種型別的資料結構:
(1)msqid_ds訊息佇列資料結構:描述整個訊息佇列的屬性,主要包括整個訊息佇列的許可權,擁有者、兩個重要的指標分別指向訊息佇列的第一個訊息和最後一個訊息。
(2)msg訊息資料結構:整個訊息佇列的主體,一個訊息佇列有若干個訊息,每個訊息資料結構的基本成員包括訊息型別、訊息大小、訊息內容指標和下一個訊息資料結構位置。
相比於 FIFO,訊息佇列具有以下優點:
(1)訊息佇列可以獨立於讀寫程序存在,從而避免了 FIFO 中同步管道的開啟和關閉時可能產生的困難;
(2)避免了 FIFO 的同步阻塞問題,不需要程序自己提供同步方法;
(3)讀程序可以根據訊息型別有選擇地接收訊息,而不像 FIFO 那樣只能預設地接收。 - 資訊量
一個計數器,用於為多個程序提供對共享資料物件的訪問 - 共享儲存
允許多個程序共享一個給定的儲存區。因為資料不需要在程序之間複製,所以這是最快的一種 IPC。需要使用訊號量用來同步對共享儲存的訪問。
多個程序可以將同一個檔案對映到它們的地址空間從而實現共享記憶體。另外 XSI 共享記憶體不是使用檔案,而是使用使用記憶體的匿名段。 - 套接字
用於不同機器間的程序通訊