1. 進程
一個正在執行的程序實例
2. 進程模型
概念上每個進程擁有自己的虛擬CPU,實際上真正的CPU在各進程之間來回切換(多道程序設計)
3. 創建進程
1) 系統初始化
2) 執行了正在運行的進程所調用的進程創建系統調用fork()
3) 用戶請求創建一個新進程
4) 一個批處理作業的初始化
4. 進程終止
1) 正常退出(自願的)
2) 出錯退出(自願的)
3) 嚴重錯誤(非自願)
4) 被其他進程殺死(非自願)
5. 進程的層次結構
UNIX:進程與其子進程構成一個進程組
WINDOWS:沒有層次概念,父進程用句柄控制子進程
6. 進程狀態
運行、阻塞、就緒
1) 運行->阻塞:進程爲等待輸入而阻塞
2) 運行->就緒:調度程序選擇另一個進程
3) 就緒->運行:調度程序選擇這個進程
4) 阻塞->就緒:出現有效輸入
其他狀態:掛起、終止
7. 進程的實現
進程表表項(進程控制塊)包括與進程管理、存儲管理、文件管理有關的信息。
中斷向量:中斷髮送時,保存狀態後,計算機跳轉到中斷向量所指示位置完成操作。
多道程序設計中的CPU利用率=1-p^n
(n進程數,p用於I/O操作的時間與等待時間的時間比)
8. 線程(迷你進程)
9. 進程與線程內容的比較
進程: 地址空間 全局變量 打開文件清單 子進程 即將發生的報警 信號與信號處理程序 賬號信息 |
線程: 程序計數器 寄存器 堆棧 狀態
|
一組線程共享資源
兩種主要方法實現進程包:在用戶空間中和在內核中(另有混合實現)
10. 內核級線程的調度程序激活機制
目的:模擬內核級線程的功能,提供(在用戶空間才能實現的)更好的性能和更大的靈活性,避免了不必要的用戶空間/內核空間轉換,提高效率。
內核給每個進程安排一定數量的虛擬處理器,將線程分配的處理器上
11. 彈出式線程
分佈式系統中另一種處理消息到來的方式,一個消息的到達導致系統創建一個處理該消息的程序。
12. 進程間通信
競爭條件:兩個或多個進程讀寫共享數據,最後結果取決於進程運行的精確時序
臨界區:對共享內存進行訪問的程序片段
避免競爭條件需滿足以下4個條件:
1) 任何兩個進程不能同時處於臨界區
2) 不應對CPU數量和速度做任何假設
3) 臨界區外運行的進程不得阻塞其他進程
4) 不得使進程無限期等待進入臨界區
13. 實現忙等待的互斥的方案
1) 屏蔽中斷
2) 鎖變量
3) 嚴格輪換法(忙等待)
4) Peterson解法
5) TSL指令(需要硬件支持,“測試並加鎖”指令)
14. 信號量
用一個整型變量累計喚醒次數
P/down:大於0,減1;爲0,將進程睡眠
V/up:增1。
整個操作是不可分割的原子操作
15. 使用信號量解決“生產者-消費者問題”
(1)含有嚴重競爭條件的生產者-消費者問題(下面代碼)
對count的訪問未加限制。
有可能出現以下情況:緩衝區爲空,消費者剛剛讀取count的值發現它爲0。此時調度程序把 CPU切換給了生產者。生產者想緩衝區中加入一個數據項,count加1,並且它推斷由於剛纔count爲0,所以消費者一定在睡眠,所以調用 wakeup來喚醒消費者。但消費者並未睡眠,所以wakeup信號丟失。當消費者下次運行時,它將測試先前讀到的count值,發現爲0,於是睡眠。生 產者遲早會填滿整個緩衝區,然後睡眠。最後兩個進程都將永遠睡眠下去。
#define N 100 /*緩衝區中的槽數目*/
int count = 0; /*緩衝區中的數據項數目*/
void producer(void)
{
int item;
while(TRUE)
{
item=produce_item();
if(count==N) sleep();
insert_item(item);
count=count+1;
if(count==1) wakeup(consumer); /*count==1說明生產之前count==0,consumer進程應該在睡眠,遂喚醒它*/
}
}
void consumer(void)
{
int item;
while(TRUE)
{
if(count==0) sleep();
item=remove_item();
count=count-1;
if(count==N-1) wakeup(producer); /*count==N-1說明消費之前緩衝區滿了,生產者應該在睡眠,遂喚醒*/
consume_item(item);
}
}
(2)使用信號量解決“生產者-消費者問題”
信號量mutex用於互斥,保證任意時刻只有一個進程讀寫緩衝區和相關變量。互斥是避免混亂所必需的操作。
信號量full和empty用於實現同步,保證某種事件的順序發生或不發生。它們保證當緩衝區滿的時候生產者停止運行,以及當緩衝區空的時候消費者停止運行。
#define N 100 /*緩衝區中的槽數目*/
typedef int semaphore; /*信號量是一種特殊的整型數據*/
semaphore mutex = 1; /*控制對臨界區的訪問*/
semaphore empty = N; /*計數緩衝區的空槽數目*/
semaphore full=0; /*計數緩衝區的滿槽數目*/
void producer(void)
{
int item;
while(TRUE)
{
item=produce_item();
down(&empty); /*空槽數目減一*/
down(&mutex); /*進入臨界區*/
insert_item(item);
up(&mutex); /*離開臨界區*/
up(&full); /*將滿槽的數目加1*/
}
}
void consumer(void)
{
int item;
while(TRUE)
{
down(&full);
down(&mutex);
item=remove_item();
up(&mutex);
up(&empty);
consume_item(item);
}
}
16. 互斥量
一個二進制位(解鎖和加鎖),是信號量的簡化版本。
17. 利用線程解決“生產者-消費者問題”
#include <stdio.h>
#include <pthread.h>
#define MAX 10 //需要生產的數量
pthread_mutex_t the_mutex;
pthread_cond_t condc, condp;
int buffer = 0;//生產者、消費者使用的緩衝區
void *producer(void *ptr)
{
int i;
for(i=1; i<=MAX; i++)
{
pthread_mutex_lock(&the_mutex); //互斥使用緩衝區
while(buffer !=0) pthread_cond_wait(&condp, &the_mutex);
printf("procucer produce item %d\n",i);
buffer = i; //將數據插入緩衝區
pthread_cond_signal(&condc);//喚醒消費者
pthread_mutex_unlock(&the_mutex);//釋放緩衝區
}
pthread_exit(0);
}
void *consumer(void *ptr)
{
int i;
for(i=1; i<=MAX; i++)
{
pthread_mutex_lock(&the_mutex);//互斥使用緩衝區
while(buffer ==0) pthread_cond_wait(&condc, &the_mutex);
printf("consumer consume item %d\n",i);
buffer = 0;//從緩衝區中取出數據
pthread_cond_signal(&condp);//喚醒生產者
pthread_mutex_unlock(&the_mutex);//釋放緩衝區
}
pthread_exit(0);
}
int main(int argc, char *argv[])
{
pthread_t pro, con;
pthread_mutex_init(&the_mutex, 0);
pthread_cond_init(&condc,0);
pthread_cond_init(&condp,0);
pthread_create(&con, 0, consumer, 0);
pthread_create(&pro, 0, producer, 0);
pthread_join(pro,0);
pthread_join(con,0);
pthread_cond_destroy(&condc);
pthread_cond_destroy(&condp);
pthread_mutex_destroy(&the_mutex);
return 0;
}
18. 其它同步機制
(1)管程
高級同步原語,一個管程是一個有過程、變量及數據結構等組成的一個集合,組成一個特殊模塊或軟件包。
任一時刻管程中只能有一個進程。
(2)消息傳遞
(3)屏障:除非所有的進程都就緒準備下一階段,否則任何進程都不能進入下一階段。
19. 調度
選擇下一個佔用資源的進程。
何時調度:
1) 創建新進程後
2) 一個進程退出時
3) 一個進程阻塞時
4) 發生I/O中斷時
5) 時鐘中斷時
調度算法的目標:公平、策略強制性、平衡
調度算法的分類:搶佔式/非搶佔式
三種環境:
1) 批處理(吞吐量、週轉時間、CPU利用率)
2) 交互式(響應時間、均衡性)
3) 實時(滿足截止時間、可預測性)
20. 調度算法
批處理:
1)先來先服務
2)最短作業優先
3)最短剩餘時間優先
交互式:
1)輪轉調度(分時間片,每個進程在一個時間片內運行)
2)優先級調度
3)多級隊列(前兩種結合)
4)最短進程優先(老化算法估計運行時間aT0+(1-a)T1,如a=1/2,時間序列爲T0,T0/2+T1/2,T0/4+T1/4+T2/2,三輪過後T0在新的估計值中所佔的比重下降到1/8)
5)保證調度
6)彩票調度
7)公平分享調度(保證用戶分配到的CPU時間的公平)
實時:
硬實時/軟實時
週期性/非週期性
21. 哲學家就餐問題的一種解法
#define N 5 /*哲學家數目*/
#define LEFT (i-1+N)%N /*i的左鄰號碼*/
#define RIGHT (i+1)%N /*i的右鄰號碼*/
#define THINKING 0 /*哲學家正在思考*/
#define HUNGRY 1 /*哲學家想取得叉子*/
#define EATING 2 /*哲學家正在吃麪*/
typedef int semaphore; /*信號量是一個特殊的整型變量*/
int state[N]; /*記錄每個人狀態的數組*/
semaphore mutex = 1; /*臨界區互斥*/
semaphore s[N]; /*每個哲學家一個信號量*/
void philosopher(int i) { /*i:哲學家號碼,從0到N-1*/
while(TURE){ /*無限循環*/
think(); /*哲學家正在思考*/
take_forks(i); /*需要兩隻叉子,或者阻塞*/
eat(); /*進餐*/
put_forks(i); /*把兩把叉子同時放回桌子*/
}
}
void take_forks(int i) { /*i:哲學家號碼,從0到N-1*/
down(&mutex); /*進入臨界區*/
state[i]= HUNGRY; /*記錄下哲學家i飢餓的事實*/
test(i); /*試圖得到兩隻叉子*/
up(&mutex); /*離開臨界區*/
down(&s[i]); /*如果得不到叉子就阻塞*/
}
void put_forks(int i) { /*i:哲學家號碼,從0到N-1*/
down(&mutex); /*進入臨界區*/
state[i]= THINKING; /*哲學家進餐結束*/
test(LEFT); /*看一下左鄰居現在是否能進餐*/
test(RIGHT); /*看一下右鄰居現在是否能進餐*/
up(&mutex); /*離開臨界區*/
}
void test(i) { /*i:哲學家號碼,從0到N-1*/
if(state[i]== HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) {
state[i]= EATING;
up(&s[i]);
}
}
22. 讀者寫者問題的一種解法
typedef int semaphore; /*定義整型的信號量*/
semaphore mutex = 1; /*爲控制對全局量rc的訪問而設置的信號量*/
semaphore db = 1; /*爲控制對數據庫的訪問而設置的信號量*/
int rc = 0; /*rc是正在讀或者等待讀的進程數,初值爲0*/
void reader(void)
{
while(TRUE) { /*設置無限循環方式*/
down(&mutex); /*獲取對rc變量的互斥訪問權*/
rc= rc + 1; /*這時又多了一個讀進程*/
if(rc == 1) down(&db); /*若是第一個讀者,則允許訪問*/
up(&mutex); /*釋放對rc的互斥操作*/
reader_data_dase(); /*完成數據訪問操作*/
down(&mutex); /*獲取對rc變量的互斥訪問權*/
rc= rc - 1; /*這時減少了一個讀者進程*/
if(rc == 0) up(&db);/*若是最後一個讀者,可允許寫*/
up(&mutex); /*釋放對rc的互斥訪問*/
use_data_read(); /*非臨界區操作,使用讀出數據*/
}
}
void writer(void)
{
while(TRUE) { /*設置無限循環方式*/
think_up_data(); /*非臨界區操作,準備希望修改的數據*/
down(&db); /*獲取數據庫互斥訪問權*/
writer_data_dase; /*更新數據庫消息*/
up(&db); /*釋放數據庫互斥訪問權*/
}
}