Linux--进程间通信(system V共享内存)
momo小菜pa 2024-07-04 13:37:03 阅读 63
目录
1.原理部分
2.系统调用接口
参数说明
返回值
1. 函数原型
2. 参数说明
3. 返回值
4. 原理
5. 注意事项
3.使用一下shmget(一段代码)
4.一个案例(一段代码)
1.简单封装一下
2.使用共享内存
2.1挂接(shmat)和取消挂接(shmdt)
2.2重新封装
2.3使用共享内存进行通信
5.共享内存的优劣
优点:
缺点:
1.原理部分
以shmget系统调用为例:
创建共享内存区域:
使用<code>shmget系统调用来请求创建一块共享内存区域。这个函数会分配一块指定大小的内存,并返回一个标识符(shmid),用于后续对这块共享内存的引用。在创建过程中,System V共享内存会在内核中创建一个特殊的数据结构(如
shmid_kernel
)来描述这块共享内存,并在特殊文件系统shm
中创建一个与该共享内存关联的文件。映射共享内存到进程地址空间:
通过
shmat
系统调用,进程可以将之前创建的共享内存区域映射到自己的虚拟地址空间中。这样,进程就可以通过指针直接访问这块共享内存,而无需通过内核进行数据的拷贝。shmat
调用会返回一个指向共享内存区域的指针,进程可以使用这个指针来读写共享内存中的数据。进程间通信:
当多个进程都映射了同一块共享内存到各自的地址空间后,它们就可以通过读写这块共享内存来进行通信了。进程A可以将数据写入共享内存,然后进程B可以从共享内存中读取这些数据,从而实现进程间的数据交换。解除映射和删除共享内存:
当进程不再需要访问共享内存时,可以使用
shmdt
系统调用来解除映射,即将共享内存从进程的地址空间中移除。当所有进程都解除了对共享内存的映射,并且确定不再需要这块共享内存时,可以使用shmctl
系统调用来删除它,释放系统资源。
2.系统调用接口
shmget
功能:创建或获取共享内存。参数:
<code>key_t key:一个键值,用于唯一标识一个共享内存段。你可以使用IPC_PRIVATE常量创建一个私有共享内存段,或使用ftok()函数根据文件路径生成一个唯一的键值。
size_t size
:共享内存的大小,以字节为单位。int shmflg
:标志位,用于控制创建和获取共享内存的行为。
IPC_CREAT
:如果指定的共享内存不存在,则创建它;如果已存在,则返回其标识符。IPC_EXCL
:与IPC_CREAT
一起使用时,如果共享内存已存在,则调用失败(返回-1)。返回值:成功时返回共享内存的标识符(一个非负整数),失败时返回-1。
ftok函数是用于生成一个唯一的键值(key)的函数,通常用于创建共享内存、消息队列等进程间通信的标识符。它的原型是:
参数说明:
<code>const char *pathname:一个文件路径名字符串,它应该指向一个已存在的文件。
int proj_id
:一个非负整数,用于与pathname一起生成唯一的键值。
ftok函数会返回一个唯一的键值,如果发生错误则返回-1。这个键值可以用于后续的shmget等系统调用中,以标识和引用特定的共享内存段。
这意味着,我们给两个进程使用同样的pathname和同样的id,调用同样的ftok,就能形成同样的key了。通过同样的key,就是能看到同一份资源的必要条件。
共享内存是需要手动释放的,不随着进程的结束而结束
ipcs -m查共享内存,ipcrm -m删除共享内存(按照共享内存的shmid删除)
当然你也可以使用函数shmctl
参数说明
<code>shmid:共享内存标识符,即要控制的共享内存段的标识符,通常由
shmget
函数返回。cmd
:控制命令,用于指定要执行的操作。常用的命令包括:
IPC_RMID
:删除共享内存段。IPC_SET
:设置共享内存段的权限和所有者。IPC_STAT
:获取共享内存段的状态信息。buf
:指向struct shmid_ds
结构的指针,用于存储共享内存段的信息或设置其属性。可以为NULL
,表示不获取或设置共享内存段的信息。
返回值
如果函数执行成功,返回值为 0;如果出现错误,返回值为 -1,并设置
errno
来指示具体的错误原因。
key vs shmid
key:属于用户形成,内核使用的一个字段,用户不能使用key来进行shm的管理,是给内核用来区分shm唯一性的(用户给操作系统用的)
shmid:内核给用户返回的一个标识符,让用户对shm进行管理的id值(操作系统给用户的)
使用共享内存标识符而不是键进行后续操作也增加了安全性。因为即使其他进程知道了你的共享内存段的键,它们也无法直接访问或修改你的共享内存段,除非它们也通过
shmget
获取了对应的共享内存标识符,并且有足够的权限来操作这个共享内存段。
使用共享内存时要挂接,这时就要用到一个函数shmat。
hmat函数在Linux系统编程中用于将进程挂接到共享内存上,从而实现不同进程间的通信。以下是关于shmat函数的详细解释:
1. 函数原型
2. 参数说明
<code>shmid:由shmget函数返回的共享内存标识符。
shmaddr
:指定共享内存连接到当前进程时的地址。有三种情况:
如果shmaddr是NULL,系统将自动选择一个合适的地址。如果shmaddr不是NULL并且没有指定SHM_RND,则此段连接到addr所指定的地址上。如果shmaddr非0并且指定了SHM_RND,则此段连接到shmaddr - (shmaddr mod SHMLAB)所表示的地址上,其中SHMLAB是低边界地址的倍数,它总是2的乘方。
shmflg
:是一组按位OR(或)在一起的标志,用来控制读写权限等。其两个可能取值是SHM_RND和SHM_RDONLY。如果指定了SHM_RDONLY,则以只读方式连接此段,否则以读写的方式连接此段。
3. 返回值
如果调用成功,返回一个指向共享内存的指针。如果出错,返回-1。
4. 原理
shmat函数从进程空间中选择一个合适的或者用户指定的线性地址,并将其挂接到共享内存的物理页上。一旦挂接完成,用户就可以通过返回的指针来访问共享内存中的数据。
5. 注意事项
在使用shmdt函数断开线性地址到物理地址的映射关系后,不能再次使用shmat函数进行挂接,否则可能会导致segment fault。共享内存是IPC(进程间通信)中效率最高的方式之一,因为它允许进程直接访问内存中的数据,而不需要像管道、消息队列那样进行内核与用户空间的数据拷贝。
shmdt
的基本使用:用于从共享内存段中分离一个进程
shmaddr
:这是指向共享内存段的指针,该指针是之前通过shmat
系统调用返回的。
返回值:
如果成功,返回 0。如果失败,返回 -1 并设置
errno
以指示错误。
使用
shmdt
时,要注意以下几点:
只影响当前进程:调用
shmdt
只影响当前进程。其他已经附加到该共享内存段的进程仍然可以访问它。数据持久性:即使调用了shmdt
,共享内存段中的数据仍然保持不变,直到所有进程都与其分离,或者调用者(通常是创建该段的进程)显式地删除了它(使用shmctl
系统调用和IPC_RMID
命令)。多次附加和分离:一个进程可以多次使用shmat
附加到同一个共享内存段,并可以多次使用shmdt
分离。但每次附加都需要一个单独的分离操作。关闭文件描述符:如果在附加共享内存时创建了一个文件描述符(例如,使用O_CREAT | O_RDWR
标志调用shmat
),则在调用shmdt
后,应使用close
系统调用来关闭该文件描述符。但请注意,这不会影响其他进程对共享内存段的访问。错误处理:如果shmdt
调用失败,应检查errno
以了解失败的原因。可能的错误包括EINVAL
(无效的shmaddr
参数)和EACCES
(调用进程没有足够的权限来分离该共享内存段)。
3.使用一下shmget(一段代码)
创建方法:
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
//用当前路径形成key值
const std::string gpathname = "/home/ubuntu/work/shm";
//随意设置一个
const int gproj_id = 0x66;
//将key转为16进制
std::string ToHex(key_t k)
{
char buffer[128];
snprintf(buffer,zizeof(buffer),"0x%x",k);
}
//形成相同的key值
key_t GetCommKey(const std::string &gpathname,int gproj_id)
{
key_t k = ftok(_pathname.c_str(), _proj_id);
if (k < 0)
{
perror("ftok");
}
return k;
}
//创建shm
int ShmGet(key_t key,int size)
{
int shmid =shmget(key,size,IPC_CREAT | IPC_EXCL);
if(shmid<0)
{
perror("shmget");
}
return shmid;
}
服务端创建:
#include "Shm.hpp"
int main()
{
key_t key=GetCommKey(gpathname,gproj_id);
std::cout<<"key:"<<ToHex(key)<<std::endl;
int shmid=ShmGet(key,4096);
std::cout<<"shmid:"<<shmid<<std::endl;
return 0;
}
客户端:
#include "Shm.hpp"
int main()
{
key_t key=GetCommKey(gpathname,gproj_id);
std::cout<<"key:"<<ToHex(key)<<std::endl;
return 0;
}
运行结果:有相同的key,
4.一个案例(一段代码)
1.简单封装一下
<code>#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
const int gCreater = 1;
const int gUser = 2;
//用当前路径形成key值
const std::string gpathname = "/home/ubuntu/work/shm";
//随意设置一个
const int gproj_id = 0x66;
//将key转为16进制
const int gShmSize = 4097;
class Shm
{
private:
key_t GetCommKey()
{
key_t k = ftok(_pathname.c_str(), _proj_id);
if (k < 0)
{
perror("ftok");
}
return k;
}
int GetShmHelper(key_t key, int size, int flag)
{
int shmid = shmget(key, size, flag);
if (shmid < 0)
{
perror("shmget");
}
return shmid;
}
public:
Shm(const std::string &pathname, int proj_id, int who)
: _pathname(pathname), _proj_id(proj_id), _who(who)
{
_key = GetCommKey();
if (_who == gCreater)
GetShmUseCreate();
else if (_who == gUser)
GetShmForUse();
std::cout << "shmid: " << _shmid << std::endl;
std::cout << "_key: " << ToHex(_key) << std::endl;
}
~Shm()
{
if (_who == gCreater)
{
int res = shmctl(_shmid, IPC_RMID, nullptr);
}
std::cout << "shm remove done..." << std::endl;
}
std::string ToHex(key_t key)
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%x", key);
return buffer;
}
bool GetShmUseCreate()
{
if (_who == gCreater)
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL);
if (_shmid >= 0)
return true;
std::cout << "shm create done..." << std::endl;
}
return false;
}
bool GetShmForUse()
{
if (_who == gUser)
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT);
if (_shmid >= 0)
return true;
std::cout << "shm get done..." << std::endl;
}
return false;
}
private:
private:
key_t _key;
int _shmid;
std::string _pathname;
int _proj_id;
//我是谁
int _who;
};
服务段:
#include "Shm.hpp"
int main()
{
Shm shm(gpathname,gproj_id,gCreater);
return 0;
}
用户段:
#include "Shm.hpp"
int main()
{
Shm shm(gpathname,gproj_id,gUser);
return 0;
}
运行结果:
2.使用共享内存
2.1挂接(shmat)和取消挂接(shmdt)
现阶段,我们只是让进程A和B看到了共享内存,使用共享内存的前提是要,将共享内存挂接(使用函数shmat)进程的地址空间上。进程可以将之前创建的共享内存区域映射到自己的虚拟地址空间中。这样,进程就可以通过指针直接访问这块共享内存,而无需通过内核进行数据的拷贝。在使用这个函数的时候,是要访问共享内存的,共享内存也是文件,当然也有权限,因此我们创建共享内存的时候是要带权限的。
<code>_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
当多个进程都映射了同一块共享内存到各自的地址空间后 ,进程A可以将数据写入共享内存,然后进程B可以从共享内存中读取这些数据,从而实现进程间的数据交换。
当你不再需要这块共享内存,或者你的进程即将结束时,你应该使用
shmdt
来从这块内存中分离。这样做可以确保系统能够正确地管理这块内存,防止资源泄露。
void *AttachShm()
{
if (_addrshm != nullptr)
DetachShm(_addrshm);
void *shmaddr = shmat(_shmid, nullptr, 0);
if (shmaddr == nullptr)
{
perror("shmat");
}
std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;
return shmaddr;
}
void DetachShm(void *shmaddr)
{
if (shmaddr == nullptr)
return;
shmdt(shmaddr);
std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;
}
2.2重新封装
这里我们新增了一个成员变量,addshm,用于记录共享内存的地址。
在向共享内存中写东西之前,我们先做清空,这里我们多加一个操作函数。
void Zero()
{
if(_addrshm)
{
memset(_addrshm, 0, gShmSize);
}
}
重新封装后:
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
const int gCreater = 1;
const int gUser = 2;
const std::string gpathname = "/home/whb/code/111/code/lesson22/4.shm";
const int gproj_id = 0x66;
const int gShmSize = 4097; // 4096*n
class Shm
{
private:
key_t GetCommKey()
{
key_t k = ftok(_pathname.c_str(), _proj_id);
if (k < 0)
{
perror("ftok");
}
return k;
}
int GetShmHelper(key_t key, int size, int flag)
{
int shmid = shmget(key, size, flag);
if (shmid < 0)
{
perror("shmget");
}
return shmid;
}
std::string RoleToString(int who)
{
if (who == gCreater)
return "Creater";
else if (who == gUser)
return "gUser";
else
return "None";
}
void *AttachShm()
{
if (_addrshm != nullptr)
DetachShm(_addrshm);
void *shmaddr = shmat(_shmid, nullptr, 0);
if (shmaddr == nullptr)
{
perror("shmat");
}
std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;
return shmaddr;
}
void DetachShm(void *shmaddr)
{
if (shmaddr == nullptr)
return;
shmdt(shmaddr);
std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;
}
public:
Shm(const std::string &pathname, int proj_id, int who)
: _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr)
{
_key = GetCommKey();
if (_who == gCreater)
GetShmUseCreate();
else if (_who == gUser)
GetShmForUse();
_addrshm = AttachShm();
std::cout << "shmid: " << _shmid << std::endl;
std::cout << "_key: " << ToHex(_key) << std::endl;
}
~Shm()
{
if (_who == gCreater)
{
int res = shmctl(_shmid, IPC_RMID, nullptr);
}
std::cout << "shm remove done..." << std::endl;
}
std::string ToHex(key_t key)
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%x", key);
return buffer;
}
bool GetShmUseCreate()
{
if (_who == gCreater)
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
if (_shmid >= 0)
return true;
std::cout << "shm create done..." << std::endl;
}
return false;
}
bool GetShmForUse()
{
if (_who == gUser)
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);
if (_shmid >= 0)
return true;
std::cout << "shm get done..." << std::endl;
}
return false;
}
void Zero()
{
if(_addrshm)
{
memset(_addrshm, 0, gShmSize);
}
}
void *Addr()
{
return _addrshm;
}
private:
key_t _key;
int _shmid;
std::string _pathname;
int _proj_id;
int _who;
void *_addrshm;
};
2.3使用共享内存进行通信
server作为读端
int main()
{
//读端
Shm shm(gpathname,gproj_id,gCreater);
char *shmaddr =(char*)shm.Addr();
//读内容
while(true)
{
std::cout<<"shm memory content:"<<shmaddr<<std::endl;
sleep(1);
}
return 0;
}
client作为写端
int main()
{
//写端
Shm shm(gpathname,gproj_id,gUser);
shm.Zero();
char *shmaddr = (char*)shm.Addr();
//写内容
char ch='A';code>
while(ch<='Z')
{
shmaddr[ch-'A'] =ch;
ch++;
sleep(2);
}
return 0;
}
运行结果:
5.共享内存的优劣
以下是对其优缺点的详细分析:
优点:
高效性:
共享内存是IPC通信中传输速度最快的通信方式。因为数据不需要在客户机和服务器之间复制,数据直接写到内存,避免了多次数据拷贝,从而大大提高了通信效率。进程对共享内存的访问就如同访问自己的内存空间一样,不需要进行额外的系统调用或内核操作,进一步提升了效率。灵活性:
允许多个进程共享数据,提供了一种灵活的通信方式。进程间可以通过共享的内存区域进行双向通信,满足了多种通信需求。支持大量数据传输:
适用于需要快速传递大量数据的场景,特别是在大数据处理、实时通信等领域表现突出。
缺点:
同步问题:
由于多个进程可以同时访问共享内存,需要额外的同步机制来避免数据竞争和一致性问题。内核并不提供任何对共享内存访问的同步机制,因此通常需要使用信号量等其他IPC机制进行读写同步与互斥。(写端没写,读端不会阻塞等待,依旧进行读取)
安全性:
需要额外的安全机制来保护数据,防止其他进程非法访问。若未采取适当的安全措施,可能导致数据泄露或被篡改。编程复杂性:
使用共享内存进行通信需要处理同步和数据一致性等复杂问题,编程复杂度较高。需要开发者具备深厚的操作系统和并发编程知识。依赖操作系统支持:
共享内存的使用依赖于操作系统的支持。不同的操作系统或版本可能对共享内存的实现和管理方式存在差异,这增加了跨平台开发的难度。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。