Linux:進程間通信(一.初識進程間通信、匿名管道與命名管道、共享內存)

上次結束了基礎IO:Linux:基礎IO(三.軟硬鏈接、動態庫和靜態庫、動精態庫的制作和加載)


文章目錄

  • 1.認識進程間通信
  • 2.管道
    • 2.1匿名管道
    • 2.2pipe()函數 —創建匿名管道
    • 2.3匿名管道的四種情況
    • 2.4管道的特征
  • 3.基于管道的進程池設計
  • 4.命名管道
    • 4.1引入與性質
    • 4.2命令行創建
    • 4.3程序中創建命名管道
    • 寫個小項目
      • 項目規劃
        • Cnmm.hpp
        • PipeClient.cpp
        • PipeServe.cpp
  • 5.System V共享內存
    • 5.1相關函數介紹


1.認識進程間通信

我們通過之前的知識知道,進程具有獨立性。兩個進程之間時不能進行數據的直接傳遞的

但我們之前學校的fork()函數不是能傳遞子進程的pid給父進程嗎?——這個嚴格來說不算通信

  • 為什么我們需要進程間通信?

    1. 數據傳輸:一個進程需要將自己的數據發送給另一個進程。這種通信方式可以實現進程之間的數據交換和共享,從而實現協作和協同工作。
    2. 資源共享:多個進程之間共享同樣的資源,如共享內存、共享文件等。通過進程間通信,可以實現多個進程對同一資源的訪問和操作,提高資源的利用率和效率。
    3. 通知事件:一個進程需要向另一個或一組進程發送消息,通知它們發生了某種事件,如進程終止、資源可用等。通過通知事件,進程可以及時響應和處理其他進程的狀態變化,實現進程之間的協作和同步。
    4. 進程控制:有些進程希望完全控制另一個進程的執行,如調試進程需要攔截另一個進程的陷入和異常,并能夠及時知道其狀態改變。通過進程控制,可以實現對其他進程的監控、調試和管理,確保系統的穩定和安全運行。

    我們往往需要多個進程協同來完成一些任務

  • 進程間通信是什么?

    一個進程能把自己的數據給另外一個進程(一直)

    本質:讓不同的進程看到同一份資源(一般都是要由OS提供)

  • 如何進行進程間通信

    1. 我們要有一個來進行數據交換的空間(一般是內存)。不是直接去另外一個進程里拿,這樣會破壞進程的獨立性
    2. 這段空間不能由這雙方來提供。由OS(話事人)來提供
    3. OS提供的”空間“有不同的樣式,就決定了有不同的通信方式

那么OS提供的樣式有:

  1. 管道(匿名、命名)
  2. 共享內存
  3. 消息隊列
  4. 信號量

2.管道

在這里插入圖片描述

基于文件的,讓不同進程看到同一份資源的通信方式叫做管道

管道只能被設計成為單向通信

在Linux中,管道確實可以被視為一種機制,同時也是一種特殊的文件類型。這種雙重性來自于Linux操作系統的設計和其對所有資源采取的抽象化處理方式。

  • 作為一種機制,管道用于進程間通信(IPC)。它允許一個進程的輸出直接成為另一個進程的輸入,從而實現了數據的快速傳遞。這種機制大大簡化了進程間的通信過程,提高了通信效率。

  • 從文件的角度來看,管道在Linux中被實現為一種特殊的文件類型。這意味著管道具有文件的某些屬性和操作方式,比如可以通過文件描述符進行打開、讀取、寫入和關閉等操作。然而,與普通文件不同的是,管道并不在磁盤上占用實際的物理空間,它的內容存儲在內核的緩沖區中,只在內存中存在。

這種雙重性使得管道既具有機制的靈活性,又具有文件的可操作性。它可以在不同的進程之間建立連接,實現數據的傳遞和共享,同時也可以通過標準的文件操作接口進行訪問和控制。

在這里插入圖片描述

為了支持管道通信,OS提供了一個接口:pipe()

2.1匿名管道

匿名管道(Anonymous Pipe)Linux中提供的一種進程間通信(IPC)機制。匿名管道沒有名字,它們僅存在于創建它們的進程及其子進程之間,并且一旦這些進程終止,管道也將隨之消失。

匿名管道的主要特點如下:

  1. 單向通信:匿名管道是半雙工的,這意味著數據只能在一個方向上流動。通常,一個進程向管道寫入數據,而另一個進程從管道讀取數據。如果需要雙向通信,則需要創建兩個管道,一個用于每個方向。
  2. 親緣關系:匿名管道只能用于具有親緣關系的進程之間,即一個進程和它的子進程之間。這是因為管道的文件描述符是通過fork()系統調用在父子進程之間復制的。
  3. 自動管理:當所有使用管道的文件描述符都被關閉時,管道將自動被刪除。這意味著不需要像命名管道那樣顯式地打開和關閉它。
  4. 內存中的緩沖區管道實際上是一個在內核中維護的緩沖區,用于存儲從寫入端發送但尚未被讀取端讀取的數據。這個緩沖區的大小是有限的,如果寫入的數據超過了緩沖區的大小,寫操作可能會被阻塞,直到有空間可用。
  • 管道文件的數據是存儲在內存中的(是內存級的文件),而不是磁盤上。這使得對管道的訪問速度非常快,類似于對內存的直接訪問

  • 匿名管道是通過創建子進程,而子進程會繼承父進程的相關屬性信息,來實現不同的進程看到同一份資源

  • 通過管道,一個進程(寫端)可以將數據發送給另一個進程(讀端),實現數據的共享和傳遞。當讀端從管道中讀取數據時,這些數據會被從內核的緩沖區中移除(或稱為消費),從而為寫端提供了更多的空間來寫入新的數據

在C語言中,可以使用pipe()函數來創建一個匿名管道。這個函數接受一個包含兩個文件描述符的數組作為參數,并返回兩個文件描述符:一個用于讀操作,另一個用于寫操作。然后,可以使用fork()創建一個子進程,并在父進程和子進程之間使用這些文件描述符進行通信。

2.2pipe()函數 —創建匿名管道

在這里插入圖片描述

pipe函數用于創建管道,這是一種特殊的文件,用于連接一個程序的標準輸出和另一個程序的標準輸入,從而實現這兩個程序之間的通信。在C語言中函數原型為:int pipe(int pipefd[2]);

參數

pipe函數接受一個整型數組作為參數(這是個輸出型參數),即int pipefd[2]。這個數組用于存儲管道的兩個文件描述符:pipefd[0]表示管道的讀端,而pipefd[1]表示管道的寫端。

作用

調用pipe函數后,系統會創建一個匿名管道,并將這個管道的兩個端點(一個用于讀,一個用于寫)的文件描述符分別賦值給pipefd[0]pipefd[1]。這樣,一個進程就可以通過pipefd[1]向管道寫入數據,而另一個進程則可以通過pipefd[0]從管道中讀取數據。這種機制使得兩個進程之間可以通過管道進行通信。

返回值

如果pipe函數成功創建了管道,則返回0。如果創建失敗,則返回-1,并將錯誤原因存儲在全局變量errno中。可能的錯誤原因包括:

  • EMFILE:進程已達到其文件描述符的最大數量。
  • ENFILE:系統已達到其文件描述符的最大數量。
  • EFAULT:傳遞給pipe函數的數組地址不合法。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>void writer(int wfd)//寫端的操作
{const char *str = "hi father, I am child";char buffer[128];int cnt = 0;pid_t pid = getpid();while(1){//先調用snprintf向buffer數組里寫,然后在把buffer數組寫到fd為wfd的文件里(這里就是管道的寫端)snprintf(buffer, sizeof(buffer), "message: %s, pid: %d, count: %d\n", str, pid, cnt);write(wfd, buffer, strlen(buffer));cnt++;sleep(1);}
}void reader(int rfd)//讀端的操作
{char buffer[1024];while(1){ssize_t n = read(rfd, buffer, sizeof(buffer)-1);(void)n;//沒有使用這個 n 變量。如果編譯器被配置為警告未使用的變量,那么它就會為 n 發出一個警告printf("father gets a message: %s", buffer);}
}int main()
{//創建管道int pipefd[2];int n = pipe(pipefd);// pipefd[0]-->read   pipefd[1]-->write  0是寫端,1是讀端// 0-->嘴巴 讀書       1-->鋼筆 寫字if(n < 0) return 1;//創建子進程pid_t id = fork();if(id == 0){//child: w 我們讓子進程來寫close(pipefd[0]);//那么就要關閉讀端writer(pipefd[1]);exit(0);}// father: r我們讓父進程來讀close(pipefd[1]);//那么就要關閉寫端reader(pipefd[0]);wait(NULL);return 0;
}

在這里插入圖片描述

2.3匿名管道的四種情況

  1. 管道內部沒有數據而且子進程不關閉自己的寫端文件fd, 讀端(父)就要阻塞等待,直到pipe有數據

    管道中沒有數據時,讀端繼續讀取的默認行為是阻塞當前正在讀取的進程。在這種情況下,進程會進入等待狀態,其進程控制塊(PCB)會被放置在管道文件的等待隊列中。只要管道中沒有新的數據到來,讀端進程就會一直阻塞等待

  2. 管道內部被寫滿而且讀端(父進程)不關閉自己的fd,寫端(子進程)寫滿之后,就要阻塞等待

    管道具有固定的緩沖區大小,當緩沖區中的數據量達到上限時,寫端進程就會被阻塞,直到有讀端進程從管道中讀取數據并釋放緩沖區空間

    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/wait.h>void writer(int wfd)//寫端的操作
    {const char *str = "hi father, I am child";char buffer[128];int cnt = 0;pid_t pid = getpid();while(1){char ch='A';write(wfd, &ch, 1);cnt++;printf("cnt=%d\n",cnt);}
    }void reader(int rfd)//讀端的操作
    {char buffer[1024];while(1){sleep(10);ssize_t n = read(rfd, buffer, sizeof(buffer)-1);(void)n;//沒有使用這個 n 變量。如果編譯器被配置為警告未使用的變量,那么它就會為 n 發出一個警告printf("father gets a message: %s", buffer);}
    }int main()
    {//創建管道int pipefd[2];int n = pipe(pipefd);// pipefd[0]-->read   pipefd[1]-->write  0是寫端,1是讀端// 0-->嘴巴 讀書       1-->鋼筆 寫字if(n < 0) return 1;//創建子進程pid_t id = fork();if(id == 0){//child: w 我們讓子進程來寫close(pipefd[0]);//那么就要關閉讀端writer(pipefd[1]);exit(0);}// father: r我們讓父進程來讀close(pipefd[1]);//那么就要關閉寫端reader(pipefd[0]);wait(NULL);return 0;
    }
    

    在這里插入圖片描述

  3. 不再向管道寫入數據并且關閉了寫端(子進程)文件描述符時,讀端(父進程)可以繼續從管道中讀取剩余的數據,直到管道中的數據全部被讀取完畢。最后就會讀到返回值為0,表示讀結束,類似讀到了文件的結尾

  4. 讀端關閉其文件描述符并且不再讀取數據時,如果寫端繼續向管道寫入數據,操作系統會發送一個SIGPIPE信號給寫端進程。默認情況下,這個信號會終止寫端進程SIGPIPE信號是一個用于處理管道寫端在寫操作時無讀端接收的情況的信號。

    SIGPIPE信號(信號編號為13)的發送是為了通知寫端進程,其寫操作因為管道的另一端沒有讀端而不再有意義。這是一種保護機制,防止寫端進程在沒有讀端的情況下無限期地等待或繼續寫入數據到一個不再被讀取的管道中。

    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/wait.h>void writer(int wfd)//寫端的操作
    {int cnt = 0;while(1){sleep(1);char ch='A';write(wfd, &ch, 1);cnt++;printf("cnt=%d\n",cnt);}//子進程一直寫
    }void reader(int rfd)//讀端的操作
    {int cnt=8;char buffer[1024];while(1){sleep(1);ssize_t n = read(rfd, buffer, sizeof(buffer)-1);if(n>0){printf("father get a message: %s, n : %ld\n", buffer, n);}else if(n==0){printf("reading has done: %s  %ld\n", buffer,n);break;}else {break;}cnt--;if(cnt==0){break;}}close(rfd);//8秒后,父進程不再讀,直接關閉printf("end");
    }int main()
    {//創建管道int pipefd[2];int n = pipe(pipefd);// pipefd[0]-->read   pipefd[1]-->write  0是寫端,1是讀端// 0-->嘴巴 讀書       1-->鋼筆 寫字if(n < 0) return 1;//創建子進程pid_t id = fork();if(id == 0){//child: w 我們讓子進程來寫close(pipefd[0]);//那么就要關閉讀端writer(pipefd[1]);exit(0);}// father: r我們讓父進程來讀close(pipefd[1]);//那么就要關閉寫端reader(pipefd[0]);int status=0;pid_t rid=waitpid(id,&status,0);printf("exit code: %d, exit signal: %d\n",WEXITSTATUS(status),status&0x7f);return 0;
    }
    

    在這里插入圖片描述

2.4管道的特征

  1. 匿名管道自帶同步機制:在匿名管道中,寫端在寫數據且沒有寫完時,讀端是不可能訪問管道這塊公共資源的。這種機制確保了數據的完整性和一致性,避免了數據沖突和錯誤

  2. 管道(Pipe)是一種常用于具有血緣關系進程間通信的機制,特別是在父子進程之間。這里的“血緣關系”指的是進程之間的創建關系,即一個進程創建了另一個進程,它們之間存在直接的父子關系

  3. 管道(pipe)是面向字節流的:這意味著管道在傳輸數據時,是以字節為單位進行處理的。無論是字符、整數還是其他類型的數據,都會被轉換成字節序列進行傳輸。因此,管道不關心數據的具體格式或類型,只負責將數據以字節流的形式從一個進程傳遞到另一個進程

  4. 管道(pipe)是半雙工的:它只能在一個方向上傳輸數據,屬于單向通信的特殊概念。具體來說,一個管道有一個輸入端和一個輸出端,數據可以從輸入端流入管道,并從輸出端流出。但管道不允許數據在相反的方向上流動,即不能從輸出端流回輸入端

    半雙工(Half Duplex)數據傳輸指的是數據可以在一個信號載體的兩個方向上傳輸,但是不能同時傳輸。也就是說,在一個時間點,數據只能在一個方向上流動

  5. 父子進程退出后,管道會自動釋放。這是由操作系統的內存管理機制決定的。當進程結束時,操作系統會回收其占用的所有資源,包括打開的文件、管道、網絡連接等

  6. 我們之前在命令行里使用的|其實就是匿名管道:在命令行中,當我們使用|來連接兩個命令時,實際上是在這兩個命令之間創建了一個匿名管道。這使得前一個命令的輸出能夠直接傳輸給后一個命令,實現了兩個命令之間的數據共享和傳輸

3.基于管道的進程池設計


4.命名管道

4.1引入與性質

我們設想一個這樣的情況:

  1. 當一個進程打開一個文件(比如log.txt),內核會為該進程創建一個struct file結構體,其中包含指向inode結構體、函數指針數組和緩沖區的指針。這個struct file結構體會指向已加載的inode結構體和緩沖區,用于表示文件在內核中的信息和緩存文件數據。
  2. 當另一個進程也打開同一個文件時,內核會為該進程創建另一個struct file結構體,其中也包含指向相同的inode結構體和緩沖區的指針。這意味著多個進程可以共享相同的inode結構體和緩沖區,而不會為每個進程創建一份完全一樣的inode結構體和緩沖區。
  3. 由于inode結構體和緩沖區是在內核中維護的,因此多個進程可以共享相同的inode結構體和緩沖區,而不需要為每個進程復制一份。這種共享機制可以節省內存空間,并確保多個進程對同一文件的操作是一致的。

在這里插入圖片描述

此時這兩個進程就看到了同一塊資源(log.txt 文件)

  1. 當兩個進程共享同一個文件(例如log.txt)時,它們實際上是在操作同一塊資源。這是因為文件系統中的路徑和文件名是唯一的,所以無論哪個進程打開同一個路徑下的文件,都會訪問到同一個文件。

  2. 在多個進程共享文件時,它們可以通過共享同一個緩沖區來進行數據交換這個緩沖區可以被看作是一個管道,用于在進程之間傳遞數據。通過這種方式,進程可以實現數據共享和通信。

  3. 在上面這種情況下,這個管道(緩沖區)可以被稱為命名管道(named pipe)。

命名管道是一種特殊的文件類型,它允許進程之間通過文件系統進行通信。通過路徑+文件名來確定(唯一的路徑+文件名來找到并訪問這個管道),多個進程可以通過打開同一個命名管道來實現數據交換。

  1. 在這種情況下,這個管道不需要與磁盤進行交互,因為數據是在內存中進行傳遞的。進程通過讀取和寫入管道來實現數據共享,而不需要直接與磁盤進行交互。

4.2命令行創建

命名管道(Named Pipe)是一種特殊的文件,用于進程間通信。它是一種半雙工通信方式,允許一個或多個進程之間通過讀寫同一個文件來進行通信。

  1. 創建命名管道
    命名管道是通過調用mkfifo系統調用來創建的。命名管道在文件系統中以文件的形式存在,但實際上它是一個FIFO(First In First Out)的通信通道。創建命名管道的語法為:

    mkfifo <管道名稱>
    

    在這里插入圖片描述

  2. 打開和關閉命名管道
    命名管道可以像普通文件一樣被打開和關閉。進程可以通過open系統調用打開一個命名管道文件,并通過close系統調用關閉它。在打開命名管道時,進程需要指定相應的讀寫權限。

  3. 讀寫數據
    進程可以通過打開的文件描述符對命名管道進行讀寫操作。一個進程往管道中寫入數據,另一個進程從管道中讀取數據。命名管道是阻塞的,如果寫入進程寫入數據時,沒有進程讀取數據,寫入進程會被阻塞直到有進程讀取數據

    在這里插入圖片描述

  4. 進程間通信
    命名管道通常用于實現進程間通信,特別是在父子進程或者**不相關進程之間**。一個進程可以向命名管道寫入數據,另一個進程可以從命名管道讀取數據,實現了進程間的數據交換。

4.3程序中創建命名管道

在這里插入圖片描述

mkfifo函數是一個UNIX系統中用于創建命名管道(named pipe)的函數。它的作用是在文件系統中創建一個特殊類型的文件,這個文件可以被多個進程用來進行進程間通信。

在C語言中,可以使用mkfifo函數來創建一個命名管道,其原型如下:

int mkfifo(const char *pathname, mode_t mode);
  • pathname參數是指定要創建的命名管道的路徑和文件名。
  • mode參數是指定創建的管道的權限模式,通常以八進制表示(例如0666)。

使用mkfifo函數創建命名管道后,其他進程可以通過打開這個路徑+文件名來訪問這個管道,從而實現進程間的通信。一旦創建了命名管道,它就可以在文件系統中像普通文件一樣被打開、讀取和寫入

寫個小項目

項目規劃

在這里插入圖片描述

  • Cnmm.hpp:管道的封裝,頭文件的包含、宏定義等任務
  • PipeClient.cpp:客戶端,進行管道的寫入
  • PipeServe.cpp:服務端(服務器),進行管道的創建、讀取
Cnmm.hpp
#ifndef __COMM_HPP__
#define __COMM_HPP__#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>using namespace std;#define Mode 0666
#define Path "./fifo"class Fifo
{
public:Fifo(const string &path) : _path(path){umask(0);int n = mkfifo(_path.c_str(), Mode);if (n == 0){cout << "mkfifo success" << endl;}else{cerr << "mkfifo failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;}}~Fifo(){int n = unlink(_path.c_str());if (n == 0){cout << "remove fifo file " << _path << " success" << endl;}else{cerr << "remove failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;}}private:string _path; // 文件路徑+文件名
};#endif//條件編譯結束

整體上使用一個條件編譯:

在C++頭文件中,通常會使用條件編譯指令來防止頭文件被多次包含,以避免重復定義的問題。條件編譯指令的一般結構如下:

#ifndef __HEADER_NAME__
#define __HEADER_NAME__// 頭文件內容#endif
  • #ifndef __HEADER_NAME__:這是條件編譯指令的開始標記,用于檢查是否已經定義了名為__HEADER_NAME__的宏。如果之前沒有定義這個宏,那么下面的代碼將被執行。
  • #define __HEADER_NAME__:在條件編譯指令的開始處,定義名為__HEADER_NAME__的宏,表示這個頭文件已經被包含過了。
  • // 頭文件內容:在這個部分可以放置頭文件的內容,包括類的定義、函數的聲明等。
  • #endif:這是條件編譯指令的結束標記,表示條件編譯的范圍結束。

#ifndef __COMM_HPP__是條件編譯指令的開始標記,而#endif是條件編譯指令的結束標記。

  1. cerr

    • cerr是C++標準庫中的標準錯誤流,它用于輸出錯誤信息到標準錯誤設備(通常是顯示器)。
    • cout(標準輸出流)類似,cerr也是一個對象,可以使用插入運算符<<來將數據插入到cerr中進行輸出。
    • cout不同的是,cerr通常用于輸出錯誤消息,而不是普通的程序輸出。它是線程安全的,可以在多線程環境中使用。
  2. errno

    • errno是一個全局變量,通常定義在<cerrno>頭文件中,用于存儲函數調用發生錯誤時的錯誤碼。
    • 當某個函數發生錯誤時,它會設置適當的錯誤碼到errno中,以便程序能夠檢測和處理錯誤。
    • 錯誤碼是整數類型,每個錯誤碼對應于一種特定類型的錯誤。可以通過查看系統的錯誤碼表來了解每個錯誤碼的含義。
  3. strerror

    • strerror是一個C標準庫函數,通常定義在<cstring><string.h>頭文件中,用于將錯誤碼轉換為對應的錯誤消息字符串。
    • strerror接受一個錯誤碼作為參數,并返回一個指向描述該錯誤的字符串的指針。
    • 通過調用strerror(errno),可以獲取與當前errno值對應的錯誤消息字符串,以便程序輸出或記錄錯誤信息。
PipeClient.cpp
#include "Comm.hpp"int main()
{// 打開管道,進行寫入,最后關閉int wfd = open(Path, O_WRONLY | O_CREAT); // 以只寫方式打開if (wfd < 0){cerr << "open failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;return 1;}string buffer; // 開始寫入while (true){cout << "please write your message:" << endl;getline(cin, buffer);ssize_t n = write(wfd, buffer.c_str(), buffer.size());if (n < 0){cerr << "write failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;break;}}close(wfd);return 0;
}
PipeServe.cpp
#include "Comm.hpp"
#include <unistd.h>int main()
{Fifo fifo(Path);// 打開管道,進行讀取,最后關閉int rfd = open(Path, O_RDONLY); // 以只讀方式打開if (rfd < 0){cerr << "open failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;return 1;}char buffer[1024];//開始讀取while (true){ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';cout << "client say : " << buffer << endl;}else if (n == 0){cout << "client quit, me too!!" << endl;break;}else{cerr << "read failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;break;}}close(rfd);return 0;
}

在這里插入圖片描述

這里我自己有個疑問:本來讀端是一直堵塞在read函數的,我們一輸入abcde,第一次read就能讀取完,然后輸出。下一次循環就應該接著讀,讀到末尾,返回0了吧? 但為什么這里是接著阻塞呢?

  • 在非阻塞模式下,如果讀取到文件末尾(沒有更多的數據可讀取),read 函數會立即返回 0。
  • 在阻塞模式下,read 函數會阻塞等待直到有數據可讀取或者發生錯誤,它不會因為讀取到文件末尾而返回 0。相反,只有當管道被關閉或者讀取操作被中斷時,read 函數才會返回 0。
  • 默認都是阻塞模式

文件描述符的阻塞模式和非阻塞模式指的是在進行I/O操作時的行為方式。

  1. 阻塞模式

    • 在阻塞模式下,當進行I/O操作時,如果數據尚未準備好或者操作無法立即完成,程序會被阻塞,也就是暫停執行,直到操作完成或者出現錯誤為止。
    • 例如,在阻塞模式下,如果調用read函數讀取一個文件描述符,但是文件中沒有數據可讀,程序將會被阻塞,直到有數據到達為止。類似地,如果調用write函數寫入數據到一個已滿的管道中,程序也會被阻塞,直到有足夠的空間寫入數據。
  2. 非阻塞模式

    • 在非阻塞模式下,進行I/O操作時,如果操作無法立即完成,程序不會被阻塞,而是立即返回一個錯誤或者一個特定的狀態碼,提示當前操作無法立即完成。
    • 例如,在非阻塞模式下,如果調用read函數讀取一個文件描述符,但是文件中沒有數據可讀,read函數將立即返回一個錯誤碼,而不會等待數據到達。類似地,如果調用write函數寫入數據到一個已滿的管道中,write函數也會立即返回一個錯誤碼,而不會等待空間可用。

5.System V共享內存

在這里插入圖片描述

實現進程間通信的前提就是如何讓不同的進程看到同一份資源

  • 匿名管道我們是通過子進程繼承父進程打開的資源
  • 命名管道是通過兩個進程都打開具有唯一性標識的命名管道文件(路徑+文件名)
  • 共享內存其實是通過OS創建一塊shm

System V共享內存(Shared Memory)是一種Linux中用于進程間通信(IPC)的機制。它允許多個進程訪問同一塊物理內存區域,從而實現數據的快速共享和交換。

  1. 原理
    • 在物理內存中申請一塊內存空間作為共享內存。
    • 將這塊內存空間與各個進程的頁表建立映射關系,使得這些進程在虛擬地址空間中可以看到并訪問這塊共享內存。
    • 通過這種方式,多個進程可以像訪問自己的內存一樣訪問共享內存,從而實現數據的快速共享和交換。
  2. 使用方式
    • 創建:使用shmget()系統調用來創建共享內存。這個函數會分配一塊指定大小的內存區域,并返回一個標識符,用于后續對這塊共享內存的操作。
    • 關聯:使用shmat()系統調用來將共享內存關聯到進程的地址空間。這個函數會將共享內存的地址告訴進程,使得進程可以通過這個地址來訪問共享內存。
    • 取消關聯:當進程不再需要訪問共享內存時,可以使用shmdt()系統調用來取消關聯。這個函數會斷開進程與共享內存之間的映射關系。
    • 釋放:當所有進程都不再需要這塊共享內存時,可以使用shmctl()系統調用來釋放它。這個函數會回收這塊內存區域,并釋放相關的資源。

5.1相關函數介紹

ftok() 函數 Linux中用于生成一個唯一的鍵值(key)的系統調用,這個鍵值通常用于在進程間通信(IPC)中標識共享內存段、消息隊列或信號量集。ftok() 函數基于一個已經存在的文件路徑和一個非零的標識符(通常是一個小的正整數)來生成這個鍵值。

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

參數:

  • pathname:指向一個已經存在的文件路徑的指針。這個文件通常被用作生成鍵值的“種子”或“基礎”。
  • proj_id:一個非零的標識符,通常是一個小的正整數。這個值將與文件路徑一起被用于生成鍵值。返回值:

如果成功,ftok() 函數返回一個唯一的鍵值key_t 類型),該鍵值可以在后續的 IPC 調用(如 shmget(), msgget(), semget() 等)中用作參數。如果失敗,則返回 (key_t) -1 并設置 errno 以指示錯誤。

  1. shmget():創建或獲取共享內存

shmget() 系統調用用于創建一個新的共享內存對象,或者如果它已存在,則返回該對象的標識符。

函數原型

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

參數

  • key:一個鍵,用于唯一標識共享內存對象。通常使用ftok()函數生成。

    1. 共享內存在內核中同時可以存在很多個,OS必須要管理所有的共享內存
    • 如何管理呢?先描述,在組織
    • 系統中會存在很多共享內存,怎么保證,多個不同的進程看到的是同共享內存呢? 要給共享內存提供唯一性的標識
    1. key便是那個唯一性標識符。那么為什么這個key要由我們用戶來傳入呢?
    • 如果然系統生成,將值返回讓我們得到。那我們如何給另外一個進程呢?要做到就要有進程間通信,這不倒反天罡了?
  • size:共享內存的大小(以字節為單位)。

  • shmflg:權限標志和選項。通常設置為IPC_CREAT如果對象不存在則創建,存在的話直接獲取)和權限(如0666)。

    若設置為IPC_CREAT|IPC_EXCL(如果對象不存在則創建,存在的話出錯返回)

返回值:成功時返回共享內存對象的標識符;失敗時返回-1并設置errno

  1. shmat():將共享內存關聯到進程的地址空間

shmat() 系統調用用于將共享內存對象關聯到調用進程的地址空間。

函數原型

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

參數

  • shmidshmget()返回的共享內存對象標識符
  • shmaddr:希望將共享內存附加到的進程的地址。如果設置為NULL,則系統選擇地址。
  • shmflg:通常設置為0或SHM_RND(使附加地址向下舍入到最接近的SHMLBA邊界)。

返回值:成功時返回共享內存附加到進程的地址;失敗時返回(void *)-1并設置errno

  1. shmdt():取消共享內存的關聯

shmdt() 系統調用用于取消之前通過shmat()附加到進程的共享內存的關聯。

函數原型

int shmdt(const void *shmaddr);

參數

  • shmaddrshmat()返回的共享內存附加到進程的地址。

返回值:成功時返回0;失敗時返回-1并設置errno

  1. shmctl():控制共享內存

shmctl() 系統調用用于獲取或設置共享內存的屬性,或者刪除共享內存對象。

函數原型

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

參數

  • shmid:共享內存對象標識符。
  • cmd:要執行的操作。例如,IPC_RMID用于刪除共享內存對象,IPC_STAT用于獲取其狀態。
  • buf:指向shmid_ds結構的指針,用于傳遞或接收共享內存的狀態信息。

返回值:成功時返回0;失敗時返回-1并設置errno


今天就到這里了,也是結束了期末周,現在就開始正常更新啦

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/40290.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/40290.shtml
英文地址,請注明出處:http://en.pswp.cn/web/40290.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于java將dicom轉化為jpg的幾種方式

參考1 JAVA代碼實現DICOM文件轉換JPG package com.example;import java.awt.image.BufferedImage; import java.io.File;import javax.imageio.ImageIO;import ij.plugin.DICOM;/*** dicom文件java解析&#xff0c;生成圖片* 不過這里不能解析壓縮的dicom文件*/ public class …

Vue3學習筆記(n.0)

vue指令之v-for 首先創建自定義組件&#xff08;practice5.vue&#xff09;&#xff1a; <!--* Author: RealRoad1083425287qq.com* Date: 2024-07-05 21:28:45* LastEditors: Mei* LastEditTime: 2024-07-05 21:35:40* FilePath: \Fighting\new_project_0705\my-vue-app\…

重載一元運算符

自增運算符 #include<iostream> using namespace std; class CGirl { public:string name;int ranking;CGirl() { name "zhongge"; ranking 5; }void show() const{ cout << "name : "<<name << " , ranking : " <…

cmake編譯源碼教程(一)

1、介紹 本次博客介紹使用cmake編譯平面點云分割的源代碼,其對室內點云以及TLS點云中平面結構進行分割,分割效果如下: 2、編譯過程 2.1 源代碼下載 首先,下載源代碼,如下所示,在該文件夾下新建一個build文件夾,用于后續生成sln工程。 同時,由于該庫依賴open…

自動化設備上位機設計 二

目錄 一 設計原型 二 后臺代碼 一 設計原型 二 后臺代碼 namespace 自動化上位機設計 {public partial class Form1 : Form{public Form1(){InitializeComponent();timer1.Enabled true;timer1.Tick Timer1_Tick;}private void Timer1_Tick(object? sender, EventArgs e)…

您的私人辦公室!-----ONLYOFFICE8.1版本的桌面編輯器測評

隨時隨地創建并編輯文檔&#xff0c;還可就其進行協作 ONLYOFFICE 文檔是一款強大的在線編輯器&#xff0c;為您使用的平臺提供文本文檔、電子表格、演示文稿、表單和 PDF 編輯工具。 網頁地址鏈接&#xff1a; https://www.onlyoffice.com/zh/office-suite.aspxhttps://www…

AJAX-day1:

注&#xff1a;文件布局&#xff1a; 一、AJAX的概念&#xff1a; AJAX是瀏覽器與服務器進行數據通信的技術 >把數據變活 二、AJAX的使用&#xff1a; 使用axios庫&#xff0c;與服務器進行數據通信 基于XMLHttpRequest封裝&#xff0c;代碼簡單 Vue,React項目使用 學習…

自定義控件繪圖篇(一)基本幾何圖形繪制

在Android開發中&#xff0c;自定義控件是一種強大的技術&#xff0c;它允許開發者創建具有獨特外觀和行為的UI組件。通過自定義控件&#xff0c;你可以實現標準組件庫中沒有的功能和設計。自定義控件通常涉及兩個主要方面&#xff1a;布局和繪圖。本回答將重點介紹如何在自定義…

哪個品牌的加密軟件穩定方便使用?

一、什么是企業加密軟件&#xff1f; 企業加密軟件是一種用于保護企業內部數據安全的工具。在數字化時代&#xff0c;隨著數據量的爆炸式增長&#xff0c;信息安全和隱私保護變得愈發重要。企業加密軟件作為保障數據安全的關鍵工具&#xff0c;受到越來越多用戶的青睞。 企業…

昆蟲學(書籍學習資料)

包括昆蟲分類&#xff08;上下冊&#xff09;、昆蟲生態大圖鑒等書籍資料。

調和均值

文章目錄 調和均值的定義和公式調和均值的幾何解釋調和均值的應用調和均值與算術平均和幾何平均的比較示例 調和均值的定義和公式 調和均值是一種特殊的平均數&#xff0c;適用于處理涉及比率或速度的數據。對于一組正數 x 1 , x 2 , … , x n x_1, x_2, \ldots, x_n x1?,x2…

Java中的AQS

Java中的AbstractQueuedSynchronizer&#xff08;AQS&#xff09;是Java并發框架的核心組件之一&#xff0c;它位于java.util.concurrent.locks包下。AQS為Java的鎖和其他同步工具提供了基礎架構&#xff0c;它使用模板設計模式和一種稱為“CLH鎖”的算法來實現高效的線程同步。…

如何使用 SwiftUI 構建 visionOS 應用

文章目錄 前言WindowsVolumes沉浸式空間結論 前言 Apple Vision Pro 即將推出&#xff0c;現在是看看 SwiftUI API 的完美時機&#xff0c;這使我們能夠將我們的應用程序適應 visionOS 提供的沉浸式世界。蘋果表示&#xff0c;構建應用程序的最佳方式是使用 Swift 和 SwiftUI。…

2024年軟件測試崗必問的100+個面試題【含答案】

一、基礎理論 1、開場介紹 介紹要領&#xff1a;個人基本信息、工作經歷、之前所做過的工作及個人專長或者技能優勢。揚長避短&#xff0c;一定要口語化&#xff0c;語速適中。溝通好的就多說幾句&#xff0c;溝通不好的話就盡量少說兩句。舉例如下&#xff1a; 面試官你好&…

Java中Predicate(謂詞),方法引用,以及正則的一些講解

1.Predicate接口簡述 FunctionalInterface public interface Predicate<T> {boolean test(T t);default Predicate<T> and(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) && other.test(t);}default Pred…

速盾:cdn加速效果

CDN&#xff08;Content Delivery Network&#xff09;即內容分發網絡&#xff0c;是一種通過在全球多個節點服務器上緩存網站的靜態資源&#xff0c;并將用戶請求導向離用戶最近的服務器節點&#xff0c;從而提供更快速的訪問體驗的技術。 在傳統的網絡架構中&#xff0c;用戶…

鴻蒙 HarmonyOs 網絡請求 快速入門

官方文檔&#xff1a; ArkUI簡介-ArkUI&#xff08;方舟UI框架&#xff09;-應用框架 | 華為開發者聯盟 (huawei.com) 一、通過原有的http組件進行網絡請求&#xff08;方式一&#xff09; 1.1 HttpRequestOptions的操作 名稱類型描述methodRequestMethod請求方式&#xff…

12款超良心好用APP推薦,每一款都值得下載!

AI視頻生成&#xff1a;小說文案智能分鏡智能識別角色和場景批量Ai繪圖自動配音添加音樂一鍵合成視頻https://aitools.jurilu.com/分享是奉獻的果實&#xff0c;分享是快樂的前提。每天給小伙伴們分享自己認可的軟件&#xff0c;也是莫大的幸福&#xff0c;今天獲得12款好用的軟…

class類和style內聯樣式的綁定

這里的綁定其實就是v-bind的綁定&#xff0c;如代碼所示&#xff0c;div后面的引號就是v-bind綁定&#xff0c;然后大括號將整個對象括起來&#xff0c;對象內先是屬性&#xff0c;屬性后接的是變量&#xff0c;這個變量是定義在script中的&#xff0c;后通過這個變量&#xff…

flutter:監聽路由的變化

問題 當從路由B頁面返回路由A頁面后&#xff0c;A頁面需要進行數據刷新。因此需要監聽路由變化 解決 使用RouteObserver進行錄音監聽 創建全局變量&#xff0c;不在任何類中 final RouteObserver<PageRoute> routeObserver RouteObserver<PageRoute>();在mai…