Linux:进程间通信(二.共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
上次结束了进程间通信一:Linux:进程间通信(一.初识进程间通信、匿名管道与命名管道、共享内存)
实现进程间通信的前提就是如何让不同的进程看到同一份资源
System V共享内存(Shared Memory)是一种Linux中用于进程间通信(IPC)的机制。它允许多个进程访问同一块物理内存区域,从而实现数据的快速共享和交换。
shmget()
系统调用来创建共享内存。这个函数会分配一块指定大小的内存区域,并返回一个标识符,用于后续对这块共享内存的操作。shmat()
系统调用来将共享内存关联到进程的地址空间。这个函数会将共享内存的地址告诉进程,使得进程可以通过这个地址来访问共享内存。shmdt()
系统调用来取消关联。这个函数会断开进程与共享内存之间的映射关系。shmctl()
系统调用来释放它。这个函数会回收这块内存区域,并释放相关的资源。无需内核参与:在共享内存中,多个进程可以直接访问同一块物理内存区域,而无需通过内核进行数据的拷贝和传输。这样可以避免了进程间切换和内核态和用户态之间的数据拷贝,从而提高了通信的效率。
ftok()
函数 Linux中用于生成一个唯一的键值(key)的系统调用,这个键值通常用于在进程间通信(IPC)中标识共享内存段、消息队列或信号量集。ftok()
函数基于一个已经存在的文件路径和一个非零的标识符(通常是一个小的正整数)来生成这个键值。
#include <sys/ipc.h>
#include <sys/types.h>
key_t ftok(const char *pathname, int proj_id);
参数:
pathname
:指向一个已经存在的文件路径的指针。这个文件通常被用作生成键值的“种子”或“基础”。proj_id
:一个非零的标识符,通常是一个小的正整数。这个值将与文件路径一起被用于生成键值。返回值:如果成功,ftok()
函数返回一个唯一的键值(key_t
类型),该键值可以在后续的 IPC 调用(如 shmget()
, msgget()
, semget()
等)中用作参数。如果失败,则返回 (key_t) -1
并设置 errno
以指示错误。
shmget()
:创建或获取共享内存shmget()
系统调用用于创建一个新的共享内存对象,或者如果它已存在,则返回该对象的标识符。
函数原型:
int shmget(key_t key, size_t size, int shmflg);
参数:
key
:一个键,用于唯一标识共享内存对象。通常使用ftok()
函数生成。
key
便是那个唯一性标识符。那么为什么这个key要由我们用户来传入呢?size
:共享内存的大小(以字节为单位)。
shmflg
:权限标志和选项。通常设置为IPC_CREAT
(如果对象不存在则创建,存在的话直接获取)和权限(如0666
)。
若设置为IPC_CREAT|IPC_EXCL
(如果对象不存在则创建,存在的话出错返回)
返回值:成功时返回共享内存对象的标识符;失败时返回-1并设置errno
。
shmctl()
:控制共享内存shmctl()
系统调用用于获取或设置共享内存的属性,或者删除共享内存对象。
函数原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid
:共享内存对象标识符。cmd
:要执行的操作。例如,IPC_RMID
用于删除共享内存对象,IPC_STAT
用于获取其状态。buf
:指向shmid_ds
结构的指针,用于传递或接收共享内存的状态信息。返回值:成功时返回0;失败时返回-1并设置errno
。
shmat()
:将共享内存关联到进程的地址空间shmat()
(attach)系统调用用于将共享内存对象关联到调用进程的地址空间。
函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid
:shmget()
返回的共享内存对象标识符。shmaddr
:希望将共享内存附加到的进程的地址。如果设置为NULL,则系统选择地址(一般都这样)。shmflg
:通常设置为0或SHM_RND
(使附加地址向下舍入到最接近的SHMLBA边界)。返回值:成功时返回共享内存附加到进程的地址;失败时返回(void *)-1并设置errno
。
shmdt()
:取消共享内存的关联shmdt()
系统调用用于取消之前通过shmat()
附加到进程的共享内存的关联。
函数原型:
int shmdt(const void *shmaddr);
参数:
shmaddr
:shmat()
返回的共享内存附加到进程的地址。返回值:成功时返回0;失败时返回-1并设置errno
。
makefile:
.PHONY:all
all:shm_client shm_server
shm_server:ShmServer.cc
g++ -o $@ $^ -std=c++11
shm_client:ShmClient.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f shm_client shm_server
#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
using namespace std;
const char *pathname = "/home/zc/study/lesson26/5.4_shm_ipc";
const int proj_id = 0x1;
const int defaultsize = 4096; // 单位是字节
key_t GetShmKeyOrDie()
{
key_t key = ftok(pathname, proj_id);
if (key == -1)
{
cerr << "ftok error, errno : " << errno << ", error string: " << strerror(errno) << std::endl;
exit(1); // 出错就直接退出
}
return key;
}
int CreateShmOrDie(key_t key, int size, int flag)
{
int shmid = shmget(key, size, flag);
if (shmid < 0)
{
std::cerr << "shmget error, errno : " << errno << ", error string: " << strerror(errno) << std::endl;
exit(2); // 出错就直接退出
}
return shmid;
}
int CreateShm(key_t key, int size)
{
return CreateShmOrDie(key, size, IPC_CREAT | IPC_EXCL | 0666); // 没有就创建,有就报错,权限666
}
int GetShm(key_t key, int size)
{
return CreateShmOrDie(key, size, IPC_CREAT); // 不存在则创建,存在的话直接获取
}
void DeleteShm(int shmid)
{
int n = shmctl(shmid, IPC_RMID, nullptr);
if (n == -1)
{
cerr << "shmctl error" << errno << endl;
}
else
{
cout << "delete successfully" << endl;
}
}
void *ShmAttach(int shmid)
{
void *addr = shmat(shmid, nullptr, 0);
if ((long long int)addr == -1)
{
std::cerr << "shmat error" << std::endl;
return nullptr;
}
return addr;
}
void ShmDt(void *addr)
{
int n = shmdt(addr);
if (n < 0)
{
cerr << "shmdt error" << endl;
}
}
#include "Comm.hpp"
int main()
{
// 获取key
key_t key = GetShmKeyOrDie();
cout << "key:" << key << endl;
// 获取shmid
int shmid = GetShm(key, defaultsize);
cout << "shmid:" << shmid << endl;
// 进行挂接
void *tem = ShmAttach(shmid);
char *addr = (char *)tem;
cout << "Attch successfully" << endl;
// 进行通信,这里进行写入
for (char ch = 'A'; ch <= 'Z'; ch++)
{
addr[ch - 'A'] = ch;
}
// 取消挂接
ShmDt(tem);
return 0;
}
#include "Comm.hpp"
#include <unistd.h>
int main()
{
// 获取key
key_t key = GetShmKeyOrDie();
cout << "key:" << key << endl;
// 创建共享内存
int shmid = CreateShm(key, defaultsize);
cout << "shmid:" << shmid << endl;
// 进行挂接
void *tem = ShmAttach(shmid);
char *addr = (char *)tem;
cout << "Attch successfully" << endl;
// 进行通信,这里进行读
for (;;)
{
cout << "shm content: " << addr << std::endl;
}
// 取消挂接
ShmDt(tem);
// 删除共享内存
sleep(50);
DeleteShm(shmid);
return 0;
}
ipcs -m
显示的以下内容:
shmid
来操作同一个共享内存段。0600
表示用户具有读写权限,其他用户没有权限。nattch
数加一;当一个进程分离(detach)时,nattch
数减一。ipcrm -m <shmid>
<shmid>
是要删除的共享内存段的标识符。通过这个命令可以删除指定的共享内存段,释放其资源。
shmid与key分辨: 在共享内存的设计中,
key
和shmid
的使用确实是为了实现内核层和用户层之间的解耦,从而使它们在宏观层面上互不影响,具有独立性。下面详细解释一下这种设计的好处和原因:
key
来唯一标识共享内存段,而用户层使用 shmid
来访问和操作已存在的共享内存段。这种设计使得内核层和用户层的代码逻辑相互独立,彼此不直接依赖。key
的逻辑,而不需要影响用户层的代码。用户层代码不需要关心内核层的具体实现细节,只需要通过 shmid
来操作共享内存即可。System V消息队列是一种进程间通信的机制,允许进程之间通过消息进行通信。消息队列提供了一个消息缓冲区,进程可以向消息队列发送消息,也可以从消息队列接收消息。下面我们来详细讲解消息队列的原理以及相关函数。
当使用System V消息队列相关函数时,需要了解函数的原型、参数和返回值。以下是这些函数的介绍:
msgget
:int msgget(key_t key, int msgflg);
key
为消息队列的键值,msgflg
为权限标志和操作标志的组合。errno
。msgctl
:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid
为消息队列的标识符,cmd
为控制命令,buf
为消息队列信息结构体指针。errno
。msgsnd
:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid
为消息队列的标识符,msgp
为消息缓冲区指针,msgsz
为消息长度,msgflg
为标志位。errno
。msgrcv
:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msqid
为消息队列的标识符,msgp
为消息缓冲区指针,msgsz
为消息长度,msgtyp
为消息类型,msgflg
为接收标志。errno
。++
和- -
不是原子的
进程申请信号量一旦成功:就一定有这个进程的资源了(相当于去看电影买票,一定有我们的座位了,而且别人也买不了这个座位) 当我们释放信号量后,这份资源才能给别人(看完电影后,这个座位才能接着被下一个买) 申请信号量和释放信号量来保护临界资源, 是大家都要遵守的规则(我们程序员)
==>
信号量本身就是共享资源内核中,所有的描述管理IPC资源的结构体,第一个成员大家都一样 kern_ipc_perm。我们可以用指针数组来进行管理
在内核中,对IPC资源的管理也是转变为对数组的增删查改
类型不同我们怎么解决呢?——直接强转
那怎么知道是什么类型呢?
kern_ipc_perm里有一个有个mode变量,能来表示类型
#define IPC TYPE SHM Ox1
#define IPC TYPE MSG (0x1<<1)
#define IPC TYPE SEM(0x1 << 2)//定义这三个宏后
shmid kernel* (kern ipc_perm* p)
{
if (p->mode & IPC TYPE SHM)
{
return (shmid kernel)ipc
}
else if (p->mode & IPC TYPE MSG)
{
///
}
else
{
///
}
}
好啦,我也是结束了实训,才到家!!! 感谢大家的支持