共享內(nèi)存的原理
共享內(nèi)存區(qū)是最快的IPC形式。一旦這樣的內(nèi)存映射到共享它的進程的地址空間,這些進程間數(shù)據(jù)傳遞不再涉及到內(nèi)核,換句話說是進程不再通過執(zhí)行進入內(nèi)核的系統(tǒng)調(diào)用來傳遞彼此的數(shù)據(jù)
理解:
- 進程間通信,是專門設(shè)計的,用來IPC
- 共享內(nèi)存是一種通信方式,所有想通信的進程,都可以用
- OS中一定可能會同時存在的很多的共享內(nèi)存
共享內(nèi)存的用法
共享內(nèi)存的概念:
通過讓不同的進程,看到同一個內(nèi)存塊的方式,就叫做共享內(nèi)存
1.使用shmget生成共享內(nèi)存
函數(shù)參數(shù)int shmflg
(標記位傳參):lPC_CREAT
:如果不存在,創(chuàng)建之,如果存在,獲取之IPC_EXCL
:
- 無法單獨使用
-
IPC_CREAT|IPC_EXCL
:如果不存在,創(chuàng)建之,如果存在,就出錯返回
(給用戶如果創(chuàng)建成功,一定是一個新的shm!)
shmget
返回值:在不同操作系統(tǒng)可能不一樣,它雖然是數(shù)組下標,但是它跟我們之前學習過的文件系統(tǒng)的下標是不一樣的,是兩套體系,而它的返回值我們把它當成是一個標識符就行了
函數(shù)參數(shù)key_t key
:
我們是只有shmget()創(chuàng)建完以后才有返回值,但是為了保證兩個需要通信的進程看到的是同一個內(nèi)存塊,需要有標識符確定。而這個key值是什么不重要,重要的是能進行唯一性標識最重要。
我們在生成key值需要調(diào)用另外一個函數(shù):ftok
2.使用ftok使得進程能看到同一內(nèi)存塊
我們只需要在
ftok
的參數(shù)傳入相同的pathname
與proj_id
那么它生成的key
值也一定是一樣的,然后我們的兩個進程就能通過這個key值找到這個內(nèi)存塊ftok
函數(shù)的原型如下:
key_t ftok(const char *pathname, int proj_id);
pathname
參數(shù)是一個指向文件路徑名的指針,proj_id
參數(shù)是一個整數(shù)值。ftok
函數(shù)會根據(jù)pathname
和proj_id
生成一個key
值,并將其返回。
使用 ftok
函數(shù)時,需要注意以下幾點:
-
pathname
參數(shù)必須指向一個存在的文件,否則 ftok 函數(shù)會返回錯誤。 -
proj_id
參數(shù)是一個可以自定義的整數(shù)值,用于在同一個文件的不同共享內(nèi)存區(qū)域之間進行區(qū)分。如果不同的共享內(nèi)存區(qū)域使用相同的 proj_id 值,則它們將被視為同一個共享內(nèi)存區(qū)域。 -
key
值的生成是基于st_dev
和st_ino
兩個文件屬性值的。因此,如果 pathname 參數(shù)對應(yīng)的文件屬性值發(fā)生了改變,那么生成的 key 值也會發(fā)生改變。
3.模擬創(chuàng)建共享內(nèi)存查看key值與shmid值
comm.hpp
:
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x66
// 共享內(nèi)存的大小,一般建議是4KB的整數(shù)倍
// 系統(tǒng)分配共享內(nèi)存是以4KB為單位的! --- 內(nèi)存劃分內(nèi)存塊的基本單位Page
#define MAX_SIZE 4096 // --- 內(nèi)核給你的會向上取整, 內(nèi)核給你的,和你能用的,是兩碼事
key_t getKey()
{
key_t k = ftok(PATHNAME, PROJ_ID); //可以獲取同樣的一個Key!
if(k < 0)
{
// cin, cout, cerr -> stdin, stdout, stderr -> 0, 1, 2
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(1);
}
return k;
}
int getShmHelper(key_t k, int flags)
{
int shmid = shmget(k, MAX_SIZE, flags);
if(shmid < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(2);
}
return shmid;
}
int getShm(key_t k)
{
return getShmHelper(k, IPC_CREAT);
}
int createShm(key_t k)
{
return getShmHelper(k, IPC_CREAT | IPC_EXCL | 0600);
}
#endif
shm_server.cpp
:
#include "comm.hpp"
#include <unistd.h>
int main()
{
key_t k = getKey();
printf("key: 0x%x\n", k); // key
int shmid = createShm(k);
printf("shmid: %d\n", shmid); //shmid
delShm(shmid);
return 0;
}
shm_client.cpp
:
#include "comm.hpp"
#include <unistd.h>
int main()
{
key_t k = getKey();
printf("key: 0x%x\n", k);
int shmid = getShm(k);
printf("shmid: %d\n", shmid);
return 0;
}
再談key
在前面我們說過,OS中那么多進程需要通信,當然共享內(nèi)存也可能同時存在很多,由于進程獨立性,共享內(nèi)存肯定是OS生成的,那么OS生成這些共享內(nèi)存肯定也需要對其進行管理 -> 管理的方法:先描述再組織:
共享內(nèi)存 = 物理內(nèi)存塊 + 共享內(nèi)存相關(guān)屬性
在創(chuàng)建共享內(nèi)存時,為了保證共享內(nèi)存存在系統(tǒng)中是唯一的,是使用key
標識
共享內(nèi)存標識符shmid
是一個整數(shù),由操作系統(tǒng)分配并唯一標識共享內(nèi)存段。在使用共享內(nèi)存時,不同的進程可以通過共享內(nèi)存標識符訪問同一個共享內(nèi)存段
key
是要shmget
,設(shè)置進入共享內(nèi)存屬性中的!用來表示該共享內(nèi)存,在內(nèi)核中的唯一性?。?br>shmid
類似于fd
,而key
類似于inode
,shmid
與key
的關(guān)系類似鎖與鑰匙
IPC資源
ipc資源的特征:
按道理我們的bash結(jié)束就代表這個進程結(jié)束,那么我們繼續(xù)執(zhí)行卻發(fā)現(xiàn)創(chuàng)建失敗,這是為什么?
共享內(nèi)存的生命周期是隨OS的,而不是隨進程的–這是所有system V
版本的共性
查看IPC資源:
ipcs -m/-q/-s
刪除這個資源:
ipcrm -m (shmid值)2
直觀上你可能想使用key值去刪除,因為key值具有唯一性,但是 不是,因為key值僅僅只是內(nèi)核里面用來標識唯一性的,并不是讓用戶去操縱共享內(nèi)存的,而指令是在應(yīng)用層,用戶要使用經(jīng)常用來控制共享內(nèi)存的id去操縱它,也就是shmid的值
4.shmctl對共享內(nèi)存進行控制
函數(shù)原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//成功返回0,失敗返回-1并設(shè)置errno變量以指示錯誤類型
參數(shù)cmd
是一個控制命令,用于指定對共享內(nèi)存的操作類型,常見的操作類型包括:
-
IPC_STAT
:獲取共享內(nèi)存的狀態(tài)信息,將共享內(nèi)存的狀態(tài)信息保存在buf參數(shù)指向的shmid_ds結(jié)構(gòu)體中。 -
IPC_SET
:設(shè)置共享內(nèi)存的狀態(tài)信息,將buf參數(shù)指向的shmid_ds結(jié)構(gòu)體中的狀態(tài)信息應(yīng)用到共享內(nèi)存中。 -
IPC_RMID
:刪除共享內(nèi)存,將共享內(nèi)存從系統(tǒng)中刪除。 -
IPC_INFO
:獲取系統(tǒng)中IPC機制的狀態(tài)信息,將IPC機制的狀態(tài)信息保存在buf參數(shù)指向的ipc_info結(jié)構(gòu)體中。 -
SHM_LOCK
:鎖定共享內(nèi)存,防止它被換出到交換空間中。 -
SHM_UNLOCK
:解鎖共享內(nèi)存,允許它被換出到交換空間中
參數(shù)buf
是一個指向共享內(nèi)存的數(shù)據(jù)結(jié)構(gòu)shmid_ds
的指針,用于獲取和修改共享內(nèi)存的狀態(tài)信息。shmid_ds結(jié)構(gòu)體定義在<sys/shm.h>頭文件中,包含了共享內(nèi)存的各種屬性信息,例如共享內(nèi)存的大小、創(chuàng)建時間、最后一次連接時間、最后一次操作時間、當前連接數(shù)等信息。
- 參數(shù)
buf
如果傳nullptr
的話,shmctl()函數(shù)會忽略buf參數(shù),并且不會獲取或修改共享內(nèi)存的狀態(tài)信息。 - 如果應(yīng)用程序不需要獲取或修改共享內(nèi)存的狀態(tài)信息,可以將buf參數(shù)傳遞為NULL,這樣可以簡化代碼并提高程序的效率。
- 如果應(yīng)用程序需要獲取或修改共享內(nèi)存的狀態(tài)信息,那么必須傳遞一個有效的
shmid_ds
結(jié)構(gòu)體指針作為buf參數(shù),否則shmctl()函數(shù)將會返回錯誤并設(shè)置errno變量
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
封裝:刪除共享內(nèi)存:
//comm.hpp
void delShm(int shmid)
{
if(shmctl(shmid, IPC_RMID, nullptr) == -1)
{
std::cerr << errno << " : " << strerror(errno) << std::endl;
}
}
5.shmat將共享內(nèi)存段連接到進程地址空間
函數(shù)原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
這個void*
的返回值就等價于以前的malloc
的返回值
函數(shù)參數(shù):
參數(shù)shmaddr
是指定共享內(nèi)存連接的地址
- 如果shmaddr參數(shù)的值為0或NULL,操作系統(tǒng)會自動選擇一個可用的地址作為共享內(nèi)存的連接地址,并返回該地址。
- 如果shmaddr參數(shù)的值不為0或NULL,則將共享內(nèi)存連接到指定的地址,這種情況應(yīng)用程序需要注意選擇合適的共享內(nèi)存連接地址,以避免與其他進程或共享庫發(fā)生地址沖突。通常情況下,應(yīng)用程序可以將shmaddr參數(shù)設(shè)置為一個特定的地址或者使用一個已經(jīng)分配好的地址
參數(shù)shmflg
是用于指定共享內(nèi)存連接的標志,它控制共享內(nèi)存連接的行為。shmflg參數(shù)可以是以下標志的按位或組合:
-
SHM_RDONLY
:只讀訪問共享內(nèi)存,即使共享內(nèi)存段是可寫的。 -
SHM_RND
:將shmaddr參數(shù)舍入到系統(tǒng)頁面大小的整數(shù)倍。這個標志在shmaddr參數(shù)不為0或NULL時才有意義。 -
SHM_REMAP
:將共享內(nèi)存連接到一個新的地址,如果shmaddr參數(shù)不為0或NULL,則將共享內(nèi)存連接到指定的地址。 -
SHM_EXEC
:允許執(zhí)行共享內(nèi)存中的代碼。這個標志只在某些體系結(jié)構(gòu)上有意義。 -
IPC_NOWAIT
:非阻塞模式,如果連接不可用,則立即返回錯誤而不是等待。 -
SHM_DEST
:在共享內(nèi)存段上設(shè)置IPC_RMID標志,表示共享內(nèi)存段已經(jīng)被刪除。 -
SHM_HUGETLB
:使用大頁面來分配共享內(nèi)存,以提高性能和效率。
不是所有的標志都適用于所有的平臺和操作系統(tǒng),因此在使用
shmat()
函數(shù)時,應(yīng)該根據(jù)具體平臺和操作系統(tǒng)的要求選擇合適的標志,并正確設(shè)置shmflg參數(shù)。
封裝:將當前進程貼到共享內(nèi)存上:
//comm.hpp
void *attachShm(int shmid)
{
void *mem = shmat(shmid, nullptr, 0); //64系統(tǒng),8
if((long long)mem == -1L)
{
std::cerr <<"shmat: "<< errno << ":" << strerror(errno) << std::endl;
exit(3);
}
return mem;
}
6.shmdt將共享內(nèi)存段與當前進程脫離
函數(shù)原型:
int shmdt(const void *shmaddr);
函數(shù)參數(shù):shmdt()
函數(shù)的參數(shù)shmaddr
是共享內(nèi)存連接的地址,它指向共享內(nèi)存段的起始地址。該地址是由shmat()
函數(shù)返回的,用于標識應(yīng)用程序與共享內(nèi)存段之間的連接。
封裝:
//comm.hpp
void detachShm(void *start)
{
if(shmdt(start) == -1)
{
std::cerr <<"shmdt: "<< errno << ":" << strerror(errno) << std::endl;
}
}
斷開與共享內(nèi)存段的連接并不會刪除共享內(nèi)存段,只是將該連接從應(yīng)用程序的地址空間中刪除。如果希望刪除共享內(nèi)存段,應(yīng)該在最后一個使用共享內(nèi)存段的進程斷開連接后,調(diào)用shmctl()函數(shù)將共享內(nèi)存段標記為刪除狀態(tài),然后等待所有進程都將其連接斷開后,再調(diào)用shmctl()函數(shù)將共享內(nèi)存段從系統(tǒng)中刪除。
7.創(chuàng)建共享內(nèi)存通信-讀取端
shm_server.cpp
:
#include "comm.hpp"
#include <unistd.h>
int main()
{
key_t k = getKey();
printf("key: 0x%x\n", k); // key
int shmid = createShm(k);
printf("shmid: %d\n", shmid); //shmid
sleep(5);
char *start = (char*)attachShm(shmid);
printf("attach success, address start: %p\n", start);
// 輸出
while(true)
{
printf("client say : %s\n", start);
sleep(1);
}
// 去關(guān)聯(lián)
detachShm(start);
sleep(10);
//刪除共享內(nèi)存
delShm(shmid);
return 0;
}
解釋:首先創(chuàng)建key值,然后創(chuàng)建共享內(nèi)存,然后將進程與共享內(nèi)存相關(guān)聯(lián),然后將從共享內(nèi)存得到的數(shù)據(jù)輸出
8.創(chuàng)建共享內(nèi)存通信-寫入端
shm_client.cpp
#include "comm.hpp"
#include <unistd.h>
int main()
{
key_t k = getKey();
printf("key: 0x%x\n", k);
int shmid = getShm(k);
printf("shmid: %d\n", shmid);
char *start = (char*)attachShm(shmid);
printf("attach success, address start: %p\n", start);
const char* message = "hello server, 我是另一個進程,正在和你通信";
pid_t id = getpid();
int cnt = 1;
while(true)
{
sleep(5);
snprintf(start, MAX_SIZE, "%s[pid:%d][消息編號:%d]", message, id, cnt++);
}
detachShm(start);
return 0;
}
解釋:
- 首先是創(chuàng)建
key
值(由于我們封裝了創(chuàng)建key值,創(chuàng)建的路徑以及id都是一樣的,所以本寫入端進程創(chuàng)建的key值與讀取端進程創(chuàng)建的key值是一樣的,這也是兩個進程看到同一份資源(可以是內(nèi)存塊)的前提條件) - 然后獲取在讀取端已經(jīng)創(chuàng)建好的共享內(nèi)存的
shmid
值 - 將寫入端的進程與共享內(nèi)存關(guān)聯(lián)
- 將該進程的pid值、計數(shù)器、message,使用
snprintf
以字符串的形式輸出到共享內(nèi)存
上面使用的都是封裝的接口,如
getKey()/getShm(k)
通信輸出結(jié)果:
共享內(nèi)存的特點
優(yōu)點
優(yōu)點:所有進程間通信,速度是最快的
進程1向進程2傳遞數(shù)據(jù),因為該內(nèi)存是被雙方所共享的,所以我們只要將數(shù)據(jù)寫入共享內(nèi)存中,對方立即就能看到,所以我們就能大大減少進程1至進程2數(shù)據(jù)拷貝的次數(shù)
面試題:同樣的代碼,綜合考慮管道和共享內(nèi)存,鍵盤輸入和顯示器輸出各自有幾次數(shù)據(jù)拷貝?
管道:
共享內(nèi)存:
這里的場景只是單純的收發(fā)消息,如果代碼有別的輸入輸出還需要具體問題具體分析
缺點
這里輸出從編號后綴會發(fā)現(xiàn)一直打印相同的消息
缺點:沒有進行同步與互斥的操作,沒有對數(shù)據(jù)進行保護
思考:如何實現(xiàn)對共享內(nèi)存的保護呢?
我們可以實現(xiàn)兩個管道與共享內(nèi)存結(jié)合的方式,寫端寫入數(shù)據(jù)到共享內(nèi)存后通過管道給讀端一個標識,而讀端在沒有這個標識的時候就阻塞,而不是一直讀取,讀端讀完以后給寫端再發(fā)一個標識,使得寫端繼續(xù)寫,而讀端又繼續(xù)阻塞。
共享內(nèi)存的內(nèi)核結(jié)構(gòu)
這是呈現(xiàn)給用戶的,大致的內(nèi)核數(shù)據(jù)結(jié)構(gòu)體,但是內(nèi)核里面更為復(fù)雜
共享內(nèi)存的屬性:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
我么可以在之前讀取端打印共享內(nèi)存的屬性以及key值:
//shm_server.cpp
while(true)
{
// char buffer[]; read(pipefd, buffer, ...)
printf("client say : %s\n", start);
struct shmid_ds ds;
shmctl(shmid, IPC_STAT, &ds);
printf("獲取屬性: size: %d, pid: %d, myself: %d, key: 0x%x",\
ds.shm_segsz, ds.shm_cpid, getpid(), ds.shm_perm.__key);
sleep(1);
}
文章來源:http://www.zghlxwxcb.cn/news/detail-570907.html
如有錯誤或者不清楚的地方歡迎私信或者評論指出????文章來源地址http://www.zghlxwxcb.cn/news/detail-570907.html
到了這里,關(guān)于【Linux】進程間通信 -- system V共享內(nèi)存的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!