Linux——System V 共享內存 IPC

文章目錄

  • 一、共享內存的原理
  • 二、信道的建立
    • 1.創建共享內存
      • 1.key的作用
      • 2.key的選取
      • 3.shmid的作用
      • 4.key和shmid的區別
      • 5.內存設定的特性
      • 6.shmflg的設定
    • 2.綁定共享內存
    • 3.代碼示例
  • 三、利用共享內存通信
    • 1.通信
    • 2.解除綁定
    • 3.銷毀共享內存
      • 1.命令行銷毀
      • 2.程序中銷毀
  • 四、共享內存的生命周期
  • 五、數據安全問題
  • 六、源碼
    • Fifo.hpp
    • Comm.hpp
    • Shm.hpp
    • server.cc
    • client.cc

一、共享內存的原理

共享內存是通過在物理內存上開辟一塊空間,然后讓需要通信的進程都映射到這一塊空間,這樣就使它們看到同一塊資源了。

在這里插入圖片描述

共享內存區是最快的IPC形式。?旦這樣的內存映射到共享它的進程的地址空間,這些進程間數據傳遞
不再涉及到內核,換句話說是進程不再通過執?進?內核的系統調?來傳遞彼此的數據

共享內存通信是雙向的,也就是說一個進程可以既讀又寫,使用起來就和C語言的malloc申請到的內存差不多。這種通信方式存在著數據安全問題,會在下文細說。

二、信道的建立

1.創建共享內存

創建共享內存使用shmget函數,它的作用是創建或獲取共享內存段的系統調用。

對于shmget的使用來說,雖然操作起來相對簡單,但要完全理解其各種參數的設定則較為困難。不過接下來我會進行詳細講解。

shmget聲明如下:

#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
  • 參數key:用戶設定任意一個數,用于區分不同共享內存,通常由ftok生成。
  • 參數size:設定共享內存的大小。
  • 參數shmflg:標志位,用于指定共享內存段的創建方式和權限。常見的標志包括:
    IPC_CREAT :如果共享內存段不存在,則創建它。
    IPC_EXCL :與 IPC_CREAT 一起使用,確保創建的共享內存段是新的。
    權限標志:如 0666,表示所有用戶都有讀寫權限。
  • 返回值: 成功時返回共享內存段的標識符 (shmid) 。 失敗時返回 -1,并設置 errno 以指示錯誤類型。

1.key的作用

  • 思考1:在用戶層面如何讓兩個獨立進程共享同一塊內存?
  • 思考2:在匿名管道和命名管道中,用戶層面是如何讓兩個進程確定同一個資源的?

問題2很顯然,管道的本質是文件,用戶通過讓兩個程序打開同一個文件名來實現看到同一個資源。

因此,共享內存同樣需要一個key來充當類似文件名的功能。

2.key的選取

key參數本質是一個int類型,我們可以直接指定一個數值傳入,當然,為了更規范,更專業,我們通常都會使用ftok來生成。

#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
  • 參數pathnme:一個存在的文件路徑(例如 /tmp/myfile),文件必須存在,否則 ftok 會失敗。
  • 參數proj_id:一個整數,用于進一步區分不同的 IPC 對象。
  • 返回值:
    成功:返回生成的 key_t 鍵值。
    失敗:返回 -1,并設置 errno 以指示錯誤原因。

3.shmid的作用

shmid是一個int類型,由shmget返回,在作用上和物理意義上與文件系統中的fd類似。

它的作用主要是讓用戶找到指定的共享內存。

在操作系統內核中,shmid 的物理意義如下:

內核維護一個 共享內存段表(如 struct shmid_kernel),每個表項對應一塊共享內存。

shmid 是該表的索引,通過它找到對應的共享內存段(含物理頁、權限、掛載進程等信息)。

共享內存最終映射到 實際的物理頁幀(通過頁表機制)。

物理本質是內存頁的映射:多個進程的虛擬地址映射到同一組物理頁。
在這里插入圖片描述

4.key和shmid的區別

key最終成為系統層區分不同IPC的標志,而shmid則是用戶層用來區分不同IPC的標志。

5.內存設定的特性

這里的內存設定指的是shmget函數中的參數size。

當傳入的內存不足4096字節(4KB)的倍數時,會擴到4096倍數。但是只會提供size大小的使用空間。這樣做可以規避掉一些因為共享內存過多帶來的問題。

6.shmflg的設定

對于共享內存,我們可以將程序簡單分為創建端和使用端,它們的shmflg設定通常是:

  • 創建端:IPC_CREAT | IPC_EXCL | 0666
  • 使用端:IPC_CREAT

創建端要保證IPC是最新的,所以需要加IPC_EXCL,然后還需要設定權限。

使用端只需要獲取共享內存段的系統調用,因此只用一個IPC_CREAT即可。

2.綁定共享內存

以上我們完成的只是共享內存的創建,接下來還需要把進程綁定到共享內存,使用函數shmat,其中at指的是單詞attach。

#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 參數shmid:傳入從shmget中返回的shmid來指定共享內存。
  • 參數shmaddr:指定共享內存段附加到進程地址空間的位置,通常設為nullptr,系統會自動選擇一個合適的地址。
  • 參數shmflg:讀寫方式,常用的有:
    SHM_RDONLY:以只讀方式附加共享內存段。
    0:以讀寫方式附加共享內存段。
  • 返回值:
    成功時,返回共享內存段附加到進程地址空間的起始地址。
    失敗時,返回 (void *) -1,并設置 errno。

3.代碼示例

創建端程序:

int main()
{//生成一個keyint key = ftok(".", 48);//創建共享內存int shmid = shmget(key, 4069, IPC_CREAT | IPC_EXCL | 0666);//連接到共享內存void* p = shmat(shmid,nullptr,0);//使用共享內存//... ...return 0;
}

使用端程序:

int main()
{//生成一個相同keyint key = ftok(".", 48);//獲取到共享內存的系統調用int shmid = shmget(key, 4069, IPC_CREAT);//連接到共享內存void* p = shmat(shmid,nullptr,0);//使用共享內存//... ...return 0;
}

注意:為了簡潔和方便說明問題,以上代碼省略了頭文件的包含和返回值有效性的判斷等等,在實際開發中可不敢省略。

三、利用共享內存通信

1.通信

上文我們只是完成了信道的建立,接下來我們進行通信,通過上面的操作,我們已經獲取到共享內存的起始地址。

它的用法與C語言的malloc申請的內存用法相同,只是共享內存可以同時被兩個進程訪問。

如下寫端:

int main()
{int key = ftok(".", 48);int shmid = shmget(key, 4069, IPC_CREAT | IPC_EXCL | 0666);void* p = shmat(shmid,nullptr,0);//使用共享內存char* chp = (char*)p;for(int i='a';i<='z';i++){sleep(1);*chp=i;chp++;}return 0;
}

讀端:

int main()
{int key = ftok(".", 48);int shmid = shmget(key, 4069, IPC_CREAT);void* p = shmat(shmid,nullptr,0);//使用共享內存char* chp = (char*)p;while(true){sleep(1);cout<<chp<<endl;}return 0;
}

注意:為了獲取到同一個共享內存,我們設定的key必須一致。

2.解除綁定

如果進程退出時沒有解除綁定,共享內存段仍然會保留在系統的共享內存資源中,直到顯式刪除(通過 shmctl 或系統重啟)。

使用shmdt來解除綁定,其中dt代表單詞delete。

int shmdt(const void *shmaddr);
  • 參數shmaddr:需要斷開連接的共享內存的起始地址。

  • 返回值:

    成功:返回0。
    失敗:返回-1,并設置errno以指示錯誤原因。
    一個共享內存,與它綁定的程序的個數是由一個引用計數機制進行維護的,當shmdt成功,引用計數減1。

3.銷毀共享內存

共享內存不會隨程序的結束而銷毀,它是隨內核的 因此需要顯式地進行銷毀,可以使用shmctl函數。或在命令行中使用指令進行銷毀。

1.命令行銷毀

查看共享內存信息

ipcs -m

如下:

在這里插入圖片描述

nattch信息:它表示與該 共享內存連接的程序個數。

銷毀共享內存

ipcrm -m 2

這里需要填入shmid(即這里的2)來指定共享內存。

2.程序中銷毀

在程序中銷毀我們使用函數shmctl,其中ctl代表單詞control。

#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 參數shmid:傳入從shmget返回的shmid來指定需要銷毀的共享內存。
  • 參數cmd:需要傳入一個操作選項,操作選項很多,而IPC_RMID就是用來銷毀共享內存的。
  • 參數shmid_ds:這是一個輸出型參數,如果你需要獲取共享內存的信息,則傳入一個shmid_ds類型的指針來接收,如果不是通常傳入nullptr即可。
  • 返回值:
    成功時返回 0。
    失敗時返回 -1,并設置 errno 以指示錯誤類型。

注:命令行銷毀和程序中銷毀效果是一樣的,因為命令行銷毀底層還是調用了shmctl函數。

四、共享內存的生命周期

共享內存的生命周期是不隨進程的,而是隨內核,如果沒有顯示刪除它就會一直存在,盡管相關的進程已經退出。直到重裝系統才得以釋放。

使用shmctl釋放共享內存存在的情況

1.正常釋放

nattach(引用計數)為0時,即沒有進程與它綁定,被正常釋放。

2.共享內存段被標記為已刪除,但仍有進程附加(shmat

共享內存段已經被標記為已刪除(不能附加到新的進程),但之前仍有一些進程附加到該共享內存段并正在使用。所以共享內存段不會被立即釋放。只有當所有附加的進程都調用 shmdt 分離后,系統才會釋放資源。

五、數據安全問題

共享內存最大的優點就是快 相比使用管道技術,它減少了中間復雜轉化和拷貝工作,而是直接對物理內存進行訪問。

但它也有一個致命的缺點,相比管道技術,共享內存它的讀端和寫端是不帶有同步機制的,這就很容易使得數據混亂,也就是造成數據不一致問題。

比如我們寫端寫入“hello world”,而讀端讀到的可能是“he”,“ll”,“o wor”,“ld”等等無法預測的奇葩數據。 讀端一個勁地讀,不會管寫端這句話是否已經說完,而且也無法知道。

當我們不了解鎖的情況下想要解決這個問題,可以利用命名管道來解決,因為命名管道帶有同步機制,我們用它的write和read函數來保護數據的安全,當然write和read并不用寫或讀什么有意義的數據。

六、源碼

Fifo.hpp

#pragma once
#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <fcntl.h>
#include <unistd.h>
using namespace std;#include "Comm.hpp"#define PATH "."
#define FILENAME "fifo"//#define FIFO_FILE "fifo"class NamedFifo
{
public:NamedFifo(const string& path,const string& name):_path(path),_name(name){_fifoname =_path + "/" + _name;umask(0);//創建管道int n = mkfifo(_fifoname.c_str(), 0666);if(n < 0){cout << "mkdir fifo error" << endl;ERR_EXIT("makefifo");}else{cout << "fifo success" << endl;}}~NamedFifo(){//刪除管道文件int n = unlink(_fifoname.c_str());if(n == 0){cout << "unlink fifo" << endl;}else{// perror("remove fifo fail");// exit(1);ERR_EXIT("unlink");}}
private:string _path;string _name;string _fifoname;
};class FileOper
{
public:FileOper(const string& path,const string& name):_path(path), _name(name), _fd(-1){_fifoname = _path + "/" + _name;}~FileOper(){}void OpenForRead(){// 打開, write 方沒有執行open的時候,read方,就要在open內部進行阻塞// 直到管道文件打開了,open才會返回!_fd = open(_fifoname.c_str(), O_RDONLY);if(_fd < 0){ERR_EXIT("open");}cout << "open fifo success" << endl;}void OpenForWrite(){//寫_fd = open(_fifoname.c_str(), O_WRONLY);if(_fd < 0){ERR_EXIT("open");}cout << "open fifo success" << endl;}void Wakeup(){//寫操作char c = ' ';int n = write(_fd, &c, 1);printf("嘗試喚醒: %d\n", n);}bool Wait(){//讀操作char c;int n = read(_fd, &c, 1);if(n > 0){printf("喚醒成功: %d\n", n);return true;}else{return false;}}void Close(){if(_fd > 0){close(_fd);}}private:string _path;string _name;string _fifoname;int _fd;
};

Comm.hpp

#pragma once#include <cstdio>
#include <cstdlib>#define ERR_EXIT(m) \
do\
{\perror(m);\exit(EXIT_FAILURE);\
}while(0)

Shm.hpp

#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
using namespace std;#include "Comm.hpp"const int gdefaultid = -1;
const int gsize = 4096;
const string pathname = ".";
const int projid = 0x66;
const int gmode = 0666;#define CREATER "creater"
#define USER "user"class Shm
{
public:Shm(const string& pathname,int projid,const string& usertype):_shmid(gdefaultid),_size(gsize),_start_mem(nullptr),_usertype(usertype){_key = ftok(pathname.c_str(), projid);if(_key < 0){ERR_EXIT("fotk");}if(_usertype == CREATER){   //創建共享內存Create();}else if(_usertype == USER){//得到共享內存Get();}else{}//鏈接共享內存Attach();}void* VirtualAddr(){printf("VirtualAddr: %p\n", _start_mem);return _start_mem;}int Size(){return _size;}void Attr(){struct shmid_ds ds;//ds輸出型參數int n = shmctl(_shmid, IPC_STAT, &ds);printf("shm_segsz: %ld\n", ds.shm_segsz);printf("key: 0x%x\n", ds.shm_perm.__key);}~Shm(){cout << _usertype << endl;if(_usertype == CREATER){Destroy();}}private://創建新的共享內存void CreateHelper(int flg){printf("key: 0x%x\n", _key);//共享內存的生命周期,跟隨內核_shmid = shmget(_key, _size, flg);if(_shmid < 0){ERR_EXIT("shmget");}printf("shmid: %d\n", _shmid);}void Create(){CreateHelper(IPC_CREAT | IPC_EXCL | gmode);}void Attach(){_start_mem = shmat(_shmid, nullptr, 0);if((long long)_start_mem < 0){ERR_EXIT("shmat");}printf("attach success\n");}void Detach(){int n = shmdt(_start_mem);if(n == 0){printf("detach success\n");}}void Get(){CreateHelper(IPC_CREAT);}void Destroy(){if(_shmid == gdefaultid){return;}Detach();if(_usertype == CREATER){int n = shmctl(_shmid, IPC_RMID,nullptr);if(n > 0){printf("shmctl delete shm: %d success!\n", n);}else{ERR_EXIT("shmctl");}}}private:int _shmid;key_t _key;int _size;void* _start_mem;string _usertype;
};

server.cc

#include "Shm.hpp"
#include "Fifo.hpp"int main()
{Shm shm(pathname, projid, CREATER);sleep(5);shm.Attr();NamedFifo fifo(PATH, FILENAME);// 文件操作FileOper readerfile(PATH, FILENAME);readerfile.OpenForRead();char* mem =  (char*)shm.VirtualAddr();//讀寫共享內存,沒有使用系統調用while(true){if(readerfile.Wait()){printf("%s\n", mem);}else{break;}}readerfile.Close();cout << "server end normal!" << endl;return 0;
}

client.cc

#include "Shm.hpp"
#include "Fifo.hpp"int main()
{FileOper writerfile(PATH, FILENAME);writerfile.OpenForWrite();Shm shm(pathname, projid, USER);char* mem = (char*)shm.VirtualAddr();int index = 0;for(char c = 'A';c <= 'Z';c++,index += 2){sleep(2);mem[index] = c;mem[index + 1] = c;mem[index + 2] = 0;writerfile.Wakeup();sleep(1);}writerfile.Close();return 0;
}

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

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

相關文章

Python 程序設計講義(9):Python 的基本數據類型——復數

Python 程序設計講義&#xff08;9&#xff09;&#xff1a;Python 的基本數據類型——復數 復數與數學中的復數概念類似。在 Python 中&#xff0c;復數表示為 abj&#xff0c;其中&#xff1a;a為實數部分&#xff0c;b為虛數部分&#xff0c;j稱為虛數單位。復數必須包含虛數…

leetcode_121 買賣股票的最佳時期

1. 題意 有一個股價變化圖&#xff0c;你可以在一天買入&#xff0c;在未來一天賣出。 求通過這樣一次操作的最大獲利。 2. 題解 2.1 枚舉 直接枚舉&#xff0c;買入賣出的時間&#xff0c;肯定會超時啦~ 時間復雜度為O(n2)O(n^2)O(n2) 空間復雜度為O(1)O(1)O(1) class …

ToBToC的定義與區別

B 端和 C 端主要是從產品所面向的用戶群體角度來區分的&#xff0c;B 端指的是企業用戶&#xff08;Business&#xff09;&#xff0c;C 端指的是個人消費者&#xff08;Consumer&#xff09;&#xff0c;它們在多個方面存在明顯區別&#xff0c;具體如下&#xff1a;用戶特征B…

Python 程序設計講義(8):Python 的基本數據類型——浮點數

Python 程序設計講義&#xff08;8&#xff09;&#xff1a;Python 的基本數據類型——浮點數 目錄Python 程序設計講義&#xff08;8&#xff09;&#xff1a;Python 的基本數據類型——浮點數一、浮點數的表示形式1、小數形式2、指數形式二、浮點數的精確度浮點數也稱小數&am…

MCP客戶端架構與實施

前言:從模型到生產力 — MCP的戰略價值 在過去的一年里,我們團隊見證了大型語言模型(LLM)從技術奇跡向企業核心生產力工具的演變。然而,一個孤立的LLM無法解決實際的業務問題。真正的價值釋放,源于將模型的認知能力與企業現有的數據、API及工作流進行無縫、安全、可擴展…

白盒測試核心覆蓋率標準詳解文檔

白盒測試核心覆蓋率標準詳解文檔 1. 什么是白盒測試與覆蓋率&#xff1f; 白盒測試&#xff08;White-box Testing&#xff09;&#xff0c;又稱結構測試或邏輯驅動測試&#xff0c;是一種測試方法&#xff0c;測試人員能夠訪問并了解被測軟件的內部結構、代碼和實現邏輯。測試…

順豐面試提到的一個算法題

順豐面試提到的一個算法題面試過程中大腦空白&#xff0c;睡了一覺后突然想明白了 原理非常簡單就是根據數組中元素的值對值對應的索引進行排序 哎&#xff0c;&#xff0c;&#xff0c;&#xff0c;具體看以下代碼吧[使用 Java 17 中 Stream 實現] 最好別用 CSDN 提供的在線運…

ChatGPT Agent深度解析:告別單純問答,一個指令搞定復雜任務?

名人說&#xff1a;博觀而約取&#xff0c;厚積而薄發。——蘇軾《稼說送張琥》 創作者&#xff1a;Code_流蘇(CSDN)&#xff08;一個喜歡古詩詞和編程的Coder&#x1f60a;&#xff09; 目錄一、什么是ChatGPT Agent&#xff1f;從"客服"到"秘書"的華麗轉…

位運算在算法競賽中的應用(基于C++語言)_位運算優化

在C算法競賽中&#xff0c;位運算優化是一種非常重要的技巧&#xff0c;因為它可以顯著提高算法的效率。以下是一些常見的位運算優化方法及其在各種算法中的應用示例&#xff1a; 常見的位運算優化 1&#xff09;位與運算 &&#xff1a; 用途&#xff1a;用于檢查某個位是否…

SpringBoot 使用Rabbitmq

1.Springboot默認MQ支持rabbitmq或者kafka maven引入依賴 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>propertis添加配置 # spring.rabbitmq.host192.168…

C++核心編程學習4--類和對象--封裝

C面向對象有三大特性&#xff1a;封裝、繼承和多態。 封裝 將屬性和行為作為一個整體。將屬性和行為加以權限控制。 例子1&#xff1a;設計一個圓類 #include <iostream> using namespace std;// 設計一個圓類&#xff0c;求圓的周長 // 圓周率&#xff1a;3.14 const do…

AC身份認證實驗之AAA服務器

一、實驗背景某公司需要在企業的公司網絡出口使用上網行為管理設備&#xff0c;以審計管理局域網的所有設備&#xff0c;同時&#xff0c;局域網內的所有設備都將上網行為代理上網&#xff0c;但是發生過訪客外傳一些非法信息&#xff0c;所以需要對外來人員進行實名認證&#…

數組算法之【數組中第K個最大元素】

目錄 LeetCode-215題 LeetCode-215題 給定整數數組nums和整數k&#xff0c;返回數組中第k個最大元素 public class Solution {/*** 這里是基于小頂堆這種數據結構來實現的*/public int findKthLargest(int[] nums, int k) {// 實例化一個小頂堆MinHeap minHeap new MinHeap…

高亮匹配關鍵詞樣式highLightMatchString、replaceHTMLChar

replaceHTMLChar: s > s.toString().replace(/</g, <).replace(/>/g, >),// 高亮匹配關鍵詞樣式----------------------------------------highLightMatchString(originStr, matchStr, customClass ) {matchStr && (matchStr matchStr.replace(/[.*?…

HUAWEI Pura80系列機型參數對比

類別HUAWEI Pura80 UltraHUAWEI Pura80 ProHUAWEI Pura80 ProHUAWEI Pura80建議零售價&#xffe5;9999起&#xffe5;7999起&#xffe5;6499起&#xffe5;4699起顏色鎏光金、鎏光黑釉紅、釉青、釉白、釉黑釉金、釉白、釉黑絲絨金、絲絨綠、絲絨白、絲絨黑外觀材質設計光芒耀…

使用 PyTorch 的 torchvision 庫加載 CIFAR-10 數據集

CIFAR-10是一個更接近普適物體的彩色圖像數據集。CIFAR-10 是由Hinton 的學生Alex Krizhevsky 和Ilya Sutskever 整理的一個用于識別普適物體的小型數據集。一共包含10 個類別的RGB 彩色圖片&#xff1a;飛機&#xff08; airplane &#xff09;、汽車&#xff08; automobile …

藍橋杯51單片機

這是我備考省賽的時候總結的錯誤點和創新點那個時候是用來提醒自己的&#xff0c;現在分享給你們看^_^一考點二注意點記得初始化&#xff39;&#xff14;&#xff0c;&#xff39;&#xff15;&#xff0c;&#xff39;&#xff16;&#xff0c;&#xff39;&#xff17;&…

【2025/07/23】GitHub 今日熱門項目

GitHub 今日熱門項目 &#x1f680; 每日精選優質開源項目 | 發現優質開源項目&#xff0c;跟上技術發展趨勢 &#x1f4cb; 報告概覽 &#x1f4ca; 統計項&#x1f4c8; 數值&#x1f4dd; 說明&#x1f4c5; 報告日期2025-07-23 (周三)GitHub Trending 每日快照&#x1f55…

【生成式AI導論 2024】第12講:淺談檢定大型語言模型能力的各種方式 學習記錄

跟標準答案做對比看是否正確 選擇題是不是正確 MMLU massive multitask Language Understanding MT-bench 使用語言模型來評分 還有其他任務的對比,也有特別刁鉆的問題 閱讀長文的能力 grep kamradt 大海撈針

嵌入式 Qt 開發:實現開機 Logo 和無操作自動鎖屏

在嵌入式設備開發中&#xff0c;為設備添加開機 Logo 和無操作自動鎖屏功能是提升用戶體驗的重要環節。本文將詳細介紹如何在 Qt 嵌入式項目中實現這兩個功能。我們將使用 Qt 5/6 和 Linux 環境&#xff0c;確保代碼的可移植性和通用性。項目結構為了實現這兩個功能&#xff0c…