【Linux】進程間通信之System V共享內存

在這里插入圖片描述

👦個人主頁:Weraphael
?🏻作者簡介:目前正在學習c++和算法
??專欄:Linux
🐋 希望大家多多支持,咱一起進步!😁
如果文章有啥瑕疵,希望大佬指點一二
如果文章對你有幫助的話
歡迎 評論💬 點贊👍🏻 收藏 📂 加關注😍


目錄

  • 一、共享內存的工作原理
  • 二、系統調用接口
      • 2.1 shmget函數 --- 創建共享內存
      • 2.2 補充:key值和shmid的區別
      • 2.3 shmctl函數 --- 釋放
      • 2.4 shmat函數 --- 進程掛接共享內存
      • 2.5 shmdt函數 --- 手動解除進程和共享內存掛接
  • 三、使用以上接口讓兩個進程通信
  • 四、共享內存的特性
  • 五、共享內存的屬性
  • 六、解決共享內存沒有同步和互斥保護機制問題
  • 七、本篇博客源代碼

一、共享內存的工作原理

請添加圖片描述

Linux操作系統除了要為進程創建結構體對象task_struct(表示進程的數據結構,包含了進程的所有屬性,如進程標識符PID);除此之外,操作系統還會為每個進程創建進程地址空間結構體對象mm_struct(存儲了進程的地址空間信息,包括堆、棧等)。該進程要如何找到自己的進程地址空間呢?因此task_struct結構體還會有該進程對應的mm_struct結構體指針字段,可以通過task_struct對象找到對應的進程地址空間。

另外,操作系統還需為每個進程創建頁表Linux操作系統系統會通過分頁機制來管理虛擬地址和物理地址之間的映射關系,用于將虛擬地址映射到物理地址。當程序訪問虛擬地址時,操作系統會根據頁表將虛擬地址轉換為物理地址。

而進程間通信的本質是:讓不同的進程看到同一份資源。因此,共享內存的原理就是:因為進程具有獨立性,無法自己提供資源給對方,因此操作系統會在物理內存中開辟一塊內存(“共享內存”由操作系統提供),再通過頁表映射到兩個不同進程的虛擬地址空間中的共享區,此時兩個獨立的進程看到同一塊空間,那么就可以進行通信了。(整個過程類似于動態庫的加載)

除此之外,因為共享內存是由程序員向操作系統申請的,當然還需要正確地釋放共享內存資源,以避免內存泄漏和資源占用。釋放共享內存的過程包括兩個主要步驟:

  • 解除所有與要釋放的共享內存有關系的進程(去關聯)
  • 最后釋放共享內存,通常由最后一個使用該共享內存段的進程來執行(釋放共享內存)

因此,System V共享內存通信的整個過程總結如下:

  • 創建共享內存段

  • 進程掛接共享內存

  • 通信

  • 解除共享內存掛接

二、系統調用接口

共享內存的創建、進程間建立映射和釋放都是由操作系統完成的。對應的操作系統也要為用戶提供訪問和管理共享內存的接口,允許用戶在程序中使用共享內存來實現進程間通信。

2.1 shmget函數 — 創建共享內存

shmget 函數是一個用于創建共享內存或者訪問已存在共享內存段,通常用于在進程之間共享數據而無需通過文件系統。它的函數原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);

參數說明:

  • key:可以唯一標識一個共享內存段(內核層使用)。這個值是多少并不重要,關鍵在于它必須具有唯一性,能夠讓不同的進程看到同一個共享內存(兩個進程傳入相同的key值即看到同一塊內存)。就這樣說吧,第一個進程通過key值創建共享內存,后面的進程要用看到同一個共享內存通信,只需拿同一個key值即可!注意:這個key值在struct shmid_ds對象中。那么接下來只有最后一個問題:如何獲取設置key?一般使用 ftok 函數生成唯一key值(常用)。(查看函數用法:點擊跳轉)

  • size指定要創建的共享內存段的大小,以字節為單位。如果創建新的共享內存段,建議分配4096的整數倍(如果你創建的大小是4097,實際上操作系統分配的大小是4096 * 2)。如果只是獲取一個已存在的共享內存段的標識符,這個參數可以設置為0來表示忽略。

  • shmflg:這是一個標志參數,用于指定操作模式和權限。可以用操作符'|'進行組合使用。它可以是以下幾個標志的組合:

    • IPC_CREAT這個選項單獨使用的話,如果申請的共享內存不存在,則創建一個新的共享內存;如果存在,獲取已存在的共享內存
    • IPC_EXCL: 一般配合 IPC_CREAT 一起使用(不單獨使用)。他主要是檢測共享內存是否存在,如果存在,則出錯返回;如果不存在就創建。確保申請的共享內存一定是新的。
    • 權限標志:以與文件權限類似的方式指定共享內存段的訪問權限(例如 0666 表示所有用戶可讀寫)。
    • 但在獲取已存在的共享內存時,可以設置為 0
  • 返回值:

    • 成功時返回共享內存段的標識符shmid。(操作系統內部分配的,提供給用戶層使用,類似于文件描述符fd

    • 失敗時返回 -1,并設置 errno 以指示錯誤原因。

  • 返回值常見錯誤errno如下:

    • EACCES:對于給定的鍵值沒有足夠的權限。

    • EEXIST: 設置了 IPC_CREAT | IPC_EXCL,但具有給定鍵值的共享內存段已經存在。

    • EINVAL: 請求的大小無效,或者給定的鍵值無效。

    • ENOENT: 沒有設置 IPC_CREAT,而且具有給定鍵值的共享內存段不存在。

    • ENOMEM: 沒有足夠的內存來滿足請求。

ftok函數是專門用于生成唯一鍵值,通常用于進程間通信的共享內存、信號量和消息隊列。它通過將文件路徑(路徑本身就具有唯一性)項目標識符ID 結合起來生成一個唯一的鍵值。其函數原型如下:

#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
  • pathname: 指向一個現有文件的路徑。這個文件可以是任何文件,但在實際應用中,通常選擇一個固定存在且不會被刪除的文件。

  • proj_id: 項目標識符,一個用戶指定的整數值。該值一般用于擴展唯一性,如果沖突該次參數就行。

  • 返回值:

    • 成功時返回生成的鍵值 (key_t 類型)。可以用于后續的函數調用(如shmget()等)。

    • 失敗時返回 -1,并設置 errno 以指示錯誤原因。

常見的 errno 值包括:

  • EACCES: 文件不可訪問。
  • ENOENT: 文件不存在。
  • ENAMETOOLONG: 路徑名過長。

代碼樣例:

resource.hpp中封裝創建共享內存段的接口,來供其他進程使用。

#pragma once#include <iostream>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string>
#include <cstring>
#include "log.hpp"using namespace std;const string pathname = "/home/wj"; // ftok函數的第一個參數
int proj_id = 'A';                  // ftok函數的第二個參數
log l;                              // 日志對象key_t Getkey()
{key_t key = ftok(pathname.c_str(), proj_id);if (key == -1){l.logmessage(Fatal, "ftok error: %s", strerror(errno));exit(1);}l.logmessage(Info, "ftok success, key is: 0x%x", key);return key;
}int GetShareMem()
{key_t key = Getkey();int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL);if (shmid == -1){l.logmessage(Fatal, "create share memory error: %s", strerror(errno));}l.logmessage(Info, "create share memory success: %d", shmid);return shmid;
}

我們先使用一個進程processA.cc來測試一下

#include "resource.hpp"int main()
{int shmid = GetShareMem();l.logmessage(Debug, "process quit...");return 0;
}

結果如下:

請添加圖片描述

2.2 補充:key值和shmid的區別

不同的進程在選擇共享內存通信時,是通過key值來保證共享內存的唯一性,那這里就存在一個問題:shmget函數也會返回一個值,我們把這個值稱為共享內存段標識符shmid,而往后要對共享內存進行操作(如附加、分離、刪除等)的時候,通常使用的是shmid來進行操作。為什么要創建兩種共享內存的標識呢?直接使用一個來進行標識不就夠了嘛?

那么這里我就可以舉一個生活中的例子來進行解釋:我們中國有14+億的人口,每個人出生之后就會擁有一個獨一無二(唯一)的身份證號,通過這個身份證號我們就可以標識一個人,那為什么還要給每個人取一個名字呢?直接使用身份證號來代替名字不就可以了嗎?之所以這么做是因為使用名字可以更加方便的社交,在短范圍內能夠更快的確定一個人,如果使用身份證號的話就會導致標識和管理的時候比較臃腫。

那么這里也是同樣的道理,key就相當于身份證號,shmid相當于姓名。站在用戶角度,使用key值來標定共享內存太難了太復雜了,所以我們使用shmid來標識共享內存這樣可以更加的方便和容易;而操作系統它不嫌麻煩他為了更加嚴謹的標識共享內存就必須得使用更加復雜的key值來進行標定,就好比政府機構在處理具體某個人的事情時,都會拿身份證號來標定某個人,因為身份證號更加的復雜、更加的準確、權威性更高。因此key是給內核層使用的。

key值在哪里存儲的呢?我們說操作系統中存在多個共享內存所以要進行先描述在組織,那么描述共享內存的shm結構體中就存在一個字段專門用來記錄共享內存的key值,所以key值會通過系統調用shmget設置進共享內存的屬性中的用來表示共享內存在內核中的唯一性。

總之,shmid相當于文件系統的文件描述符fd,而key就相當于文件系統的inode編號,inode編號是用來給操作系統看的,fd是用來給我們操作者使用的,操作系統之所以這么做是為了方便標識符的解藕,用戶層和操作系統層使用不同的東西來標識內存,這樣用戶層和操作系統層就不會發生相互的干擾,也就是一個層面出現了問題不會影響另外的一個層面,那么這就是key(內核層使用)和shmid(用戶層使用)的區別。

2.3 shmctl函數 — 釋放

請添加圖片描述

如上,當我再次啟動進程processA的時候,我們發現使用shmget函數創建共享內存失敗了,原因是文件存在(共享內存段存在)。通過分析我們知道:我使用IPC_CREATIPC_EXCL選項保證進程每次申請的共享內存一定是新的,而在第一次運行完進程processA,我們的代碼內并沒有手動釋放共享內存段,因此導致報錯!

Linux中,如果想查看操作系統管理的共享內存段,我們可以使用以下命令:

ipcs -m

請添加圖片描述

共享內存的生命周期是隨內核的(操作系統重啟,共享內存通常才會被釋放)!因為共享內存是由用戶向操作系統申請的,如果不主動關閉,共享內存會一直存在。另外,如果程序頻繁地分配共享內存而不釋放,系統的可用內存資源會逐漸減少,可能導致系統性能下降或者其它進程受到影響(內存泄漏)。

有兩種方法可以釋放共享內存段:

  • 使用以下命令來指定釋放共享內存段
ipcrm -m <shmid>

請添加圖片描述

  • 使用系統調用接口shmctl函數

shmctl 函數用于對共享內存進行控制操作,例如獲取共享內存信息、設置共享內存權限、銷毀共享內存等。它的原型如下:

#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);

參數說明:

  • shmid:共享內存標識符shmid

  • cmd:控制命令,可以是下列之一:

    • IPC_STAT:獲取共享內存的狀態信息,并將其存儲在 buf 結構體中。
    • IPC_SET:設置共享內存的狀態信息,使用 buf 結構體中提供的新值。
    • IPC_RMID:刪除共享內存段。
  • buf:指向描述共享內存的結構體shmid_ds的指針,用于存儲或傳遞共享內存的狀態信息。如果是刪除共享內存段,此參數可以設置為nullptr

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

【代碼樣例】

#include "resource.hpp"int main()
{// 創建共享內存段int shmid = GetShareMem();l.logmessage(Debug, "create shm done");// 銷毀共享內存段int res = shmctl(shmid, IPC_RMID, nullptr);if (res == -1){l.logmessage(Error, "share Memory destroy failed, shmid is %d", shmid);exit(Error);}l.logmessage(Debug, "share Memory destroy success, shmid is %d", shmid);l.logmessage(Debug, "process quit...");return 0;
}

【程序結果】

請添加圖片描述

2.4 shmat函數 — 進程掛接共享內存

shmat函數的基本作用是將一個共享內存段映射到調用進程的地址空間的共享區,從而使得進程可以直接訪問共享內存中的數據。

函數基本原型如下:

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • shmid:共享內存的標識符shmid

  • shmaddr:指定共享內存段映射到進程地址空間的起始地址,通常設為 nullptr,讓系統自動選擇合適的地址。

  • shmflg:附加標志,通常設為 0

  • 返回值:成功返回指向共享內存的起始地址的指針;失敗返回nullptr

  • 代碼寫法有點類似于malloc函數

  • 注意:進程退出后,會自動解除掛接。

為了更好觀察進程掛接共享內存的個數,在銷毀共享內存段之前休眠10

#include "resource.hpp"int main()
{// 1. 創建共享內存段int shmid = GetShareMem();l.logmessage(Debug, "create shm done");// 2. 掛接共享內存段char *shmaddress = (char *)shmat(shmid, nullptr, 0);if (shmaddress == nullptr){l.logmessage(Error, "processA attach failed, shmid is %d", shmid);}l.logmessage(Debug, "processA attach success, shmid is %d", shmid);// 休眠10ssleep(10);// 3. 銷毀共享內存段int res = shmctl(shmid, IPC_RMID, nullptr);if (res == -1){l.logmessage(Error, "share Memory destroy failed, shmid is %d", shmid);exit(Error);}l.logmessage(Debug, "share Memory destroy success, shmid is %d", shmid);l.logmessage(Debug, "process quit...");return 0;
}

【程序結果】

請添加圖片描述

2.5 shmdt函數 — 手動解除進程和共享內存掛接

shmdt 函數用于將共享內存從當前進程的地址空間中分離,即取消共享內存的映射。這個函數的原型如下:

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
  • shmaddr 參數是一個指向共享內存段起始地址的指針,通常是shmat函數的返回值。
  • 返回值:成功返回0,失敗返回-1

代碼演示略 ~

三、使用以上接口讓兩個進程通信

一般而言,只要有一方進程創建了共享內存段,另一方進程直接獲取其共享內存段標識符shmid后,即可進行通信。

  • resource.hpp主要是封裝獲取共享內存段接口,確保兩個進程使用的是同一塊共享內存。
#pragma once#include <iostream>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include "log.hpp"using namespace std;const string pathname = "/home/wj"; // ftok函數的第一個參數
int proj_id = 'A';                  // ftok函數的第二個參數
log l;                              // 日志對象key_t Getkey()
{key_t key = ftok(pathname.c_str(), proj_id);if (key == -1){l.logmessage(Fatal, "ftok error: %s", strerror(errno));exit(1);}l.logmessage(Info, "ftok success, key is: 0x%x", key);return key;
}int GetShareMem()
{key_t key = Getkey();int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);if (shmid == -1){l.logmessage(Fatal, "create share memory error: %s", strerror(errno));}l.logmessage(Info, "create share memory success, shmid is %d", shmid);return shmid;
}int GetShm()
{// 大小:獲取一個已存在的共享內存段的標識符,這個參數可以設置為0來表示忽略// 第三個參數:獲取已存在的共享內存時,可以設置為 0return shmget(Getkey(), 0, 0);
}
  • processA.cc主要是負責讀取共享內存段的數據。注意:此進程要先運行起來。
#include "resource.hpp"int main()
{// 1. 創建共享內存段int shmid = GetShareMem();// 2. 掛接共享內存段char *shmaddress = (char *)shmat(shmid, nullptr, 0);// 3. 通信while (true){// 假設processA進程作為客戶端,負責讀取cout << "client say@ " << shmaddress << endl;if (strcmp(shmaddress, "quit\n") == 0){break;}sleep(1);}// 4. 取消掛接shmdt(shmaddress);// 5. 銷毀共享內存段int res = shmctl(shmid, IPC_RMID, nullptr);return 0;
}
  • processB主要是向共享內存段寫入數據。當寫入quit時整個通信過程結束。
#include "resource.hpp"int main()
{// 1. 獲取shmidint shmid = GetShm();// 2. 掛接共享內存char *shmaddress = (char *)shmat(shmid, nullptr, 0);// 3. 通信while (true){// processB進程作為服務端,負責寫cout << "請輸入:";char *context = fgets(shmaddress, 4096, stdin);if (context != nullptr && strcmp(shmaddress, "quit\n") == 0){break;}}// 4. 取消掛接shmdt(shmaddress);return 0;
}
  • 程序結果

在這里插入圖片描述

四、共享內存的特性

  • 共享內存沒有同步和互斥之類的保護機制。即讀寫雙方可以同時訪問共享內存,這會導致數據不一致問題,這個問題的解決方案將在下方會介紹到。

  • 共享內存是所有的進程間通信中,速度最快的!原因在于它減少數據拷貝。在使用共享內存時,多個進程可以直接訪問同一塊物理內存,而不需要將數據從一個進程的地址空間復制到另一個進程的地址空間。這避免了數據在內存之間的復制,從而減少了通信的開銷和延遲。

  • 共享內存內部的數據由用戶自己維護(讀完要自己清空)。

  • 共享內存的生命周期是隨內核的,用戶不主動刪除,共享內存會一直存在(除非內核重啟或用戶釋放)

  • 共享內存的大小一般建議是4096的整數倍,內存管理的一頁大小為4096字節(4KB)。若申請4097,則系統會分配4096 * 2,但用戶還是只能使用4097的空間,會存在4095字節空間的浪費。

五、共享內存的屬性

而我們知道,因為系統中不止一對進程在進行通信,可能會存在多個,那么操作系統就要在物理內存上開辟多個共享內存,那么操作系統就必須對這些區域進行管理,這又得搬出管理的六字真言:先描述,再組織。在Unix/Linux中,描述共享內存段的信息通常通過 struct shmid_ds 結構體來表示:

struct shmid_ds 
{/*這是一個 struct ipc_perm 結構體,用于描述共享內存的操作權限struct ipc_perm 包含了共享內存段的擁有者、組和訪問權限等信息。*/struct ipc_perm shm_perm; // shm_segsz表示共享內存段的大小,單位是字節int shm_segsz; // shm_atime表示最后一次附加該共享內存段的時間。__kernel_time_t shm_atime; // shm_dtime表示最后一次分離該共享內存段的時間。__kernel_time_t shm_dtime; // 表示最后一次更改該共享內存段的時間。__kernel_time_t shm_ctime;// shm_cpid表示關聯共享內存的進程標識符PID__kernel_ipc_pid_t shm_cpid;// shm_lpid表示最后一個操作該共享內存段的進程的 PID__kernel_ipc_pid_t shm_lpid; // shm_nattch表示當前使用到該共享內存段的進程數unsigned short shm_nattch; // ...
};
//  struct ipc_perm 結構體
struct ipc_perm 
{// key 用于標識共享內存段。不同的進程可以通過這個key來訪問同一個共享內存段。key_t  __key;    // uid 擁有者(owner)的有效用戶ID(UID),即對共享內存段有讀寫權限的用戶的UID。uid_t  uid;      // gid 這是擁有者的有效組ID(GID),即對共享內存段有讀寫權限的用戶所在的組的GID。gid_t  gid;     // cuid 這是創建者(creator)的有效用戶ID(UID),即創建共享內存段的進程的UID。 uid_t  cuid;    // cgid 這是創建者的有效組ID(GID),即創建共享內存段的進程所在的組的GID。 gid_t  cgid;  // mode 這個字段包含了權限和一些特定標志,如使用IPC_CREAT來創建IPC對象。unsigned short mode;     // __seq 這個字段是序列號,用于維護IPC對象的序列。                                 unsigned short __seq;    
};

最后再通過諸如鏈表、順序表等數據結構將這些結構體對象管理起來。因此,往后我們對共享內存的管理,只需轉化為對某種數據結構的增刪查改。

我們可以使用shmctl函數來獲取屬性信息(具體用法查看往上翻)

int main()
{//  創建共享內存段int shmid = GetShareMem();// 掛接共享內存段char *shmaddress = (char *)shmat(shmid, nullptr, 0);// 獲取共享內存的屬性struct shmid_ds shmds;shmctl(shmid, IPC_STAT, &shmds);cout << "共享內存的大小:" << shmds.shm_segsz << endl;cout << "共享內存的連接數:" << shmds.shm_nattch << endl;printf("共享內存的key值:0x%x\n", shmds.shm_perm.__key);// 取消掛接shmdt(shmaddress);return 0;
}

【程序結果】

請添加圖片描述

六、解決共享內存沒有同步和互斥保護機制問題

共享內存的特點是 無讀寫規則限制,進程即可讀也可寫,容易造成沖突,因此我們可以對其加以限制,所使用的工具正是命名管道。

邏輯思路是這樣的:當共享內存的寫方向共享內存段寫完數據后,使用命名管道向讀方發送一條通知,說明可以向共享內存讀取數據了。

  • resource.hpp增加了創建命名管道的類
#pragma once#include <iostream>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string>
#include <sys/stat.h>
#include <cstdlib>
#include <cstring>
#include "log.hpp"using namespace std;const string pathname = "/home/wj"; // ftok函數的第一個參數
int proj_id = 'A';                  // ftok函數的第二個參數
log l;                              // 日志對象key_t Getkey()
{key_t key = ftok(pathname.c_str(), proj_id);if (key == -1){l.logmessage(Fatal, "ftok error: %s", strerror(errno));exit(1);}l.logmessage(Info, "ftok success, key is: 0x%x", key);return key;
}int GetShareMem()
{key_t key = Getkey();int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);if (shmid == -1){l.logmessage(Fatal, "create share memory error: %s", strerror(errno));}l.logmessage(Info, "create share memory success, shmid is %d", shmid);return shmid;
}int GetShm()
{// 大小:獲取一個已存在的共享內存段的標識符,這個參數可以設置為0來表示忽略// 第三個參數:獲取已存在的共享內存時,可以設置為 0return shmget(Getkey(), 0, 0);
}// ================== 命名管道 ==============================
enum
{// 規定錯誤碼從1開始遞增MKFIFO_FAIL = 1, // 創建匿名管道失敗UNLINK_FAIL,     // 刪除匿名管道失敗OPEN_FAIL        // 打開文件失敗
};class Init
{
public:Init(){// 創建管道int n = mkfifo("./myfifo", 0664);if (n == -1){perror("mkfifo");exit(MKFIFO_FAIL);}}~Init(){int m = unlink("./myfifo");if (m == -1){perror("unlink");exit(UNLINK_FAIL);}}
};
  • processB.cc是寫方,當寫完后就向管道寫入一個字符作為通知。
int main()
{// 1. 獲取shmidint shmid = GetShm();// 2. 掛接共享內存char *shmaddress = (char *)shmat(shmid, nullptr, 0);// 打開命名管道int fd = open("./myfifo", O_WRONLY);if (fd == -1){exit(OPEN_FAIL);}// 3. 通信while (true){// processB進程作為服務端,負責寫cout << "請輸入:";char *context = fgets(shmaddress, 4096, stdin);// 寫完之后,通知對方來讀取。write(fd, "c", 1);if (context != nullptr && strcmp(shmaddress, "quit\n") == 0){break;}}// 4. 取消掛接shmdt(shmaddress);close(fd);return 0;
}
  • processA.cc是讀方,在向共享內存段讀取之前,先判斷管道是否有“通知”,有則可以讀取。
int main()
{// 創建管道Init init;// 1. 創建共享內存段int shmid = GetShareMem();// 2. 掛接共享內存段char *shmaddress = (char *)shmat(shmid, nullptr, 0);// 打開命名管道int fd = open("./myfifo", O_RDONLY);if (fd == -1){exit(OPEN_FAIL);}// 3. 通信while (true){// 假設processA進程作為客戶端,負責讀取// 在讀取之前先去管道看看是否有通知char c;ssize_t s = read(fd, &c, 1);// s == 0 說明沒讀到,那就繼續讀取if (s == 0){continue;}// s == -1說明讀取發生錯誤,那就退出if (s == -1){break;}cout << "client say@ " << shmaddress << endl;if (strcmp(shmaddress, "quit\n") == 0){break;}sleep(1);}// 4. 取消掛接shmdt(shmaddress);// 5. 銷毀共享內存段int res = shmctl(shmid, IPC_RMID, nullptr);close(fd);return 0;
}
  • 程序結果

在這里插入圖片描述

七、本篇博客源代碼

Gitee鏈接:點擊跳轉

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

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

相關文章

Prometheus+Grafana監控Linux主機

1、安裝Prometheus 1.1 、下載Prometheus 下載網址 https://github.com/prometheus/prometheus/releases選擇需要的版本 wget https://github.com/prometheus/prometheus/releases/download/v2.53.0/prometheus-2.53.0.linux-amd64.tar.gz1.2、安裝Prometheus軟件 1.2.1、…

解決鴻蒙開發中克隆項目無法簽名問題

文章目錄 問題描述問題分析解決方案 問題描述 在一個風和日麗的早晨&#xff0c;這是我學習鴻蒙開發的第四天&#xff0c;把文檔過了一遍的我準備看看別人的項目學習一下&#xff0c;于是就用git去clone了一個大佬的開源項目&#xff0c;在簽名的時候遇到了問題&#xff1a; h…

在攻防演練中遇到的一個“有馬蜂的蜜罐”

在攻防演練中遇到的一個“有馬蜂的蜜罐” 有趣的結論&#xff0c;請一路看到文章結尾 在前幾天的攻防演練中&#xff0c;我跟隊友的氣氛氛圍都很好&#xff0c;有說有笑&#xff0c;恐怕也是全場話最多、笑最多的隊伍了。 也是因為我們遇到了許多相當有趣的事情&#xff0c;其…

Spring JDBC 具名參數用法

Spring JDBC中具名參數的用法 maven引入Spring jdbc <dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.19</version></dependency> 在Spring配置中配置 <!-…

【leetcode】滑動窗口專題

文章目錄 1.長度最小的子數組2.無重復字符的最長子串3.最大連續1的個數III4.將x減小到0的最小操作數5.水果成籃6.找到字符串中所有字母異位詞7.串聯所有單詞的子串8.最小覆蓋子串 1.長度最小的子數組 leetcode 209.長度最小的子數組 看到這個題目&#xff0c;第一眼肯定想到的…

正則表達式控制everything等搜索工具更快速的對需要的內容進行檢索

正則表達式對文件搜索工具規則 表格模式 匹配模式描述abgr(ale)y匹配 “gray” 或 “grey”.匹配除換行符之外的任意單個字符[abc]匹配字符 “a”、“b” 或 “c” 中的任意一個[^abc]匹配除了 “a”、“b”、“c” 之外的任意單個字符[a-z]匹配小寫字母 a 到 z 之間的任意一…

科普文:深入理解Mybatis

概敘 (1) JDBC JDBC(Java Data Base Connection,java數據庫連接)是一種用于執行SQL語句的Java API,可以為多種關系數據庫提供統一訪問,它由一組用Java語言編寫的類和接口組成.JDBC提供了一種基準,據此可以構建更高級的工具和接口,使數據庫開發人員能夠編寫數據庫應用程序。 優點…

Vue3 + Echarts堆疊折線圖的tooltip不顯示問題

問題介紹 使用Echarts在Vue3Vite項目中繪制堆疊折線圖的的時候&#xff0c;tooltip總是不顯示&#xff0c;經過很長時間的排查和修改&#xff0c;最后發現是在使用上有錯誤導致的。 錯誤圖片展示 問題原因 由于Vue3底層使用proxy代理創建示例&#xff0c;使用其創建出來的實…

RDD 專項練習

RDD 專項練習 現有分數信息文件 scores.txt 班級ID 姓名 年齡 性別 科目 成績 12 張三 25 男 chinese 50 12 張三 25 男 math 60 12 張三 25 男 english 70 12 李四 20 男 chinese 50 12 李四 20 男 math 50 12 李四 20 男 english 50 12 王芳 19 女 chinese 70 12 王芳 19 女…

FPGA-Verilog-Vivado-軟件使用

這里寫目錄標題 1 軟件配置2 FPGA-7000使用2.1 運行啟動方式 1 軟件配置 編輯器綁定為Vscode&#xff0c;粘貼VS code運行文件的目錄&#xff0c;后綴參數保持不變&#xff1a; 如&#xff1a; D:/Users/xdwu/AppData/Local/Programs/Microsoft VS Code/Code.exe [file name]…

從技術到管理:你必須知道的七個轉變

在職業生涯的道路上&#xff0c;很多技術骨干會逐步轉向管理崗位。這不僅是職位的晉升&#xff0c;更是角色、思維和能力的全方位轉變。以下是七個關鍵的轉變&#xff0c;幫助技術人員順利完成這一跨越。 一、從個人貢獻者到團隊領導者的轉變 在技術崗位上&#xff0c;成功往…

(19)夾鉗(用于送貨)

文章目錄 前言 1 常見的抓手參數 2 參數說明 前言 Copter 支持許多不同的抓取器&#xff0c;這對送貨應用和落瓶很有用。 按照下面的鏈接&#xff08;或側邊欄&#xff09;&#xff0c;根據你的設置了解配置信息。 Electro Permanent Magnet v3 (EPMv3)Electro Permanent M…

bug記錄 qInstallMessageHandler的使用

QT (純C)項目 ‘Qxxx‘ file not found 和 編譯報錯問題(已解決)_qt頭文件file not found-CSDN博客 qInstallMessageHandler&#xff08;指針函數參數&#xff09; 需要靜態指針&#xff0c;這個函數 #include <iostream> #include "singleton.h" #include &…

Linux操作系統CentOS如何更換yum鏡像源

簡介 CentOS&#xff0c;是基于Red Hat Linux提供的可自由使用源代碼的企業級Linux發行版本&#xff1b;是一個穩定&#xff0c;可預測&#xff0c;可管理和可復制的免費企業級計算平臺。 下載地址: centos安裝包下載_開源鏡像站-阿里云 相關倉庫&#xff1a; CentOS過期源&…

職業教育人工智能實驗實訓室建設應用案例

隨著人工智能技術的快速發展&#xff0c;其在職業教育領域的應用逐漸深入。唯眾作為一家專注于教育技術領域的企業&#xff0c;積極響應國家關于人工智能教育的政策號召&#xff0c;通過建設人工智能實驗實訓室&#xff0c;為學生提供了一個實踐操作與創新思維相結合的學習平臺…

C++ STL iter_swap用法和實現

一&#xff1a;功能 交換兩個迭代器指向的元素值&#xff0c;一般用在模板中 二&#xff1a;使用 #include <vector> #include <iostream>template <typename It, typename Cond>requires std::forward_iterator<It> && std::indirectly_swa…

富格林:曝光糾正安全交易誤區

富格林指出&#xff0c;貴金屬投資是許多投資者追求資產多樣化和風險管理的重要途徑。然而&#xff0c;正如任何投資領域一樣&#xff0c;不少投資者也對貴金屬投資產生了一些誤區和錯誤觀念。但事實上&#xff0c;如果這種誤區一直伴隨著我們的交易進程&#xff0c;是很難做到…

34 超級數據查看器 關聯圖片

超級數據查看器app&#xff08;excel工具&#xff0c;數據庫軟件&#xff0c;表格app&#xff09; 關聯圖片講解 點擊 打開該講的視頻 點擊訪問app下載頁面 豌豆莢 下載地址 大家好&#xff0c;今天我們講一下超級數據查看器的關聯圖片功能 這個功能能讓表中的每一條信息&…

數據結構-散列表(hash table)

6.1 散列表的概念 散列表又叫哈希&#xff08;hash&#xff09;表&#xff0c;是根據鍵&#xff08;key&#xff09;直接訪問在內存存儲位置的值&#xff08;value&#xff09;的數據結構&#xff0c;由數組演化而來&#xff08;根據數組支持按照下標進行隨機訪問數據的特性&a…

windows腳本獲取 svn版本號

簡介 需要使用項目中svn的最新版本號 命令 set svnURL"URL" svn info %svnURL% | findstr "Revision:" > Version.txt for /f "token2 delims " %%i in (Version.txt) do set rev%%i echo %rev% pause