关于操作系统中进程、线程、死锁、同步、进程间通信(IPC)的超详细详解整理
???????作者主頁(yè):https://www.zhihu.com/people/san-hao-bai-du-ren-79
一、什么是進(jìn)程?什么是線程?
1.1 進(jìn)程定義
1.2 線程定義
1.3 進(jìn)程與線程的區(qū)別
1.4 進(jìn)程狀態(tài)轉(zhuǎn)換圖
1.5? 線程比進(jìn)程具有哪些優(yōu)勢(shì)?
1.6 什么時(shí)候用多進(jìn)程?什么時(shí)候用多線程?
1.7 協(xié)程是什么?
二、進(jìn)程間的通信方式
2.1 管道通信
2.1.1 特點(diǎn)
2.1.2 函數(shù)原型
2.1.3 實(shí)例
2.1.4 代碼實(shí)現(xiàn)
2.2 FIFO(有名管道)
2.2.1 特點(diǎn)
2.2.2 函數(shù)原型
2.2.3 實(shí)例
2.3 消息隊(duì)列??
2.3.1 特點(diǎn)
2.3.2 函數(shù)原型
2.3.3 實(shí)例
2.4 信號(hào)量
2.4.1 特點(diǎn)
2.4.2 函數(shù)原型
2.4.3 實(shí)例
2.5 共享內(nèi)存
2.5.1 特點(diǎn)
2.5.2 函數(shù)原型
2.5.3 實(shí)例
2.6 套接字通信
2.6.1 命名socket
2.6.2 綁定
2.6.3 監(jiān)聽(tīng)
2.6.4 連接服務(wù)器
2.6.5 客戶端與服務(wù)端通信
2.6.6 斷開(kāi)連接
三、死鎖是什么?必要條件?如何解決?
3.1 死鎖定義
3.2 死鎖發(fā)生的必要條件
3.3 解決死鎖問(wèn)題
四、線程的同步方式
作者主頁(yè):https://www.zhihu.com/people/san-hao-bai-du-ren-79
期待關(guān)注。
一、什么是進(jìn)程?什么是線程?
1.1 進(jìn)程定義
進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,程序一旦運(yùn)行就是進(jìn)程。
進(jìn)程是操作系統(tǒng)資源分配的最小單位,且每個(gè)進(jìn)程擁有獨(dú)立的地址空間;
進(jìn)程與進(jìn)程之間資源分隔,不能共享,一個(gè)進(jìn)程無(wú)法直接訪問(wèn)另一個(gè)進(jìn)程的變量和數(shù)據(jù)結(jié)構(gòu),如果希望一個(gè)進(jìn)程去訪問(wèn)另一個(gè)進(jìn)程的資源,需要使用進(jìn)程間的通信;
(1)進(jìn)程是程序的一次執(zhí)行,該程序可以與其他程序并發(fā)執(zhí)行;
(2)進(jìn)程有運(yùn)行、阻塞、就緒三個(gè)基本狀態(tài);
(3)進(jìn)程調(diào)度算法:先來(lái)先服務(wù)調(diào)度算法、短作業(yè)優(yōu)先調(diào)度算法、非搶占式優(yōu)先級(jí)調(diào)度算法、搶占式優(yōu)先級(jí)調(diào)度算法、高響應(yīng)比優(yōu)先調(diào)度算法、時(shí)間片輪轉(zhuǎn)法調(diào)度算法;
1.2 線程定義
線程是進(jìn)程的一個(gè)實(shí)體,是進(jìn)程的一條執(zhí)行路徑;比進(jìn)程更小的獨(dú)立運(yùn)行的基本單位,線程也被稱為輕量級(jí)進(jìn)程,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程;
線程是操作系統(tǒng)資源調(diào)配的最小單位,是程序執(zhí)行的最小單位。
1.3 進(jìn)程與線程的區(qū)別
(1)一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程;
本文作者:https://www.zhihu.com/people/san-hao-bai-du-ren-79
(由于文章總是被三無(wú)號(hào)到處復(fù)制發(fā)布,選擇這種方式插入原文鏈接影響閱讀實(shí)在抱歉!)
本文原文鏈接:https://blog.csdn.net/qq_41687938/article/details/118179581
(2)同一進(jìn)程的線程共享本進(jìn)程的地址空間,而進(jìn)程之間則是獨(dú)立的地址空間。同一進(jìn)程內(nèi)的線程共享本進(jìn)程的資源,但是進(jìn)程之間的資源是獨(dú)立的。進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率;
(3)一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其他進(jìn)程產(chǎn)生影響,但是一個(gè)線程崩潰整個(gè)進(jìn)程崩潰,所以多進(jìn)程比多線程健壯;
(4)進(jìn)程切換,消耗的資源大。所以涉及到頻繁的切換,使用線程要好于進(jìn)程;
(5)兩者均可并發(fā)執(zhí)行,但線程的劃分尺度小于進(jìn)程,使得多線程程序的并發(fā)性高;
(6)每個(gè)獨(dú)立的進(jìn)程有一個(gè)程序的入口、程序出口。但是線程不能獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線程執(zhí)行控制。
(7)多線程的意義在于一個(gè)應(yīng)用程序中,有多個(gè)執(zhí)行部分可以同時(shí)執(zhí)行。但操作系統(tǒng)并沒(méi)有將多個(gè)線程看做多個(gè)獨(dú)立的應(yīng)用,來(lái)實(shí)現(xiàn)進(jìn)程的調(diào)度和管理以及資源分配;
(8)一個(gè)進(jìn)程中的所有線程共享該進(jìn)程的地址空間,但它們有各自獨(dú)立的(/私有的)棧(stack),Windows線程的缺省堆棧大小為1M。堆(heap)的分配與棧有所不同,一般是一個(gè)進(jìn)程有一個(gè)C運(yùn)行時(shí)堆,這個(gè)堆為本進(jìn)程中所有線程共享,windows進(jìn)程還有所謂進(jìn)程默認(rèn)堆,用戶也可以創(chuàng)建自己的堆。?
線程私有:線程棧,寄存器,程序寄存器
共享:堆,地址空間,全局變量,靜態(tài)變量
進(jìn)程私有:地址空間,堆,全局變量,棧,寄存器
共享:代碼段,公共數(shù)據(jù),進(jìn)程目錄,進(jìn)程ID
1.4 進(jìn)程狀態(tài)轉(zhuǎn)換圖
(1)新?tīng)顟B(tài):進(jìn)程已經(jīng)創(chuàng)建
(2)就緒態(tài):進(jìn)程做好了準(zhǔn)備,準(zhǔn)備執(zhí)行,等待分配處理機(jī)
(3)執(zhí)行態(tài):該進(jìn)程正在執(zhí)行;
(4)阻塞態(tài):等待某事件發(fā)生才能執(zhí)行,如等待I/O完成;
(5)終止?fàn)顟B(tài)
1.5? 線程比進(jìn)程具有哪些優(yōu)勢(shì)?
(1)線程在程序中是獨(dú)立的,并發(fā)的執(zhí)行流,但是,進(jìn)程中的線程之間的隔離程度要小;
(2)線程比進(jìn)程更具有更高的性能,這是由于同一個(gè)進(jìn)程中的線程都有共性:多個(gè)線程將共享同一個(gè)進(jìn)程虛擬空間;
(3)當(dāng)操作系統(tǒng)創(chuàng)建一個(gè)進(jìn)程時(shí),必須為進(jìn)程分配獨(dú)立的內(nèi)存空間,并分配大量相關(guān)資源;
1.6 什么時(shí)候用多進(jìn)程?什么時(shí)候用多線程?
(1)需要頻繁創(chuàng)建銷毀的優(yōu)先用線程;
(2)需要進(jìn)行大量計(jì)算的優(yōu)先使用線程;
(3)強(qiáng)相關(guān)的處理用線程,弱相關(guān)的處理用進(jìn)程;
(4)可能要擴(kuò)展到多機(jī)分布的用進(jìn)程,多核分布的用線程;
1.7 協(xié)程是什么?
(1)是一種比線程更加輕量級(jí)的存在。正如一個(gè)進(jìn)程可以擁有多個(gè)線程一樣,一個(gè)線程可以擁有多個(gè)協(xié)程;協(xié)程不是被操作系統(tǒng)內(nèi)核管理,而完全是由程序所控制。
(2)協(xié)程的開(kāi)銷遠(yuǎn)遠(yuǎn)小于線程;
(3)協(xié)程擁有自己寄存器上下文和棧。協(xié)程調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切換回來(lái)的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧。
(4)每個(gè)協(xié)程表示一個(gè)執(zhí)行單元,有自己的本地?cái)?shù)據(jù),與其他協(xié)程共享全局?jǐn)?shù)據(jù)和其他資源。
(5)跨平臺(tái)、跨體系架構(gòu)、無(wú)需線程上下文切換的開(kāi)銷、方便切換控制流,簡(jiǎn)化編程模型;
(6)協(xié)程又稱為微線程,協(xié)程的完成主要靠yeild關(guān)鍵字,協(xié)程執(zhí)行過(guò)程中,在子程序內(nèi)部可中斷,然后轉(zhuǎn)而執(zhí)行別的子程序,在適當(dāng)?shù)臅r(shí)候再返回來(lái)接著執(zhí)行;
(7)協(xié)程極高的執(zhí)行效率,和多線程相比,線程數(shù)量越多,協(xié)程的性能優(yōu)勢(shì)就越明顯;
(8)不需要多線程的鎖機(jī)制;
二、進(jìn)程間的通信方式
IPC這部分的參考鏈接:https://www.cnblogs.com/CheeseZH/p/5264465.html
進(jìn)程間通信(IPC,InterProcess Communication)是指在不同進(jìn)程之間傳播或交換信息。
IPC的方式通常有管道(包括無(wú)名管道和命名管道)、消息隊(duì)列、信號(hào)量、共享存儲(chǔ)、Socket、Streams等。其中 Socket和Streams支持不同主機(jī)上的兩個(gè)進(jìn)程IPC。
以Linux中的C語(yǔ)言編程為例。
2.1 管道通信
管道,通常指無(wú)名管道,是 UNIX 系統(tǒng)IPC最古老的形式。
2.1.1 特點(diǎn)
(1)它是半雙工的(即數(shù)據(jù)只能在一個(gè)方向上流動(dòng)),具有固定的讀端和寫(xiě)端。
(2)它只能用于具有親緣關(guān)系的進(jìn)程之間的通信(也是父子進(jìn)程或者兄弟進(jìn)程之間)。
(3)它可以看成是一種特殊的文件,對(duì)于它的讀寫(xiě)也可以使用普通的read、write 等函數(shù)。但是它不是普通的文件,并不屬于其他任何文件系統(tǒng),并且只存在于內(nèi)存中。
(4)Int pipe(int fd[2]);當(dāng)一個(gè)管道建立時(shí),會(huì)創(chuàng)建兩個(gè)文件描述符,要關(guān)閉管道只需將這兩個(gè)文件描述符關(guān)閉即可。
2.1.2 函數(shù)原型
#include <unistd.h> int pipe(int fd[2]); // 返回值:若成功返回0,失敗返回-1當(dāng)一個(gè)管道建立時(shí),它會(huì)創(chuàng)建兩個(gè)文件描述符:fd[0]為讀而打開(kāi),fd[1]為寫(xiě)而打開(kāi)。如下圖:
要關(guān)閉管道只需將這兩個(gè)文件描述符關(guān)閉即可。
2.1.3 實(shí)例
單個(gè)進(jìn)程中的管道幾乎沒(méi)有任何用處。所以,通常調(diào)用 pipe 的進(jìn)程接著調(diào)用 fork,這樣就創(chuàng)建了父進(jìn)程與子進(jìn)程之間的 IPC 通道。如下圖所示:
若要數(shù)據(jù)流從父進(jìn)程流向子進(jìn)程,則關(guān)閉父進(jìn)程的讀端(fd[0])與子進(jìn)程的寫(xiě)端(fd[1]);反之,則可以使數(shù)據(jù)流從子進(jìn)程流向父進(jìn)程。
2.1.4 代碼實(shí)現(xiàn)
#include<stdio.h> #include<unistd.h>int main() {int fd[2]; // 兩個(gè)文件描述符pid_t pid;char buff[20];if(pipe(fd) < 0) // 創(chuàng)建管道printf("Create Pipe Error!\n");if((pid = fork()) < 0) // 創(chuàng)建子進(jìn)程printf("Fork Error!\n");else if(pid > 0) // 父進(jìn)程{close(fd[0]); // 關(guān)閉讀端write(fd[1], "hello world\n", 12);}else{close(fd[1]); // 關(guān)閉寫(xiě)端read(fd[0], buff, 20);printf("%s", buff);}return 0; }2.2 FIFO(有名管道)
FIFO,也稱為命名管道,它是一種文件類型。
2.2.1 特點(diǎn)
(1)FIFO可以在無(wú)關(guān)的進(jìn)程之間交換數(shù)據(jù),與無(wú)名管道不同。
(2)FIFO有路徑名與之相關(guān)聯(lián),它以一種特殊設(shè)備文件形式存在于文件系統(tǒng)中。
(3)Int mkfifo(const char* pathname,mode_t mode);
本文作者:https://www.zhihu.com/people/san-hao-bai-du-ren-79
(由于文章總是被三無(wú)號(hào)到處復(fù)制發(fā)布,選擇這種方式插入原文鏈接影響閱讀實(shí)在抱歉!)
本文原文鏈接:https://blog.csdn.net/qq_41687938/article/details/118179581
2.2.2 函數(shù)原型
#include <sys/stat.h> // 返回值:成功返回0,出錯(cuò)返回-1 int mkfifo(const char *pathname, mode_t mode);其中的 mode 參數(shù)與open函數(shù)中的 mode 相同。一旦創(chuàng)建了一個(gè) FIFO,就可以用一般的文件I/O函數(shù)操作它。
當(dāng) open 一個(gè)FIFO時(shí),是否設(shè)置非阻塞標(biāo)志(O_NONBLOCK)的區(qū)別:
? ? ? (1)若沒(méi)有指定O_NONBLOCK(默認(rèn)),只讀 open 要阻塞到某個(gè)其他進(jìn)程為寫(xiě)而打開(kāi)此 FIFO。類似的,只寫(xiě) open 要阻塞到某個(gè)其他進(jìn)程為讀而打開(kāi)它。
? ? ? (2)若指定了O_NONBLOCK,則只讀 open 立即返回。而只寫(xiě) open 將出錯(cuò)返回 -1 如果沒(méi)有進(jìn)程已經(jīng)為讀而打開(kāi)該 FIFO,其errno置ENXIO。
2.2.3 實(shí)例
FIFO的通信方式類似于在進(jìn)程中使用文件來(lái)傳輸數(shù)據(jù),只不過(guò)FIFO類型文件同時(shí)具有管道的特性。在數(shù)據(jù)讀出時(shí),FIFO管道中同時(shí)清除數(shù)據(jù),并且“先進(jìn)先出”。下面的例子演示了使用 FIFO 進(jìn)行 IPC 的過(guò)程:
write_fifo.c
#include<stdio.h> #include<stdlib.h> // exit #include<fcntl.h> // O_WRONLY #include<sys/stat.h> #include<time.h> // timeint main() {int fd;int n, i;char buf[1024];time_t tp;printf("I am %d process.\n", getpid()); // 說(shuō)明進(jìn)程IDif((fd = open("fifo1", O_WRONLY)) < 0) // 以寫(xiě)打開(kāi)一個(gè)FIFO{perror("Open FIFO Failed");exit(1);}for(i=0; i<10; ++i){time(&tp); // 取系統(tǒng)當(dāng)前時(shí)間n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp));printf("Send message: %s", buf); // 打印if(write(fd, buf, n+1) < 0) // 寫(xiě)入到FIFO中{perror("Write FIFO Failed");close(fd);exit(1);}sleep(1); // 休眠1秒}close(fd); // 關(guān)閉FIFO文件return 0; }read_fifo.c
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<fcntl.h> #include<sys/stat.h>int main() {int fd;int len;char buf[1024];if(mkfifo("fifo1", 0666) < 0 && errno!=EEXIST) // 創(chuàng)建FIFO管道perror("Create FIFO Failed");if((fd = open("fifo1", O_RDONLY)) < 0) // 以讀打開(kāi)FIFO{perror("Open FIFO Failed");exit(1);}while((len = read(fd, buf, 1024)) > 0) // 讀取FIFO管道printf("Read message: %s", buf);close(fd); // 關(guān)閉FIFO文件return 0; }在兩個(gè)終端里用 gcc 分別編譯運(yùn)行上面兩個(gè)文件,可以看到輸出結(jié)果如下:
[cheesezh@localhost]$ ./write_fifo I am 5954 process. Send message: Process 5954's time is Mon Apr 20 12:37:28 2015 Send message: Process 5954's time is Mon Apr 20 12:37:29 2015 Send message: Process 5954's time is Mon Apr 20 12:37:30 2015 Send message: Process 5954's time is Mon Apr 20 12:37:31 2015 Send message: Process 5954's time is Mon Apr 20 12:37:32 2015 Send message: Process 5954's time is Mon Apr 20 12:37:33 2015 Send message: Process 5954's time is Mon Apr 20 12:37:34 2015 Send message: Process 5954's time is Mon Apr 20 12:37:35 2015 Send message: Process 5954's time is Mon Apr 20 12:37:36 2015 Send message: Process 5954's time is Mon Apr 20 12:37:37 2015 [cheesezh@localhost]$ ./read_fifo Read message: Process 5954's time is Mon Apr 20 12:37:28 2015 Read message: Process 5954's time is Mon Apr 20 12:37:29 2015 Read message: Process 5954's time is Mon Apr 20 12:37:30 2015 Read message: Process 5954's time is Mon Apr 20 12:37:31 2015 Read message: Process 5954's time is Mon Apr 20 12:37:32 2015 Read message: Process 5954's time is Mon Apr 20 12:37:33 2015 Read message: Process 5954's time is Mon Apr 20 12:37:34 2015 Read message: Process 5954's time is Mon Apr 20 12:37:35 2015 Read message: Process 5954's time is Mon Apr 20 12:37:36 2015 Read message: Process 5954's time is Mon Apr 20 12:37:37 2015上述例子可以擴(kuò)展成 客戶進(jìn)程—服務(wù)器進(jìn)程 通信的實(shí)例,write_fifo的作用類似于客戶端,可以打開(kāi)多個(gè)客戶端向一個(gè)服務(wù)器發(fā)送請(qǐng)求信息,read_fifo類似于服務(wù)器,它適時(shí)監(jiān)控著FIFO的讀端,當(dāng)有數(shù)據(jù)時(shí),讀出并進(jìn)行處理,但是有一個(gè)關(guān)鍵的問(wèn)題是,每一個(gè)客戶端必須預(yù)先知道服務(wù)器提供的FIFO接口,下圖顯示了這種安排:
2.3 消息隊(duì)列??
消息隊(duì)列,是消息的鏈接表,存放在內(nèi)核中。一個(gè)消息隊(duì)列由一個(gè)標(biāo)識(shí)符(即隊(duì)列ID)來(lái)標(biāo)識(shí)。
2.3.1 特點(diǎn)
(1)消息隊(duì)列是面向記錄的,其中的消息具有特定的格式以及特定的優(yōu)先級(jí)。
(2)消息隊(duì)列獨(dú)立于發(fā)送與接收進(jìn)程。進(jìn)程終止時(shí),消息隊(duì)列及其內(nèi)容并不會(huì)被刪除。
(3)消息隊(duì)列可以實(shí)現(xiàn)消息的隨機(jī)查詢,消息不一定要以先進(jìn)先出的次序讀取,也可以按消息的類型讀取。
2.3.2 函數(shù)原型
#include <sys/msg.h> // 創(chuàng)建或打開(kāi)消息隊(duì)列:成功返回隊(duì)列ID,失敗返回-1 int msgget(key_t key, int flag); // 添加消息:成功返回0,失敗返回-1 int msgsnd(int msqid, const void *ptr, size_t size, int flag); // 讀取消息:成功返回消息數(shù)據(jù)的長(zhǎng)度,失敗返回-1 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag); // 控制消息隊(duì)列:成功返回0,失敗返回-1 int msgctl(int msqid, int cmd, struct msqid_ds *buf);在以下兩種情況下,msgget將創(chuàng)建一個(gè)新的消息隊(duì)列:
? ? ? ?(1)如果沒(méi)有與鍵值key相對(duì)應(yīng)的消息隊(duì)列,并且flag中包含了IPC_CREAT標(biāo)志位。
? ? ? ?(2)key參數(shù)為IPC_PRIVATE。
函數(shù)msgrcv在讀取消息隊(duì)列時(shí),type參數(shù)有下面幾種情況:
? ? ? (1)type == 0,返回隊(duì)列中的第一個(gè)消息;
? ? ? (2)type > 0,返回隊(duì)列中消息類型為 type 的第一個(gè)消息;
? ? ? (3)type < 0,返回隊(duì)列中消息類型值小于或等于 type 絕對(duì)值的消息,如果有多個(gè),則取類型值最小的消息。
可以看出,type值非 0 時(shí)用于以非先進(jìn)先出次序讀消息。也可以把 type 看做優(yōu)先級(jí)的權(quán)值。(其他的參數(shù)解釋,請(qǐng)自行Google之)
2.3.3 實(shí)例
下面寫(xiě)了一個(gè)簡(jiǎn)單的使用消息隊(duì)列進(jìn)行IPC的例子,服務(wù)端程序一直在等待特定類型的消息,當(dāng)收到該類型的消息以后,發(fā)送另一種特定類型的消息作為反饋,客戶端讀取該反饋并打印出來(lái)。
msg_server.c
msg_client.c
#include <stdio.h> #include <stdlib.h> #include <sys/msg.h>// 用于創(chuàng)建一個(gè)唯一的key #define MSG_FILE "/etc/passwd"// 消息結(jié)構(gòu) struct msg_form {long mtype;char mtext[256]; };int main() {int msqid;key_t key;struct msg_form msg;// 獲取key值if ((key = ftok(MSG_FILE, 'z')) < 0){perror("ftok error");exit(1);}// 打印key值printf("Message Queue - Client key is: %d.\n", key);// 打開(kāi)消息隊(duì)列if ((msqid = msgget(key, IPC_CREAT|0777)) == -1){perror("msgget error");exit(1);}// 打印消息隊(duì)列ID及進(jìn)程IDprintf("My msqid is: %d.\n", msqid);printf("My pid is: %d.\n", getpid());// 添加消息,類型為888msg.mtype = 888;sprintf(msg.mtext, "hello, I'm client %d", getpid());msgsnd(msqid, &msg, sizeof(msg.mtext), 0);// 讀取類型為777的消息msgrcv(msqid, &msg, 256, 999, 0);printf("Client: receive msg.mtext is: %s.\n", msg.mtext);printf("Client: receive msg.mtype is: %d.\n", msg.mtype);return 0; }2.4 信號(hào)量
信號(hào)量(semaphore)與已經(jīng)介紹過(guò)的 IPC 結(jié)構(gòu)不同,它是一個(gè)計(jì)數(shù)器。信號(hào)量用于實(shí)現(xiàn)進(jìn)程間的互斥與同步,而不是用于存儲(chǔ)進(jìn)程間通信數(shù)據(jù)。
2.4.1 特點(diǎn)
(1)信號(hào)量用于進(jìn)程間同步,若要在進(jìn)程間傳遞數(shù)據(jù)需要結(jié)合共享內(nèi)存。
(2)信號(hào)量基于操作系統(tǒng)的 PV 操作,程序?qū)π盘?hào)量的操作都是原子操作。
(3)每次對(duì)信號(hào)量的 PV 操作不僅限于對(duì)信號(hào)量值加 1 或減 1,而且可以加減任意正整數(shù)。
(4)支持信號(hào)量組。
2.4.2 函數(shù)原型
最簡(jiǎn)單的信號(hào)量是只能取 0 和 1 的變量,這也是信號(hào)量最常見(jiàn)的一種形式,叫做二值信號(hào)量(Binary Semaphore)。而可以取多個(gè)正整數(shù)的信號(hào)量被稱為通用信號(hào)量。
Linux 下的信號(hào)量函數(shù)都是在通用的信號(hào)量數(shù)組上進(jìn)行操作,而不是在一個(gè)單一的二值信號(hào)量上進(jìn)行操作。
當(dāng)semget創(chuàng)建新的信號(hào)量集合時(shí),必須指定集合中信號(hào)量的個(gè)數(shù)(即num_sems),通常為1; 如果是引用一個(gè)現(xiàn)有的集合,則將num_sems指定為 0 。
在semop函數(shù)中,sembuf結(jié)構(gòu)的定義如下:
其中 sem_op 是一次操作中的信號(hào)量的改變量:
-
若sem_op > 0,表示進(jìn)程釋放相應(yīng)的資源數(shù),將 sem_op 的值加到信號(hào)量的值上。如果有進(jìn)程正在休眠等待此信號(hào)量,則換行它們。
-
若sem_op < 0,請(qǐng)求 sem_op 的絕對(duì)值的資源。
- 如果相應(yīng)的資源數(shù)可以滿足請(qǐng)求,則將該信號(hào)量的值減去sem_op的絕對(duì)值,函數(shù)成功返回。
- 當(dāng)相應(yīng)的資源數(shù)不能滿足請(qǐng)求時(shí),這個(gè)操作與sem_flg有關(guān)。
- sem_flg 指定IPC_NOWAIT,則semop函數(shù)出錯(cuò)返回EAGAIN。
- sem_flg 沒(méi)有指定IPC_NOWAIT,則將該信號(hào)量的semncnt值加1,然后進(jìn)程掛起直到下述情況發(fā)生:
- 當(dāng)相應(yīng)的資源數(shù)可以滿足請(qǐng)求,此信號(hào)量的semncnt值減1,該信號(hào)量的值減去sem_op的絕對(duì)值。成功返回;
- 此信號(hào)量被刪除,函數(shù)smeop出錯(cuò)返回EIDRM;
- 進(jìn)程捕捉到信號(hào),并從信號(hào)處理函數(shù)返回,此情況下將此信號(hào)量的semncnt值減1,函數(shù)semop出錯(cuò)返回EINTR
若sem_op == 0,進(jìn)程阻塞直到信號(hào)量的相應(yīng)值為0:
- 當(dāng)信號(hào)量已經(jīng)為0,函數(shù)立即返回。
- 如果信號(hào)量的值不為0,則依據(jù)sem_flg決定函數(shù)動(dòng)作:
- sem_flg指定IPC_NOWAIT,則出錯(cuò)返回EAGAIN。
- sem_flg沒(méi)有指定IPC_NOWAIT,則將該信號(hào)量的semncnt值加1,然后進(jìn)程掛起直到下述情況發(fā)生:
- 信號(hào)量值為0,將信號(hào)量的semzcnt的值減1,函數(shù)semop成功返回;
- 此信號(hào)量被刪除,函數(shù)smeop出錯(cuò)返回EIDRM;
- 進(jìn)程捕捉到信號(hào),并從信號(hào)處理函數(shù)返回,在此情況將此信號(hào)量的semncnt值減1,函數(shù)semop出錯(cuò)返回EINTR
在semctl函數(shù)中的命令有多種,這里就說(shuō)兩個(gè)常用的:
- SETVAL:用于初始化信號(hào)量為一個(gè)已知的值。所需要的值作為聯(lián)合semun的val成員來(lái)傳遞。在信號(hào)量第一次使用之前需要設(shè)置信號(hào)量。
- IPC_RMID:刪除一個(gè)信號(hào)量集合。如果不刪除信號(hào)量,它將繼續(xù)在系統(tǒng)中存在,即使程序已經(jīng)退出,它可能在你下次運(yùn)行此程序時(shí)引發(fā)問(wèn)題,而且信號(hào)量是一種有限的資源。
2.4.3 實(shí)例
#include<stdio.h> #include<stdlib.h> #include<sys/sem.h>// 聯(lián)合體,用于semctl初始化 union semun {int val; /*for SETVAL*/struct semid_ds *buf;unsigned short *array; };// 初始化信號(hào)量 int init_sem(int sem_id, int value) {union semun tmp;tmp.val = value;if(semctl(sem_id, 0, SETVAL, tmp) == -1){perror("Init Semaphore Error");return -1;}return 0; }// P操作: // 若信號(hào)量值為1,獲取資源并將信號(hào)量值-1 // 若信號(hào)量值為0,進(jìn)程掛起等待 int sem_p(int sem_id) {struct sembuf sbuf;sbuf.sem_num = 0; /*序號(hào)*/sbuf.sem_op = -1; /*P操作*/sbuf.sem_flg = SEM_UNDO;if(semop(sem_id, &sbuf, 1) == -1){perror("P operation Error");return -1;}return 0; }// V操作: // 釋放資源并將信號(hào)量值+1 // 如果有進(jìn)程正在掛起等待,則喚醒它們 int sem_v(int sem_id) {struct sembuf sbuf;sbuf.sem_num = 0; /*序號(hào)*/sbuf.sem_op = 1; /*V操作*/sbuf.sem_flg = SEM_UNDO;if(semop(sem_id, &sbuf, 1) == -1){perror("V operation Error");return -1;}return 0; }// 刪除信號(hào)量集 int del_sem(int sem_id) {union semun tmp;if(semctl(sem_id, 0, IPC_RMID, tmp) == -1){perror("Delete Semaphore Error");return -1;}return 0; }int main() {int sem_id; // 信號(hào)量集IDkey_t key;pid_t pid;// 獲取key值if((key = ftok(".", 'z')) < 0){perror("ftok error");exit(1);}// 創(chuàng)建信號(hào)量集,其中只有一個(gè)信號(hào)量if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1){perror("semget error");exit(1);}// 初始化:初值設(shè)為0資源被占用init_sem(sem_id, 0);if((pid = fork()) == -1)perror("Fork Error");else if(pid == 0) /*子進(jìn)程*/{sleep(2);printf("Process child: pid=%d\n", getpid());sem_v(sem_id); /*釋放資源*/}else /*父進(jìn)程*/{sem_p(sem_id); /*等待資源*/printf("Process father: pid=%d\n", getpid());sem_v(sem_id); /*釋放資源*/del_sem(sem_id); /*刪除信號(hào)量集*/}return 0; }上面的例子如果不加信號(hào)量,則父進(jìn)程會(huì)先執(zhí)行完畢。這里加了信號(hào)量讓父進(jìn)程等待子進(jìn)程執(zhí)行完以后再執(zhí)行。
2.5 共享內(nèi)存
共享內(nèi)存(Shared Memory),指兩個(gè)或多個(gè)進(jìn)程共享一個(gè)給定的存儲(chǔ)區(qū)。
2.5.1 特點(diǎn)
(1)共享內(nèi)存是最快的一種 IPC,因?yàn)檫M(jìn)程是直接對(duì)內(nèi)存進(jìn)行存取。
(2)因?yàn)槎鄠€(gè)進(jìn)程可以同時(shí)操作,所以需要進(jìn)行同步。
(3)信號(hào)量+共享內(nèi)存通常結(jié)合在一起使用,信號(hào)量用來(lái)同步對(duì)共享內(nèi)存的訪問(wèn)。
2.5.2 函數(shù)原型
#include <sys/shm.h> // 創(chuàng)建或獲取一個(gè)共享內(nèi)存:成功返回共享內(nèi)存ID,失敗返回-1 int shmget(key_t key, size_t size, int flag); // 連接共享內(nèi)存到當(dāng)前進(jìn)程的地址空間:成功返回指向共享內(nèi)存的指針,失敗返回-1 void *shmat(int shm_id, const void *addr, int flag); // 斷開(kāi)與共享內(nèi)存的連接:成功返回0,失敗返回-1 int shmdt(void *addr); // 控制共享內(nèi)存的相關(guān)信息:成功返回0,失敗返回-1 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);當(dāng)用shmget函數(shù)創(chuàng)建一段共享內(nèi)存時(shí),必須指定其 size;而如果引用一個(gè)已存在的共享內(nèi)存,則將 size 指定為0 。
當(dāng)一段共享內(nèi)存被創(chuàng)建以后,它并不能被任何進(jìn)程訪問(wèn)。必須使用shmat函數(shù)連接該共享內(nèi)存到當(dāng)前進(jìn)程的地址空間,連接成功后把共享內(nèi)存區(qū)對(duì)象映射到調(diào)用進(jìn)程的地址空間,隨后可像本地空間一樣訪問(wèn)。
shmdt函數(shù)是用來(lái)斷開(kāi)shmat建立的連接的。注意,這并不是從系統(tǒng)中刪除該共享內(nèi)存,只是當(dāng)前進(jìn)程不能再訪問(wèn)該共享內(nèi)存而已。
shmctl函數(shù)可以對(duì)共享內(nèi)存執(zhí)行多種操作,根據(jù)參數(shù) cmd 執(zhí)行相應(yīng)的操作。常用的是IPC_RMID(從系統(tǒng)中刪除該共享內(nèi)存)。
2.5.3 實(shí)例
下面這個(gè)例子,使用了【共享內(nèi)存+信號(hào)量+消息隊(duì)列】的組合來(lái)實(shí)現(xiàn)服務(wù)器進(jìn)程與客戶進(jìn)程間的通信。
(1)共享內(nèi)存用來(lái)傳遞數(shù)據(jù);
(2)信號(hào)量用來(lái)同步;
(3)消息隊(duì)列用來(lái) 在客戶端修改了共享內(nèi)存后 通知服務(wù)器讀取。
server.c
client.c
#include<stdio.h> #include<stdlib.h> #include<sys/shm.h> // shared memory #include<sys/sem.h> // semaphore #include<sys/msg.h> // message queue #include<string.h> // memcpy// 消息隊(duì)列結(jié)構(gòu) struct msg_form {long mtype;char mtext; };// 聯(lián)合體,用于semctl初始化 union semun {int val; /*for SETVAL*/struct semid_ds *buf;unsigned short *array; };// P操作: // 若信號(hào)量值為1,獲取資源并將信號(hào)量值-1 // 若信號(hào)量值為0,進(jìn)程掛起等待 int sem_p(int sem_id) {struct sembuf sbuf;sbuf.sem_num = 0; /*序號(hào)*/sbuf.sem_op = -1; /*P操作*/sbuf.sem_flg = SEM_UNDO;if(semop(sem_id, &sbuf, 1) == -1){perror("P operation Error");return -1;}return 0; }// V操作: // 釋放資源并將信號(hào)量值+1 // 如果有進(jìn)程正在掛起等待,則喚醒它們 int sem_v(int sem_id) {struct sembuf sbuf;sbuf.sem_num = 0; /*序號(hào)*/sbuf.sem_op = 1; /*V操作*/sbuf.sem_flg = SEM_UNDO;if(semop(sem_id, &sbuf, 1) == -1){perror("V operation Error");return -1;}return 0; }int main() {key_t key;int shmid, semid, msqid;char *shm;struct msg_form msg;int flag = 1; /*while循環(huán)條件*/// 獲取key值if((key = ftok(".", 'z')) < 0){perror("ftok error");exit(1);}// 獲取共享內(nèi)存if((shmid = shmget(key, 1024, 0)) == -1){perror("shmget error");exit(1);}// 連接共享內(nèi)存shm = (char*)shmat(shmid, 0, 0);if((int)shm == -1){perror("Attach Shared Memory Error");exit(1);}// 創(chuàng)建消息隊(duì)列if ((msqid = msgget(key, 0)) == -1){perror("msgget error");exit(1);}// 獲取信號(hào)量if((semid = semget(key, 0, 0)) == -1){perror("semget error");exit(1);}// 寫(xiě)數(shù)據(jù)printf("***************************************\n");printf("* IPC *\n");printf("* Input r to send data to server. *\n");printf("* Input q to quit. *\n");printf("***************************************\n");while(flag){char c;printf("Please input command: ");scanf("%c", &c);switch(c){case 'r':printf("Data to send: ");sem_p(semid); /*訪問(wèn)資源*/scanf("%s", shm);sem_v(semid); /*釋放資源*//*清空標(biāo)準(zhǔn)輸入緩沖區(qū)*/while((c=getchar())!='\n' && c!=EOF);msg.mtype = 888;msg.mtext = 'r'; /*發(fā)送消息通知服務(wù)器讀數(shù)據(jù)*/msgsnd(msqid, &msg, sizeof(msg.mtext), 0);break;case 'q':msg.mtype = 888;msg.mtext = 'q';msgsnd(msqid, &msg, sizeof(msg.mtext), 0);flag = 0;break;default:printf("Wrong input!\n");/*清空標(biāo)準(zhǔn)輸入緩沖區(qū)*/while((c=getchar())!='\n' && c!=EOF);}}// 斷開(kāi)連接shmdt(shm);return 0; }注意:當(dāng)scanf()輸入字符或字符串時(shí),緩沖區(qū)中遺留下了\n,所以每次輸入操作后都需要清空標(biāo)準(zhǔn)輸入的緩沖區(qū)。但是由于 gcc 編譯器不支持fflush(stdin)(它只是標(biāo)準(zhǔn)C的擴(kuò)展),所以我們使用了替代方案:
while((c=getchar())!='\n' && c!=EOF);2.6 套接字通信
套接字( socket ) : 套接口也是一種進(jìn)程間通信機(jī)制,與其他通信機(jī)制不同的是,它可用于不同機(jī)器間的進(jìn)程通信,一般說(shuō)的進(jìn)程間五大通信方式不包括這個(gè)。
通信過(guò)程如下:
2.6.1 命名socket
SOCK_STREAM 式本地套接字的通信雙方均需要具有本地地址,其中服務(wù)器端的本地地址需要明確指定,指定方法是使用 struct sockaddr_un 類型的變量。
2.6.2 綁定
SOCK_STREAM 式本地套接字的通信雙方均需要具有本地地址,其中服務(wù)器端的本地地址需要明確指定,指定方法是使用 struct sockaddr_un 類型的變量,將相應(yīng)字段賦值,再將其綁定在創(chuàng)建的服務(wù)器套接字上,綁定要使用 bind 系統(tǒng)調(diào)用,其原形如下:
int bind(int socket, const struct sockaddr *address, size_t address_len);其中 socket表示服務(wù)器端的套接字描述符,address 表示需要綁定的本地地址,是一個(gè) struct sockaddr_un 類型的變量,address_len 表示該本地地址的字節(jié)長(zhǎng)度。
2.6.3 監(jiān)聽(tīng)
服務(wù)器端套接字創(chuàng)建完畢并賦予本地地址值(名稱,本例中為Server Socket)后,需要進(jìn)行監(jiān)聽(tīng),等待客戶端連接并處理請(qǐng)求,監(jiān)聽(tīng)使用 listen 系統(tǒng)調(diào)用,接受客戶端連接使用accept系統(tǒng)調(diào)用,它們的原形如下:
int listen(int socket, int backlog); int accept(int socket, struct sockaddr *address, size_t *address_len);其中 socket 表示服務(wù)器端的套接字描述符;backlog 表示排隊(duì)連接隊(duì)列的長(zhǎng)度(若有多個(gè)客戶端同時(shí)連接,則需要進(jìn)行排隊(duì));address 表示當(dāng)前連接客戶端的本地地址,該參數(shù)為輸出參數(shù),是客戶端傳遞過(guò)來(lái)的關(guān)于自身的信息;address_len 表示當(dāng)前連接客戶端本地地址的字節(jié)長(zhǎng)度,這個(gè)參數(shù)既是輸入?yún)?shù),又是輸出參數(shù)。
2.6.4 連接服務(wù)器
客戶端套接字創(chuàng)建完畢并賦予本地地址值后,需要連接到服務(wù)器端進(jìn)行通信,讓服務(wù)器端為其提供處理服務(wù)。
對(duì)于SOCK_STREAM類型的流式套接字,需要客戶端與服務(wù)器之間進(jìn)行連接方可使用。連接要使用 connect 系統(tǒng)調(diào)用,其原形為
其中socket為客戶端的套接字描述符,address表示當(dāng)前客戶端的本地地址,是一個(gè) struct sockaddr_un 類型的變量,address_len 表示本地地址的字節(jié)長(zhǎng)度。實(shí)現(xiàn)連接的代碼如下:
connect(client_sockfd, (struct sockaddr*)&client_address, sizeof(client_address));2.6.5 客戶端與服務(wù)端通信
無(wú)論客戶端還是服務(wù)器,都要和對(duì)方進(jìn)行數(shù)據(jù)上的交互,這種交互也正是我們進(jìn)程通信的主題。一個(gè)進(jìn)程扮演客戶端的角色,另外一個(gè)進(jìn)程扮演服務(wù)器的角色,兩個(gè)進(jìn)程之間相互發(fā)送接收數(shù)據(jù),這就是基于本地套接字的進(jìn)程通信。發(fā)送和接收數(shù)據(jù)要使用 write 和 read 系統(tǒng)調(diào)用,它們的原形為:
int read(int socket, char *buffer, size_t len); int write(int socket, char *buffer, size_t len);其中 socket 為套接字描述符;len 為需要發(fā)送或需要接收的數(shù)據(jù)長(zhǎng)度;
對(duì)于 read 系統(tǒng)調(diào)用,buffer 是用來(lái)存放接收數(shù)據(jù)的緩沖區(qū),即接收來(lái)的數(shù)據(jù)存入其中,是一個(gè)輸出參數(shù);
對(duì)于 write 系統(tǒng)調(diào)用,buffer 用來(lái)存放需要發(fā)送出去的數(shù)據(jù),即 buffer 內(nèi)的數(shù)據(jù)被發(fā)送出去,是一個(gè)輸入?yún)?shù);返回值為已經(jīng)發(fā)送或接收的數(shù)據(jù)長(zhǎng)度。
2.6.6 斷開(kāi)連接
交互完成后,需要將連接斷開(kāi)以節(jié)省資源,使用close系統(tǒng)調(diào)用,其原形為:
int close(int socket);三、死鎖是什么?必要條件?如何解決?
3.1 死鎖定義
所謂死鎖,是指多個(gè)進(jìn)程循環(huán)等待它方占有的資源而無(wú)限期地僵持下去的局面。很顯然,如果沒(méi)有外力的作用,那麼死鎖涉及到的各個(gè)進(jìn)程都將永遠(yuǎn)處于封鎖狀態(tài)。當(dāng)兩個(gè)或兩個(gè)以上的進(jìn)程同時(shí)對(duì)多個(gè)互斥資源提出使用要求時(shí),有可能導(dǎo)致死鎖。
本文作者:https://www.zhihu.com/people/san-hao-bai-du-ren-79
(由于文章總是被三無(wú)號(hào)到處復(fù)制發(fā)布,選擇這種方式插入原文鏈接影響閱讀實(shí)在抱歉!)
本文原文鏈接:https://blog.csdn.net/qq_41687938/article/details/118179581
3.2 死鎖發(fā)生的必要條件
3.3 解決死鎖問(wèn)題
死鎖的預(yù)防是保證系統(tǒng)不進(jìn)入死鎖狀態(tài)的一種策略。它的基本思想是要求進(jìn)程申請(qǐng)資源時(shí)遵循某種協(xié)議,從而打破產(chǎn)生死鎖的四個(gè)必要條件中的一個(gè)或幾個(gè),保證系統(tǒng)不會(huì)進(jìn)入死鎖狀態(tài)。
<1>打破互斥條件。即允許進(jìn)程同時(shí)訪問(wèn)某些資源。但是,有的資源是不允許被同時(shí)訪問(wèn)的,像打印機(jī)等等,這是由資源本身的屬性所決定的。所以,這種辦法并無(wú)實(shí)用價(jià)值。
<2>打破不可搶占條件。即允許進(jìn)程強(qiáng)行從占有者那里奪取某些資源。就是說(shuō),當(dāng)一個(gè)進(jìn)程已占有了某些資源,它又申請(qǐng)新的資源,但不能立即被滿足時(shí),它必須釋放所占有的全部資源,以后再重新申請(qǐng)。它所釋放的資源可以分配給其它進(jìn)程。這就相當(dāng)于該進(jìn)程占有的資源被隱蔽地強(qiáng)占了。這種預(yù)防死鎖的方法實(shí)現(xiàn)起來(lái)困難,會(huì)降低系統(tǒng)性能。????
<3>打破占有且申請(qǐng)條件。可以實(shí)行資源預(yù)先分配策略。即進(jìn)程在運(yùn)行前一次性地向系統(tǒng)申請(qǐng)它所需要的全部資源。如果某個(gè)進(jìn)程所需的全部資源得不到滿足,則不分配任何資源,此進(jìn)程暫不運(yùn)行。只有當(dāng)系統(tǒng)能夠滿足當(dāng)前進(jìn)程的全部資源需求時(shí),才一次性地將所申請(qǐng)的資源全部分配給該進(jìn)程。由于運(yùn)行的進(jìn)程已占有了它所需的全部資源,所以不會(huì)發(fā)生占有資源又申請(qǐng)資源的現(xiàn)象,因此不會(huì)發(fā)生死鎖。
<4>打破循環(huán)等待條件,實(shí)行資源有序分配策略。采用這種策略,即把資源事先分類編號(hào),按號(hào)分配,使進(jìn)程在申請(qǐng),占用資源時(shí)不會(huì)形成環(huán)路。所有進(jìn)程對(duì)資源的請(qǐng)求必須嚴(yán)格按資源序號(hào)遞增的順序提出。進(jìn)程占用了小號(hào)資源,才能申請(qǐng)大號(hào)資源,就不會(huì)產(chǎn)生環(huán)路,從而預(yù)防了死鎖
死鎖避免:銀行家算法
四、線程的同步方式
總結(jié)
以上是生活随笔為你收集整理的关于操作系统中进程、线程、死锁、同步、进程间通信(IPC)的超详细详解整理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 程序员情人节送这些!
- 下一篇: 电力系统matlab实验报告,基于mat