一、共享內(nèi)存的概念及原理
共享內(nèi)存是通過(guò)讓不同的進(jìn)程看到同一個(gè)內(nèi)存塊的方式。
我們知道,每一個(gè)進(jìn)程都會(huì)有對(duì)應(yīng)的PCB-task_struct ,獨(dú)立的進(jìn)程地址空間,然后通過(guò)頁(yè)表將地址映射到物理內(nèi)存中。此時(shí)我們就可以讓OS在內(nèi)存中申請(qǐng)一塊空間,然后將創(chuàng)建好的內(nèi)存空間映射到進(jìn)程的地址空間中,兩個(gè)需要進(jìn)行通信的進(jìn)程經(jīng)過(guò)映射之后就看到了同一塊內(nèi)存空間,在未來(lái)兩個(gè)進(jìn)程不想進(jìn)行通信之后,取消進(jìn)程和內(nèi)存的映射關(guān)系—去關(guān)聯(lián),然后釋放內(nèi)存即可。
我們需要注意的是,進(jìn)程地址空間,是專門設(shè)計(jì)的,用來(lái)IPC,共享內(nèi)存是一種通信方式,所以想通信的進(jìn)程,都可以使用,那么OS中一定會(huì)同時(shí)存在很多的共享內(nèi)存。
二、共享內(nèi)存相關(guān)接口說(shuō)明
1.shmget函數(shù)
功能:用來(lái)創(chuàng)建共享內(nèi)存
原型
int shmget(key_t key, size_t size, int shmflg);
頭文件
#include <sys/ipc.h>
#include <sys/shm.h>
參數(shù)
key:這個(gè)共享內(nèi)存段名字
size:共享內(nèi)存大小
shmflg:由九個(gè)權(quán)限標(biāo)志構(gòu)成,它們的用法和創(chuàng)建文件時(shí)使用的mode模式標(biāo)志是一樣的
其中最常用的兩個(gè)標(biāo)志位:
IPC_CREAT:如果文件不存在就創(chuàng)建,存在就獲取
IPC_EXCL:無(wú)法單獨(dú)使用,IPC_CREAT | IPC_EXCL,如果不存在就創(chuàng)建,如果存在就出錯(cuò)返回,這樣就保證如果創(chuàng)建成功,一定是一個(gè)新的shm
返回值:成功返回一個(gè)非負(fù)整數(shù),即該共享內(nèi)存段的標(biāo)識(shí)碼;失敗返回-1
2.ftok函數(shù)
功能;獲取key
原型
key_t ftok(const char *pathname, int proj_id);
頭文件
#include <sys/types.h>
#include <sys/ipc.h>
參數(shù)
pathname:文件名
proj_id:非0整數(shù),用于形成key
返回值:成功返回key,失敗返回-1,錯(cuò)誤碼被設(shè)置
我們?cè)谏暾?qǐng)內(nèi)存使用malloc函數(shù)需要告訴要申請(qǐng)多大的空間,而釋放的時(shí)候只需要告訴內(nèi)存的起始地址即可,那么操作系統(tǒng)是如何知道我們申請(qǐng)了多大的內(nèi)存呢,實(shí)際上,我們?cè)谏暾?qǐng)內(nèi)存的時(shí)候,OS為我們提供了申請(qǐng)的空間和該空間的屬性,如大小等等。一個(gè)程序多次申請(qǐng)空間,那么操作系統(tǒng)就需要將這些空間管理起來(lái),管理的方法是先描述再組織,描述的對(duì)象的就是這些空間的屬性。共享內(nèi)存也是如此,OS先描述再組織,所以共享內(nèi)存=共享內(nèi)存塊+共享內(nèi)存的相關(guān)屬性。那么我們創(chuàng)建共享內(nèi)存的時(shí)候,如何保證共享內(nèi)存在系統(tǒng)中是唯一的,答案是通過(guò)key來(lái)進(jìn)行唯一標(biāo)識(shí),那么key在哪呢,key在共享內(nèi)存的屬性struct shm{key_t key;};中
3.shmat函數(shù)
功能:將共享內(nèi)存段連接到進(jìn)程地址空間
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
頭文件
#include <sys/types.h>
#include <sys/shm.h>
參數(shù)
shmid: 共享內(nèi)存標(biāo)識(shí)
shmaddr:指定連接的地址
shmflg:它的兩個(gè)可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一個(gè)指針,指向共享內(nèi)存第一個(gè)節(jié)(起始地址);失敗返回-1
shmaddr為NULL,核心自動(dòng)選擇一個(gè)地址
shmaddr不為NULL且shmflg無(wú)SHM_RND標(biāo)記,則以shmaddr為連接地址。
shmaddr不為NULL且shmflg設(shè)置了SHM_RND標(biāo)記,則連接的地址會(huì)自動(dòng)向下調(diào)整為SHMLBA的整數(shù)倍。公式:shmaddr -
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示連接操作用來(lái)只讀共享內(nèi)存
4.shmdt函數(shù)
功能:將共享內(nèi)存段與當(dāng)前進(jìn)程脫離
原型
int shmdt(const void *shmaddr);
頭文件
#include <sys/types.h>
#include <sys/shm.h>
參數(shù)
shmaddr: 由shmat所返回的指針
返回值:成功返回0;失敗返回-1
注意:將共享內(nèi)存段與當(dāng)前進(jìn)程脫離不等于刪除共享內(nèi)存段
5.shmctl函數(shù)
功能:用于控制共享內(nèi)存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
參數(shù)
shmid:由shmget返回的共享內(nèi)存標(biāo)識(shí)碼
cmd:將要采取的動(dòng)作(有三個(gè)可取值)
IPC_STAT:將shmid的屬性拷貝到buf中
IPC_SET:將buf中的屬性寫入到shmid中
IPC_RMID:是否shmid的內(nèi)存
buf:指向一個(gè)保存著共享內(nèi)存的模式狀態(tài)和訪問(wèn)權(quán)限的數(shù)據(jù)結(jié)構(gòu)
返回值:成功返回0;失敗返回-1
三、用共享內(nèi)存實(shí)現(xiàn)server&client通信
1.shm_server.cc
#include "comm.hpp"
int main()
{
key_t key = getkey();
printf("key:0x%x\n", key);
int shmid = createShm(key);
printf("shmid:%d\n", shmid);
char *start = (char *)attachShm(shmid);
printf("attach success, address start: %p\n", start);
while (true)
{
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);
}
// 去關(guān)聯(lián)
detachShm(start);
// 刪除共享內(nèi)存
delShm(shmid);
return 0;
}
2.shm_client.cc
#include "comm.hpp"
int main()
{
key_t key = getkey();
printf("key:0x%x\n", key);
int shmid = getShm(key);
printf("shmid:%d\n", shmid);
char *start = (char *)attachShm(shmid);
printf("attach success, address start: %p\n", start);
const char *message = "hello server, 我是另一個(gè)進(jìn)程,正在和你通信";
pid_t id = getpid();
int cnt = 1;
// char buffer[1024];
while (true)
{
snprintf(start, MAX_SIZE, "%s[pid:%d][消息編號(hào)%d]", message, id, cnt++);
// snprintf(buffer, sizeof(buffer), "%s[pid:%d][消息編號(hào):%d]", message, id, cnt++);
// memcpy(start, buffer, strlen(buffer)+1);
// pid, count, message
sleep(1);
}
detachShm(start);
return 0;
}
3.comm.hpp
#ifndef __COMM_HPP_
#define __COMM_HPP_
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#define PATHNAME "."
#define PROJ_ID 0x666
// 共享內(nèi)存的大小,一般建議是4KB的整數(shù)倍
// 系統(tǒng)分配共享內(nèi)存是以4KB為單位的! --- 內(nèi)存劃分內(nèi)存塊的基本單位Page
#define MAX_SIZE 4096 // --- 內(nèi)核給你的會(huì)向上取整, 內(nèi)核給你的,和你能用的,是兩碼事
key_t getkey()
{
key_t key = ftok(PATHNAME, PROJ_ID);
if (key == -1)
{
std::cerr << "error" << strerror(errno) << std::endl;
exit(1);
}
return key;
}
int getShmHelper(key_t key, int flags)
{
int shmid = shmget(key, MAX_SIZE, flags);
if (shmid < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(2);
}
return shmid;
}
int getShm(key_t key)
{
return getShmHelper(key, IPC_CREAT);
}
int createShm(key_t key)
{
return getShmHelper(key, IPC_CREAT | IPC_EXCL | 0600);
}
void *attachShm(int shmid)
{
void *mem = shmat(shmid, nullptr, 0);
if ((long long)mem == -1L)
{
std::cerr << "shmat:" << errno << ":" << strerror(errno) << std::endl;
exit(3);
}
return mem;
}
void detachShm(void *start)
{
if (shmdt(start) == -1)
{
std::cerr << "shmdt:" << errno << ":" << strerror(errno) << std::endl;
}
}
void delShm(int shmid)
{
if (shmctl(shmid, IPC_RMID, nullptr) == -1)
{
std::cerr << "shmctl:" << errno << ":" << strerror(errno) << std::endl;
}
}
#endif
4.查看ipc資源及其特征
查看IPC資源的指令
ipcs -m/-q/-s
分別查看共享內(nèi)存/消息隊(duì)列/信號(hào)量
刪除使用 ipcrm -m shmid
IPC資源的特征
共享內(nèi)存的生命周期是隨OS的,不是隨進(jìn)程的
我們使用ctrl C終止程序之后,第二次顯示文件存在
5.共享內(nèi)存的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):在所有進(jìn)程間通信中是最快的,能大大的減少數(shù)據(jù)拷貝的次數(shù)
共享內(nèi)存區(qū)是最快的IPC形式。一旦這樣的內(nèi)存映射到共享它的進(jìn)程的地址空間,這些進(jìn)程間數(shù)據(jù)傳遞不再涉及到
內(nèi)核,換句話說(shuō)是進(jìn)程不再通過(guò)執(zhí)行進(jìn)入內(nèi)核的系統(tǒng)調(diào)用來(lái)傳遞彼此的數(shù)據(jù)
同樣的代碼,如果用管道來(lái)實(shí)現(xiàn),綜合考慮管道和共享內(nèi)存,考慮鍵盤輸入和顯示器輸出,共享內(nèi)存和管道各有幾次數(shù)據(jù)拷貝
對(duì)于管道,我們將輸入的數(shù)據(jù)線拷貝到我們自己定義的緩沖區(qū)buffer中,然后通過(guò)系統(tǒng)調(diào)用write寫到管道中,然后通過(guò)read讀到自己定義的緩沖區(qū)buffer中,然后再拷貝到顯示器上,這里我們不考慮輸入時(shí)從鍵盤到stdin的拷貝,stdout到顯示器的拷貝,所以管道一共需要拷貝4次
對(duì)于共享內(nèi)存,我們只需要將數(shù)據(jù)拷貝到共享內(nèi)存中,然后從共享內(nèi)存中拷貝到顯示器上,同樣這里不考慮鍵盤到stdin,stdout到顯示器的拷貝,所以一共需要拷貝2次
缺點(diǎn):不能進(jìn)行同步和互斥操作,沒有對(duì)數(shù)據(jù)做任何保護(hù)
6.共享內(nèi)存的數(shù)據(jù)結(jié)構(gòu)
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
struct shmid_ds結(jié)構(gòu)體中包含了struct ipc_perm結(jié)構(gòu)體,其中就包含了key
四、system V消息隊(duì)列
消息隊(duì)列就是內(nèi)核提供了隊(duì)列這種數(shù)據(jù)結(jié)構(gòu)用于進(jìn)程間通信,兩個(gè)進(jìn)程可以相互通信,隊(duì)列的每一個(gè)節(jié)點(diǎn)包含了類型標(biāo)識(shí)符,表示是各自讀的數(shù)據(jù)還是寫的數(shù)據(jù)
消息隊(duì)列的接口如下:
int msgget(key_t key, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
消息隊(duì)列提供了一個(gè)從一個(gè)進(jìn)程向另外一個(gè)進(jìn)程發(fā)送一塊數(shù)據(jù)的方法
每個(gè)數(shù)據(jù)塊都被認(rèn)為是有一個(gè)類型,接收者進(jìn)程接收的數(shù)據(jù)塊可以有不同的類型值特性方面
IPC資源必須刪除,否則不會(huì)自動(dòng)清除,除非重啟,所以system V IPC資源的生命周期隨內(nèi)核
這里就不做過(guò)多的介紹了,消息隊(duì)列我們了解即可。
五、system V信號(hào)量
信號(hào)量本質(zhì)是一個(gè)計(jì)數(shù)器,通常用來(lái)表示公共資源中,資源數(shù)量的多少問(wèn)題的。公共資源是被多個(gè)進(jìn)程同時(shí)可以訪問(wèn)的資源。訪問(wèn)沒有保護(hù)的公共資源,那么就會(huì)導(dǎo)致數(shù)據(jù)不一致的問(wèn)題。
我們?yōu)槭裁匆尣煌倪M(jìn)程看到同一份資源呢,因?yàn)槲覀円M(jìn)行通信,進(jìn)程間實(shí)現(xiàn)協(xié)同,但是進(jìn)程具有獨(dú)立性,我們讓進(jìn)程看到就提出一些方法,但是又引入了一些新的問(wèn)題(數(shù)據(jù)不一致問(wèn)題)
我們未來(lái)將被保護(hù)起來(lái)的公共資源:臨界資源,大部分是獨(dú)立的
資源(內(nèi)存,文件,網(wǎng)絡(luò)等)是要被使用的,如何被進(jìn)程使用呢,一定是該進(jìn)程有對(duì)應(yīng)的代碼來(lái)訪問(wèn)這部分臨界資源,這部分代碼被稱為臨界區(qū),其他的代碼被稱為非臨界區(qū)
我們?nèi)绾螌?duì)數(shù)據(jù)進(jìn)行保護(hù)呢:同步和互斥。互斥就是我在訪問(wèn)臨界資源的時(shí)候,其他人不能夠進(jìn)行訪問(wèn)
原子性:要么不做,要做就做完,只有兩態(tài)
為什么要有信號(hào)量呢?
我們舉一個(gè)例子,我們?cè)陔娪霸嚎措娪?,我們不是只有坐在電影院?duì)應(yīng)的座位上的時(shí)候這個(gè)座位CIA屬于我,而是我們買票的時(shí)候,就對(duì)放映廳中的座位進(jìn)行了預(yù)定,此時(shí)座位就已經(jīng)屬于我們了。即當(dāng)我們想要某種資源的時(shí)候,我們可以進(jìn)行預(yù)定。假如放映廳有100個(gè)座位,那么電影院會(huì)不會(huì)賣出101張票呢,答案是不會(huì),我們?cè)撊绾巫龅侥亍N覀兛梢远x一個(gè)信號(hào)量,初始值為100,賣出一張票就-1,為0的時(shí)候就停止售票
信號(hào)量sem=100
sem--;//預(yù)定資源 --- P操作
訪問(wèn)公共資源
sem++;//釋放公共資源 --- V操作
所以的進(jìn)程在訪問(wèn)公共資源之前,都必須先申請(qǐng)sem信號(hào)量 ->先申請(qǐng)信號(hào)量的前提,是所有進(jìn)程都必須先看到同一個(gè)信號(hào)量 ->信號(hào)量本身就是公共資源 -> 信號(hào)量本身也要保證自身的安全,–,++ ->信號(hào)量必須保證自身操作的安全性,–,++操作是原子
共享資源可以被當(dāng)做一個(gè)整體資源,也可以被劃分為一個(gè)一個(gè)資源的子部分
當(dāng)一個(gè)信號(hào)量的初始值為1的時(shí)候,那么該信號(hào)量我們稱為二元信號(hào)量,具有互斥的功能
由于各進(jìn)程要求共享資源,而且有些資源需要互斥使用,因此各進(jìn)程間競(jìng)爭(zhēng)使用這些資源,進(jìn)程的這種關(guān)系為進(jìn)程的互斥
系統(tǒng)中某些資源一次只允許一個(gè)進(jìn)程使用,稱這樣的資源為臨界資源或互斥資源。
在進(jìn)程中涉及到互斥資源的程序段叫臨界區(qū)
IPC資源必須刪除,否則不會(huì)自動(dòng)清除,除非重啟,所以system V IPC資源的生命周期隨內(nèi)核
六、IPC資源的組織方式
我們可以發(fā)現(xiàn)system V三種方式的數(shù)據(jù)結(jié)構(gòu)如下:
共享內(nèi)存:
消息隊(duì)列:
信號(hào)量:
我們發(fā)現(xiàn),他們的接口的相似度非常高,這樣說(shuō)明他們都是system V方式的進(jìn)程間通信
我們可以創(chuàng)建一個(gè)struct ipc_perm *perms[]的數(shù)組,用于存放struct ipc_perm
我們創(chuàng)建一個(gè)共享內(nèi)存數(shù)據(jù)結(jié)構(gòu)的對(duì)象 struct shmid_ds myshm,然后perms[0] = &myshm.shm_perm
創(chuàng)建一個(gè)消息隊(duì)列的數(shù)據(jù)結(jié)構(gòu)對(duì)象 struct msqid_ds mymsg, prems[1] = &mymsg.msg_perm
然后我們?cè)讷@取共享內(nèi)存的屬性的時(shí)候,就可以通過(guò)如下的方式進(jìn)行獲取
(struct shmid_ds*)perms[0] ->其他屬性
這是因?yàn)橐粋€(gè)結(jié)構(gòu)體的第一個(gè)成員的地址,在數(shù)字上,和結(jié)構(gòu)體對(duì)象本身的地址數(shù)字是相等的文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-755248.html
這種方式向C++的多態(tài)一樣,數(shù)組中的對(duì)象為基類,具體的對(duì)象(共享內(nèi)存,消息隊(duì)列,信號(hào)量)為子類文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-755248.html
到了這里,關(guān)于【Linux】進(jìn)程間通信之共享內(nèi)存/消息隊(duì)列/信號(hào)量的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!