一 進(jìn)程線程的區(qū)別
官方概念:進(jìn)程是資源分配的基本單位,線程的cpu調(diào)度的基本單位。任一時(shí)刻,CPU總是運(yùn)行一個(gè)進(jìn)程,其他進(jìn)程處于非運(yùn)行狀態(tài)。
1. PCB控制塊
為了描述控制進(jìn)程的運(yùn)行,系統(tǒng)中存放進(jìn)程的管理和控制信息的數(shù)據(jù)結(jié)構(gòu)稱為進(jìn)程控制塊,它是進(jìn)程實(shí)體的一部分,是操作系統(tǒng)中最重要的記錄性數(shù)據(jù)結(jié)構(gòu)。它是進(jìn)程管理和控制的最重要的數(shù)據(jù)結(jié)構(gòu),每一個(gè)進(jìn)程均有一個(gè)PCB,在創(chuàng)建進(jìn)程時(shí),建立PCB,伴隨進(jìn)程運(yùn)行的全過程,直到進(jìn)程撤消而撤消。
cpu對pcb的控制達(dá)到對進(jìn)程的控制,pcb控制塊中包含著進(jìn)程的基本信息。linux下pcb數(shù)據(jù)結(jié)構(gòu):
struct task_struct{
unsigned short uid;
int pid;
int processor;
volatile long state;
long prority;
unsighed long rt_prority;
long counter;
unsigned long flags;
unsigned long policy;
Struct task_struct *next_task, *prev_task;
Struct task_struct *next_run,*prev_run;
Struct task_struct *p_opptr,*p_pptr,*p_cptr,*pysptr,*p_ptr;
};
(1)unsigned short pid 為用戶標(biāo)識
(2)int pid 為進(jìn)程標(biāo)識
(3)int processor標(biāo)識用戶正在使用的CPU,以支持對稱多處理機(jī)方式;
(4)volatile long state 標(biāo)識進(jìn)程的狀態(tài),可為下列六種狀態(tài)之一:
可運(yùn)行狀態(tài)(TASK-RUNING);
可中斷阻塞狀態(tài)(TASK-UBERRUPTIBLE)
不可中斷阻塞狀態(tài)(TASK-UNINTERRUPTIBLE)
僵死狀態(tài)(TASK-ZOMBLE)
暫停態(tài)(TASK_STOPPED)
交換態(tài)(TASK_SWAPPING)
(5)long prority表示進(jìn)程的優(yōu)先級
(6)unsigned long rt_prority 表示實(shí)時(shí)進(jìn)程的優(yōu)先級,對于普通進(jìn)程無效
(7)long counter 為進(jìn)程動(dòng)態(tài)優(yōu)先級計(jì)數(shù)器,用于進(jìn)程輪轉(zhuǎn)調(diào)度算法
(8)unsigned long policy 表示進(jìn)程調(diào)度策略,其值為下列三種情況之一:
SCHED_OTHER(值為0)對應(yīng)普通進(jìn)程優(yōu)先級輪轉(zhuǎn)法(round robin)
SCHED_FIFO(值為1)對應(yīng)實(shí)時(shí)進(jìn)程先來先服務(wù)算法;
SCHED_RR(值為2)對應(yīng)實(shí)時(shí)進(jìn)程優(yōu)先級輪轉(zhuǎn)法
(9)struct task_struct *next_task,*prev_task為進(jìn)程PCB雙向鏈表的前后項(xiàng)指針
(10)struct task_struct *next_run,*prev_run為就緒隊(duì)列雙向鏈表的前后項(xiàng)指針
(11)struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_ptr指明進(jìn)程家族間的關(guān)系,分別為指向祖父進(jìn)程、父進(jìn)程、子進(jìn)程以及新老進(jìn)程的指針。
2. 進(jìn)程和線程的區(qū)別
線程不能脫離進(jìn)程而存在,一個(gè)進(jìn)程至少有一個(gè)線程。進(jìn)程是程序在某個(gè)數(shù)據(jù)集合上的一次運(yùn)行活動(dòng);線程是進(jìn)程中的一個(gè)執(zhí)行路徑。 角色方面:在支持線程機(jī)制的系統(tǒng)中,進(jìn)程是系統(tǒng)資源分配的單位,線程是系統(tǒng)調(diào)度的單位。 資源共享方面:進(jìn)程之間不能共享資源,而線程共享所在進(jìn)程的地址空間和其它資源。同時(shí)線程還有自己的棧和棧指針,程序計(jì)數(shù)器等寄存器。 獨(dú)立性方面:進(jìn)程有自己獨(dú)立的地址空間,而線程沒有,線程必須依賴于進(jìn)程而存在。對于操作系統(tǒng)來說,進(jìn)程是一個(gè)比較重的概念,線程是輕量級的概念。
3. 進(jìn)程同步
臨界區(qū)
在同步的程序設(shè)計(jì)中,臨界區(qū)段(Critical section)指的是一個(gè)訪問共享資源(例如:共享設(shè)備或是共享存儲器)的程序片段,而這些共享資源有無法同時(shí)被多個(gè)線程訪問的特性。
當(dāng)有線程進(jìn)入臨界區(qū)段時(shí),其他線程或是進(jìn)程必須等待(例如:bounded waiting 等待法),有一些同步的機(jī)制必須在臨界區(qū)段的進(jìn)入點(diǎn)與離開點(diǎn)實(shí)現(xiàn),以確保這些共享資源是被異或的使用,例如:semaphore。
總結(jié):所謂的臨界區(qū)就是每次只能有一個(gè)線程訪問。
同步
處理競爭就是同步,安排進(jìn)程執(zhí)行的先后順序就是同步,每個(gè)進(jìn)程都有一定的個(gè)先后執(zhí)行順序。
互斥
互斥訪問不可共享的臨界資源,同時(shí)會引發(fā)兩個(gè)新的控制問題(互斥可以說是特殊的同步)。
信號量
信號量(Semaphore)是一個(gè)整型變量,可以對其執(zhí)行 down 和 up 操作,也就是常見的 P 和 V 操作。
- down : 如果信號量大于 0 ,執(zhí)行 -1 操作;如果信號量等于 0,進(jìn)程睡眠,等待信號量大于 0;
-
up :對信號量執(zhí)行 +1 操作,喚醒睡眠的進(jìn)程讓其完成 down 操作。
down 和 up 操作需要被設(shè)計(jì)成原語,不可分割,通常的做法是在執(zhí)行這些操作的時(shí)候屏蔽中斷。
如果信號量的取值只能為 0 或者 1,那么就成為了 互斥量(Mutex) ,0 表示臨界區(qū)已經(jīng)加鎖,1 表示臨界區(qū)解鎖。
4 進(jìn)程間通信
匿名管道(pipe)
半雙工,只能在有親緣關(guān)系的進(jìn)程之間進(jìn)行通信,一般就是父子進(jìn)程。
典型的匿名管道代碼
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int pipefd[2];
pid_t cpid;
char buf;
if (argc != 2) {
fprintf(stderr, "Usage: %s <string>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) { /* Child reads from pipe */
close(pipefd[1]); /* Close unused write end */
while (read(pipefd[0], &buf, 1) > 0)
write(STDOUT_FILENO, &buf, 1);
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else { /* Parent writes argv[1] to pipe */
close(pipefd[0]); /* Close unused read end */
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]); /* Reader will see EOF */
wait(NULL); /* Wait for child */
exit(EXIT_SUCCESS);
}
}
父進(jìn)程創(chuàng)建管道,得到兩個(gè)?件描述符,進(jìn)程執(zhí)行fork,?進(jìn)程也有兩個(gè)?件描述符指向同?管道。
父進(jìn)程關(guān)閉fd[0],子進(jìn)程關(guān)閉fd[1],即?進(jìn)程關(guān)閉管道讀端,?進(jìn)程關(guān)閉管道寫端(因?yàn)楣艿乐恢С謫蜗蛲ㄐ牛?進(jìn)程可以往管道?寫,?進(jìn)程可以從管道?讀,管道是?環(huán)形隊(duì)列實(shí)現(xiàn)的,數(shù)據(jù)從寫端流?從讀端流出,這樣就實(shí)現(xiàn)了進(jìn)程間通信。
具名管道
與傳統(tǒng)的無名的shell管道不同,命名管道利用了文件系統(tǒng)。使用mkfifo()或mknod()創(chuàng)建命名管道。兩個(gè)進(jìn)程可以通過管道的名字打開、讀寫管道。
消息隊(duì)列
消息隊(duì)列本身是異步的,它允許接收者在消息發(fā)送很長時(shí)間后再取回消息,這和大多數(shù)通信協(xié)議是不同的。例如中使用的HTTP協(xié)議是同步的,因?yàn)榭蛻舳嗽诎l(fā)出請求后必須等待服務(wù)器回應(yīng)。然而,很多情況下我們需要異步的通信協(xié)議。比如,一個(gè)進(jìn)程通知另一個(gè)進(jìn)程發(fā)生了一個(gè)事件,但不需要等待回應(yīng)。但消息隊(duì)列的異步特點(diǎn),也造成了一個(gè)缺點(diǎn),就是接收者必須輪詢消息隊(duì)列,才能收到最近的消息。
重點(diǎn):消息隊(duì)列是一個(gè)異步的通信過程,接受者一般需要輪訓(xùn)監(jiān)聽。常用的rocketMQ就是一種消息隊(duì)列機(jī)制。
共享內(nèi)存
多個(gè)進(jìn)程通過將同一塊屋里內(nèi)存綁定到自己的虛擬地址空間,實(shí)現(xiàn)共享內(nèi)存。
進(jìn)程通過調(diào)用shmget(Shared Memory GET,獲取共享內(nèi)存)來分配一個(gè)共享內(nèi)存塊。
該函數(shù)的第一個(gè)參數(shù)是一個(gè)用來標(biāo)識共享內(nèi)存塊的鍵值。彼此無關(guān)的進(jìn)程可以通過指定同一個(gè)鍵以獲取對同一個(gè)共享內(nèi)存塊的訪問。不幸的是,其它程序也可能挑選了同樣的特定值作為自己分配共享內(nèi)存的鍵值,從而產(chǎn)生沖突。用特殊常量IPC_PRIVATE作為鍵值可以保證系統(tǒng)建立一個(gè)全新的共享內(nèi)存塊。該函數(shù)的第二個(gè)參數(shù)指定了所申請的內(nèi)存塊的大小。因?yàn)檫@些內(nèi)存塊是以頁面為單位進(jìn)行分配的,實(shí)際分配的內(nèi)存塊大小將被擴(kuò)大到頁面大小的整數(shù)倍。第三個(gè)參數(shù)是一組標(biāo)志,通過特定常量的按位或操作來shmget。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
套接字
這個(gè)用的太多了,所有的網(wǎng)絡(luò)編程都是基于套接字的
5 孤兒進(jìn)程與僵尸進(jìn)程
孤兒進(jìn)程
孤兒進(jìn)程指的是父進(jìn)程執(zhí)行完或終止后仍然存在的進(jìn)程。unix從機(jī)制上盡量避免產(chǎn)生孤兒進(jìn)程:
- 為避免孤兒進(jìn)程退出時(shí)無法釋放所占用的資源而僵死,任何孤兒進(jìn)程產(chǎn)生時(shí)都會立即為系統(tǒng)進(jìn)程init或systemd自動(dòng)接收為子進(jìn)程,這一過程也被稱為“收養(yǎng)”。
- 因?yàn)楦高M(jìn)程終止或崩潰都會導(dǎo)致對應(yīng)子進(jìn)程成為孤兒進(jìn)程,所以也無法預(yù)料一個(gè)子進(jìn)程執(zhí)行期間是否會被“遺棄”。有鑒于此,多數(shù)類UNIX系統(tǒng)都引入了進(jìn)程組以防止產(chǎn)生孤兒進(jìn)程:在父進(jìn)程終止后,用戶的Shell會將父進(jìn)程所在進(jìn)程組標(biāo)為“孤兒進(jìn)程組”,并向終止的進(jìn)程下屬所有子進(jìn)程發(fā)出SIGHUP信號,以試圖結(jié)束其運(yùn)行,如此避免子進(jìn)程繼續(xù)以“孤兒進(jìn)程”的身份運(yùn)行[2]。
僵尸進(jìn)程
僵尸進(jìn)程是指完成執(zhí)行系統(tǒng)調(diào)用,或運(yùn)行時(shí)發(fā)生致命錯(cuò)誤或收到終止信號但在操作系統(tǒng)的進(jìn)程表中仍然有一個(gè)表項(xiàng)(進(jìn)程控制塊PCB),處于"終止?fàn)顟B(tài)"的進(jìn)程。這發(fā)生于子進(jìn)程需要保留表項(xiàng)以允許其父進(jìn)程讀取子進(jìn)程的exit status:一旦退出態(tài)通過wait或waitpid讀取,僵尸進(jìn)程條目就從進(jìn)程表中刪除,稱之為"回收(reaped)"。正常情況下,進(jìn)程直接被其父進(jìn)程wait或waitpid并由系統(tǒng)回收。進(jìn)程長時(shí)間保持僵尸狀態(tài)一般是錯(cuò)誤的并導(dǎo)致資源泄漏。
注:wait是waitpid的一個(gè)特例。