【linux--进程通信之共享内存】

发布时间:2023年12月17日

一、共享内存的原理

共享内存实际是操作系统在实际物理内存中开辟的一段内存。
共享内存实现进程间通信,是操作系统在实际物理内存开辟一块空间,一个进程在自己的页表中,将该空间和进程地址空间上的共享区的一块地址空间形成映射关系。另外一进程在页表上,将同一块物理空间和该进程地址空间上的共享区的一块地址空间形成映射关系。
当一个进程往该空间写入内容时,另外一进程访问该空间,会得到写入的值,即实现了进程间的通信。
注意:共享内存实现进程间通信是进程间通信最快的。(相比较管道共享内存不需要两次拷贝)
在这里插入图片描述

二、共享内存的数据结构

在系统当中可能会有大量的进程在进行通信,因此系统当中就可能存在大量的共享内存,那么操作系统必然要对其进行管理,所以共享内存除了在内存当中真正开辟空间之外,为了维护管理共享内存,系统一定要"描述"共享内存

/* Obsolete, used only for backwards compatibility and libc5 compiles */
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 */
};

描述共享内存的数据结构里保存了一个ipc_perm结构体,这个结构体保存了IPC(进程将通信)的关键信息。

/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct ipc_perm
{
	__kernel_key_t	key;//共享内存的唯一标识符
	__kernel_uid_t	uid;
	__kernel_gid_t	gid;
	__kernel_uid_t	cuid;
	__kernel_gid_t	cgid;
	__kernel_mode_t	mode; //权限
	unsigned short	seq;
};

三、共享内存使用的函数

2.1ftok函数

在这里插入图片描述

  • 参数:第一个是地址,第二个是至少8位的项目id,不能为0,两参数可以是任意值,但是要符合格式。
  • 返回值:ftok如果成功返回一个key值,如果失败返回-1。如果失败了再重新填写参数即可。
  • 作用:算出一个唯一的key返回。

ftok中的参数可以随便填写,但是要符合格式,ftok只是利用参数,再运用一套算法,算出一个唯一的key值返回。这个key值可以传给共享内存参数,作为struct
ipc_perm中唯一标识共享内存的key。

2.2shmget函数

在这里插入图片描述

  • 参数:
    key:为共享内存的名字,一般是ftok的返回值。 size:共享内存的大小,以page为单位,大小为4096的整数倍。
    shmflg:权限标志,常用两个IPC_CREAT和IPC_EXCL,一般后面还加一个权限,相当于文件的权限。
    IPC_CREAT:创建一个共享内存返回,已存在打开返回
    IPC_EXCL:配合着IPC_CREAT使用,共享内存已存在出错返回。
    使用:IPC_CREAT | IPC_EXCL | 0666
  • 返回值:
    成功返回一个非负整数,即共享内存的标识码,失败返回-1。
  • 作用:创建一个共享内存
    为什么已经有一个key来标识共享内存,还需要一个返回值来标识共享内存?因为key是内核级别的,供内核标识,shmget返回值是用户级别的,供用户使用的。
    command.hpp
#include<iostream>
using namespace std;
#include<sys/types.h>     
#include<sys/ipc.h>  
#include<sys/shm.h>
#define PATHNAME "/home/zuofangting"
#define PROJ_ID 0x888
#define SIZE 4096
key_t GetKey()
{ 
    key_t ret=ftok(PATHNAME,PROJ_ID);
    if(ret==-1) return -1;
    cout<<"key:"<<ret<<endl;
    return ret;
}

int GetShmget()
{
    key_t key=GetKey();
    int shmid=shmget(key,SIZE,IPC_CREAT | IPC_EXCL | 0666);
    if(shmid==-1)
    {
        cout<<"GetShmget fail"<<endl;
        return -1;
    }
    cout<<"shmid:"<<shmid<<endl;
    return shmid;
}

processa.cc

#include"command.hpp"
int main()
{
    GetShmget();

    return 0;
}

现象:
在这里插入图片描述
注意:IPC(进程将通信)资源生命周期不随进程,而是随内核的,不释放会一直占用,除非重启。所以,shmget创建的共享内存要释放掉,不然会内存泄漏。如图:
在这里插入图片描述
可以用命令行来释放共享内存**:ipcrm -m shmid**(shmget返回值)
在这里插入图片描述

2.3shmctr函数

在这里插入图片描述

  • 作用:用于控制共享内存
  • 参数:
    shmid:共享内存的标识
    cmd:以什么方式来控制共享内存。IPC_RMID是释放共享内存
    buf:指向一个共享内存的数据结构 。struct shmid_ds
  • 返回值:成功返回0,失败返回-1。
    使用样例代码:
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/ipc.h>
  4 #include<sys/shm.h>
  5 #include"com.h"
  6 
  7 int main(){
  8   key_t k = ftok(PATHNAME,PROJ_ID);//获取一个唯一标识符key
  9   if(k==-1){
 10     perror("ftok error");
 11     return 1;
 12   }
 13 
 14   printf("ftok : %d\n",k);
 15 
 16   int shmid = shmget(k,SIZE,IPC_CREAT | IPC_EXCL | 0666);//创建共享内存
 17   if(shmid == -1){
 18     perror("shmget error");
 19     return 1;
 20   }
 21   printf("shmid : %d\n",shmid);
 22 
 23   int sh = shmctl(shmid,IPC_RMID,NULL);//删除共享内存
 24   if(sh == -1){                                                                                                                                          
 25     perror("shmctl");
 26     return 1;
 27   }
 28   return 0;
 29 }

2.4shmat函数

在这里插入图片描述

  • 作用:使创建的共享内存与调用该函数进程的进程地址空间参数关联。
  • 参数:
    shmid:共享内存的标识,shmget的返回值。
    shmaddr:指定进程地址空间连接的地址。如果设置为null,默认让系统定要关联的地址。
    shmflg:权限,常见有两个SHM_RDONLY(只读)和SHM_REMAP(重新映射一个进程地址空间没这样shmaddr不能为空)。设为0,系统默认。
  • 返回值:返回映射到进程地址空间共享区的开始地址。

2.5shmdt函数

在这里插入图片描述
作用:删除共享内存与进程地址空间的映射关系,将页表映射关系删除,释放进程地址空间。

  • 参数:

     shmaddr:共享内存映射到进程地址空间的地址。shmat返回值。
    
  • 返回值:

     成功返回0,失败返回-1
    

四、实现进程通信

makefile

  .PHONY:all
all:processa processb
processa:processa.cc
	g++ -o $@ $^ -std=c++11
processb:processb.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f processa processb

command.hpp

#include<iostream>
using namespace std;
#include<string.h>
#include<sys/types.h>     
#include<sys/ipc.h>  
#include<sys/shm.h>
#include<unistd.h>
#define PATHNAME "/home/zuofangting"
#define PROJ_ID 0x888
#define SIZE 4096

key_t GetKey()
{ 
    key_t ret=ftok(PATHNAME,PROJ_ID);
    if(ret==-1) return -1;
    cout<<"key:"<<ret<<endl;
    return ret;
}

int GetShmget(int flag)
{
    key_t key=GetKey();
    int shmid=shmget(key,SIZE,flag);
    if(shmid==-1)
    {
        cout<<"GetShmget fail"<<endl;
        return -1;
    }
    cout<<"shmid:"<<shmid<<endl;
    return shmid;
}

int CreateShm()
{
    return GetShmget(IPC_CREAT | IPC_EXCL | 0666);
}

int GetShm()
{
    return GetShmget(IPC_CREAT);
}

processa.cc

#include"command.hpp"
int main()
{
    //创建共享内存
    int shmid=CreateShm();

    //挂接
    char *shmaddr=(char*)shmat(shmid,nullptr,0);

    //通信
    while(true)
    {
        cout<<"client@:"<<shmaddr<<endl;
    }

    //去挂接
    int ret=shmdt(shmaddr);

    //释放共享内存
    shmctl(shmid,IPC_RMID,nullptr);

    return 0;
}

processb.cc

#include"command.hpp"
int main()
{
    //创建共享内存
    int shmid=GetShm();

    //挂接
    char *shmaddr=(char*)shmat(shmid,nullptr,0);

    //通信
    while(true)
    {
        char buffer[1024];
        cout<<"please enter";
        fgets(buffer,sizeof(buffer),stdin);
        memcpy(shmaddr,buffer,strlen(buffer)+1);

    }

    //去挂接
    int ret=shmdt(shmaddr);

    return 0;
}

共享内存实现的进程间通信底层不提供任何同步与互斥机制。如果想让两进程很好的合作起来,在IPC里要有信号量来支撑。

文章来源:https://blog.csdn.net/qq_69544758/article/details/135046752
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。