Linux进程间通信----共享内存

By AverageJoeWang
 标签:

一.简介

system V IPC机制下的共享内存本质是一段特殊的内存区域,进程间需要共享的数据被放在该共享内存区域中,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。这样一个使用共享内存的进程可以将信息写入该空间,而另一个使用共享内存的进程又可以通过简单的内存读操作获取刚才写入的信息,使得两个不同进程之间进行了一次信息交换,从而实现进程间的通信。

共享内存允许一个或多个进程通过同时出现在它们的虚拟地址空间的内存进行通信,而这块虚拟内存的页面被每个共享进程的页表条目所引用,同时并不需要在所有进程的虚拟内存都有相同的地址。

进程对象对于共享内存的访问通过key(键)来控制,同时通过key进行访问权限的检查。

二.共享内存的使用

在Linux中提供了一组函数接口用于使用共享内存,它们声明在头文件 sys/shm.h中。

查看共享内存

ipcs #查看共享内存
ipcrm -m shmid #删除共享内存

相应的函数为

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
key_t ftok(const char *pathname, int proj_id);
int shmget(key_t key, int size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

2.1.ftok函数

key_t ftok(const char *pathname, int proj_id);

函数ftok用于创建一个关键字,可以用该关键字关联一个共享内存段。

  • 参数pathname为一个全路径文件名,并且该文件必须可访问。
  • 参数proj_id通常传入一非0字符
  • 通过pathname和proj_id组合可以创建唯一的key
  • 如果调用成功,返回一关键字,否则返回-1

2.2.shmget函数

int shmget(key_t key, int size, int shmflg);

函数shmget用于创建或打开一共享内存段,该内存段由函数的第一个参数唯一创建。函数成功,则返回一个唯一的共享内存标识号(相当于进程号,唯一的标识着共享内存),失败返回-1。

  • 参数key是一个与共享内存段相关联关键字如果事先已经存在一个与指定关键字关联的共享内存段,则直接返回该内存段的标识,表示打开,如果不存在,则创建一个新的共享内存段。key的值既可以用ftok函数产生,也可以是IPC_PRIVATE(用于创建一个只属于创建进程的共享内存,主要用于父子通信),表示总是创建新的共享内存段;
  • 参数size指定共享内存段的大小,以字节为单位;
  • 参数shmflg是一掩码合成值,可以是访问权限值与(IPC_CREAT或IPC_EXCL)的合成。IPC_CREAT表示如果不存在该内存段,则创建它。IPC_EXCL表示如果该内存段存在,则函数返回失败结果(-1)。如果调用成功,返回内存段标识,否则返回-1

2.3.shmat函数

void *shmat(int shmid, const void *shmaddr, int shmflg);
`

函数shmat将共享内存段映射到进程空间的某一地址。

  • 参数shmid是共享内存段的标识 通常应该是shmget的成功返回值
  • 参数shmaddr指定的是共享内存连接到当前进程中的地址位置。通常是NULL,表示让系统来选择共享内存出现的地址。
  • 参数shmflg是一组位标识,通常为0即可。
  • 如果调用成功,返回映射后的进程空间的首地址,否则返回(char *)-1。

2.4.shmdt函数

int shmdt(const void *shmaddr);

函数shmdt用于将共享内存段与进程空间分离。

  • 参数shmaddr通常为shmat的成功返回值。
  • 函数成功返回0,失败时返回-1.

注意,将共享内存分离并没删除它,只是使得该共享内存对当前进程不在可用。

2.5.shmctl函数

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

函数shmctl是共享内存的控制函数,可以用来删除共享内存段。

  • 参数shmid是共享内存段标识 通常应该是shmget的成功返回值
  • 参数cmd是对共享内存段的操作方式,可选为IPC_STAT,IPC_SET,IPC_RMID。通常为IPC_RMID,表示删除共享内存段。
  • 参数buf是表示共享内存段的信息结构体数据,通常为NULL。

例如shmctl(kshareMem,IPC_RMID,NULL)表示删除调共享内存段kHareMem

三.实例

3.1.具有亲属关系的调用

 ///
 /// @file    01_relation_share.cpp
 /// @author  AverageJoeWang(AverageJoeWang@gmail.com)
 ///

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PERM S_IRUSR|S_IWUSR
using std::cout;
using std::endl;
//两个有亲属关系进程进行通信
int main(int argc, char* argv[])
{
    int shmid = shmget(IPC_PRIVATE, 1024, PERM);
    if(-1 == shmid)
    {
        fprintf(stderr, "Create share memory error: %s\n\a", strerror(errno));
        exit(1);
    }
    if(fork > 0)//父进程
    {
        char *p_addr = (char*)shmat(shmid, NULL, 0);//获取该段共享内存
        memset(p_addr, '\0', 1024);
        strncpy(p_addr, "share memory", 1024);
        printf("parent %d write buffer: %s\n", getpid(), p_addr);
        sleep(2);
        wait(NULL);//等待子进程,防止僵尸进程
        shmctl(shmid, IPC_RMID, 0);//删除共享内存,用ipcs -m查看共享内存
        exit(0);
    }
    else//子进程
    {
        sleep(5);
        char *c_addr = (char*)shmat(shmid, NULL, 0);//取出内容
        printf("Client pid = %d, shmid = %d, read buffer: %s\n", getpid(), shmid, c_addr);
        exit(0);
    }
    return 0;
}

3.2.不同进程使用共享内存

  • 写入共享内存
 ///
 /// @file    02_write.cpp
 /// @author  AverageJoeWang(AverageJoeWang@gmail.com)
 ///

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
using std::cout;
using std::endl;

int main()
{
    key_t key = ftok("11", 1);//1.写入端使用ftok函数获得key
    if(-1 == key)
    {
        perror("ftok");
        exit(-1);
    }
    int shmid = shmget(key, 1024, IPC_CREAT|0777);//2.写入端创建一共享内存段
    if(-1 == shmid)
    {
        perror("shmget");
        exit(-1);
    }
    char *pMap = (char*)shmat(shmid, NULL, 0);//3.获得共享内存段的首地址
    if((char*)-1 == pMap)
    {
        perror("shmat");
        exit(-1);
    }
    pMap[0] = 'w';//4.往共享内存段中写内容
    if(-1 == shmdt(pMap)) //5.关闭共享内存段
    {
        perror("shmdt");
        exit(-1);
    }
    return 0;
}
  • 读共享内存内容
 ///
 /// @file    02_read.cpp
 /// @author  AverageJoeWang(AverageJoeWang@gmail.com)
 ///

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
using std::cout;
using std::endl;

int main()
{
    key_t key = ftok("11", 1);//1.读入端用ftok函数获得key
    if(-1 == key)
    {
        perror("ftok");
        exit(-1);
    }
    int shmid = shmget(key, 1024, 0777|IPC_CREAT);//2.读入端用shmget函数打开共享内存段
    if(-1 == shmid)
    {
        perror("shmget");
        exit(-1);
    }
    char *pMap = (char*)shmat(shmid, NULL, 0);//3.获取共享内存段的首地址
    printf("receive the data:%s\n", pMap);//4.读取共享内存段中的内容
    if(-1 == shmctl(shmid, IPC_RMID, 0))//5.删除共享内存段
    {
        perror("shmctl");
        exit(-1);
    }
    return 0;
}
  • 运行
touch 11
g++ 02_read.cpp -o read
g++ 02_write.cpp -o write
#一个终端
./write
#另一个终端
./read

以上为使用共享内存分别进行有亲属关系与无亲属关系进程间通信的方式。

四.总结

4.1.共享内存优点

使用共享内存进行进程间的通信十分非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。

4.2.共享内存缺点

共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。