中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

小公司如何做網(wǎng)站隔離直通車推廣計劃方案

小公司如何做網(wǎng)站隔離,直通車推廣計劃方案,門戶網(wǎng)站簡稱,wordpress 購物 主題目錄 一、進(jìn)程間通信概念 二、進(jìn)程間通信的發(fā)展 三、進(jìn)程間通信的分類 四、管道 4.1 什么是管道 4.2 匿名管道 4.2 基于匿名管道設(shè)計進(jìn)程池 4.3 命名管道 4.4 用命名管道實現(xiàn)server&client通信 五、system V共享內(nèi)存 5.1 system V共享內(nèi)存的引入 5.2 共享內(nèi)存的…

目錄

一、進(jìn)程間通信概念

二、進(jìn)程間通信的發(fā)展

三、進(jìn)程間通信的分類

四、管道

4.1 什么是管道

4.2 匿名管道

4.2?基于匿名管道設(shè)計進(jìn)程池

4.3?命名管道

4.4 用命名管道實現(xiàn)server&client通信

五、system V共享內(nèi)存

5.1 system V共享內(nèi)存的引入

5.2?共享內(nèi)存的原理

5.3 共享內(nèi)存函數(shù)

5.4 使用共享內(nèi)存的步驟

5.5 基于共享內(nèi)存的進(jìn)程間通信示例

5.6 共享內(nèi)存的特點

5.7 共享內(nèi)存數(shù)據(jù)結(jié)構(gòu)

六、簡述system V消息隊列和system V信號量

6.1 system V消息隊列

6.2 system V信號量

七、回顧共享內(nèi)存數(shù)據(jù)結(jié)構(gòu)


一、進(jìn)程間通信概念

進(jìn)程雖然具有獨立性,但是進(jìn)程和進(jìn)程之間是可能進(jìn)行協(xié)作的。協(xié)作的前提是進(jìn)程之間可以傳遞信息,即需要進(jìn)程間通信。

Linux中進(jìn)程間通信(Inter-Process Communication,IPC)是指為了協(xié)調(diào)進(jìn)程之間的行為,不同進(jìn)程之間進(jìn)行信息交換和資源共享的機(jī)制。

進(jìn)程間通信的目的包括:

  • 數(shù)據(jù)傳輸:允許一個進(jìn)程將數(shù)據(jù)發(fā)送給另一個進(jìn)程。
  • 資源共享:允許多個進(jìn)程訪問相同的資源,如文件、內(nèi)存區(qū)域等。
  • 通知事件:一個進(jìn)程可以向另一個或一組進(jìn)程發(fā)送消息,通知它(它們)某個事件的發(fā)生,如進(jìn)程終止時通知父進(jìn)程。
  • 進(jìn)程控制:允許一個進(jìn)程完全控制另一個進(jìn)程的執(zhí)行。例如調(diào)試進(jìn)程需要攔截另一個進(jìn)程的所有陷入和異常,并能夠及時知道它的狀態(tài)改變。
    (“陷入”通常指的是程序的執(zhí)行被操作系統(tǒng)或其他進(jìn)程強(qiáng)制暫停,以便處理系統(tǒng)調(diào)用或硬件中斷。)

進(jìn)程間通信如何實現(xiàn)呢?之前講到進(jìn)程具有獨立性,那么A進(jìn)程的數(shù)據(jù)要交給B進(jìn)程,不能直接把A進(jìn)程的數(shù)據(jù)直接給B進(jìn)程,因為A進(jìn)程訪問B進(jìn)程的內(nèi)存區(qū)域把數(shù)據(jù)拷貝進(jìn)去,或者B進(jìn)程訪問A進(jìn)程的內(nèi)存區(qū)域把數(shù)據(jù)拷貝出來,這兩種都不行,會破壞進(jìn)程的獨立性。所以就需要進(jìn)程通信時的中間媒介。這樣既能保持進(jìn)程的獨立性也能實現(xiàn)進(jìn)程間通信。因此進(jìn)程間通信的本質(zhì)就是讓不同的進(jìn)程看到OS中的同一份資源,從而實現(xiàn)數(shù)據(jù)的傳遞和共享。(該資源不能由A/B進(jìn)程提供,但是能由A/B進(jìn)程申請)

二、進(jìn)程間通信的發(fā)展

進(jìn)程間通信的發(fā)展經(jīng)歷了以下幾個階段:

  • 管道:包括匿名管道(pipe)和命名管道(FIFO)。匿名管道只能用于具有親緣關(guān)系的進(jìn)程間通信,而命名管道可以用于不具有親緣關(guān)系的進(jìn)程間通信。
  • System V進(jìn)程間通信:包括System V消息隊列、System V共享內(nèi)存、System V信號量等。這些機(jī)制提供了更為復(fù)雜的IPC功能,支持多種形式的通信和同步。
  • POSIX進(jìn)程間通信:包括POSIX消息隊列、POSIX共享內(nèi)存、POSIX信號量、互斥量、條件變量、讀寫鎖等。POSIX IPC提供了與System V IPC類似的功能,但具有更好的可移植性。

三、進(jìn)程間通信的分類

Linux中的進(jìn)程間通信可以分為以下幾類:

  • 管道:
    匿名管道:用于具有親緣關(guān)系的進(jìn)程間通信。
    命名管道:用于不具有親緣關(guān)系的進(jìn)程間通信。
  • System V IPC:
    消息隊列:用于進(jìn)程間傳遞消息。
    共享內(nèi)存:用于進(jìn)程間共享內(nèi)存區(qū)域。
    信號量:用于進(jìn)程間同步和互斥。
  • POSIX IPC:
    消息隊列:與System V消息隊列類似。
    共享內(nèi)存:與System V共享內(nèi)存類似。
    信號量:與System V信號量類似。
    互斥量:用于進(jìn)程間同步。
    條件變量:用于進(jìn)程間同步。
    讀寫鎖:用于進(jìn)程間同步。

四、管道

4.1 什么是管道

管道(Pipe)是Unix系統(tǒng)中用于進(jìn)程間通信的一種機(jī)制,它允許一個進(jìn)程的輸出直接作為另一個進(jìn)程的輸入。管道是一種單向的通信通道,數(shù)據(jù)只能從管道的一端流向另一端。

回顧文件系統(tǒng):
【Linux】文件描述符和重定向-CSDN博客? ??【Linux】文件系統(tǒng)和軟硬鏈接-CSDN博客

如何做到讓不同的進(jìn)程看到了同一個管道文件?

進(jìn)程是具有獨立性的,一個進(jìn)程的數(shù)據(jù),另一個數(shù)據(jù)是無法直接拿到的。就連父子進(jìn)程也會因為修改數(shù)據(jù)而觸發(fā)寫時拷貝。所以不能通過數(shù)據(jù)傳遞(這里指命名的變量),而是使用其他方式。

可執(zhí)行程序加載到內(nèi)存時,要創(chuàng)建task_struct,其中包含指向files_struct結(jié)構(gòu)體的指針,在該結(jié)構(gòu)體中有一個fd_array指針數(shù)組。當(dāng)加載一個文件到內(nèi)存時,會創(chuàng)建struct file ,結(jié)構(gòu)體中會包含文件的inode、方法集、文件緩沖區(qū)。并將自己鏈入到fd_array中。

在上層用戶使用某個方法向磁盤寫入數(shù)據(jù)時,會打開文件、得到fd、找到struct inode、文件緩沖區(qū)、通過方法集的方法將數(shù)據(jù)刷新到磁盤。

創(chuàng)建子進(jìn)程,父進(jìn)程的task_struct 、flies_struct 都要給子進(jìn)程拷貝一份(flies_struct屬于進(jìn)程部分的數(shù)據(jù)),flies_struct是淺拷貝,直接拷貝里面的指針。因此父子進(jìn)程的fd_array[]指向相同的file。struct file不需要重新拷貝,此時不同的進(jìn)程看到OS中的同一份資源,父進(jìn)程只需要向自己的文件緩沖區(qū)中寫入數(shù)據(jù),子進(jìn)程就可以通過它的文件描述符得到該數(shù)據(jù)。

打開普通文件就要有路徑,最終數(shù)據(jù)刷新到磁盤上。父進(jìn)程想要給子進(jìn)程發(fā)消息,如果通過這種把數(shù)據(jù)寫到緩沖區(qū)里,再寫到磁盤中的方式,效率就太低下了(一般文件緩沖區(qū)的數(shù)據(jù)都要刷新到磁盤)?,F(xiàn)在就需要這個文件是一個純內(nèi)存級的文件,不需要在磁盤中存在,甚至不需要名字,只要保證父子進(jìn)程能訪問到它即可。這種文件就叫做管道文件,所以管道文件也是純內(nèi)存級的文件,不需要向磁盤刷新。不需要名字也不需要路徑,所以也叫匿名管道。

管道文件有一個特點:實現(xiàn)了資源共享之后,只允許單向通信。

這種單向傳遞的通信特征很像日常生活中的管道,所以起名叫做管道。例如家里自來水永遠(yuǎn)都是自來水公司到家里。

在Unix系統(tǒng)中,管道通常通過命令行中的管道符號('|')來創(chuàng)建。例如在命令行中輸入以下命令時:

command1 | command2

命令'command1'的輸出會被重定向到管道中,而命令'command2'的輸入會從管道中讀取。這樣,'command1'的輸出就會成為'command2'的輸入,實現(xiàn)了兩個進(jìn)程之間的數(shù)據(jù)傳遞。

除了命令行中的管道,Unix系統(tǒng)還提供了兩種類型的管道:匿名管道和命名管道。

4.2 匿名管道

匿名管道(pipe)是在命令行中自動創(chuàng)建的,用于具有親緣關(guān)系的進(jìn)程間通信,如父進(jìn)程和子進(jìn)程。它有一個管道文件描述符,分別對應(yīng)讀端和寫端。
匿名管道在創(chuàng)建后不能被其他進(jìn)程打開。

#include <unistd.h>
功能:創(chuàng)建一匿名管道
原型
int pipe(int fd[2]);
參數(shù)
fd:文件描述符數(shù)組,其中fd[0]表示讀端, fd[1]表示寫端
返回值:成功返回0,失敗返回錯誤代碼

注:fd是輸出型參數(shù),返回讀寫端對應(yīng)的fd,用來關(guān)掉讀/寫端。

匿名管道的原理:(這里實現(xiàn)父寫子讀)

  1. 把一個文件按讀方式和寫方式打開。
  2. 創(chuàng)建子進(jìn)程時,子進(jìn)程直接拷貝父進(jìn)程的文件描述符表。
  3. 父進(jìn)程關(guān)閉fd[0],留下寫端,子進(jìn)程關(guān)閉fd[1],留下讀端。
  4. 就形成了單向通信的管道通路。

1. 為什么最開始時把一個文件按讀方式和寫方式打開?
因為只保留讀端或?qū)懚?#xff0c;創(chuàng)建子進(jìn)程時不能保留單向信道。保留讀端和寫端,子進(jìn)程也有讀端和寫端,再進(jìn)行適當(dāng)?shù)年P(guān)閉。就可以實現(xiàn)單向信道,父進(jìn)程讀子進(jìn)程寫,或者父進(jìn)程寫子進(jìn)程讀。

2. 同一個進(jìn)程把文件分別進(jìn)行讀打開和寫打開,在內(nèi)存里,文件的內(nèi)容和屬性會存在幾份?

只用存在一份。這是因為文件的內(nèi)容和屬性(如權(quán)限、所有者、大小、創(chuàng)建和修改時間等)都存儲在文件的 inode 結(jié)構(gòu)體中,而 inode 結(jié)構(gòu)體在文件系統(tǒng)中被唯一標(biāo)識。

3. 同一個進(jìn)程把文件分別進(jìn)行讀打開和寫打開,需要幾個struct file結(jié)構(gòu)體?

需要兩個struct file結(jié)構(gòu)體。struct file內(nèi)部有一個字段f_pos ,表示當(dāng)前的操作位置,相當(dāng)于文件內(nèi)部的偏移量。文件讀和寫打開時,它讀位置和寫位置不一樣。同一個進(jìn)程把文件分別進(jìn)行讀打開和寫打開,需要創(chuàng)建兩個struct file結(jié)構(gòu)體,一個用來讀取、一個用來寫入。只不過這兩個struct file結(jié)構(gòu)體會指向同樣的一個inode、同一個方法集、同一個緩沖區(qū)。

4. 進(jìn)程結(jié)束時,文件會被直接關(guān)閉嗎?

不會。創(chuàng)建子進(jìn)程時,由于files_struct是淺拷貝,所以指向相同的struct file結(jié)構(gòu)體。形成管道時父子進(jìn)程關(guān)閉各自的讀/寫端。struct file 中有一個引用計數(shù)的字段f_count,用于跟蹤有多少個進(jìn)程正在使用這個文件。當(dāng)進(jìn)程打開該文件時,f_count 會增加;當(dāng)進(jìn)程關(guān)閉文件時,f_count 會減少。所以進(jìn)程關(guān)閉讀/寫端的實質(zhì)是把文件描述符表內(nèi)部指向struct file 的指針清空,然后依次將引用計數(shù)f_count--,此時進(jìn)程就認(rèn)為把文件關(guān)了,但最后文件是否關(guān)閉是由操作系統(tǒng)決定的,要判斷f_count是否減到0。最終,是否關(guān)閉文件由操作系統(tǒng)決定,它會在所有引用計數(shù)減到0時釋放與文件相關(guān)的資源。

5. 引用計數(shù)f_count和硬鏈接數(shù)的不同

硬鏈接是在磁盤中用來統(tǒng)計有多少文件名和我的文件inode產(chǎn)生映射關(guān)系的;但是上面的引用計數(shù)f_count是用來記述內(nèi)核數(shù)據(jù)結(jié)構(gòu)struct file被多少進(jìn)程文件描述符表指向的。兩者雖然都是引用計數(shù),但引用的場景不同

現(xiàn)讓父進(jìn)程創(chuàng)建一個管道文件,進(jìn)行父讀子寫,即父進(jìn)程關(guān)閉寫端,子進(jìn)程關(guān)閉讀端?

#include <iostream>
#include <unistd.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>#define MAX_SIZE 1024int main()
{int pipefd[2] = {0};int ret = pipe(pipefd);assert(ret == 0); //防止編譯器告警,意料之中的錯誤用assert,意料之外的錯誤用if(void)ret;pid_t id = fork();if(id < 0){perror("fork");return 1;}if(id == 0){//子進(jìn)程寫close(pipefd[0]);//關(guān)閉讀端int n = 5;while(n--){char buffer[MAX_SIZE];snprintf(buffer, sizeof(buffer),"child progress,pid: %d, n: %d\n",getpid(),n);write(pipefd[1], buffer,strlen(buffer));sleep(1);}exit(0);}else{//父進(jìn)程讀close(pipefd[1]);//關(guān)閉寫端char buffer2[MAX_SIZE];while(true){ssize_t n = read(pipefd[0],buffer2,sizeof(buffer2)-1);if(n > 0){buffer2[n] = 0;std::cout << getpid() << ", child words: "<<buffer2 << std::endl;}else {break;}}}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id){std::cout << "wait success" << std::endl;}return 0;
}

注:

  1. 系統(tǒng)調(diào)用的接口是C語言的,為了更好地適應(yīng)某些極端場景,可以使用C語言的接口,例如示例中使用了snprintf接口。
  2. sizeof()-1是為了傳遞字符串時預(yù)留一個\0,雖然大部分場景也會預(yù)留\0,甚至字符串截斷也會預(yù)留\0,但在某些場景還是要sizeof()-1。例如read,它不知道傳進(jìn)來的是二進(jìn)制還是字符串還是其它類型。\0結(jié)尾是字符串的標(biāo)準(zhǔn),讀寫文件沒有義務(wù)在數(shù)據(jù)后面預(yù)留\0,所以需要我們自己預(yù)留維護(hù)。

?a. 管道的4種情況

  1. 正常情況,如果管道沒有數(shù)據(jù)了,讀端必須等待,直到有數(shù)據(jù)為止(寫端寫入數(shù)據(jù))。
  2. 正常情況,如果管道被寫滿了,寫端必須等待,直到有空間為止(讀端讀走數(shù)據(jù))。
  3. 寫端關(guān)閉,讀端繼續(xù)讀取,它將讀到管道中的所有數(shù)據(jù),直到read返回值為0, 表示讀到文件結(jié)尾。
  4. 讀端關(guān)閉,寫端寫入時,OS會直接殺掉寫端進(jìn)程,通過向目標(biāo)進(jìn)程發(fā)送SIGPIPE(13)信號,終止寫端進(jìn)程。

?b. 管道的5種特性

  1. 匿名管道,可以允許具有血緣關(guān)系的進(jìn)程之間進(jìn)行進(jìn)程間通信。(父子、爺孫...)
  2. 匿名管道,默認(rèn)給讀寫端要提供同步機(jī)制 --- 了解現(xiàn)象:讀端和寫端是順序進(jìn)行的,它們之間不會同時進(jìn)行。
  3. 面向字節(jié)流??--- 現(xiàn)象:不關(guān)心數(shù)據(jù)的格式,只關(guān)心數(shù)據(jù)的大小和順序,按字節(jié)一次性將數(shù)據(jù)獲取。管道可以傳輸任何類型的數(shù)據(jù)
  4. 管道的生命周期是隨進(jìn)程的。當(dāng)創(chuàng)建管道的進(jìn)程結(jié)束時,管道也隨之消失。
  5. 管道是單向通信的,半雙工通信的一種特殊情況

補(bǔ)充:如果 read 成功讀取數(shù)據(jù),它會返回實際讀取的字節(jié)數(shù)。如果 read 調(diào)用失敗,它將返回 -1 并設(shè)置 errno 以指示錯誤。如果到達(dá)文件末尾,read 將返回 0。

例如命令: sleep 1000 | sleep 2000 | sleep 3000
操作系統(tǒng)創(chuàng)建了3個進(jìn)程,兩個管道。

使用管道之后,原本向標(biāo)準(zhǔn)輸出輸出的內(nèi)容將重定向到管道文件中。原本從標(biāo)準(zhǔn)輸入獲得的內(nèi)容將重定向到從管道文件中獲取。

4.2?基于匿名管道設(shè)計進(jìn)程池

進(jìn)程池的概念:

????????一個進(jìn)程可以創(chuàng)建很多進(jìn)程,通過管道與每個進(jìn)程相連。正常情況,如果管道沒有數(shù)據(jù)了,讀端必須等待,直到有數(shù)據(jù)為止。這樣就可以通過對特定的管道傳輸數(shù)據(jù)實現(xiàn)喚醒特定的進(jìn)程。

????????創(chuàng)建進(jìn)程會消耗時間和空間資源,如果要處理一個任務(wù)要等到任務(wù)來到時再處理,進(jìn)行創(chuàng)建進(jìn)程、分配資源,這樣就有些耽誤時間,如果提前把進(jìn)程創(chuàng)建好,等任務(wù)來到時讓已經(jīng)創(chuàng)建好的進(jìn)程完成任務(wù),這樣就可以節(jié)省創(chuàng)建進(jìn)程的時間。這些提前創(chuàng)建好的進(jìn)程就叫做進(jìn)程池。

補(bǔ)充內(nèi)存池的概念:

????????調(diào)用系統(tǒng)調(diào)用是有成本的。調(diào)用自己的函數(shù)也有成本,所以才有了宏函數(shù)、內(nèi)聯(lián)函數(shù)。調(diào)用系統(tǒng)調(diào)用時操作系統(tǒng)會做很多事情,比如申請內(nèi)存,如果內(nèi)存不足,操作系統(tǒng)就要執(zhí)行內(nèi)存管理算法協(xié)調(diào)內(nèi)存,釋放、調(diào)整、置換掛起等等。一次性申請100MB內(nèi)存比申請十次10MB內(nèi)存效率更高。在C++標(biāo)準(zhǔn)模板庫(STL)中,有一個參數(shù)為內(nèi)存配置器,它是一個模板類,用于指定用于存儲容器元素的內(nèi)存管理策略。它定義了如何分配內(nèi)存、如何構(gòu)造新元素、如何釋放內(nèi)存以及如何管理內(nèi)存池等。在申請內(nèi)存時它會額外多申請一部分,這樣在需要擴(kuò)容時就可以減少系統(tǒng)調(diào)用,這種多申請內(nèi)存的方法就叫做內(nèi)存池。

模擬實現(xiàn)進(jìn)程池

Task.hpp如下:

#pragma once#include <iostream>
#include <vector>
#include <unistd.h>
#include <functional>
#include <ctime>typedef std::function<void()> task_t;
void Download()
{std::cout << "我是一個下載任務(wù)"<< " 處理者: " << getpid() << std::endl;
}void PrintLog()
{std::cout << "我是一個打印日志的任務(wù)"<< " 處理者: " << getpid() << std::endl;
}void PushVideoStream()
{std::cout << "這是一個推送視頻流的任務(wù)"<< " 處理者: " << getpid() << std::endl;
}class Init
{
public:// 任務(wù)碼const static int g_download_code = 0;const static int g_printlog_code = 1;const static int g_push_videostream_code = 2;// 任務(wù)集合std::vector<task_t> tasks;public:Init(){tasks.push_back(Download);tasks.push_back(PrintLog);tasks.push_back(PushVideoStream);srand(time(nullptr) ^ getpid());}// 檢查任務(wù)碼bool CheckCode(int code){if (code >= 0 && code < tasks.size())return true;elsereturn false;}// 運行任務(wù)void RunTask(int code){return tasks[code]();}// 隨機(jī)選擇任務(wù)int SelectTask(){return rand() % tasks.size();}// 描述任務(wù)碼對應(yīng)的任務(wù)名稱std::string ToDesc(int code){switch (code){case g_download_code:return "Download";case g_printlog_code:return "PrintLog";case g_push_videostream_code:return "PushVideoStream";default:return "Unknow";}}
};
Init init;

ProcessPool.cc如下:

#include <iostream>
#include <unistd.h>
#include <string>
#include <cassert>
#include <vector>
#include "Task.hpp"
#include <sys/types.h>
#include <sys/wait.h>static int number = 0; // 管道的編號
const int count = 5;   // 子進(jìn)程和管道個數(shù)// 用來確定有哪些任務(wù)
class Channel
{
public:Channel(int fd, pid_t workerid): _fd(fd), _workerid(workerid){_name = "channel: " + std::to_string(number++);}public:// 管道fd  子進(jìn)程pid  管道名int _fd;pid_t _workerid;std::string _name;
};void Work()
{while (true){int code = 0;                             // 用來規(guī)定buffer,讀取必須是4個字節(jié),得到任務(wù)碼ssize_t n = read(0, &code, sizeof(code)); // 已經(jīng)完成輸入重定向// read讀到數(shù)據(jù)長度n必須等于sizeof(code)if (n == sizeof(code)) // 讀到正確的code{if (!init.CheckCode(code)) // 不合法直接continuecontinue;init.RunTask(code); // 合法,執(zhí)行任務(wù),相當(dāng)于init.tasks[code]()}else if (n == 0) // 寫端關(guān)閉,讀端繼續(xù)讀取,它將讀到管道中的所有數(shù)據(jù),直到read返回值為0{break;}else{}}std::cout << "child quit" << std::endl;
}void PrintFd(const std::vector<int> &fds)
{std::cout << getpid() << " close fds: ";for (auto fd : fds){std::cout << fd << " ";}std::cout << std::endl;
}// 傳參形式:
// 1. 輸入?yún)?shù):const &
// 2. 輸出參數(shù):*
// 3. 輸入輸出參數(shù):&
void CreatChannel(std::vector<Channel> *c)
{// bug// 父進(jìn)程在不斷創(chuàng)建管道時,創(chuàng)建第一個進(jìn)程,父進(jìn)程的信道寫端已經(jīng)在文件描述符里,// 再創(chuàng)建第二個管道和進(jìn)程時,除了建立正常的通信信道以外,上一個信道在父進(jìn)程的寫端也會被下一個進(jìn)程繼承,// 再創(chuàng)建第三個管道和進(jìn)程時,這個子進(jìn)程的文件描述符表將包含指向三個信道。// 一直創(chuàng)建管道和進(jìn)程,只有最后一個創(chuàng)建的管道只有一個寫端指向,其它的管道都有多個寫端指向。// 所以回收時要關(guān)閉全部信道寫端再wait,如果close和wait同時進(jìn)行,關(guān)閉信道寫端從上往下關(guān),關(guān)閉后還有無數(shù)個進(jìn)程指向該信道,引用計數(shù)不為0,管道不釋放,read讀不到0,也就阻塞了std::vector<int> old;for (int i = 0; i < count; i++){// 1. 定義并創(chuàng)建管道int pipefd[2];int n = pipe(pipefd);assert(n == 0);(void)n;// 2. 創(chuàng)建進(jìn)程pid_t id = fork();assert(id != -1);// 3. 構(gòu)建單向信道if (id == 0) // 子進(jìn)程{if (!old.empty()){for (auto fd : old){close(fd); // 把不屬于自己的管道的寫端關(guān)閉}PrintFd(old);}close(pipefd[1]);dup2(pipefd[0], 0); // 使用dup2后就不用給Work傳參了,只用從標(biāo)準(zhǔn)輸入拿數(shù)據(jù)即可Work();exit(0); // 會自動關(guān)閉自己打開的所有的fd}// 父進(jìn)程close(pipefd[0]);c->push_back(Channel(pipefd[1], id)); // 之后對信道的增刪查改就變成了對該vector的增刪查改old.push_back(pipefd[1]);             // 記錄父進(jìn)程的管道寫端}
}void SendCommand(const std::vector<Channel> &c, bool flag, int num = -1)
{int pos = 0;while (true){// 1. 選擇任務(wù),得到任務(wù)碼,4字節(jié)int taskcode = init.SelectTask();// 2. 選擇信道(進(jìn)程),輪詢或隨機(jī),較為平均地將任務(wù)給進(jìn)程,要考慮子進(jìn)程完成任務(wù)的負(fù)載均衡const auto &channel = c[pos++];pos %= c.size();// debug 查看任務(wù)發(fā)送給誰了std::cout << "send taskcode " << init.ToDesc(taskcode) << "[" << taskcode << "]"<< " in "<< channel._name << " worker is : " << channel._workerid << std::endl;// 3. 發(fā)送任務(wù)write(channel._fd, &taskcode, sizeof(taskcode));// 4. 判斷是否退出if (!flag){num--;if (num <= 0)break;}sleep(1);}std::cout << "SendCommand done..." << std::endl;
}void ReleaseChannel(const std::vector<Channel> &c)
{// 父進(jìn)程退出了,與信道寫端對應(yīng)的文件描述符自動關(guān)閉// 寫端關(guān)閉,讀端繼續(xù)讀取,它將讀到管道中的所有數(shù)據(jù),直到read返回值為0for (const auto &channel : c){close(channel._fd);waitpid(channel._workerid, nullptr, 0);}// for (const auto &channel : c)// {//     pid_t rid = waitpid(channel._workerid, nullptr, 0);//     if (rid == channel._workerid)//     {//         std::cout << "wait child: " << channel._workerid << " success" << std::endl;//     }// }// 還有一種方法,不用使用old關(guān)閉不屬于自己的寫端:倒?fàn)罨厥?/ int pos = c.size();// for (; pos >= 0; pos--)// {//     close(c[pos]._fd);//     waitpid(c[pos]._workerid, nullptr, 0);// }
}int main()
{std::vector<Channel> channels;// 創(chuàng)建信道、創(chuàng)建進(jìn)程CreatChannel(&channels);// 向不同的管道發(fā)送不同任務(wù)const bool g_always_loop = true;// SendCommand(channels,g_always_loop);SendCommand(channels, !g_always_loop, 10);// 回收資源,子進(jìn)程退出、釋放管道ReleaseChannel(channels);return 0;
}

4.3?命名管道

命名管道(也稱為FIFO)在Linux中是一種特殊的文件類型,它允許不同進(jìn)程之間通過一個命名的管道進(jìn)行通信。命名管道在文件系統(tǒng)中有一個可見的名稱,可以像普通文件一樣訪問,但它們的操作方式與匿名管道不同。

命名管道是通過系統(tǒng)調(diào)用'mkfifo'創(chuàng)建的,可以用于不具有親緣關(guān)系/毫不相關(guān)的進(jìn)程進(jìn)行進(jìn)程間通信。它是一個文件,通常具有特定的擴(kuò)展名(如'.fifo'? 點表示匿名文件),但它實際上并不是文件系統(tǒng)中的普通文件,而是一個特殊的文件。

創(chuàng)建命名管道

  • 命名管道可以從命令行上創(chuàng)建:
    $ mkfifo filename
  • 命名管道也可以從程序里創(chuàng)建:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname是命名管道的路徑名。
mode是設(shè)置命名管道的權(quán)限模式,與open函數(shù)的mode參數(shù)類似。注意與umask的運算
成功返回0,失敗返回-1。

命名管道文件是創(chuàng)建出來的磁盤級的符號,實際在進(jìn)行數(shù)據(jù)通信時,由于該文件是管道文件,被打開時數(shù)據(jù)也不會向磁盤刷新。命名管道文件有路徑和文件名,因為路徑是具有唯一性的,所以,我們可以使用路徑+文件名,來唯一的讓不同進(jìn)程看到同一份資源!

創(chuàng)建名為filename的命名管道,使用ll命令,發(fā)現(xiàn)命名管道文件類型為p,即管道文件。

匿名管道與命名管道的區(qū)別

  • 匿名管道由pipe函數(shù)創(chuàng)建并打開。
  • 命名管道由mkfifo函數(shù)創(chuàng)建,打開用open
  • FIFO(命名管道)與pipe(匿名管道)之間唯一的區(qū)別在它們創(chuàng)建與打開的方式不同,一旦這些工作完成之后,它們具有相同的語義。(原理和特征一樣)

4.4 用命名管道實現(xiàn)server&client通信

文件:comm.h? ?client.cc? ?server.cc?? Makefile

Makefile如下:

.PHONY:all
all:clientPipe serverPipeclientPipe:client.ccg++ -o $@ $^ -std=c++11serverPipe:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f clientPipe serverPipe

comm.h如下:

#pragma once#define FILENAME "fifo"

client.cc如下:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include "comm.h"
#include <fcntl.h>
#include <cstring>
#include <unistd.h>
#include <string>int main()
{// 打開命名管道int fifo_wfd = open(FILENAME, O_WRONLY);if (fifo_wfd < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;exit(0);}std::cout << "open fifo success-------write" << std::endl;// 向管道寫入數(shù)據(jù)std::string message;while (true){std::cout << "Please Enter# ";std::getline(std::cin, message);ssize_t num = write(fifo_wfd, message.c_str(), message.size());if (num < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;break;}}close(fifo_wfd);std::cout << "close fifo success..." << std::endl;return 0;
}

server.cc?如下:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include "comm.h"
#include <fcntl.h>
#include <cstring>
#include <unistd.h>// 創(chuàng)建命名管道
bool MakeFifo()
{int n = mkfifo(FILENAME, 0666);if (n < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return false;}std::cout << "mkfifo success-------read" << std::endl;return true;
}
int main()
{
Start:// 不管有沒有管道,直接打開命名管道,有管道就會返回fifo_rfdint fifo_rfd = open(FILENAME, O_RDONLY);if (fifo_rfd < 0)//沒有管道就創(chuàng)建,然后再次打開{std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;if(MakeFifo()) goto Start;else return 1;}std::cout << "open fifo success-------read" << std::endl;// version 1  命名管道創(chuàng)建后再運行serverPipe會提示管道文件已存在//  // 創(chuàng)建命名管道//  int n = mkfifo(FILENAME, 0666);//  if (n < 0)//  {//      std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;//      exit(0);//  }//  std::cout << "mkfifo success-------read" << std::endl;// // 打開命名管道// int fifo_rfd = open(FILENAME, O_RDONLY);// if (fifo_rfd < 0)// {//     std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;//     exit(0);// }// std::cout << "open fifo success-------read" << std::endl;// 從管道讀數(shù)據(jù)char buffer[1024];while (true){ssize_t num = read(fifo_rfd, buffer, sizeof(buffer) - 1);if (num > 0){buffer[num] = 0; // 或等于'\0'std::cout << "Client say: " << buffer << std::endl;}else if (num == 0){std::cout << "client quit, server quit too!" << std::endl;break;}}close(fifo_rfd);std::cout << "close fifo success..." << std::endl;return 0;
}

五、system V共享內(nèi)存

5.1 system V共享內(nèi)存的引入

管道不是為了通信而專門設(shè)置的一套方案,而是為了通信復(fù)用了之前的代碼。而實際上OS在通信時場景很多,只有一種通信方式是不夠的,因此,操作系統(tǒng)提供了多種IPC機(jī)制,包括但不限于:

  • 管道(Pipe)和命名管道(FIFO):用于單向數(shù)據(jù)流通信。
  • 消息隊列(Message Queue):允許一個或多個進(jìn)程向隊列中寫入消息,其他進(jìn)程則可以讀取隊列中的消息。
  • 信號量(Semaphore):用于同步進(jìn)程間的訪問共享資源。
  • 共享內(nèi)存(Shared Memory):允許多個進(jìn)程共享一段內(nèi)存區(qū)域,是最快的IPC方式,因為它不需要數(shù)據(jù)復(fù)制。
  • 套接字(Socket):提供了在網(wǎng)絡(luò)上的不同主機(jī)間進(jìn)行通信的能力,也可以用于同一主機(jī)上的不同進(jìn)程間通信。

System V共享內(nèi)存是操作系統(tǒng)中提供的一種IPC機(jī)制,它允許不同的進(jìn)程訪問同一塊內(nèi)存區(qū)域,從而實現(xiàn)數(shù)據(jù)共享。

共享內(nèi)存區(qū)是最快的IPC形式。一旦這樣的內(nèi)存映射到共享它的進(jìn)程的地址空間,這些進(jìn)程間數(shù)據(jù)傳遞不再涉及到內(nèi)核,換句話說是進(jìn)程不再通過執(zhí)行進(jìn)入內(nèi)核的系統(tǒng)調(diào)用來傳遞彼此的數(shù)據(jù)

5.2?共享內(nèi)存的原理

  • 共享內(nèi)存允許多個進(jìn)程共享一段內(nèi)存區(qū)域,而且共享內(nèi)存段是物理內(nèi)存中的一部分。
  • 在物理內(nèi)存新增共享內(nèi)存段時,要對共享內(nèi)存段先描述再組織,即使用struct shmid_ds描述了共享內(nèi)存段的屬性,如大小、訪問權(quán)限、創(chuàng)建者信息等。通過鏈表進(jìn)行對共享內(nèi)存段的管理。
  • 共享內(nèi)存的創(chuàng)建是進(jìn)程發(fā)起的。每個進(jìn)程在Linux內(nèi)核中都有一個task_struct結(jié)構(gòu)來表示,這個結(jié)構(gòu)包含了進(jìn)程的所有信息,其中包括它的地址空間。地址空間被分為多個部分,包括代碼段、數(shù)據(jù)段、堆、棧、共享區(qū)等。
  • 每個進(jìn)程都有自己的頁表,通過頁表可以將虛擬地址翻譯成物理地址。在新增共享內(nèi)存時,要在頁表中進(jìn)行映射,共享內(nèi)存被映射到進(jìn)程地址空間的共享區(qū)中,并向上層返回所在共享區(qū)的起始地址,使得進(jìn)程可以通過地址空間,像訪問自己的內(nèi)存一樣訪問共享內(nèi)存。
  • 在使用System V共享內(nèi)存時,每個共享內(nèi)存段都有一個唯一的鍵(key),用于在進(jìn)程間標(biāo)識和訪問共享內(nèi)存段。內(nèi)核使用這個鍵來查找或創(chuàng)建對應(yīng)的struct shmid_ds。

OS中會存在很多進(jìn)程,這些進(jìn)行都有可能申請和使用共享內(nèi)存,OS一定會允許系統(tǒng)中同時存在多個共享內(nèi)存。共享內(nèi)存,也要被操作系統(tǒng)管理,管理的方法就是先描述再組織,即上面講到的struct shmid_ds結(jié)構(gòu)體。但是上面的步驟只是一個進(jìn)程創(chuàng)建共享內(nèi)存,那么如何保證第二個之后的參與通信的進(jìn)程,看到的就是同一個共享內(nèi)存呢?

注意,進(jìn)程不能直接給另一個進(jìn)程直接傳值,因為如果這樣就說明已經(jīng)能通信了,就不需要共享內(nèi)存來傳遞消息了。所以進(jìn)程不能將key傳給另一個進(jìn)程。方法:提前進(jìn)行約定,讓使用同一塊共享內(nèi)存的進(jìn)程使用相同的key,這個key可以用戶自己定義,也可以使用庫方法,只要保證key唯一即可。

5.3 共享內(nèi)存函數(shù)

shmget函數(shù):既能創(chuàng)建也能獲取

shmget函數(shù)用于創(chuàng)建一個新的共享內(nèi)存段或者獲取一個已經(jīng)存在的共享內(nèi)存段的標(biāo)識符。

原型:

????????#include <sys/ipc.h>
????????#include <sys/shm.h>

????????int shmget(key_t key, size_t size, int shmflg);

參數(shù):

  • key:一個鍵值,用于唯一標(biāo)識共享內(nèi)存段。在創(chuàng)建共享內(nèi)存時就將key加載到其中。
  • size:共享內(nèi)存的大小。
  • shmflg:一個標(biāo)志位,用于控制共享內(nèi)存的創(chuàng)建和訪問權(quán)限。(使用方法類似open的flags標(biāo)志位)

返回值:

????????成功時,返回共享內(nèi)存段的標(biāo)識符shmid;失敗時,返回-1。

補(bǔ)充:

1. shmflg參數(shù)中常用的標(biāo)志位

  • IPC_CREAT:如果這個標(biāo)志位被設(shè)置,并且共享內(nèi)存段不存在,那么shmget函數(shù)會創(chuàng)建一個新的共享內(nèi)存段。如果共享內(nèi)存段已經(jīng)存在,shmget會返回已存在的共享內(nèi)存段的標(biāo)識符。
  • IPC_EXCL:這個標(biāo)志位必須與IPC_CREAT標(biāo)志位一起使用。如果IPC_CREAT和IPC_EXCL都被設(shè)置,并且共享內(nèi)存段不存在,shmget函數(shù)會創(chuàng)建一個新的共享內(nèi)存段。如果共享內(nèi)存段已經(jīng)存在,shmget函數(shù)會失敗,并返回-1。用來保證共享內(nèi)存段是新創(chuàng)建的。
  • mode:這個值通常作為shmflg參數(shù)的低位部分,它表示共享內(nèi)存段的權(quán)限模式。例如0666。
  • 示例:int shmid = shmget(11223344, 4096, IPC_CREAT | 0666);

2. ftok函數(shù)來生成一個鍵值

  • #include <sys/types.h>
    #include <sys/ipc.h>
    key_t ftok(const char *pathname, int proj_id);
  • 用戶可以通過ftok函數(shù)來生成一個鍵值,這個鍵值通?;谝粋€路徑名和一個項目ID。ftok函數(shù)返回一個整數(shù),這個整數(shù)就是用于shmget函數(shù)的key參數(shù)。
  • 示例:
    key_t key;
    key = ftok("path/to/file", 1); // "path/to/file"是文件路徑,1是項目ID
    int shmid = shmget(key, 4096, IPC_CREAT | 0666);
  • 因為用戶定義的key不容易保證唯一性,所以使用ftok函數(shù)獲取key。(相同的參數(shù)相同的算法,最終得到相同的值)

注意:

  1. key和shmid的區(qū)別
    key是操作系統(tǒng)用來區(qū)分共享內(nèi)存段的,shmid是用戶用來進(jìn)行對共享內(nèi)存段的操作的。下面的shmat、shmctl都是使用shmid來對指定的共享內(nèi)存段操作。包括命令行指令也是通過shmid進(jìn)行操作。
  2. ?共享內(nèi)存(IPC資源)的生命周期是隨內(nèi)核的!共享內(nèi)存需要用戶主動釋放,除非重啟OS
    ipcs -m shmid 命令,查看有多少共享內(nèi)存
    ipcrm -m 命令,刪除指定的共享內(nèi)存

shmat函數(shù):at->attach建立關(guān)聯(lián)

shmat函數(shù)用于將共享內(nèi)存段連接到進(jìn)程的地址空間。

原型:

????????#include <sys/types.h>
????????#include <sys/shm.h>

????????void *shmat(int shmid, const void *shmaddr, int shmflg);

參數(shù):

  • shmid:共享內(nèi)存段的標(biāo)識符。
  • shmaddr:指定連接的地址,如果為NULL,內(nèi)核將自動選擇地址。
  • shmflg:連接標(biāo)志,可以指定讀寫權(quán)限等。

返回值:
????????成功時,返回指向共享內(nèi)存的指針,映射到地址空間的起始虛擬地址;失敗時,返回-1。

說明:

  • shmaddr為NULL,核心自動選擇一個地址
  • shmaddr不為NULL且shmflg無SHM_RND標(biāo)記,則以shmaddr為連接地址。
  • shmaddr不為NULL且shmflg設(shè)置了SHM_RND標(biāo)記,則連接的地址會自動向下調(diào)整為SHMLBA的整數(shù)倍。公式:shmaddr -?(shmaddr % SHMLBA)
  • shmflg=SHM_RDONLY,表示連接操作用來只讀共享內(nèi)存

shmdt函數(shù):dt->detach去關(guān)聯(lián)

shmdt函數(shù)用于將共享內(nèi)存段與當(dāng)前進(jìn)程的地址空間脫離,即解除映射。

原型:

????????#include <sys/types.h>
????????#include <sys/shm.h>

????????int shmdt(const void *shmaddr);

參數(shù):

  • shmaddr:由shmat返回的指針。

返回值:
????????成功時,返回0;失敗時,返回-1。

注意:將共享內(nèi)存段與當(dāng)前進(jìn)程脫離不等于刪除共享內(nèi)存段。只是將頁表中與共享內(nèi)存段的映射清空。

什么時候刪除共享內(nèi)存?

struct shmid_ds中有shm_nattch字段,它是一個引用計數(shù)器,表示有多少個進(jìn)程正在使用這個共享內(nèi)存段。當(dāng)一個進(jìn)程使用shmat函數(shù)將共享內(nèi)存段映射到自己的地址空間時,shm_nattch的值會增加;當(dāng)進(jìn)程使用shmdt函數(shù)將共享內(nèi)存段從自己的地址空間脫離時,shm_nattch的值會減少。

當(dāng)shm_nattch的值降至0時,意味著沒有進(jìn)程在使用這個共享內(nèi)存段。在這種情況下,內(nèi)核會考慮刪除共享內(nèi)存段,但還需要滿足其他條件,比如共享內(nèi)存段沒有被其他進(jìn)程以只讀方式映射。只有當(dāng)所有使用該共享內(nèi)存段的進(jìn)程都調(diào)用了shmdt函數(shù)后,操作系統(tǒng)才會刪除共享內(nèi)存段。

shmctl函數(shù):ctl->control?

shmctl函數(shù)用于控制共享內(nèi)存,如刪除共享內(nèi)存段、改變共享內(nèi)存的權(quán)限等。

原型:

????????#include <sys/ipc.h>
????????#include <sys/shm.h>

????????int shmctl(int shmid, int cmd, struct shmid_ds *buf);

參數(shù):

  • shmid:共享內(nèi)存段的標(biāo)識符。
  • cmd:將要采取的動作,如刪除(IPC_RMID)、改變權(quán)限(IPC_SET)等。
    IPC_STAT:獲取共享內(nèi)存段的當(dāng)前狀態(tài),并將其存儲在buf指向的struct shmid_ds結(jié)構(gòu)體中。
    IPC_SET:設(shè)置共享內(nèi)存段的當(dāng)前狀態(tài),并從buf指向的struct shmid_ds結(jié)構(gòu)體中讀取信息。
    IPC_RMID:刪除共享內(nèi)存段,釋放系統(tǒng)資源。(remove id 或remove immediately)
  • buf:指向一個'struct shmid_ds'結(jié)構(gòu)體,該結(jié)構(gòu)體包含共享內(nèi)存的屬性信息。

返回值:
????????成功時,返回0;失敗時,返回-1。

5.4 使用共享內(nèi)存的步驟

  1. 生成key:通過ftok函數(shù)來生成一個鍵值,基于一個路徑名和一個項目ID。
  2. 創(chuàng)建共享內(nèi)存段:使用shmget函數(shù),指定key和共享內(nèi)存的大小及其他屬性來創(chuàng)建一個新的共享內(nèi)存段或者獲取一個已經(jīng)存在的共享內(nèi)存段的標(biāo)識符。內(nèi)核會創(chuàng)建一個struct shmid_ds來描述這個共享內(nèi)存段,并在文件系統(tǒng)中創(chuàng)建一個對應(yīng)的特殊文件。
  3. 映射共享內(nèi)存段:使用shmat函數(shù),將共享內(nèi)存段映射到進(jìn)程的地址空間中。內(nèi)核會更新進(jìn)程的頁表,將共享內(nèi)存的虛擬地址映射到物理內(nèi)存的頁面。
  4. 訪問共享內(nèi)存:進(jìn)程可以使用指針操作來讀取和寫入共享內(nèi)存中的數(shù)據(jù)。當(dāng)進(jìn)程訪問共享內(nèi)存時,它的頁表會將虛擬地址翻譯成物理地址,從而訪問共享內(nèi)存的物理頁面。
  5. 解除映射:當(dāng)進(jìn)程完成共享內(nèi)存的使用后,應(yīng)該使用shmdt函數(shù)來解除映射。內(nèi)核會更新進(jìn)程的頁表,取消共享內(nèi)存的虛擬地址到物理地址的映射。
  6. 刪除共享內(nèi)存段:如果共享內(nèi)存不再需要,可以使用shmctl函數(shù)來標(biāo)記刪除。內(nèi)核會刪除對應(yīng)的struct shmid_ds,并在文件系統(tǒng)中刪除對應(yīng)的特殊文件。

5.5 基于共享內(nèi)存的進(jìn)程間通信示例

文件:comm.hpp? ?client.cc? ?server.cc?? Makefile

Makefile如下:

.PHONY:all
all:clientPipe serverPipeclientPipe:client.ccg++ -o $@ $^ -std=c++11serverPipe:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f clientPipe serverPipe

comm.hpp如下:

#pragma once#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <unistd.h>const char *pathname = "/home/zzx/2024/0604/shm";
const int projectID = 1111;    // 項目ID
const int Size = 4096;         // 文件大小
const char *filename = "fifo"; // 命名管道key_t GetKey()
{return ftok(pathname, projectID);
}// int CreateShm(key_t key)
// {
//     int shmid = shmget(key, Size, IPC_CREAT | 0666);
//     if(shmid < 0)
//     {
//         std::cerr << "errno" << errno << ",errnostring: " << strerror(errno) << endl;
//         exit(2);
//     }
//     return shmid;
// }int __CreateOrGetShm(key_t key, int flag)
{int shmid = shmget(key, Size, flag);if (shmid < 0){std::cerr << "errno" << errno << ",errnostring: " << strerror(errno) << std::endl;exit(2);}return shmid;
}int CreateShm(key_t key)
{return __CreateOrGetShm(key, IPC_CREAT | IPC_EXCL | 0666);
}int GetShm(key_t key)
{return __CreateOrGetShm(key, IPC_CREAT /*0也可以*/);
}bool MakeFifo()
{int n = mkfifo(filename, 0666);if (n < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return false;}std::cout << "mkfifo success... read" << std::endl;return true;
}

client.cc如下:

#include "comm.hpp"int main()
{// 使用共享內(nèi)存key_t key = GetKey();int shmid = GetShm(key);std::cout << "GetShm success --- client" << std::endl;char* shmaddr = (char*)shmat(shmid, nullptr,0);std::cout << "attach success --- client" << std::endl;int fd = open(filename,O_WRONLY);char c = 'a';while (c < 'z'){shmaddr[c-'a'] = c;std::cout << "write: " << shmaddr << std::endl;sleep(1);int code = 1;//只是通知作用,用來同步write(fd,&code,sizeof(code));c++;}shmdt(shmaddr);close(fd);return 0;
}

server.cc如下:

#include "comm.hpp"class Init
{
public:Init(){// 創(chuàng)建管道文件,復(fù)用同步機(jī)制bool r = MakeFifo();if (!r)return;// 創(chuàng)建共享內(nèi)存key_t key = GetKey();shmid = CreateShm(key); // 封裝了底層接口,其它函數(shù)也可以這樣實現(xiàn),在此不作實現(xiàn)std::cout << "CreateShm success --- server" << std::endl;// 與進(jìn)程地址空間進(jìn)行關(guān)聯(lián)shmaddr = (char *)shmat(shmid, nullptr, 0);std::cout << "shmat success --- server" << std::endl;fd = open(filename, O_RDONLY);}~Init(){// 與進(jìn)程地址空間去關(guān)聯(lián)shmdt(shmaddr);std::cout << "shmdt success --- server" << std::endl;// 刪除共享內(nèi)存shmctl(shmid, IPC_RMID, nullptr);std::cout << "shmctl success --- server" << std::endl;}public:int fd;int shmid;char *shmaddr;
};int main()
{Init init;while (true){int code = 0;ssize_t n = read(init.fd, &code, sizeof(code));if (n > 0){std::cout << "共享內(nèi)存的內(nèi)容:" << init.shmaddr << std::endl;}else if (n == 0){break;}}return 0;
}

5.6 共享內(nèi)存的特點

  1. 共享內(nèi)存的通信方式,不會提供同步機(jī)制,共享內(nèi)存是直接裸露給所有的使用者的,一定要注意共享內(nèi)存的使用安全問題。
  2. 共享內(nèi)存是所有進(jìn)程間通信,速度最快的。
  3. 共享內(nèi)存可以提供較大的空間

共享內(nèi)存通信速度快是因為它減少了數(shù)據(jù)拷貝次數(shù)。在使用管道傳遞數(shù)據(jù)時要先創(chuàng)建管道,然后不同端向管道寫入或讀取數(shù)據(jù),調(diào)用write或read等系統(tǒng)調(diào)用。在計算機(jī)中,凡是數(shù)據(jù)遷移,都是對數(shù)據(jù)的拷貝。用戶通過進(jìn)程A將數(shù)據(jù)寫到管道,進(jìn)程B從管道讀出數(shù)據(jù)寫入顯示器,用戶把數(shù)據(jù)傳給進(jìn)程A,進(jìn)程B把數(shù)據(jù)打印到顯示器文件也都用到了拷貝,拷貝也有代價。

使用共享內(nèi)存,用戶把數(shù)據(jù)傳給進(jìn)程A,就直接傳到了共享內(nèi)存中,數(shù)據(jù)一旦進(jìn)入共享內(nèi)存,進(jìn)程B立即就能知道(因為沒有同步機(jī)制),進(jìn)程B直接共享區(qū)數(shù)據(jù)傳給顯示器,中間就至少減少兩次系統(tǒng)調(diào)用(write, read)。

簡而言之,在傳統(tǒng)的IPC機(jī)制中,如管道,數(shù)據(jù)需要經(jīng)過以下步驟:

  • 用戶空間到內(nèi)核空間:用戶通過系統(tǒng)調(diào)用(如write)將數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間。
  • 內(nèi)核空間到內(nèi)核空間:數(shù)據(jù)在內(nèi)核空間之間傳遞,可能需要通過網(wǎng)絡(luò)堆棧、文件系統(tǒng)等。
  • 內(nèi)核空間到用戶空間:數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間,通過系統(tǒng)調(diào)用(如read)被進(jìn)程讀取

這個過程涉及了多次數(shù)據(jù)拷貝,并且每次拷貝都會帶來一定的開銷。

相比之下,共享內(nèi)存通信的過程是這樣的:

  • 用戶空間到共享內(nèi)存:用戶進(jìn)程將數(shù)據(jù)寫入共享內(nèi)存。
  • 共享內(nèi)存到用戶空間:另一個進(jìn)程從共享內(nèi)存中讀取數(shù)據(jù)。

在這個過程中,只有兩次數(shù)據(jù)拷貝

5.7 共享內(nèi)存數(shù)據(jù)結(jié)構(gòu)

上面講到的shmid_ds結(jié)構(gòu)體,包括buf參數(shù)也使用一個指向shmid_ds結(jié)構(gòu)的指針,shmid_ds結(jié)構(gòu)體在<sys/shm.h>中定義如下:

struct shmid_ds {struct ipc_perm shm_perm; ? ?/* Ownership and permissions */size_t ? ? ? ? ?shm_segsz; ? /* Size of segment (bytes) */time_t ? ? ? ? ?shm_atime; ? /* Last attach time */time_t ? ? ? ? ?shm_dtime; ? /* Last detach time */time_t ? ? ? ? ?shm_ctime; ? /* Last change time */pid_t ? ? ? ? ? shm_cpid; ? ?/* PID of creator */pid_t ? ? ? ? ? shm_lpid; ? ?/* PID of last shmat(2)/shmdt(2) */shmatt_t ? ? ? ?shm_nattch; ?/* No. of current attaches */...
};

ipc_perm結(jié)構(gòu)定義如下(突出顯示的字段可以使用IPC_SET設(shè)置):

struct ipc_perm {key_t ? ? ? ? ?__key; ? ?/* Key supplied to shmget(2) */uid_t ? ? ? ? ?uid; ? ? ?/* Effective UID of owner */gid_t ? ? ? ? ?gid; ? ? ?/* Effective GID of owner */uid_t ? ? ? ? ?cuid; ? ? /* Effective UID of creator */gid_t ? ? ? ? ?cgid; ? ? /* Effective GID of creator */unsigned short mode; ? ? /* Permissions + SHM_DEST andSHM_LOCKED flags */unsigned short __seq; ? ?/* Sequence number */
};

從中可以看到shmid_ds結(jié)構(gòu)體的首元素是一個結(jié)構(gòu)體ipc_perm,它包含創(chuàng)建共享內(nèi)存段時提供的鍵值。

要想了解shmid_ds和ipc_perm就要介紹一下system V消息隊列和system V信號量

六、簡述system V消息隊列和system V信號量

6.1 system V消息隊列

消息隊列的特性:

  1. 消息隊列提供了一個從一個進(jìn)程向另外一個進(jìn)程發(fā)送一個數(shù)據(jù)塊的方法。這個數(shù)據(jù)塊也叫消息。
  2. 每個數(shù)據(jù)塊都被認(rèn)為是有一個類型,接收者進(jìn)程接收的數(shù)據(jù)塊可以有不同的類型值。接收者進(jìn)程可以指定它只接收特定類型的消息。這允許不同的消息可以同時存在于隊列中,而不需要接收者知道隊列中有哪些類型的消息。例如進(jìn)程A要求進(jìn)程B能看到,類型就設(shè)置為B。
  3. 每個消息隊列都有一個唯一的標(biāo)識符msqid,用于在系統(tǒng)中標(biāo)識和訪問該隊列。
  4. 與共享內(nèi)存段類似,消息隊列也可以通過鍵來標(biāo)識,用于在系統(tǒng)中唯一標(biāo)識消息隊列。
  5. 與System V的其他IPC資源一樣,消息隊列需要顯式地刪除,否則不會自動清除,除非重啟,所以system V 消息隊列資源的生命周期隨內(nèi)核。
  6. 系統(tǒng)中可以同時存在多個消息隊列,消息隊列在內(nèi)核中管理,也要先描述,再組織,因此消息隊列=隊列+隊列的屬性。

System V消息隊列函數(shù):

msgget:創(chuàng)建或獲取一個消息隊列標(biāo)識符。
原型:
? ? ? ?#include <sys/types.h>
? ? ? ?#include <sys/ipc.h>
? ? ? ?#include <sys/msg.h>
???????int msgget(key_t key, int msgflg);
參數(shù):

  • ??key:用于標(biāo)識消息隊列的鍵值,可以是一個已存在的鍵值或者通過ftok函數(shù)生成的鍵值。
  • ??msgflg:標(biāo)志位,用于控制消息隊列的創(chuàng)建和訪問權(quán)限。

返回值:成功時返回消息隊列標(biāo)識符,失敗時返回-1。
注意:msgflg參數(shù)可以設(shè)置權(quán)限標(biāo)志,如IPC_CREAT(創(chuàng)建消息隊列)、IPC_EXCL(創(chuàng)建時檢查消息隊列是否存在)等。用法與System V共享內(nèi)存shmget函數(shù)的shmflg參數(shù)相同。

msgctl:控制消息隊列。
原型:
? ? ? ?#include <sys/types.h>
? ? ? ?#include <sys/ipc.h>
? ? ? ?#include <sys/msg.h>
???????int msgctl(int msqid, int cmd, struct msqid_ds *buf);
參數(shù):

  • ??msqid:消息隊列標(biāo)識符。
  • ??cmd:操作命令,如IPC_STAT(獲取消息隊列狀態(tài))、IPC_SET(設(shè)置消息隊列屬性)、IPC_RMID(刪除消息隊列)等。
  • ??buf:指向struct msqid_ds的指針,用于存儲消息隊列的狀態(tài)信息。

返回值:成功時返回0,失敗時返回-1。

msgsnd:向消息隊列發(fā)送消息。
原型:
? ? ? ?#include <sys/types.h>
? ? ? ?#include <sys/ipc.h>
? ? ? ?#include <sys/msg.h>
???????int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
參數(shù):

  • ??msqid:消息隊列標(biāo)識符。
  • ??msgp:指向消息的指針。
  • ??msgsz:消息的大小。
  • ??msgflg:標(biāo)志位,用于控制發(fā)送操作的行為。

返回值:成功時返回0或消息大小,失敗時返回-1。

msgrcv:從消息隊列接收消息。
原型:
? ? ? ?#include <sys/types.h>
? ? ? ?#include <sys/ipc.h>
? ? ? ?#include <sys/msg.h>
???????ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
參數(shù):

  • ??msqid:消息隊列標(biāo)識符。
  • ??msgp:指向接收消息緩沖區(qū)的指針。
  • ??msgsz:接收緩沖區(qū)的大小。
  • ??msgtyp:接收消息的類型值。
  • ??msgflg:標(biāo)志位,用于控制接收操作的行為。

返回值:成功時返回接收到的消息大小,失敗時返回-1。

msgsnd和msgrcv函數(shù)的msgflg參數(shù)可以設(shè)置阻塞標(biāo)志,如MSG_EXCEPT(接收除指定類型外的消息)、MSG_NOERROR(如果接收消息失敗,返回-1而不是設(shè)置錯誤碼)等。

msqid_ds數(shù)據(jù)結(jié)構(gòu)定義如下:

struct msqid_ds {struct ipc_perm msg_perm; ? ? /* Ownership and permissions */time_t ? ? ? ? ?msg_stime; ? ?/* Time of last msgsnd(2) */time_t ? ? ? ? ?msg_rtime; ? ?/* Time of last msgrcv(2) */time_t ? ? ? ? ?msg_ctime; ? ?/* Time of last change */unsigned long ? __msg_cbytes; /* Current number of bytes inqueue (nonstandard) */msgqnum_t ? ? ? msg_qnum; ? ? /* Current number of messagesin queue */msglen_t ? ? ? ?msg_qbytes; ? /* Maximum number of bytesallowed in queue */pid_t        msg_lspid; ? ?/* PID of last msgsnd(2) */pid_t        msg_lrpid; ? ?/* PID of last msgrcv(2) */
};

ipc_perm結(jié)構(gòu)定義如下:

struct ipc_perm {key_t ? ? ? ? ?__key; ? ? ? /* Key supplied to msgget(2) */uid_t ? ? ? ? ?uid; ? ? ? ? /* Effective UID of owner */gid_t ? ? ? ? ?gid; ? ? ? ? /* Effective GID of owner */uid_t ? ? ? ? ?cuid; ? ? ? ?/* Effective UID of creator */gid_t ? ? ? ? ?cgid; ? ? ? ?/* Effective GID of creator */unsigned short mode; ? ? ? ?/* Permissions */unsigned short __seq; ? ? ? /* Sequence number */
};

6.2 system V信號量

System V信號量函數(shù)也有semget、semctl、semop函數(shù),在此不講述。它們用法也和共享內(nèi)存、消息隊列類似,因為都是system V系列的。

semid_ds結(jié)構(gòu)體定義如下:

struct semid_ds {struct ipc_perm sem_perm; ?/* Ownership and permissions */time_t ? ? ? ? ?sem_otime; /* Last semop time */time_t ? ? ? ? ?sem_ctime; /* Last change time */unsigned long ? sem_nsems; /* No. of semaphores in set */
};

ipc_perm定義如下:

struct ipc_perm {key_t ? ? ? ? ?__key; /* Key supplied to semget(2) */uid_t ? ? ? ? ?uid; ? /* Effective UID of owner */gid_t ? ? ? ? ?gid; ? /* Effective GID of owner */uid_t ? ? ? ? ?cuid; ?/* Effective UID of creator */gid_t ? ? ? ? ?cgid; ?/* Effective GID of creator */unsigned short mode; ?/* Permissions */unsigned short __seq; /* Sequence number */
};

?信號量的本質(zhì)是一組計數(shù)器。信號量主要用于同步和互斥。

為了讓進(jìn)程間能夠通信,就要讓多個進(jìn)程看到同一份資源,這份資源稱為公共資源,使用公共資源就可能導(dǎo)致并發(fā)訪問、數(shù)據(jù)不一致問題,例如讀的時候另一個進(jìn)程讀、讀的時候另一個進(jìn)行寫、寫的時候。。。所以就需要在一個進(jìn)程使用資源的時候,將這份資源保護(hù)起來,所有進(jìn)程按順序使用,這就是互斥和同步。

互斥:任何一個時刻只允許一個執(zhí)行流(進(jìn)程)訪問公共資源,(加鎖實現(xiàn)的)

同步:多個執(zhí)行流執(zhí)行時,按照一定的順序執(zhí)行。

臨界資源:被保護(hù)起來的公共資源。(不是臨界資源的就是非臨界資源)

臨界區(qū):訪問該臨界區(qū)的代碼。(維護(hù)臨界資源就是維護(hù)臨界區(qū))

原子性:只有兩態(tài),要么沒做,要么做完。

比如在電影院買票,電影院和內(nèi)部座位就是多人共享的資源?--- 公共資源(可能被拆分為多份資源)。我們買票的本質(zhì):是對資源的預(yù)訂機(jī)制。可以看成,電影院有一個計數(shù)器用來表示公共資源的個數(shù)。別人買票時要先看計數(shù)器內(nèi)還有沒有剩余的座位,有的話就分配,計數(shù)器--,沒有就讓那人等著。

如果公共資源沒有被拆分只有一份,用二元信號量int sem =1表示互斥鎖來完成互斥功能,在臨界區(qū)前面和后面加上維護(hù)代碼,檢測sem是否有剩余,如果有剩余就允許繼續(xù)臨界區(qū)的代碼、sem--,沒有剩余就繼續(xù)等待,直至有一個臨界區(qū)完成并sem++。其實這個信號量也可以看作一個結(jié)構(gòu)體,里面有一個計數(shù)器和一個等待隊列,沒有剩余就將進(jìn)程放入等待隊列中,知道有一個sem++,就執(zhí)行等待隊列的下一個進(jìn)程。

信號量:表示資源數(shù)目的計數(shù)器,每一個執(zhí)行流想訪問公共資源內(nèi)的某一份資源,不應(yīng)該讓執(zhí)行流直接訪問,而是先申請信號量資源,其實就是先對信號量計數(shù)器進(jìn)行--操作。本質(zhì)上,只要--成功,完成了對資源的預(yù)訂機(jī)制,如果申請不成功,執(zhí)行流被掛起阻塞。

七、回顧共享內(nèi)存數(shù)據(jù)結(jié)構(gòu)

在看到共享內(nèi)存、消息隊列和信號量的數(shù)據(jù)結(jié)構(gòu)后,發(fā)現(xiàn)它們都使用了ipc_perm結(jié)構(gòu)體,而且都是位于對應(yīng)數(shù)據(jù)結(jié)構(gòu)的第一個,這是因為在底層中,在系統(tǒng)層面有一個類型為kern_ipc_perm *p[0]的柔性指針數(shù)組,通過該數(shù)組管理所有的IPC資源。例如創(chuàng)建一個共享內(nèi)存的數(shù)據(jù)結(jié)構(gòu)shmid_ds,在柔性指針數(shù)組中加上對應(yīng)的ipc_perm結(jié)構(gòu)體的地址,將來對shmid_ds進(jìn)行管理時,由于ipc_perm結(jié)構(gòu)體是shmid_ds第一個元素,所以只需要對它進(jìn)行類型轉(zhuǎn)換,就可以變成shmid_ds的地址,就可以對它的數(shù)據(jù)成員進(jìn)行操作。例如(shmid_ds *)p[1] 、 (msqid_ds*)p[2]。

http://www.risenshineclean.com/news/48078.html

相關(guān)文章:

  • 學(xué)會建網(wǎng)站如何做網(wǎng)絡(luò)營銷友情鏈接的作用大不大
  • 網(wǎng)站開發(fā)的背景百度百科官網(wǎng)
  • 公司網(wǎng)站抬頭用什么軟件做關(guān)鍵詞搜索數(shù)據(jù)
  • 網(wǎng)站開發(fā)簡單的框架營銷云
  • 答題app怎么制作淘寶seo優(yōu)化是什么
  • 大連企業(yè)公司網(wǎng)站建設(shè)百度指數(shù)移動版怎么用
  • 校園招聘網(wǎng)站開發(fā)研究背景排名公式
  • wordpress導(dǎo)航菜單404百度seo優(yōu)化軟件
  • 建網(wǎng)站 外貿(mào)微信推廣平臺哪里找
  • 做網(wǎng)站的IDE百度廣告電話號碼是多少
  • 酒業(yè)為什么做網(wǎng)站綜合搜索引擎
  • b2c電子商務(wù)網(wǎng)站建設(shè)費用河北網(wǎng)站建設(shè)案例
  • 微軟網(wǎng)站做u盤啟動教程網(wǎng)頁設(shè)計基礎(chǔ)
  • 網(wǎng)易做網(wǎng)站百度助手安卓版下載
  • 網(wǎng)站建設(shè)通路品牌網(wǎng)站建設(shè)
  • 如何查一個網(wǎng)站有沒有做外鏈鄭州網(wǎng)站制作選擇樂云seo
  • 代刷網(wǎng)站系統(tǒng)怎么做天貓關(guān)鍵詞排名怎么控制
  • 做網(wǎng)站需要學(xué)那幾個軟件百度網(wǎng)址大全設(shè)為主頁
  • 微網(wǎng)站建設(shè)高端網(wǎng)站定制排名seo公司哪家好
  • 網(wǎng)站側(cè)邊欄代碼企業(yè)培訓(xùn)考試平臺官網(wǎng)
  • 有實力自適應(yīng)網(wǎng)站建設(shè)哪家好視頻號推廣
  • 網(wǎng)站開發(fā)技術(shù)的現(xiàn)狀及發(fā)展趨勢今日十大熱點新聞
  • 做字的網(wǎng)站關(guān)鍵詞優(yōu)化公司網(wǎng)站
  • 做網(wǎng)站百度網(wǎng)盤網(wǎng)頁版登錄入口
  • 漳州最專業(yè)的網(wǎng)站建設(shè)公司南寧百度首頁優(yōu)化
  • seo優(yōu)化操作淘寶怎么優(yōu)化關(guān)鍵詞步驟
  • 網(wǎng)站設(shè)計用什么軟件實現(xiàn)營銷渠道有哪幾種
  • 做品管圈網(wǎng)站企業(yè)seo培訓(xùn)
  • 怎么在網(wǎng)站中做彈窗廣告百度官方官網(wǎng)
  • 做網(wǎng)站需要ui設(shè)計嗎淘寶新店怎么快速做起來