日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

【Linux】-- 进程间通讯

發(fā)布時(shí)間:2024/3/24 linux 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Linux】-- 进程间通讯 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

進(jìn)程間通訊概念的引入

意義(手段)

思維構(gòu)建

進(jìn)程間通信方式

管道

站在用戶角度-淺度理解管道

匿名管道 pipe函數(shù)

站在文件描述符角度-深度理解管道

管道的特點(diǎn)總結(jié)

管道的拓展

單機(jī)版的負(fù)載均衡

匿名管道讀寫規(guī)則

命名管道

前言

原理

創(chuàng)建一個(gè)命名管道

用命名管道實(shí)現(xiàn)myServer&myClient通信

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

命名管道的打開規(guī)則

system V共享內(nèi)存

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

共享內(nèi)存的創(chuàng)建

key概念引入

key概念解析

基于共享內(nèi)存理解信號(hào)量

總結(jié)


進(jìn)程間通訊概念的引入

意義(手段)

? ? ? ? 在沒有進(jìn)程間通訊之前,理論上都是單進(jìn)程的,那么也就無法使用并發(fā)能力,更無法實(shí)現(xiàn)多進(jìn)程協(xié)同(將一個(gè)事,分幾個(gè)進(jìn)程做)。而進(jìn)程間通訊,就是對(duì)于實(shí)現(xiàn)多進(jìn)程協(xié)同的手段

  • 數(shù)據(jù)傳輸:一個(gè)進(jìn)程需要將它的數(shù)據(jù)發(fā)送給另一個(gè)進(jìn)程
  • 資源共享:多個(gè)進(jìn)程之間共享同樣的資源。
  • 通知事件:一個(gè)進(jìn)程需要向另一個(gè)或一組進(jìn)程發(fā)送消息,通知它(它們)發(fā)生了某種事件(如進(jìn)程終止時(shí)要通知父進(jìn)程)。
  • 進(jìn)程控制:有些進(jìn)程希望完全控制另一個(gè)進(jìn)程的執(zhí)行(如Debug進(jìn)程),此時(shí)控制進(jìn)程希望能夠攔截另一個(gè)進(jìn)程的所有陷入和異常,并能夠及時(shí)知道它的狀態(tài)改變。

思維構(gòu)建

? ? ? ? 進(jìn)程間通訊重點(diǎn),就在與如何讓不同的進(jìn)程資源的傳遞。而進(jìn)程是具有獨(dú)立性的,也就是說進(jìn)程相通訊會(huì)難度較大? --? 因?yàn)檫M(jìn)程間通訊的本質(zhì)是:先讓不同的進(jìn)程看見同一份資源

融匯貫通的理解:

? ? ? ? 進(jìn)程的設(shè)計(jì)天然就是為了保證獨(dú)立性的(即,進(jìn)程之間無瓜葛),所以深入的說:所謂的同一份資源不能所屬于任何一個(gè)進(jìn)程,更強(qiáng)調(diào)共享,不屬于任何一個(gè)進(jìn)程。

進(jìn)程間通信方式

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息隊(duì)列
  • System V 共享內(nèi)存
  • System V 信號(hào)量

POSIX IPC

  • 消息隊(duì)列
  • 共享內(nèi)存
  • 信號(hào)量
  • 互斥量
  • 條件變量
  • 讀寫鎖

管道

????????我們把從一個(gè)進(jìn)程連接到另一個(gè)進(jìn)程的數(shù)據(jù)流稱為一個(gè)“管道”。

????????當(dāng)在兩個(gè)命令之間設(shè)置管道 "|" 時(shí),管道符 "|" 左邊命令的輸出就變成了右邊命令的輸入。只要第一個(gè)命令向標(biāo)準(zhǔn)輸出寫入,而第二個(gè)令是從標(biāo)準(zhǔn)輸入讀取,那么這兩個(gè)命令就可以形成一個(gè)管道。大部分的 Linux 命令都可以用來形成管道。

命令:who | wc -l


用于查看當(dāng)前服務(wù)器下登陸的用戶人數(shù)。

補(bǔ)充:

????????Linux who命令:用于顯示系統(tǒng)中有哪些使用者正在上面,顯示的資料包含了使用者 ID、使用的終端機(jī)、從哪邊連上來的、上線時(shí)間、呆滯時(shí)間、CPU 使用量、動(dòng)作等等。使用權(quán)限:所有使用者都可使用。


????????Linux wc命令:用于計(jì)算字?jǐn)?shù)。在此處由于who中一個(gè)用戶為一行,所以此處用 -l?顯示行數(shù),即登錄用戶個(gè)數(shù)。

? ? ? ? 其中,運(yùn)行起來后who命令與wc命令就是兩個(gè)不同的進(jìn)程。who進(jìn)程作為數(shù)據(jù)提供方,通過標(biāo)準(zhǔn)輸入將數(shù)據(jù)寫入管道,wc進(jìn)程再通過標(biāo)準(zhǔn)輸入將數(shù)據(jù)從管道中讀取出,進(jìn)而再將數(shù)據(jù)進(jìn)行處理?"-l" ,后以標(biāo)準(zhǔn)輸出的方式將結(jié)果給用戶。

站在用戶角度-淺度理解管道

匿名管道 pipe函數(shù)

#include <unistd.h>

功能: ????????創(chuàng)建一無名管道。 原型: ????????int pipe(int?pipefd[2]); 參數(shù): ? ? ? ? 輸出型參數(shù),通過調(diào)用該參數(shù),得到被打開的文件fd。 數(shù)組元素含義
pipefd[0]管道讀端文件描述符
pipefd[1]管道寫端文件描述符

返回值:
????????成功時(shí),返回0。出現(xiàn)錯(cuò)誤時(shí),返回-1。

1. 父進(jìn)程創(chuàng)建管道?

2. 父進(jìn)程fork出子進(jìn)程

3. 父進(jìn)程關(guān)閉讀 / 寫,子進(jìn)程關(guān)閉寫 / 讀。(fork之后各自關(guān)掉不用的描述符)

Note:對(duì)于pipe函數(shù)創(chuàng)建的管道,只能夠進(jìn)行單向通信。(反之,會(huì)導(dǎo)致讀寫導(dǎo)致管道中數(shù)據(jù)污染、混亂)。我們需要對(duì)于父或子進(jìn)程中的fd參數(shù)中的,文件符號(hào)進(jìn)行關(guān)閉。

????????pipe函數(shù)的使用需要結(jié)合fork函數(shù)的父子進(jìn)程。

站在文件描述符角度-深度理解管道

#問:如何做到讓不同的進(jìn)程,看到同一份資源?

? ? ? ? 以fork讓子進(jìn)程繼承,能夠讓具有“血緣關(guān)系”的進(jìn)程進(jìn)行進(jìn)程間通訊。(管道:常用于父進(jìn)程進(jìn)程)

融匯貫通的理解:

? ? ? ? fork創(chuàng)建子進(jìn)程,等于系統(tǒng)中多了一個(gè)子進(jìn)程。而進(jìn)程 = 內(nèi)核數(shù)據(jù)結(jié)構(gòu) + 進(jìn)程代碼和數(shù)據(jù)。進(jìn)程相關(guān)內(nèi)核數(shù)據(jù)結(jié)構(gòu)來源于操作系統(tǒng),進(jìn)程代碼和數(shù)據(jù)一般來源于磁盤。

????????而由于為了進(jìn)程具有獨(dú)立性,所以創(chuàng)建子進(jìn)程的同時(shí),需要分配對(duì)應(yīng)的進(jìn)程相關(guān)內(nèi)核結(jié)構(gòu)。對(duì)于數(shù)據(jù),被寫入更改時(shí)操作系統(tǒng)采用寫時(shí)拷貝技術(shù),進(jìn)行對(duì)父子進(jìn)程數(shù)據(jù)的分離。

? ? ? ? 父進(jìn)程與子進(jìn)程擁有自身的fd_array[]存儲(chǔ)文件描述符fd,但是其中存儲(chǔ)的fd時(shí)相同的,而文件相關(guān)內(nèi)核數(shù)據(jù),并不屬于進(jìn)程數(shù)據(jù)結(jié)構(gòu),所以并不會(huì)單獨(dú)為子進(jìn)程創(chuàng)建。于是:父進(jìn)程與子進(jìn)程指向的是一個(gè)文件? ->? 這就讓不同的進(jìn)程看到了同一份資源。

? ? ? ? 管道本質(zhì)上就是一個(gè)文件。一個(gè)具有讀寫功能,并且無需放入磁盤的文件(通道是進(jìn)程進(jìn)行通訊的臨時(shí)內(nèi)存空間,無需將內(nèi)容放入磁盤中保留)。

(tty:標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤)?

1. 父進(jìn)程創(chuàng)建管道?

?2. 父進(jìn)程fork出子進(jìn)程

?3. 父進(jìn)程關(guān)閉讀 / 寫,子進(jìn)程關(guān)閉寫 / 讀。(fork之后各自關(guān)掉不用的描述符)

代碼實(shí)現(xiàn)的關(guān)鍵:

  • 創(chuàng)建管道 -- 分別以讀寫方式打開同一個(gè)問題
  • 創(chuàng)建子進(jìn)程 -- 以fork函數(shù)創(chuàng)建子進(jìn)程
  • 構(gòu)造單向通訊的通道 -- 雙方進(jìn)程各自關(guān)閉自己不需要的文件描述符
  • #include <iostream> #include <unistd.h> #include <assert.h> #include <string> #include <string.h> #include <sys/wait.h> #include <sys/types.h>using namespace std;int main() {//1.創(chuàng)建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n; // 只被定義沒有被使用,Release下就會(huì)出現(xiàn)代碼大量告警 ?-- ?證明使用過// 用于調(diào)試驗(yàn)證fd申請(qǐng) #ifdef DEBUGcout << "pipefd[0]: " << pipefd[0] << endl;cout << "pipefd[1]: " << pipefd[1] << endl; #endif//2.創(chuàng)建子進(jìn)程pid_t id = fork();assert(id != -1);if(id > 1){// 子進(jìn)程 -- 只讀// 3.構(gòu)造單向通訊的通道, 父進(jìn)程寫入,子進(jìn)程讀取// 3.1 關(guān)閉子進(jìn)程不需要的fdclose(pipefd[1]);char child_buffer[1024*4];while(true){ssize_t s = read(pipefd[0], child_buffer, sizeof(child_buffer) - 1);//3.2 訪問控制:// a、寫入的一方,fd沒有關(guān)閉,如果有數(shù)據(jù),就讀,沒有數(shù)據(jù)就等// b、寫入的一方,fd關(guān)閉, 讀取的一方,read會(huì)返回0,表示讀到了文件的結(jié)尾!if(s > 0){child_buffer[s] = 0;cout << "child get a message[" << getpid() << "] Father# " << child_buffer << endl;}else if(s == 0){cout << "-----------writer quit(father), me quit!-----------" << endl;break;}}exit(0);}// 父進(jìn)程 -- 只寫// 3.構(gòu)造單向通訊的通道, 父進(jìn)程寫入,子進(jìn)程讀取// 3.1 關(guān)閉父進(jìn)程不需要的fdclose(pipefd[0]);string message = "我是父進(jìn)程,發(fā)送有效信息。";int count = 0; // 傳遞的次數(shù)char father_buffer[1024*4];while(true){//3.2 構(gòu)建一個(gè)變化的字符串snprintf(father_buffer, sizeof(father_buffer), "%s[%d] : %d",message.c_str(), getpid(), count++);//3.3 寫入write(pipefd[1], father_buffer, strlen(father_buffer));//3.4 故意sleep凸顯訪問控制sleep(1);if(count == 3){cout << "----------------father wirte quit!----------------" <<endl;break;} }close(pipefd[1]);pid_t ret = waitpid(id, nullptr, 0);assert(ret > 0);(void)ret;return 0; }

    管道的特點(diǎn)總結(jié)

    1. 管道是用來進(jìn)程具有血緣關(guān)系的進(jìn)程進(jìn)行進(jìn)程間通訊。

    2. 管道具有通過讓進(jìn)程間通訊,提供訪問控制。

    ? ? ? ? a、寫端快,讀端慢,寫滿了不能再寫了。

    ? ? ? ? b、寫端慢,讀端快,管道沒有數(shù)據(jù)的時(shí)候,讀需要等待。

    補(bǔ)充:

    ? ? ? ? c、寫端關(guān)閉,讀端為0,標(biāo)識(shí)讀到了文件結(jié)尾。

    ? ? ? ? d、讀端關(guān)閉,寫端繼續(xù)寫,操作系統(tǒng)終止寫端進(jìn)程。

    3. 管道提供的是面向流式的通信服務(wù) -- 面向字節(jié)流。

    4. 管道是基于文件的,文件的生命周期是隨進(jìn)程的,所以管道的生命周期是隨進(jìn)程的。

    5. 管道是單向通行的,就是半雙工通信的一種特殊情況。

    數(shù)據(jù)的傳送方式可以分為三種:

    ? ? ? ? 單工通信(Half Duplex)是通訊傳輸?shù)囊粋€(gè)術(shù)語。一方固定為發(fā)送端,另一方固定為接收端。即:一方只能寫一方只能讀。

    ????????半雙工通信(Half Duplex)是通訊傳輸?shù)囊粋€(gè)術(shù)語。指數(shù)據(jù)傳輸指數(shù)據(jù)可以在一個(gè)信號(hào)載體的兩個(gè)方向上傳輸,但是不能同時(shí)傳輸。即:一段時(shí)間內(nèi),只能一方寫一方讀。

    ????????全雙工通信(Full Duplex)是通訊傳輸?shù)囊粋€(gè)術(shù)語。指通信允許數(shù)據(jù)在兩個(gè)方向上同時(shí)傳輸,它在能力上相當(dāng)于兩個(gè)單工通信方式的結(jié)合。即:一段時(shí)間內(nèi),每方能寫且讀。

    管道的拓展

    單機(jī)版的負(fù)載均衡

    ? ? ? ? 以循環(huán)fork函數(shù)開辟多個(gè)子進(jìn)程,并利用pipe函數(shù)。針對(duì)于每一個(gè)子進(jìn)程開辟一個(gè)管道,父進(jìn)程通過管道安排其中一個(gè)子進(jìn)程做某任務(wù)。

    #pragma once#include <iostream> #include <unordered_map> #include <string> #include <functional>typedef std::function<void()> func;std::vector<func> callbacks; std::unordered_map<int, std::string> desc;void readMySQL() {std::cout << "sub process[" << getpid() << "]執(zhí)行訪問數(shù)據(jù)庫的任務(wù)" << std::endl; }void executeUlt() {std::cout << "sub process[" << getpid() << "]執(zhí)行url解析\n" << std::endl; }void cal() {std::cout << "sub process[" << getpid() << "] 執(zhí)行加密任務(wù)\n" << std::endl; }void save() {std::cout << "sub process[" << getpid() << "] 執(zhí)行數(shù)據(jù)持久化任務(wù)\n" << std::endl; }void load() {callbacks.push_back(readMySQL);desc.insert({callbacks.size(), "readMySQL: 執(zhí)行訪問數(shù)據(jù)庫的任務(wù)"});callbacks.push_back(executeUlt);desc.insert({callbacks.size(), "executeUlt: 進(jìn)行url解析"});callbacks.push_back(cal);desc.insert({callbacks.size(), "cal: 進(jìn)行加密計(jì)算"});callbacks.push_back(save);desc.insert({callbacks.size(), "save: 執(zhí)行數(shù)據(jù)持久化任務(wù)"}); }// 功能展示 void showHandler() {for(const auto &iter : desc)std::cout << iter.first << " -> " << iter.second << std::endl; }// 具有的功能數(shù) int handlerSize() {return callbacks.size(); } #include <iostream> #include <vector> #include <unistd.h> #include <cassert> #include <sys/wait.h> #include <sys/types.h> #include "Task.hpp"using namespace std;#define PROCESS_NUM 4int waitCommand(int waitfd, bool& quit) {//此處由于是父進(jìn)程寫入一個(gè)整數(shù) -- 用以子進(jìn)程執(zhí)行相關(guān)內(nèi)容//規(guī)定:子進(jìn)程讀取的數(shù)據(jù)必須是4字節(jié)uint32_t command = 0;ssize_t s = read(waitfd, &command, sizeof(command));if(s == 0){quit = 1;return -1;}assert(s == sizeof(uint32_t));return command; }void wakeUp(pid_t who, int fd, uint32_t command) {write(fd, &command, sizeof(command));cout << "main process call: " << who << "process," << " execute: " << desc[command] << ", through write fd: " << fd << endl; }int main() {load();// 存儲(chǔ):<子進(jìn)程id,父進(jìn)程對(duì)應(yīng)寫端符fd>vector<pair<pid_t, int>> slots;//1. 創(chuàng)建多個(gè)進(jìn)程for(int i = 0; i < PROCESS_NUM; ++i){//1.1 創(chuàng)建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);(void)n;//1.2 fork創(chuàng)建子進(jìn)程pid_t id = fork();assert(id != -1);(void)id;if(id == 0){// 子進(jìn)程 -- 關(guān)閉寫端close(pipefd[1]);while(true){// 用于判斷是否bool quit = 0;int command = waitCommand(pipefd[0], quit);if(quit){break;}if(command >= 1 && command <= handlerSize())callbacks[command - 1]();elsecout << "error, 非法操作" << endl;}exit(1);}//將父進(jìn)程讀端關(guān)閉close(pipefd[0]);slots.push_back(make_pair(id, pipefd[1]));}while(true){int select;int command;cout << "############################################" << endl;cout << "## 1. show funcitons 2.command ##" << endl;cout << "############################################" << endl;cout << "Please Select> ";cin >> select;if(select == 1)showHandler();else if(select == 2){cout << "Enter command" << endl;cin >> command;// 隨機(jī)挑選進(jìn)程int choice = rand() % PROCESS_NUM;//將任務(wù)指派給指定的進(jìn)程wakeUp(slots[choice].first, slots[choice].second, command);}elsecout << "輸入錯(cuò)誤,請(qǐng)重新輸入" << endl;}// 關(guān)閉父進(jìn)程寫端fd,所有的子進(jìn)程都會(huì)退出for(const auto &slot : slots)close(slot.second);// 回收所有的子進(jìn)程信息for(const auto &slot : slots)waitpid(slot.first, nullptr, 0);return 0; }

    匿名管道讀寫規(guī)則

    • 當(dāng)沒有數(shù)據(jù)可讀時(shí)
      • O_NONBLOCK disable:read調(diào)用阻塞,即進(jìn)程暫停執(zhí)行,一直等到有數(shù)據(jù)來到為止
      • O_NONBLOCK enable:read調(diào)用返回-1,errno值為EAGAIN。
    • 當(dāng)管道滿的時(shí)候
      • O_NONBLOCK disable: write調(diào)用阻塞,直到有進(jìn)程讀走數(shù)據(jù)
      • O_NONBLOCK enable:調(diào)用返回-1,errno值為EAGAIN
    • 如果所有管道寫端對(duì)應(yīng)的文件描述符被關(guān)閉,則read返回0
    • 如果所有管道讀端對(duì)應(yīng)的文件描述符被關(guān)閉,則write操作會(huì)產(chǎn)生信號(hào)SIGPIPE,進(jìn)而可能導(dǎo)致write進(jìn)程退出
    • 當(dāng)要寫入的數(shù)據(jù)量不大于PIPE_BUF時(shí),linux將保證寫入的原子性。
    • 當(dāng)要寫入的數(shù)據(jù)量大于PIPE_BUF時(shí),linux將不再保證寫入的原子性。

    原子性:要么做,要么不做,沒有所謂的中間狀態(tài)。

    POSIX.1-2001要求PIPE_BUF至少為512字節(jié)。(在Linux上,PIPE_BUF為4096字節(jié)。)

    拓展:

    ? ? ? ? 討論原子性,需要在多執(zhí)行流下,數(shù)據(jù)出現(xiàn)并發(fā)訪問的時(shí)候,討論原子性才有意義。(此處不深入)

    融會(huì)貫通的理解:

    ? ? ? ? 匿名管道就是一個(gè)文件,一個(gè)內(nèi)存級(jí)別的文件,并不會(huì)在磁盤上存儲(chǔ),并不會(huì)有自身的文件名。作為基礎(chǔ)間通訊的方式是:看見同一個(gè)文件 -- 通過父子進(jìn)程父子繼承的方式看見。

    ? ? ? ? 是一個(gè),只有通過具有 “血緣關(guān)系” 的進(jìn)程進(jìn)行使用,可以稱做:父子進(jìn)程通訊。

    命名管道

    前言

    ? ? ? ? 匿名管道只能使用于具有“親緣關(guān)系”的進(jìn)程之間通信,而對(duì)于毫無關(guān)系的兩個(gè)進(jìn)程無法使用匿名管道通訊,如果我們想在不相關(guān)的進(jìn)程之間交換數(shù)據(jù),可以使用FIFO文件來做這項(xiàng)工作,它經(jīng)常被稱為命名管道。命名管道是一種特殊類型的文件。

    原理

    ? ? ? ? 當(dāng)兩個(gè)進(jìn)程需要同時(shí)帶開一個(gè)文件的時(shí)候,由于為了保證進(jìn)程的獨(dú)立性,所以兩個(gè)進(jìn)程會(huì)有各自的files_struct,而對(duì)于文件數(shù)據(jù),并不會(huì)為每一個(gè)進(jìn)程都備一份(是內(nèi)存的浪費(fèi)),此時(shí)A進(jìn)程的files_struct與B進(jìn)程的files_struct是不同的,但是其中的文件符fd指向的是由磁盤文件加載到內(nèi)存中的同一份數(shù)據(jù)空間。

    ? ? ? ? 命名管道就是如此,其原理與匿名管道很相識(shí)。命名管道在磁盤中,所以其有自己的文件名、屬性信息、路徑位置……,但是其沒有文件內(nèi)容即,命名管道是內(nèi)存文件,其在磁盤中的本質(zhì)是命名管道在磁盤中的映像,且映像的大小永遠(yuǎn)為0。意義就是為了讓毫無關(guān)系的基進(jìn)程,皆能夠調(diào)用到命名管道。而管道中的數(shù)據(jù)是進(jìn)程通訊時(shí)的臨時(shí)數(shù)據(jù),無存儲(chǔ)的意義,所以命名管道在磁盤中為空。

    創(chuàng)建一個(gè)命名管道

    • 命名管道可以從命令行上創(chuàng)建:

    命令:mkfifo fifo


    創(chuàng)建一個(gè)名為fifo命名管道

    此時(shí)文件類型不是常用 - d 而是 p ,此文件的類型為管道:

    ?? ? ? ? 此時(shí)會(huì)發(fā)現(xiàn)處于等待狀態(tài)因?yàn)?/span>由于我們寫了,但是對(duì)方還沒有打開,于是處于阻塞狀態(tài)。

    ? ? ? ? 此時(shí) echo "hello name_pipe"(進(jìn)程A)就是寫入的進(jìn)程, cat(進(jìn)程B)就是讀取的進(jìn)程。這就是所謂的一個(gè)進(jìn)程向另一個(gè)進(jìn)程寫入消息的過程(通過管道寫入的方式)。

    ? ? ? ? 我們可以在命令行上使用循環(huán)的方式,往管道內(nèi)每隔1s寫入數(shù)據(jù)。即,進(jìn)程A原來應(yīng)向顯示器文件寫入的數(shù)據(jù),通過輸入重定向的方式,將數(shù)據(jù)寫入管道中,再將管道中數(shù)據(jù)通過輸出重定向,通過進(jìn)程B將數(shù)據(jù)寫入到顯示器文件中。如此,以毫無相關(guān)的進(jìn)程A與進(jìn)程B通過命名管道進(jìn)行數(shù)據(jù)傳輸 -?進(jìn)程間通信。

    ? ? ? ? 此時(shí)我們通過終止讀取進(jìn)程方,導(dǎo)致寫入端向管道寫入的數(shù)據(jù)無意義了(無讀取端),此時(shí)作為寫入端的進(jìn)程就應(yīng)該被操作系統(tǒng)殺掉。此時(shí)需要注意,echo是內(nèi)置命令,所以是bush本身自行執(zhí)行的命令,所以此時(shí)殺掉寫入端的進(jìn)程無疑就是殺掉bush。于是bush被操作系統(tǒng)殺死,云服務(wù)器即退出。

    內(nèi)置命令:讓父進(jìn)程(myshell)自己執(zhí)行的命令,叫做內(nèi)置命令,內(nèi)建命令。

    • 命名管道可以從程序里創(chuàng)建:

    #include <sys/types.h>

    #include <sys/stat.h>

    int mkfifo(const char *pathname, mode_t mode);

    參數(shù):

    ? ? pathname:創(chuàng)建的命名管道文件。

    • 路徑的方式給出。(會(huì)在對(duì)應(yīng)路徑下創(chuàng)建)
    • 文件名的方式給出。(默認(rèn)當(dāng)前路徑下創(chuàng)建)

    ????mode:創(chuàng)建命名管道文件的默認(rèn)權(quán)限。

    • 我們創(chuàng)建的文件權(quán)限會(huì)被umask(文件默認(rèn)掩碼)進(jìn)行影響,umask的默認(rèn)值:0002,而實(shí)際創(chuàng)建出來文件的權(quán)限為:mode&(~umask)。于是導(dǎo)致我們創(chuàng)建的權(quán)限未隨我們的想法,如:0666 -> 0664。需要我們利用umask函數(shù)更改默認(rèn)。
    • umask(0); //將默認(rèn)值設(shè)為 0000

    返回值:????????

    ????????命名管道創(chuàng)建成功,返回0。

    ????????命名管道創(chuàng)建失敗,返回-1。

    用命名管道實(shí)現(xiàn)myServer&myClient通信

    ? ? ? ? 利用命名管道,實(shí)現(xiàn)服務(wù)端myServer與客戶端myClient之間進(jìn)行通訊。將服務(wù)端myServer運(yùn)行起來并用mkfifo函數(shù)開辟一個(gè)命名管道。而客戶端myClient中利用open打開命名管道(命名管道本質(zhì)為文件),以write向管道中輸入數(shù)據(jù)。以此服務(wù)端myServer利用open打開命名管道,以read從管道中讀取數(shù)據(jù)。

    comm.hpp

    ? ? ? ? 所展開的頭文件集合。

    #ifndef _COMM_H_ #define _COMM_H_#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h>std::string ipcPath = "./fifo.ipc";#endif

    Log.hpp

    ? ? ? ? 編程的日志:就是當(dāng)前程序運(yùn)行的狀態(tài)。

    #ifndef _LOG_H_ #define _LOG_H_#include <iostream> #include <ctime>#define Debug 0 #define Notice 1 #define Warning 2 #define Error 3std::string msg[] = {"Debug","Notice","Warning","Error" }std::ostream &Log(std::string message, int level) {std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message; }#endif

    myServer.cc?

    細(xì)節(jié):

    ? ? ? ? mkfifo的第二個(gè)參數(shù)傳入權(quán)限0666之前需要以u(píng)mask(0),對(duì)于服務(wù)端因?yàn)橹恍枰诿艿乐凶x取數(shù)據(jù),所以以只讀的方式(O_RDONLY)open管道文件,后序以fork開辟子進(jìn)程,讓子進(jìn)程read讀取即可,同時(shí)也需要注意,C語言的字符串結(jié)尾必須是 '\0'(讀取大小:sizeof(buffer) - 1)。

    ? ? ? ? 由于我們讓子進(jìn)程執(zhí)行讀取工作,所以需要以waitpid等在子進(jìn)程(此處我們讓nums個(gè)子進(jìn)程進(jìn)行,所以waitpid的第一個(gè)參數(shù)為 -1 ,等待任意一個(gè)子進(jìn)程)。

    ? ? ? ? 由于open打開了管道類型的文件,所以需要以close(fd)關(guān)閉文件,由于mkfifo開辟了管道,所以需要以u(píng)nlink刪除管道文件。

    #include "comm.hpp"// 管道文件創(chuàng)建權(quán)限(umask == 0) #define MODE 0x0666// 讀取數(shù)據(jù)大小 #define READ_SIZE 64// 從管道文件讀取數(shù)據(jù) static void getMessage(int fd) {char buffer[READ_SIZE];while(true){memset(buffer, '\0', sizeof(buffer));ssize_t s = read(fd, buffer, sizeof(buffer) - 1); // C語言字符串需要保證結(jié)尾為'\0'if(s > 0){std::cout <<"[" << getpid() << "] "<< "myClient say> " << buffer << std::endl;}else if(s == 0){// 寫端關(guān)閉 - 讀到文件結(jié)尾std::cerr <<"[" << getpid() << "] " << "read end of file, clien quit, server quit too!" << std::endl;}else{// 讀取錯(cuò)誤perror("read");exit(3);}} }int main() {//1. 創(chuàng)建管道文件umask(0);if(mkfifo(ipcPath.c_str(), MODE) < 0){perror("mkfifo");exit(1);}#ifdef DEBUGLog("創(chuàng)建管道文件成功", Debug) << " step 1 " << std::endl;#endif//2. 正常的文件操作int fd = open(ipcPath.c_str(), O_RDONLY);if(fd < 0){perror("open");exit(2);}#ifdef DEBUGLog("打開管道文件成功", Debug) << " step 2 " << std::endl;#endifint nums = 3;// 創(chuàng)建3個(gè)子進(jìn)程for(int i = 0; i < nums; ++i){pid_t id = fork();if(fd == 0){// 子進(jìn)程 - 讀取管道數(shù)據(jù)getMessage(fd);exit(1);}}// 父進(jìn)程 - 等待子進(jìn)程for(int i = 0; i < nums; i++){waitpid(-1, nullptr, 0);}// 4. 關(guān)閉管道文件close(fd);#ifdef DEBUGLog("關(guān)閉管道文件成功", Debug) << " step 3 " << std::endl;#endifunlink(ipcPath.c_str()); // 通信完畢,就刪除管道文件#ifdef DEBUGLog("刪除管道文件成功", Debug) << " step 4 " << std::endl;#endifreturn 0; }

    myClient.cc

    細(xì)節(jié):

    ? ? ? ? 對(duì)于客戶端因?yàn)橹恍枰诿艿乐袑懭霐?shù)據(jù),所以以只寫的方式(O_WRONLY)open管道文件,后序write即可。

    #include "comm.hpp"int main() {//1. 獲取管道文件 - 以寫的方式打開命名管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd < 0){perror("open");exit(1);}//2. ipc過程std::string buffer; //用戶級(jí)緩沖區(qū)while(true){std::cout << "Please Enter Message Line :> ";std::getline(std::cin, buffer);write(fd, buffer.c_str(), buffer.size());}//3. 通信完畢,關(guān)閉命名管道文件close(fd);return 0; }

    ????????由于命名管道的創(chuàng)建是在服務(wù)端myServer中,所以需要先運(yùn)行myServer。

    ? ? ? ? 服務(wù)端myServer進(jìn)程運(yùn)行起來,我們就能看到創(chuàng)建的命名管道文件。此時(shí)服務(wù)端myServer處于阻塞狀態(tài)也是管道文件的特性(寫入端未開辟,讀取端需要等待寫入端開辟)。

    ? ? ? ? 可以通過 ps 命令查看進(jìn)程是否相關(guān):

    ? ? ? ? 從此可以看出myServer與myClient是毫無相關(guān)的進(jìn)程,即myServer的三個(gè)子進(jìn)程與myClient也是毫無相關(guān)的進(jìn)程。

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

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

    命名管道的打開規(guī)則

    • 如果當(dāng)前打開操作是為讀而打開FIFO時(shí)
      • O_NONBLOCK disable:阻塞直到有相應(yīng)進(jìn)程為寫而打開該FIFO
      • O_NONBLOCK enable:立刻返回成功
    • 如果當(dāng)前打開操作是為寫而打開FIFO時(shí)
      • O_NONBLOCK disable:阻塞直到有相應(yīng)進(jìn)程為讀而打開該FIFO
      • O_NONBLOCK enable:立刻返回失敗,錯(cuò)誤碼為ENXIO

    system V共享內(nèi)存

    ????????system V共享內(nèi)存是與管道不同的,管道基于操作系統(tǒng)已有的文件操作。文件部分,無論有沒有通訊的需求,這個(gè)文件都需要維護(hù),有沒有通訊都需要和指定進(jìn)程建立關(guān)聯(lián),通不通訊都會(huì)有。

    ????????而共享內(nèi)存是,不用來通訊,操作系統(tǒng)就不用進(jìn)行管理,只有需要使用時(shí),操作系統(tǒng)才提供 - 有通訊才會(huì)有,共享內(nèi)存。共享內(nèi)存是操作系統(tǒng)單獨(dú)設(shè)立的內(nèi)核模塊,專門為進(jìn)程間通訊設(shè)計(jì)?--? 這個(gè)內(nèi)核模塊就是system V。

    ? ? ? ? 即:前面的匿名管道、命名管道通訊是恰好使用文件方案可以實(shí)現(xiàn)。而共享內(nèi)存是操作系統(tǒng)專門為了通訊設(shè)計(jì)。

    共享內(nèi)存的建立:

    • 共享區(qū):共享內(nèi)存、內(nèi)存映射和共享庫保存位置。

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

    ? ? ? ? 共享內(nèi)存的提供者,是操作系統(tǒng)。

    ? ? ? ? 大量的進(jìn)程進(jìn)行通訊 -> 共享內(nèi)存是大量的。所以,操作系統(tǒng)對(duì)于共享內(nèi)存需要進(jìn)行管理,需要管理 -> 先描述,再組織 -> 重新理解:共享內(nèi)存 = 共享內(nèi)存塊 + 對(duì)應(yīng)的共享內(nèi)存的內(nèi)核數(shù)據(jù)結(jié)構(gòu)。

    共享內(nèi)存的數(shù)據(jù)結(jié)構(gòu)?shmid_ds ?/usr/include/linux/shm.h 中定義:

    (cat命令即可)

    struct shmid_ds

    {
    ????????struct ipc_perm?? ??? ?shm_perm;?? ?
    /* operation perms */
    ????????int?? ??? ??? ?shm_segsz;?? ?/* size of segment (bytes) */
    ????????__kernel_time_t?? ??? ?shm_atime;?? ?/* last attach time */
    ????????__kernel_time_t?? ??? ?shm_dtime;?? ?/* last detach time */
    ????????__kernel_time_t?? ??? ?shm_ctime;?? ?/* last change time */
    ????????__kernel_ipc_pid_t?? ?shm_cpid;?? ?/* pid of creator */
    ????????__kernel_ipc_pid_t?? ?shm_lpid;?? ?/* pid of last operator */
    ????????unsigned short?? ??? ?shm_nattch;?? ?/* no. of current attaches */
    ????????unsigned short ?? ??? ?shm_unused;?? ?/* compatibility */
    ????????void ?? ??? ??? ?*shm_unused2;?? ?/* ditto - used by DIPC */
    ????????void?? ??? ??? ?*shm_unused3;?? ?/* unused */
    };

    ????????此處首先提一下key值(后面共享內(nèi)存的建立引入),其是在上面的共享內(nèi)存的第一個(gè)參數(shù)struct ipc_perm類型的shm_perm變量中的一個(gè)變量。

    ?/usr/include/linux/ipc.h?中定義:

    struct ipc_perm
    {
    ????????__kernel_key_t?? ?key;
    ????????__kernel_uid_t?? ?uid;
    ????????__kernel_gid_t?? ?gid;
    ????????__kernel_uid_t?? ?cuid;
    ????????__kernel_gid_t?? ?cgid;
    ????????__kernel_mode_t?? ?mode;?
    ????????unsigned short?? ?seq;
    };

    共享內(nèi)存的創(chuàng)建

    #include <sys/ipc.h> #include <sys/shm.h> // 用來創(chuàng)建共享內(nèi)存 int shmget(key_t key, size_t size, int shmflg);

    參數(shù):

    ????????key:這個(gè)共享內(nèi)存段名字。

    ????????size:共享內(nèi)存大小。

    • 大小建議為4096的整數(shù)倍。(原因使用時(shí)講解)

    ????????shmflg:由九個(gè)權(quán)限標(biāo)志構(gòu)成,它們的用法和創(chuàng)建文件時(shí)使用的mode模式標(biāo)志是一樣的。

    組合方式作用
    IPC_CREAT創(chuàng)建共享內(nèi)存,如果底層已經(jīng)存在,獲取之,并且返回。如果底層不存在,創(chuàng)建之,并且返回。
    IPC_EXCL沒有意義
    IPC_CREAT | IPC_EXCL創(chuàng)建共享內(nèi)存,如果底層不存在,創(chuàng)建之,并且返回。如果底層存在,出錯(cuò)返回。

    IPC_CREAT | IPC_EXCL意義:可以保證,放回成功一定是一個(gè)全新的共享內(nèi)存(shm)。

    此外創(chuàng)建需要權(quán)限的初始化:

    ????????如:IPC_CREAT | IPC_EXCL | 0666

    返回值:

    ????????成功返回一個(gè)非負(fù)整數(shù),即該共享內(nèi)存段的標(biāo)識(shí)碼(用戶層標(biāo)識(shí)符);失敗返回-1。

    key概念引入

    ????????進(jìn)程間通訊,首先需要保證的看見同一份資源。

    融會(huì)貫通的理解:

    • 匿名管道:通過pipe函數(shù)開辟內(nèi)存級(jí)管道 -- 本質(zhì)是文件 -- 通過pipe函數(shù)的參數(shù)(文件符fd)--?看見同一份資源。
    • 命名管道:通過mkfifo函數(shù)根據(jù)路徑開辟管道文件(可以從權(quán)限p看出)-- 本質(zhì)是開辟一個(gè)文件(可以從第二個(gè)參數(shù)需要初始化權(quán)限看出)-- 利用open、write、read、close文件級(jí)操作?--?看見同一份資源。

    ????????管道 -- 內(nèi)存級(jí)文件 -- 恰巧利用文件操作。前面已有所提system V共享內(nèi)存,是操作系統(tǒng)為進(jìn)程間通訊專門設(shè)計(jì)?,并無法利用類似于管道利用文件實(shí)現(xiàn)。于是便有了key。

    key概念解析

    ? ? ? ? key其實(shí)就是一個(gè)整數(shù),是一個(gè)利用算法實(shí)現(xiàn)的整數(shù)。我們可以將key想象為一把鑰匙,而共享內(nèi)存為一把鎖。

    ? ? ? ? 更像是同心鎖和一對(duì)對(duì)情侶,情侶拿著同樣的鑰匙只可解一堆鎖中的一把鎖。

    ? ? ? ? 如同一把鑰匙會(huì)按照固定的形狀制造。其會(huì)使用同樣的算法規(guī)則形成一個(gè)唯一值key,同時(shí)再創(chuàng)建共享內(nèi)存時(shí),會(huì)將key值設(shè)置進(jìn)其中,此時(shí)兩個(gè)毫無關(guān)系的進(jìn)程,就可以通過key值用共享內(nèi)存進(jìn)行通訊(一方創(chuàng)建共享內(nèi)存,一方獲取共享內(nèi)存)。

    制造唯一值key的算法:

    #include <sys/types.h> #include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);

    ????????其不進(jìn)行任何系統(tǒng)調(diào)用,其內(nèi)部是一套算法,該算法就是將兩個(gè)參數(shù)合起來,形成一個(gè)唯一值就可以,數(shù)值是幾不重要。(對(duì)于第一個(gè)參數(shù),ftok是拿帶文件的inode標(biāo)號(hào),所以路徑可以隨意寫,但必須保證具體訪問權(quán)限),proj_id(項(xiàng)目id),隨意寫即可,一般是0~255之間,可以隨便寫,因?yàn)槌似湟矔?huì)直接截?cái)唷?/p>

    返回值:

    ? ? ? ? 成功后,返回生成的key_t值。失敗時(shí)返回-1。


    note:

    • 終究就是個(gè)簡易的算法,所以key值可能會(huì)產(chǎn)生沖突,于是可以對(duì)傳入ftok函數(shù)的參數(shù)進(jìn)行修改。
    • 需要保證需要通訊的進(jìn)程使用的?pathname??proj_id 相同,如此才能保證生成的是同一個(gè)key值。

    簡易的使用shmget函數(shù)結(jié)合ftok函數(shù):

    ????????其不進(jìn)行任何系統(tǒng)調(diào)用,其內(nèi)部是一套算法,該算法就是將兩個(gè)參數(shù)合起來,形成一個(gè)唯一值就可以,數(shù)值是幾不重要。(對(duì)于第一個(gè)參數(shù),ftok是拿帶文件的inode標(biāo)號(hào),路徑可以隨意寫,但必須保證具體訪問權(quán)限)

    ? ? ? ? 兩個(gè)進(jìn)程要通訊,就要保證兩個(gè)看見統(tǒng)一個(gè)共享內(nèi)存,本質(zhì)上:保證兩個(gè)看到同一個(gè)key。

    ? ? ? ? 與文件不同,文件是打開了,最后進(jìn)程退出,文件沒有進(jìn)程與其關(guān)聯(lián),文件就會(huì)自動(dòng)釋放。

    ? ? ? ? 操作系統(tǒng)為了維護(hù)共享內(nèi)存,就需要先描述,再組織。所以,共享內(nèi)存在內(nèi)核里,處理共享內(nèi)存的存儲(chǔ)內(nèi)存空間,也需要存儲(chǔ)對(duì)其描述信息的數(shù)據(jù)結(jié)構(gòu)。所以,為了設(shè)置或獲取其的屬性,就通過第三個(gè)參數(shù)。(當(dāng)只需要?jiǎng)h除的時(shí)候,第三個(gè)參數(shù)設(shè)為nullptr即可)

    ? ? ? ? 操作系統(tǒng)管理物理內(nèi)存的時(shí)候,頁得大小是以4KB為單位。也就是4096byte,如果我們用4097byte,就多這1byte,操作系統(tǒng)就會(huì)在底層,直接創(chuàng)建4096 * 2byte的空間,此時(shí)多余的4095byte并不會(huì)使用,就浪費(fèi)了。

    ? ? ? ? 此處,我們以4097byte申請(qǐng),操作系統(tǒng)開辟了4096 * 2byte,但是查詢下是4097byte,因?yàn)?#xff0c;操作系統(tǒng)分配了空間,但是并不代表對(duì)所有都有權(quán)利訪問,我們要的是4097byte,那操作系統(tǒng)只會(huì)給對(duì)應(yīng)的權(quán)限。所以建議配4096byte的整數(shù)倍

    ????????prems:權(quán)限。此處為0 ,代表任何一個(gè)人,包括我們,都沒有權(quán)力讀寫共享內(nèi)存,此時(shí)創(chuàng)建共性內(nèi)存也就沒了意義。于是我們需要再加一個(gè)選項(xiàng),設(shè)置權(quán)限。

    ? ? ? ? nattch:n標(biāo)識(shí)個(gè)數(shù),attch表示關(guān)聯(lián)。表示有多少個(gè)進(jìn)程與該共享內(nèi)存關(guān)聯(lián)。

    ? ? ? ? 需要將指定的共享內(nèi)存,掛接到自己的進(jìn)程的地址空間。

    參數(shù):

  • 要掛接的共享內(nèi)存的用戶管理的對(duì)應(yīng)的id。(獲取共享內(nèi)存時(shí)的id)
  • 我們需要指定的虛擬地址。共享內(nèi)存掛接時(shí),可將其掛接到指定的虛擬地址。(一般不推薦,因?yàn)樘摂M地址的使用情況我們并不是十分的清楚。即使,我們能獲取到),設(shè)置為nullptr讓操作系統(tǒng)自行掛接即可。
  • 掛接方式。設(shè)置為0即可,默認(rèn)會(huì)以讀寫的方式掛好。
  • ·? ? ? ? 范圍值,共享內(nèi)存的起始地址。

    文件描述符,文件有其對(duì)應(yīng)的文件指針,可用戶從來不會(huì)用文件指針,用的全是文件描述符,它們都可以用來標(biāo)定一個(gè)文件。同樣的道理shmid與key,它們都可以用來標(biāo)定共享內(nèi)存的唯一性。(key:標(biāo)定共享內(nèi)存在系統(tǒng)級(jí)別上的唯一性。shmid:標(biāo)定共享內(nèi)存的用戶級(jí)別上的唯一性。)所以我們?cè)谟玫臅r(shí)候全部都是shmid。只要是指令編寫的時(shí)候,就是在用戶層次的,所以ipcs等用的是shmid。

    ????????system V IPC資源,生命周期隨內(nèi)核,與之相對(duì)的是生命周期隨進(jìn)程。即,操作系統(tǒng)會(huì)一直保存這個(gè)資源,除非用戶用手動(dòng)命令刪除,否則用代碼刪除。

    ????????共享內(nèi)存由操作系統(tǒng)提供,并對(duì)其進(jìn)行管理(先描述,再組織) -> 共享內(nèi)存 = 共享內(nèi)存塊 + 對(duì)應(yīng)的共享內(nèi)存的內(nèi)核數(shù)據(jù)結(jié)構(gòu)。

    融會(huì)貫通的理解:

    ????????一個(gè)內(nèi)存為4G的地址空間,0~3G屬于用戶,3~4G屬于內(nèi)核。所謂的操作系統(tǒng)在進(jìn)行調(diào)度的時(shí)候,執(zhí)行系統(tǒng)調(diào)用接口、庫函數(shù)。本質(zhì)上都是要將代碼映射到地址空間當(dāng)中,所以我們的代碼無論是執(zhí)行動(dòng)態(tài)庫,還是執(zhí)行操作系統(tǒng)的代碼。都是在其地址空間中完成的。所以對(duì)于任何進(jìn)程,3~4G都是操作系統(tǒng)的代碼和數(shù)據(jù),所以無論進(jìn)程如何千變?nèi)f化,操作系統(tǒng)永遠(yuǎn)都能被找到。

    ? ? ? ? 堆棧之間的共享區(qū):是用戶空間,該空間拿到了,無需經(jīng)過系統(tǒng)調(diào)用便可直接訪問。 -- 共享內(nèi)存,是不用經(jīng)過系統(tǒng)調(diào)用,直接可以進(jìn)行訪問!雙方進(jìn)程如果要通訊,直接進(jìn)行內(nèi)存級(jí)的讀和寫即。

    融會(huì)貫通的理解:

    ????????前面所說的匿名管道(pipe)、命名管道(fifo)。都需要通過read、write(IO系統(tǒng)調(diào)用)來進(jìn)行通訊。因?yàn)檫@兩個(gè)屬于文件,而文件是在內(nèi)核當(dāng)中的特定數(shù)據(jù)結(jié)構(gòu),所以其是操作系統(tǒng)維護(hù)的 -- 其是在3~4G的操作系統(tǒng)空間范圍中。(無權(quán)訪問,必須使用系統(tǒng)接口)

    ?共享內(nèi)存在被創(chuàng)建號(hào)之后,默認(rèn)被清成全0,所以打印字符是空串。

    ????????共享內(nèi)存就是天然的為了讓我們可以快速訪問的機(jī)制,所以其內(nèi)部沒有提供任何的控制策略。(共享內(nèi)存中有數(shù)據(jù)讀端讀,沒數(shù)據(jù)讀端也讀。甚至客戶端(寫入端)不在了,其也讀。)更直接的說:寫入端和讀取端根本不知道對(duì)方的存在。

    ? ? ? ? 缺乏控制策略 -- 會(huì)帶來并發(fā)的問題。

    拓展:

    并發(fā)的問題,如:

    ????????客戶端想讓一個(gè)進(jìn)程處理一個(gè)完整的數(shù)據(jù)內(nèi)容,然而客戶端在未完全寫入共享內(nèi)存時(shí),讀取方就將不完整的數(shù)據(jù)讀取并處理,此時(shí)處理結(jié)果為未定義。 --? 數(shù)據(jù)不一致問題

    基于共享內(nèi)存理解信號(hào)量

    根據(jù)前面的學(xué)習(xí):

    • 匿名管道通過派生子進(jìn)程的方式看見同一份資源。
    • 命名管道通過路徑的方式看見同一份資源。
    • 共享內(nèi)存通過key值得方式看見同一份資源。

    ????????所以,為了讓進(jìn)程間通訊?-> 讓不同的進(jìn)程之間,看見同一份資源 -> 本質(zhì):讓不同的進(jìn)程看見同一份資源。

    ? ? ? ? 通過前面得到學(xué)習(xí)我們會(huì)發(fā)現(xiàn),如共享進(jìn)程,其并沒有訪問控制,即:獨(dú)斷讀取的時(shí)機(jī)是不確定的,這也就帶來了一些時(shí)序問題 ——?照成數(shù)據(jù)的不一致問題。

    引入兩個(gè)概念:

  • 臨界資源:我們把多個(gè)進(jìn)程(執(zhí)行流)看到的公共的一份志愿,稱作臨界資源。
  • 臨界區(qū):我們把自己的進(jìn)程,訪問的臨界資源的代碼,稱作臨界區(qū)。
  • ? ? ? ? 所以,多個(gè)進(jìn)程(執(zhí)行流),互相運(yùn)行的時(shí)候互相干擾,主要是我們不加以保護(hù)的訪問了相同的資源(臨界資源),在非臨界區(qū)多個(gè)進(jìn)程(執(zhí)行流)互相是不干擾的。

    ? ? ? ? 而為了更好的進(jìn)行臨界資源的保護(hù),可以讓多個(gè)進(jìn)程(執(zhí)行流)在任何時(shí)刻,都只能有一個(gè)進(jìn)程進(jìn)入臨界區(qū) ——? 互斥?

    互斥的理解:

    ? ? ? ? 我們可以將,一個(gè)執(zhí)行流:人,臨界區(qū):電影院(一個(gè)位置的電影院)。

    ? ? ? ? 看電影一定要有位置(電影院中的唯一位子)。當(dāng)前一個(gè)人在其中看電影,那么其他人必須等待他看完才可進(jìn)入觀看。并且電影院中,此唯一的位置是并不屬于觀影人的,而是買票,只要買了票,即在你進(jìn)去看完電影之前,就擁有了這個(gè)位置。買票:就是對(duì)座位的?預(yù)定?機(jī)制。

    ? ? ? ? 同樣的道理,進(jìn)程想進(jìn)入臨界資源,訪問臨界資源,不能讓進(jìn)程直接去使用臨界資源(不能讓用戶直接去電影院內(nèi)部占資源),需要先申請(qǐng)票 ——? 信號(hào)量

    ? ? ? ? 信號(hào)量 的存在是等于一張票。"票"的意義是互斥,而互斥的本質(zhì)是串形化,互斥就是一個(gè)在跑另一個(gè)就不能跑,需要等待跑完才能跑。其必須串形的去執(zhí)行。但是一旦串形的去執(zhí)行,多并發(fā)的效率就差了。所以:

    ? ? ? ? 當(dāng)有一份公共資源,只要有多個(gè)執(zhí)行流訪問的是這個(gè)公共資源的不同區(qū)域,這個(gè)時(shí)候可以允許多個(gè)執(zhí)行流同時(shí)進(jìn)入臨界區(qū)。這個(gè)時(shí)候可以根據(jù)區(qū)域的數(shù)量(如同電影院座位的個(gè)數(shù) -> 允許觀影的人數(shù))可以讓對(duì)應(yīng)的進(jìn)程個(gè)數(shù)并發(fā)的執(zhí)行自己臨界區(qū)的代碼(看電影的自行觀影)

    ? ? ? ? 信號(hào)量本質(zhì)上:就是一個(gè)計(jì)數(shù)器,類似于int count = n(n張票)。

    申請(qǐng)信號(hào)量:

  • 申請(qǐng)信號(hào)量的本質(zhì):讓信號(hào)量計(jì)數(shù)器 -- 。
  • 釋放信號(hào)量的本質(zhì):讓信號(hào)量計(jì)數(shù)器++。
  • 信號(hào)量申請(qǐng)成功,臨界資源內(nèi)部就一定會(huì)預(yù)留所需要的資源 —— 申請(qǐng)信號(hào)量本質(zhì)其實(shí)是對(duì)臨界資源的一種“ 預(yù)定 ”機(jī)制。
  • ????????只要申請(qǐng)信號(hào)量成功 ……只要申請(qǐng)成功,一定在臨界區(qū)中有一個(gè)資源對(duì)應(yīng)提供的。

    ????????換句話說:首先,我們要進(jìn)行訪問信號(hào)量計(jì)數(shù)器,要每一個(gè)線程訪問計(jì)數(shù)器,必須保證信號(hào)量本身的 --操作 以及 ++操作原子的。否者很難保護(hù)臨界資源。其次,信號(hào)量需要是公共的,能被所有進(jìn)程能看到的資源,叫做臨界資源 —— 而信號(hào)量計(jì)數(shù)器存在的意義就是保護(hù)臨界資源,但是其有又成了臨界資源,所以其必須保證自己是安全的,才能保證臨界資源的安全。

    #:如果用一個(gè)整數(shù),表示信號(hào)量。假設(shè)讓多個(gè)進(jìn)程(整數(shù)n在共享內(nèi)存里),看見同一個(gè)全局變量,都可以進(jìn)行申請(qǐng)信號(hào)量?—— 不可以的。

    CPU執(zhí)行指令的時(shí)候:

  • 將內(nèi)存中的數(shù)據(jù)加載到CPU內(nèi)的寄存器中(讀指令)。
  • n--(分析 && 執(zhí)行指令)。
  • 將CPU修改完的數(shù)據(jù)n寫回到內(nèi)存(寫回結(jié)果)。
  • 復(fù)習(xí):

    ? ? ? ? 執(zhí)行流在執(zhí)行的時(shí)候,在任何時(shí)刻都可能被切換。

    切換的本質(zhì):CPU內(nèi)的寄存器是只有一份的,但是寄存器需要存儲(chǔ)的臨時(shí)數(shù)據(jù)(上下文)是多份的,分別對(duì)應(yīng)不同的進(jìn)程!

    ? ? ? ? 我們知道,每一個(gè)進(jìn)程的上下文是不一樣的,寄存器只有一份,那么根據(jù)并發(fā),為下一個(gè)進(jìn)程讓出位置。并且由于,上下文數(shù)據(jù)絕而對(duì)不可以被拋棄!

    ? ? ? ? 當(dāng)進(jìn)程A暫時(shí)被切下來的時(shí)候,需要進(jìn)程A順便帶走直接的上下文數(shù)據(jù)!帶走暫時(shí)保存數(shù)據(jù)的是為了下一次回來的時(shí)候,能夠恢復(fù)上去,以此繼續(xù)按照之前的邏輯繼續(xù)向后運(yùn)行,就如同沒有中斷過一樣。

    ? ? ? ? 由于寄存器只有一套,被所有的執(zhí)行流共享,但是寄存器里面的數(shù)據(jù),屬于一個(gè)執(zhí)行流(屬于該執(zhí)行流的上下文數(shù)據(jù))。所以對(duì)應(yīng)的執(zhí)行流需要將上下文數(shù)據(jù)進(jìn)行保護(hù),方便與上下文數(shù)據(jù)恢復(fù)(重新回到CPU,更具上下文數(shù)據(jù)繼續(xù)執(zhí)行)。

    ????????當(dāng)myClient執(zhí)行的時(shí)候,重點(diǎn)在于n--,到n++,因?yàn)闀r(shí)序的問題,會(huì)導(dǎo)致n有中間狀態(tài)。切換為myServer執(zhí)行的時(shí)候,中間狀態(tài)會(huì)導(dǎo)致數(shù)據(jù)不一致。

    ? ? ? ? 即,CPU執(zhí)行myClient中的寫入數(shù)據(jù)到共享內(nèi)存時(shí),就被替換了:

    (CUP執(zhí)行到n的中間狀態(tài))

    (myClient被切換為myServer)

    (myServer信號(hào)量執(zhí)行完了,并將n寫回

    (myCilent帶著自己的上下文數(shù)據(jù),并將n寫回)

    ????????此時(shí)1 -> 2,凸顯了信號(hào)量操作必須是原子性的,只有原子性才不會(huì)怕因時(shí)序,導(dǎo)致的數(shù)據(jù)不一致問題。

    總結(jié):

    • 申請(qǐng)信號(hào)量 -> 計(jì)數(shù)器-- -> P操作 -> 必須是原子的
    • 申請(qǐng)信號(hào)量 -> 計(jì)數(shù)器++?-> V操作 -> 必須是原子的

    總結(jié)

    ????????所以,由于信號(hào)量的思想,也是讓我們看見同一份資源,所以其本質(zhì)與上面的管道、共享內(nèi)存沒有太大的區(qū)別。所以,信號(hào)量被納入進(jìn)程間通訊的范疇。

    ? ? ? ? 信號(hào)量是為了保證特定的臨界資源不被受侵害,保證臨界資源數(shù)據(jù)一致性。前面所講:信號(hào)量也是一個(gè)臨界資源,所以首先其需要保證自己的安全性 ——?提出信號(hào)量操作需是原子性的。?

    ? ? ? ? 而信號(hào)量理論的提出是由于:臨界區(qū)、臨界資源的?互斥 ,當(dāng)多個(gè)執(zhí)行流(進(jìn)程)才會(huì)真正的凸顯出來,所以此處由于是進(jìn)程間通訊 —— 需要提出信號(hào)量,但作用凸顯在多線程 —— 多線程再深入講解信號(hào)量。

    總結(jié)

    以上是生活随笔為你收集整理的【Linux】-- 进程间通讯的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。