?
? 本篇文章接著上篇文章通信 | 管道通信(匿名管道 & 命名管道)進(jìn)行講解。本篇文章的中點(diǎn)內(nèi)容是共享內(nèi)存。
文章目錄
?一、初識(shí)與創(chuàng)建共享內(nèi)存
1、1 什么是共享內(nèi)存
1、2 共享內(nèi)存函數(shù)
1、2、1 創(chuàng)建共享內(nèi)存 shmget
1、2、2 ftok 生成 key
1、2、3 獲取共享內(nèi)存 shmget
1、3 demo 代碼
二、對(duì)共享內(nèi)存進(jìn)行相關(guān)操作?
2、1 查看/刪除 共享內(nèi)存資源
2、2?共享內(nèi)存掛接和訪問
2、2、1 共享內(nèi)存的掛接 shmat()
2、2、2 共享內(nèi)存的訪問
2、3 刪除共享內(nèi)存 shmctl?
三、完整共享內(nèi)存通信 demo 代碼
3、1 Log.hpp 日志
3、2 comm.hpp
3、3 shmClient.cpp
3、4 shmServer.cpp
???♂??作者:@Ggggggtm????♂?
???專欄:Linux從入門到精通? ??
???標(biāo)題:共享內(nèi)存 ??
????寄語:與其忙著訴苦,不如低頭趕路,奮路前行,終將遇到一番好風(fēng)景???
?一、初識(shí)與創(chuàng)建共享內(nèi)存
1、1 什么是共享內(nèi)存
? 我們?cè)谥皩W(xué)管道通信時(shí),是怎么實(shí)現(xiàn)通信的呢?匿名管道通信的方式是子進(jìn)程繼承父進(jìn)程的內(nèi)核數(shù)據(jù)結(jié)構(gòu),使得父子進(jìn)程能夠看到同一塊空間。命名管道通信是讓不同進(jìn)程打開同一份文件。我們發(fā)現(xiàn)通信的前提就是讓不同的進(jìn)程看到同一份“資源”。當(dāng)然,共享內(nèi)存也不例外。
? ?每個(gè)進(jìn)程都有自己獨(dú)立的地址空間,所以它們彼此之間不能直接訪問對(duì)方的內(nèi)存。而共享內(nèi)存則提供了一種特殊的內(nèi)存區(qū)域,允許多個(gè)進(jìn)程可以同時(shí)訪問和操作同一塊內(nèi)存。具體如下圖:
? 這里再次解釋一下上圖。我們創(chuàng)建共享內(nèi)存的過程:一個(gè)進(jìn)程在內(nèi)核空間申請(qǐng)一個(gè)共享內(nèi)存對(duì)象,讓后通過頁表建立與物理內(nèi)存的映射。讓后另一個(gè)進(jìn)程通過特殊的方法和算法來找到該共享內(nèi)存并且與其建立映射。下面我們會(huì)對(duì)上述過程進(jìn)行詳細(xì)解釋。?
1、2 共享內(nèi)存函數(shù)
1、2、1 創(chuàng)建共享內(nèi)存 shmget
? shmget函數(shù)用于創(chuàng)建一個(gè)新的共享內(nèi)存段,或者獲取現(xiàn)有的共享內(nèi)存段的標(biāo)識(shí)符。
函數(shù)原型為:
int shmget(key_t key, size_t size, int shmflg);
參數(shù)說明:
key
:用于標(biāo)識(shí)共享內(nèi)存段的鍵值??梢?strong>使用ftok函數(shù)生成。size
:指定共享內(nèi)存段的大小,以字節(jié)為單位。shmflg
:用于指定共享內(nèi)存段的訪問權(quán)限和標(biāo)志位,可以使用IPC_CREAT、IPC_EXCL等宏進(jìn)行設(shè)置。返回值:
- 如果成功,返回共享內(nèi)存段的標(biāo)識(shí)符(即共享內(nèi)存ID)。
- 如果失敗,返回-1,并設(shè)置errno。
? 更加詳細(xì)的如下圖:
? 我們?cè)谶@里具體解釋一下第三個(gè)參數(shù) shmflg。這個(gè)參數(shù)是可以有多個(gè)選擇的。底層是利用了位圖的思想。主要是IPC_CREAT和IPC_EXCL兩個(gè)選項(xiàng)。
? IPC_CREAT和IPC_EXCL是在shmget函數(shù)中使用的標(biāo)志位,用于指定共享內(nèi)存段的訪問權(quán)限和標(biāo)志。它們?cè)趕hmget函數(shù)的第三個(gè)參數(shù)shmflg中使用。
IPC_CREAT:該標(biāo)志用于創(chuàng)建一個(gè)新的共享內(nèi)存段。如果指定的key對(duì)應(yīng)的共享內(nèi)存段不存在,則創(chuàng)建一個(gè)新的共享內(nèi)存段。如果共享內(nèi)存段已經(jīng)存在,則返回該共享內(nèi)存段的標(biāo)識(shí)符(即共享內(nèi)存ID)。
IPC_EXCL:該標(biāo)志與IPC_CREAT一起使用,在創(chuàng)建共享內(nèi)存段時(shí)起作用。如果指定的key對(duì)應(yīng)的共享內(nèi)存段已經(jīng)存在,則shmget函數(shù)會(huì)失敗,并返回-1,并且置errno為EEXIST(資源已存在)。
? ?我們接下來再看一下 shmget 的具體使用例子。
int shmid = shmget(key, size, IPC_CREAT | permission_flags);
? 上述代碼中,IPC_CREAT標(biāo)志位用于創(chuàng)建共享內(nèi)存段。如果指定的key對(duì)應(yīng)的共享內(nèi)存段已經(jīng)存在,那么shmget函數(shù)會(huì)返回該共享內(nèi)存段的標(biāo)識(shí)符;如果共享內(nèi)存段不存在,則會(huì)創(chuàng)建一個(gè)新的共享內(nèi)存段,并返回新創(chuàng)建的共享內(nèi)存段的標(biāo)識(shí)符。
1、2、2 ftok 生成 key
??在函數(shù)shmget中,key值是用于標(biāo)識(shí)或檢索共享內(nèi)存段的關(guān)鍵值。它在創(chuàng)建或訪問共享內(nèi)存時(shí)起到重要作用。具體來說,key值用于以下兩個(gè)目的:
- 當(dāng)多個(gè)進(jìn)程需要訪問同一個(gè)共享內(nèi)存段時(shí),它們可以使用相同的key值來標(biāo)識(shí)這個(gè)共享內(nèi)存段。
- 如果一個(gè)共享內(nèi)存段已經(jīng)存在,并且其他進(jìn)程想要訪問它,那么只需要提供相同的key值即可找到該共享內(nèi)存段。
? 那在使用 shmget 函數(shù)之前,我們應(yīng)該使用 ftok 函數(shù)生成key值,來表示這個(gè)共享內(nèi)存段。由于是標(biāo)示共享空間,所以應(yīng)該確定唯一性。至于key的值是多少并不關(guān)鍵。那我們看一下ftok 函數(shù)的用法。具體如下:
參數(shù)說明:
pathname
:一個(gè)包含一個(gè)現(xiàn)有文件的路徑名,用于生成k值。最好是有權(quán)訪問這個(gè)文件。proj_id
:不同的proj_id可以被用作區(qū)分不同類型的通信方式或不同的ipc資源,來生成不同的k值。其實(shí)就是一個(gè)任意整型值。? 我們?cè)倏匆幌缕渚唧w的例子:
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> int main() { key_t key; char *path = "./example.txt"; int proj_id = 0x66; // 使用ftok函數(shù)生成k值 key = ftok(path, proj_id); printf("Generated key: %d\n", key); return 0; }
? 另一個(gè)進(jìn)程也可以用相同的方法生成相同的key值。ftok函數(shù)根據(jù)給定的路徑名和proj_id生成k值。當(dāng)路徑名和proj_id相同時(shí),生成的k值也相同。這是因?yàn)閒tok函數(shù)內(nèi)部使用了哈希運(yùn)算,將路徑名和proj_id轉(zhuǎn)化為一個(gè)唯一的整數(shù)。盡管可能存在哈希沖突(即不同的路徑名和proj_id生成相同的k值),但概率非常低。通常情況下,不同的進(jìn)程可以使用相同的路徑名和proj_id生成相同的k值是非常罕見的。即使出現(xiàn)相同的k值,由于進(jìn)程間通信中還有其他參數(shù)的限制(如消息隊(duì)列標(biāo)識(shí)、共享內(nèi)存標(biāo)識(shí)等),不同進(jìn)程之間的IPC通信仍然可以正常進(jìn)行。??
? 生成 key 值后,我們就可以用key值創(chuàng)建共享內(nèi)存,或者來獲取共享內(nèi)存。下面我們看一下獲取使用key值來獲取共享內(nèi)存的方法。
1、2、3 獲取共享內(nèi)存 shmget
? shmget 函數(shù)還可用來獲取共享內(nèi)存。當(dāng)生成的key值已經(jīng)有對(duì)應(yīng)的共享內(nèi)存時(shí),shmget 函數(shù)就會(huì)返回這段共享內(nèi)存的標(biāo)識(shí)碼。我么不只需要將第三個(gè)參數(shù)修改為0,就是來獲取對(duì)應(yīng)key值的共享內(nèi)存。
1、3 demo 代碼
? 我們接下來寫一段代碼測(cè)試和總結(jié)一下我們上面所學(xué)到的函數(shù)。下面為實(shí)例:
// Log.hpp #include <iostream> #include <ctime> #define Debug 0 #define Notice 1 #define Warning 2 #define Error 3 const std::string msg[] = { "Debug", "Notice", "Warning", "Error" }; std::ostream &Log(std::string message, int level) { std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message; return std::cout; } // comm.hpp #pragma once #include <iostream> #include <cstdio> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <cassert> #include <cstring> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include "Log.hpp" using namespace std; #define PATH_NAME "/home/gtm" #define PROJ_ID 0x66 #define SHM_SIZE 4096 //共享內(nèi)存的大小,最好是頁(PAGE: 4096)的整數(shù)倍 #define FIFO_NAME "./fifo" // shmClient.cc #include"comm.hpp" int main() { Log("child pid is : ", Debug) << getpid() << endl; key_t k = ftok(PATH_NAME, PROJ_ID); if (k < 0) { Log("create key failed", Error) << " client key : " << k << endl; exit(1); } Log("create key done", Debug) << " client key : " << k << endl; // 獲取共享內(nèi)存 int shmid = shmget(k, SHM_SIZE, 0); if(shmid < 0) { Log("create shm failed", Error) << " client key : " << k << endl; exit(2); } Log("create shm success", Error) << " client shmid : " << shmid << endl; return 0; } // shmServer.cc #include "comm.hpp" string TransToHex(key_t k) { char buffer[32]; snprintf(buffer, sizeof buffer, "0x%x", k); return buffer; } int main() { // 我們之前為了通信,所做的所有的工作,屬于什么工作呢:讓不同的進(jìn)程看到了同一份資源(內(nèi)存) // 1. 創(chuàng)建公共的Key值 key_t k = ftok(PATH_NAME, PROJ_ID); assert(k != -1); Log("create key done", Debug) << " server key : " << TransToHex(k) << endl; // 2. 創(chuàng)建共享內(nèi)存 -- 建議要?jiǎng)?chuàng)建一個(gè)全新的共享內(nèi)存 -- 通信的發(fā)起者 int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); // if (shmid == -1) { perror("shmget"); exit(1); } Log("create shm done", Debug) << " shmid : " << shmid << endl; return 0; }
? shmServer.cc文件中的代碼是服務(wù)端代碼。首先調(diào)用ftok函數(shù)生成一個(gè)唯一的鍵值,并將其轉(zhuǎn)換為十六進(jìn)制字符串表示。然后使用shmget函數(shù)創(chuàng)建一個(gè)共享內(nèi)存段,創(chuàng)建時(shí)指定了IPC_CREAT標(biāo)志,用于新建共享內(nèi)存段。如果創(chuàng)建成功,返回一個(gè)共享內(nèi)存標(biāo)識(shí)符shmid。
? shmClient.cc文件中的代碼是客戶端代碼。首先也是調(diào)用ftok函數(shù)生成一個(gè)唯一的鍵值。然后通過shmget函數(shù)打開已存在的共享內(nèi)存段,打開時(shí)不需要指定IPC_CREAT標(biāo)志,而是提供即將打開的共享內(nèi)存段的鍵值和大小。如果打開成功,返回一個(gè)共享內(nèi)存標(biāo)識(shí)符shmid。
? Log.hpp文件定義了一個(gè)宏和一個(gè)Log函數(shù)。宏定義了四個(gè)日志級(jí)別,分別對(duì)應(yīng)Debug、Notice、Warning和Error四個(gè)字符串。Log函數(shù)負(fù)責(zé)輸出日志信息,接受一個(gè)字符串信息和一個(gè)日志級(jí)別參數(shù)。Log函數(shù)將時(shí)間戳、日志級(jí)別和消息內(nèi)容輸出到標(biāo)準(zhǔn)輸出流中。
? 上述代碼就是完成了創(chuàng)建共享內(nèi)存的功能,并且在其中打印了一些日志信息。我們不妨來看一下運(yùn)行結(jié)果。具體如下圖:
? 我們發(fā)現(xiàn)對(duì)應(yīng)的shmid是相同的,說明Server和Client確實(shí)獲得了相同的共享內(nèi)存塊。他們所生成的key值相同嗎?其實(shí)是相同的,如下圖:
二、對(duì)共享內(nèi)存進(jìn)行相關(guān)操作?
2、1 查看/刪除 共享內(nèi)存資源
? 當(dāng)我們?cè)俅芜\(yùn)行時(shí),就會(huì)發(fā)生錯(cuò)誤。具體如下圖:
? 為什么呢?原因是我們剛剛創(chuàng)建的共享內(nèi)存依然存在。當(dāng)進(jìn)程結(jié)束時(shí),共享內(nèi)存并不會(huì)自動(dòng)釋放。為什么呢?我們可以認(rèn)為共享內(nèi)存是屬于操作系統(tǒng)。所以共享內(nèi)存的生命周期隨操作系統(tǒng)!這時(shí)我們可以手動(dòng)關(guān)閉共享內(nèi)存。在關(guān)閉前首先要查看共享內(nèi)存,指令:ipcs -m。具體如下圖:
? 當(dāng)然,查到共享內(nèi)存后,可以用指令進(jìn)行刪除。那么問題來了,使用key值刪除呢?還是用shmid 進(jìn)行刪除呢?我們這里需要注意:共享內(nèi)存中的shmid和key值是兩個(gè)不同的概念。
shmid(Shared Memory ID)是共享內(nèi)存的標(biāo)識(shí)符,由操作系統(tǒng)分配,并作為一個(gè)非負(fù)整數(shù)對(duì)共享內(nèi)存進(jìn)行引用。在使用共享內(nèi)存時(shí),我們需要通過shmid來進(jìn)行操作,如創(chuàng)建、附加、訪問和刪除等。shmid可以看作是內(nèi)核用于標(biāo)識(shí)某個(gè)特定共享內(nèi)存段的一個(gè)唯一值。
key值是用戶定義的一個(gè)標(biāo)識(shí)符,通常是一個(gè)整數(shù)值。在創(chuàng)建共享內(nèi)存時(shí),我們可以使用ftok函數(shù)生成一個(gè)key值,以便其他進(jìn)程可以通過這個(gè)key值來獲取相同的共享內(nèi)存區(qū)域。key值是用于標(biāo)識(shí)共享內(nèi)存的用戶級(jí)別的標(biāo)識(shí)符,不同于shmid,其值不受內(nèi)核控制。
? 所以刪除共享內(nèi)存,我們使用的shmid。具體指令:ipcrm -m shmid。如下圖:
? 當(dāng)我們刪除共享內(nèi)存后,再次進(jìn)行查找發(fā)現(xiàn)就沒有了,且程序能夠正常運(yùn)行。
2、2?共享內(nèi)存掛接和訪問
2、2、1 共享內(nèi)存的掛接 shmat()
??在調(diào)用shmget()函數(shù)時(shí),內(nèi)核會(huì)在內(nèi)部維護(hù)一個(gè)共享內(nèi)存表格,其中包含了共享內(nèi)存的相關(guān)信息,包括共享內(nèi)存的大小、權(quán)限等。當(dāng)調(diào)用成功后,將返回一個(gè)唯一的共享內(nèi)存標(biāo)識(shí)符,該標(biāo)識(shí)符可以用于后續(xù)的共享內(nèi)存操作。
? 那么正常來說,我們?cè)L問共享內(nèi)存是需要通過系統(tǒng)調(diào)用的。但是我們這里可以將內(nèi)核級(jí)別的共享內(nèi)存掛接到進(jìn)程的地址空間。然后用戶就可以直接進(jìn)行訪問。
? 進(jìn)程可以使用系統(tǒng)提供的函數(shù)(如shmat())將自己的地址空間映射到共享內(nèi)存。也可以理解為shmat()函數(shù)將共享內(nèi)存附加到進(jìn)程的虛擬地址空間中,使得進(jìn)程可以訪問該共享內(nèi)存所指向的物理內(nèi)存區(qū)域。具體用法如下:
? 參數(shù)說明:
shm_id
:共享內(nèi)存標(biāo)識(shí)符,通過調(diào)用 shmget 獲取。shm_addr
:內(nèi)存段的地址,通常傳入?NULL
,表示由系統(tǒng)自動(dòng)選擇一個(gè)適合的地址。shmflg
:控制共享內(nèi)存段的附加方式和權(quán)限,可以使用 IPC_CREAT 標(biāo)志創(chuàng)建新的共享內(nèi)存段。通常傳入0。? 返回值:
- 如果成功,返回指向共享內(nèi)存段第一個(gè)字節(jié)的指針;
- 如果失敗,返回?
void *
?類型的錯(cuò)誤值?-1
。? 其實(shí)我們看完其使用方法后,有沒有發(fā)現(xiàn)與 malloc 很相似。malloc 申請(qǐng)空間成功后,會(huì)返回所申請(qǐng)空間的起始地址。否則就會(huì)返回NULL。shmat 與其確實(shí)有些相似。我們可結(jié)合如下例子一起理解:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> int main() { int shm_id; key_t key; int *shared_memory; // 獲取共享內(nèi)存標(biāo)識(shí)符 key = ftok("。/file", 0x66); shm_id = shmget(key, sizeof(int), IPC_CREAT | 0666); // 將共享內(nèi)存段附加到進(jìn)程的地址空間中 shared_memory = shmat(shm_id, NULL, 0); // 訪問共享內(nèi)存 printf("共享內(nèi)存中的值為:%d\n", *shared_memory); // 分離共享內(nèi)存段 shmdt(shared_memory); return 0; }
? 我們也看到了最后是有一個(gè)去關(guān)聯(lián)的 shmdt 函數(shù)。參數(shù)就是我們所獲取的共享內(nèi)存的起始地址,這里就不再過多解釋此函數(shù)。
? 這里有會(huì)有一個(gè)問題:將內(nèi)核級(jí)別的共享內(nèi)存掛接到進(jìn)程地址空間的哪里了呢?我們看如下圖:
? ?我們之前學(xué)進(jìn)程地址空間時(shí),知道堆和棧的中間有大量的鏤空,而這段位置就是內(nèi)核級(jí)別的共享內(nèi)存所掛接到的位置!
2、2、2 共享內(nèi)存的訪問
? 到這里,我們已經(jīng)學(xué)習(xí)了共享內(nèi)存的大部分內(nèi)容。只差對(duì)共享內(nèi)存的訪問了。當(dāng)我們對(duì)共享內(nèi)存進(jìn)行掛接后,?就可以得到共享內(nèi)存掛接后的起始地址。我們用戶可以對(duì)其進(jìn)行直接訪問(寫入/讀?。?。我們給出如下偽代碼:
// shmServer.cpp char *shmaddr = (char *)shmat(shmid, nullptr, 0); Log("attach shm done", Debug) << " shmid : " << shmid << endl; for(;;) { printf("%s\n", shmaddr); if(strcmp(shmaddr, "quit") == 0) break; sleep(1); } // shmClient.cpp // 掛接并獲得共享內(nèi)存起始地址 char *shmaddr = (char *)shmat(shmid, nullptr, 0); if(shmaddr == nullptr) { Log("attach shm failed", Error) << " client key : " << k << endl; exit(3); } char a = 'a'; for(; a <= 'c'; a++) { // 我們是每一次都向shmaddr[共享內(nèi)存的起始地址]寫入 snprintf(shmaddr, SHM_SIZE - 1,\ "hello server, 我是其他進(jìn)程,我的pid: %d, inc: %c\n",\ getpid(), a); sleep(3); }
? 對(duì)上述代碼是一個(gè)使用共享內(nèi)存進(jìn)行進(jìn)程間通信的偽代碼。下面對(duì)代碼進(jìn)行詳解:
首先,在服務(wù)端(shmServer.cpp)中,通過shmat函數(shù)將共享內(nèi)存連接到當(dāng)前進(jìn)程的地址空間。shmat函數(shù)的第一個(gè)參數(shù)是共享內(nèi)存的標(biāo)識(shí)符shmid,第二個(gè)參數(shù)為NULL表示讓系統(tǒng)自動(dòng)選擇合適的地址分配給共享內(nèi)存,第三個(gè)參數(shù)為0表示以默認(rèn)權(quán)限進(jìn)行操作。連接完成后,返回共享內(nèi)存的起始地址,并賦值給shmaddr指針。
在服務(wù)器端的for循環(huán)中,通過printf函數(shù)將shmaddr指向的共享內(nèi)存內(nèi)容輸出到標(biāo)準(zhǔn)輸出(讀取)。然后通過strcmp函數(shù)判斷共享內(nèi)存中的內(nèi)容是否為"quit",如果是,則跳出循環(huán),結(jié)束程序。否則,通過sleep函數(shù)暫停1秒鐘。
在客戶端(shmClient.cpp)中,同樣通過shmat函數(shù)連接到共享內(nèi)存,并將共享內(nèi)存的起始地址賦給shmaddr指針。若連接失?。╯hmaddr為nullptr),則輸出錯(cuò)誤信息并退出程序。
在客戶端的for循環(huán)中,使用snprintf函數(shù)將格式化的字符串寫入shmaddr指向的共享內(nèi)存中(寫入)。該字符串包含了客戶端進(jìn)程的PID(進(jìn)程標(biāo)識(shí)符)和一個(gè)遞增的字符,以展示多次寫入的內(nèi)容。然后通過sleep函數(shù)暫停3秒鐘。
? 運(yùn)行結(jié)果如下:
? 我們通過運(yùn)行結(jié)果發(fā)現(xiàn):在客戶端沒有寫入的情況下,服務(wù)端進(jìn)行讀取時(shí)也會(huì)讀到內(nèi)容。讀到的是空字符串(共享內(nèi)存默認(rèn)會(huì)初始化為0)。我們發(fā)現(xiàn)共享內(nèi)存的讀寫并沒有訪問控制。我們知道命名管道通信是由訪問控制的。但是當(dāng)一個(gè)進(jìn)程寫入時(shí),另一個(gè)就能夠馬上看到寫入的內(nèi)容。所以共享內(nèi)存是所有進(jìn)程間通信(IPC),速度最快的!不需要過多的拷貝?。。ú恍枰獙?shù)據(jù)給操作系統(tǒng))。如果我想一定程度的訪問控制呢?可以在共享內(nèi)存讀寫的過程中加入命名管道來控制。
2、3 刪除共享內(nèi)存 shmctl?
? 上面我們了解了可以使用Linux指令對(duì)共享內(nèi)存進(jìn)行刪除,我們也可以使用系統(tǒng)調(diào)用 shmctl()函數(shù) 對(duì)其進(jìn)行刪除。具體使用如下:
?參數(shù)說明:
- shmid:共享內(nèi)存標(biāo)識(shí)符,通過shmget函數(shù)獲取得到。
- cmd:表示對(duì)共享內(nèi)存進(jìn)行的操作類型,可以選擇的參數(shù)有:
- IPC_STAT:獲取共享內(nèi)存的狀態(tài)信息,將共享內(nèi)存的屬性保存在buf所指向的結(jié)構(gòu)體中。
- IPC_SET:設(shè)置共享內(nèi)存的屬性,使用buf所指向的結(jié)構(gòu)體中的值進(jìn)行設(shè)置。
- IPC_RMID:刪除共享內(nèi)存。
- buf:指向一個(gè)struct shmid_ds結(jié)構(gòu)體的指針,用于存儲(chǔ)共享內(nèi)存的屬性信息。通常使用nullptr。
??shmctl函數(shù)可以用于對(duì)共享內(nèi)存段進(jìn)行控制操作。它能夠?qū)崿F(xiàn)共享內(nèi)存的創(chuàng)建、刪除、以及獲取和修改共享內(nèi)存的屬性。但是我們?cè)摵瘮?shù)最常用刪除共享內(nèi)存。使用IPC_RMID操作可以刪除指定的共享內(nèi)存段,并釋放系統(tǒng)資源。這個(gè)操作會(huì)立即刪除共享內(nèi)存段,以及與它關(guān)聯(lián)的任何進(jìn)程中的鍵和id。
三、完整共享內(nèi)存通信 demo 代碼
3、1 Log.hpp 日志
??文章來源:http://www.zghlxwxcb.cn/news/detail-703805.html
#include <iostream> #include <ctime> #define Debug 0 #define Notice 1 #define Warning 2 #define Error 3 const std::string msg[] = { "Debug", "Notice", "Warning", "Error" }; std::ostream &Log(std::string message, int level) { std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message; return std::cout; }
3、2 comm.hpp
#pragma once #include <iostream> #include <cstdio> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <cassert> #include <cstring> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include "Log.hpp" using namespace std; //不推薦 #define PATH_NAME "/home/whb" #define PROJ_ID 0x66 #define SHM_SIZE 4096 //共享內(nèi)存的大小,最好是頁(PAGE: 4096)的整數(shù)倍 #define FIFO_NAME "./fifo" class Init { public: Init() { umask(0); int n = mkfifo(FIFO_NAME, 0666); assert(n == 0); (void)n; Log("create fifo success",Notice) << "\n"; } ~Init() { unlink(FIFO_NAME); Log("remove fifo success",Notice) << "\n"; } }; #define READ O_RDONLY #define WRITE O_WRONLY int OpenFIFO(std::string pathname, int flags) { int fd = open(pathname.c_str(), flags); assert(fd >= 0); return fd; } void Wait(int fd) { Log("等待中....", Notice) << "\n"; uint32_t temp = 0; ssize_t s = read(fd, &temp, sizeof(uint32_t)); assert(s == sizeof(uint32_t)); (void)s; } void Signal(int fd) { uint32_t temp = 1; ssize_t s = write(fd, &temp, sizeof(uint32_t)); assert(s == sizeof(uint32_t)); (void)s; Log("喚醒中....", Notice) << "\n"; } void CloseFifo(int fd) { close(fd); }
3、3 shmClient.cpp
#include "comm.hpp" int main() { Log("child pid is : ", Debug) << getpid() << endl; key_t k = ftok(PATH_NAME, PROJ_ID); if (k < 0) { Log("create key failed", Error) << " client key : " << k << endl; exit(1); } Log("create key done", Debug) << " client key : " << k << endl; // 獲取共享內(nèi)存 int shmid = shmget(k, SHM_SIZE, 0); if(shmid < 0) { Log("create shm failed", Error) << " client key : " << k << endl; exit(2); } Log("create shm success", Error) << " client key : " << k << endl; // sleep(10); char *shmaddr = (char *)shmat(shmid, nullptr, 0); if(shmaddr == nullptr) { Log("attach shm failed", Error) << " client key : " << k << endl; exit(3); } Log("attach shm success", Error) << " client key : " << k << endl; int fd = OpenFIFO(FIFO_NAME, WRITE); // client將共享內(nèi)存看做一個(gè)char 類型的buffer while(true) { ssize_t s = read(0, shmaddr, SHM_SIZE-1); if(s > 0) { shmaddr[s-1] = 0; Signal(fd); if(strcmp(shmaddr,"quit") == 0) break; } } CloseFifo(fd); // 去關(guān)聯(lián) int n = shmdt(shmaddr); assert(n != -1); Log("detach shm success", Error) << " client key : " << k << endl; return 0; }
3、4 shmServer.cpp
#include "comm.hpp" Init init; string TransToHex(key_t k) { char buffer[32]; snprintf(buffer, sizeof buffer, "0x%x", k); return buffer; } int main() { // 1. 創(chuàng)建公共的Key值 key_t k = ftok(PATH_NAME, PROJ_ID); assert(k != -1); Log("create key done", Debug) << " server key : " << TransToHex(k) << endl; // 2. 創(chuàng)建共享內(nèi)存 -- 建議要?jiǎng)?chuàng)建一個(gè)全新的共享內(nèi)存 -- 通信的發(fā)起者 int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); // if (shmid == -1) { perror("shmget"); exit(1); } Log("create shm done", Debug) << " shmid : " << shmid << endl; // 3. 將指定的共享內(nèi)存,掛接到自己的地址空間 char *shmaddr = (char *)shmat(shmid, nullptr, 0); Log("attach shm done", Debug) << " shmid : " << shmid << endl; int fd = OpenFIFO(FIFO_NAME, READ); for(;;) { Wait(fd); // 臨界區(qū) printf("%s\n", shmaddr); if(strcmp(shmaddr, "quit") == 0) break; // sleep(1); } // 4. 將指定的共享內(nèi)存,從自己的地址空間中去關(guān)聯(lián) int n = shmdt(shmaddr); assert(n != -1); (void)n; Log("detach shm done", Debug) << " shmid : " << shmid << endl; // 5. 刪除共享內(nèi)存,IPC_RMID即便是有進(jìn)程和當(dāng)下的shm掛接,依舊刪除共享內(nèi)存 n = shmctl(shmid, IPC_RMID, nullptr); assert(n != -1); (void)n; Log("delete shm done", Debug) << " shmid : " << shmid << endl; CloseFifo(fd); return 0; }
? 上述共享內(nèi)存代碼是結(jié)合了命名管道通信進(jìn)行了訪問控制。文章來源地址http://www.zghlxwxcb.cn/news/detail-703805.html
到了這里,關(guān)于【Linux從入門到精通】通信 | 共享內(nèi)存(System V)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!