进程间通信(9) - 共享内存(System V)

1.前言

本篇文章的所有例子,基于RHEL6.5平台(linux kernal: 2.6.32-431.el6.i686)。

2.介绍

共享内存也是一种IPC,它是目前最快的IPC,它的使用方式是将同一个内存区映射到共享它的不同进程的地址空间中,这样这些进程间的通信就不再需要通过内核,只需对该共享的内存区域进程操作就可以了。

共享内存与其他的进程间通信最大的优点是:数据的复制只有两次,一次是从输入文件到共享内存区,一次从共享内存区到输出文件。而其他的则是需要复制4次:服务器将输入文件读入自己的进程空间,再从自己的进程空间写入管道/消息队列等;客户进程从管道/消息队列中读出数据到自己的进程空间,最后输出到客户指定的文件中。

要使用共享内存,应该有如下步骤:

1.开辟一块共享内存         shmget()

2.允许本进程使用共某块共享内存 shmat()

3.写入/读出

4.禁止本进程使用这块共享内存  shmdt()

5.删除这块共享内存         shmctl()或者使用命令行ipcrm

前一篇的文章中已经介绍过了Posix共享内存(点此链接),System V共享内存区在概念上与Poisx共享内存区类似,Posix共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而System V共享内存区则是调用shmget创建共享内存区然后调用shmat进行内存区的映射。

对每个System V共享内存区,内核会维护一个shmid_ds的数据结构,Linux 2.6.32中的定义如下,参考/usr/include/sys/shm.h。

<sys/shm.h>
typedef unsigned long int shmatt_t;   //连接共享内存区的进程数量的数据类型

struct shmid_ds
{
    struct ipc_perm shm_perm;       //operation permission struct
    size_t shm_segsz;                   //共享存储段的最大字节数

    __time_t shm_atime;                //time of last shmat()
    __time_t shm_dtime;                //time of last shmdt()
    __time_t shm_ctime;                //time of last change by shmctl()

    __pid_t shm_cpid;                   //pid of creator
    __pid_t shm_lpid;                    //pid of last shmop
    shmatt_t shm_nattch;              //连接共享内存区的进程数

    //保留字段
    unsigned long int __unused1;
    unsigned long int __unused2;
    unsigned long int __unused3;
    unsigned long int __unused4;
    unsigned long int __unused5;
};

3.创建共享内存shmget

#include <sys/shm.h>
int  shmget( key_t key, size_t size , int shmflg); //成功返回共享内存标识符,失败返回-1

shmget函数用于创建或打开一个共享内存区对象,shmget成功调用会返回一个共享内存区的标识符,供其它的共享内存区操作函数使用。

key:用于创建共享内存区的键值,这个在前面其他System IPC创建的时候已经讨论过了,System IPC都有一个key,作为IPC的外部标识符,创建成功后返回的描述符作为IPC的内部标识符使用。key的主要目的就是使不同进程在同一IPC汇合。

它是这块共享内存的标识符。如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来代替。

key具体说可以有三种方式生成:

· 不同的进程约定好的一个值;

· 通过相同的路径名和项目ID,调用ftok()函数,生成一个键;

· 还可以设置为IPC_PRIVATE,这样就会创建一个新的,唯一的IPC对象;然后将返回的描述符通过某种方式传递给其他进程;

size:指定创建共享内存区的大小,单位是字节。如果实际操作为创建一个共享内存区时,必须指定一个非0值,如果实际操作是访问一个已存在的共享内存区,那么size应为0。因为linux的内存分配操作都是以页为单位的。所以如果一段进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE=4096字节)这样,新创建的共享内存的大小实际上是从size这个参数调整而来的页面大小。即如果size为1至4096,则实际申请到的共享内存大小为4K(一页);4097到8192,则实际申请到的共享内存大小为8K(两页),依此类推。

shmflg:指定创建或打开消息队列的标志和读写权限(ipc_perm中的mode成员)。我们知道System V IPC定义了自己的操作标志和权限设置标志,而且都是通过该参数传递,这和open函数存在差别,open函数第三个参数mode用于传递文件的权限标志。System V IPC的操作标志包含:IPC_CREAT,IPC_EXCL。

--IPC_CREAT 如果共享内存不存在,则创建一个共享内存,否则打开操作。

--IPC_EXCL 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。

如果单独使用IPC_CREAT,shmget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。

如果将IPC_CREAT和IPC_EXCL标志一起使用,shmget()将返回一个新建的共享内存的标识符;如果该共享内存已存在,或者返回-1。

IPC_EXCL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象。

读写权限如下图:

System V共享内存区的读写权限标志

System V共享内存区在创建后,该size大小的内存区会被初始化为0,这和POSIX共享内存不同,POSIX标准并没有规定新创建的POSIX共享内存区的初始内容。

测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
main()
{
	key_t lKey;
	int nShmId;
	if ((lKey = ftok("/etc/profile", 1)) < 0)
	{
		perror("ftok");
		exit(1);
	}
	if ((nShmId = shmget(lKey, 256, IPC_CREAT | 0666)) == -1)//创建
	{
		perror("shmget");
		exit(2);
	}
	printf("Shmid=%d\n", nShmId);
	return 0;
}

输出:

[[email protected] csdnblog]# ./a.out

Shmid=0

4.映射共享内存shmat

通过shmget创建或打开共享内存区对象后,并没有将该共享内存区映射到调用进程的地址空间中,所以无法访问该共享内存区,需要通过shmat函数,将该共享内存区连接到调用进程的地址空间中,才能进行访问,当该进程完成对该共享内存区的访问后,可以调用shmdt断接这个共享内存区,当然进程结束后会自动断接所有连接的共享内存区。下面是shmat和shmdt函数的接口以及说明:

//shmat用于将一个共享内存区连接到调用进程的地址空间中。
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg); //成功返回映射区的起始地址,失败返回-1

shmid:打开的System V共享内存对象的标识符,即那块共享内存的ID。

shmaddr是共享内存的起始地址。shmaddr和shmflg参数共同决定了共享内存区连接到调用进程的具体地址,规则如下:

· shmaddr为空指针:连接的地址由系统内核决定,这是推荐的方法,具有可移植性。

· shmaddr非空:此时还要根据shmflg参数是否指定SHM_RND标志进行判断:

--没有指定SHM_RND:共享内存区连接到调用进程的shmaddr指定的地址;

--指定SHM_RND:共享内存区连接到shmaddr指定地址向下舍入SHMLBA的位置。

shmflg:除了上面说的SHM_RND外,还有可以指定SHM_RDONLY标志,限定只读访问。如果是SHM_RDONLY的话,就是只读模式。其它的是读写模式。一般该标志置为0。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
typedef struct
{
    int n;
    char str[256];
} ShmStru;
main()
{
    key_t lKey;
    int nShmId;
    ShmStru *pstru;
    if((lKey = ftok("/etc/profile",2)) < 0)
    {
        perror("ftok");
        exit(1);
    }
    if((nShmId = shmget(lKey,sizeof(ShmStru),IPC_CREAT|0666)) == -1)//创建共享内存
    {
        perror("shmget");
        exit(2);
    }
    if((pstru = shmat(nShmId,NULL,0)) == (void *)-1)//映射共享内存到本地
    {
        perror("shmat");
        exit(3);
    }
    pstru->n = 1;//修改共享内存
    strcpy(pstru->str,"123456");//向共享内存写入数据
    if( shmdt(pstru) == -1)//解除共享内存映射
    {
        perror("shmdt");
        exit(4);
    }
    return 0;
}

5.删除共享内存映射shmdt

shmdt用于将一个共享内存区从该进程内断接,当一个进程终止时,它连接的所有共享内存区会自动断接。

int shmdt(const void *shmaddr);  //成功返回0,失败返回-1

shmaddr是那块共享内存的起始地址。

6.控制共享内存映射shmctrl

shmctl函数可以对共享内存区进行多种控制操作,下面是shmctl函数的接口以及说明:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);  //成功返回0,失败返回-1

shmid:是共享内存的ID,也即共享内存区的标识符。

cmd:对共享内存区控制操作的命令,Open Group 的SUS定义了一下三个操作命令:

· IPC_SET:改变共享内存的状态。按第三个参数buf所指定的内容,设置共享内存区的操作权限字段的:shm_perm.uid,shm_perm.gid和shm_perm.mode。此命令只能由以下进程执行:有效用户ID等于shm_perm.uid或shm_perm.cuid,以及有超级用户权限的进程。

· IPC_STAT:得到共享内存的状态。获取共享内存区的shmid_ds结构,存放到传入的第三个参数中。

· IPC_RMID:删除共享内存。从系统内核中删除该共享内存区。因为每个共享内存区有一个连接数据的计数,除非连接该共享内存区的最后一个进程断接或终止,否
则该共享内存区不会被实际删除。这和其他的System V IPC,例如System V消息 队列的删除差别很大,倒是和POSIX IPC的xxx_unlink删除操作很相识。调用该命令后,该共享内存区标识符不能再继续被连接。此命令也只能由以下进程执行:有效用户ID等于shm_perm.uid或shm_perm.cuid,以及有超级用户权限的进程。

buf:是一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的状态,用这个结构体指定。

使用共享内存时,结束程序退出后,如果你没在程序中用shmctl()删除共享内存的话,一定要在命令行下用ipcrm命令删除这块共享内存。你要是不管的话,它就一直在那儿放着了。

下面是创建System V共享内存区和查看其属性的测试代码:

#include <iostream>
#include <cstring>
#include <errno.h>  

#include <unistd.h>
#include <fcntl.h>
#include <sys/shm.h>  

#define PATH_NAME "/tmp/shm"  

int main()
{
        int fd;

        if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
        {
                std::cout << "open file " << PATH_NAME << "failed.";
                std::cout << strerror(errno) << std::endl;
                return -1;
        }

        close(fd);

        key_t key = ftok(PATH_NAME, 0);

        int shmID;

        if ((shmID = shmget(key, sizeof(int), IPC_CREAT | 0666)) < 0)
        {
                std::cout << "shmget failed..." << strerror(errno) << std::endl;
                return -1;
        }

        shmid_ds shmInfo;
        shmctl(shmID, IPC_STAT, &shmInfo);

        std::cout << "shm key:0x" << std::hex << key << std::dec << key << std::endl;
        std::cout << "shm id:" << shmID << std::endl;
        std::cout << "shm_segsz:" << shmInfo.shm_segsz << std::endl;
        std::cout << "shm_nattch:" << shmInfo.shm_nattch << std::endl;

        return 0;
}

执行结果如下:

[[email protected] csdnblog]# ./a.out

shm key:0x32aa12970

shm id:65538

shm_segsz:4    //共享内存区的大小

shm_nattch:0    //共享内存区的连接数目

通过ipcs命令查看该新创建的共享内存区对象:

[[email protected] csdnblog]# ipcs -m -i 65538

Shared memory Segment shmid=65538

uid=0   gid=0   cuid=0  cgid=0

mode=0666       access_perms=0666

bytes=4 lpid=0  cpid=13414      nattch=0

att_time=Not set

det_time=Not set

change_time=Mon Jun 22 00:21:34 2015

7.共享内存区的限制

和其中的System V IPC一样,System V共享内存也存在系统的限制,关于系统范围内对共享内存的限制,在Linux 2.6.18 <bits/shm.h>中定义了shminfo结构,该结构显示了系统内核的限制,如下:

#include <bits/shm.h>
struct  shminfo
{
    unsigned long int shmmax;   //一个共享内存区的最大字节数
    unsigned long int shmmin;   //一个共享内存区的最小字节数
    unsigned long int shmmni;   //系统范围内的共享内存区对象的最大个数
    unsigned long int shmseg;   //每个进程连接的最大共享内存区的数目
    unsigned long int shmall;   //系统范围内的共享内存区的最大页数
    unsigned long int __unused1;
    unsigned long int __unused2;
    unsigned long int __unused3;
    unsigned long int __unused4;
};

在Linux 下shmctl中可以指定IPC_INFO来获取上面结构所示的系统范围内的限制。在Linux下,具体的限制值可以通过sysctl来查看,如下:

[[email protected] csdnblog]# sysctl -a | grep shm

kernel.shmmax = 4294967295    //一个共享内存区的最大字节数

kernel.shmall = 268435456         //系统范围内的共享内存区的最大页数

kernel.shmmni = 4096                 //系统范围内的共享内存区对象的最大个数

..............

一般情况下不需要对System V共享内存区的系统限制进程修改,因为基本可以满足应用需求,如果要在系统范围内对内核限制进行修改,在Linux下面可以通过修改/etc/sysctl.conf 内核参数配置文件,然后配合sysctl命令来对内核参数进行设置。例如下面示例:

[[email protected] csdnblog]#echo "kernel.shmmni= 1000" >>/etc/sysctl.conf

[[email protected] csdnblog]#sysctl -p

[[email protected] csdnblog]#sysctl -a |grep shm

kernel.shmmni = 1000

kernel.shmall = 268435456

kernel.shmmax = 4294967295

8.共享内存区的使用

下面是System V共享内存区的使用示例,进程1通过共享内存区向进程2发送一条消息,对共享内存区的同步采用System V信号量来完成。

测试代码如下:

//进程1
#include <iostream>
#include <cstring>
#include <errno.h>  

#include <unistd.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/sem.h>  

using namespace std;

#define PATH_NAME "/tmp/shm"  

union semun
{
	int val;
	struct semid_ds *buf;
	unsigned short int *array;
	struct seminfo *__buf;
};

int main()
{
	int fd;

	if ((fd = open(PATH_NAME, O_RDONLY | O_CREAT, 0666)) < 0)
	{
		cout << "open file " << PATH_NAME << "failed.";
		cout << strerror(errno) << endl;
		return -1;
	}

	close(fd);

	key_t keyShm = ftok(PATH_NAME, 0);
	key_t keySem = ftok(PATH_NAME, 1);

	int shmID, semID;

	if ((shmID = shmget(keyShm, sizeof(int), IPC_CREAT | 0666)) < 0)
	{
		cout << "shmget failed." << strerror(errno) << endl;
		return -1;
	}

	int *buf = (int *)shmat(shmID, 0, 0);

	if ((semID = semget(keySem, 1, IPC_CREAT | 0666)) < 0)
	{
		cout << "semget failed." << strerror(errno) << endl;
		return -1;
	}

	semun arg;
	arg.val = 0;   //初始化信号量资源的数目为0

	if (semctl(semID, 0, SETVAL, arg) < 0)
	{
		cout << "semctl error " << strerror(errno) << endl;
		return -1;
	}

	struct sembuf buffer;
	buffer.sem_num = 0;
	buffer.sem_op = 1;
	buffer.sem_flg = 0;

	*buf = 12345678;
	cout << "process 1:send " << *buf << endl;

        //将信号量资源加1,以表示共享内存区内已有资源
        semop(semID, &buffer, 1);

	return 0;
}

//进程2
#include <iostream>
#include <cstring>
#include <errno.h>  

#include <unistd.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/sem.h>  

using namespace std;

#define PATH_NAME "/tmp/shm"  

union semun
{
	int val;
	struct semid_ds *buf;
	unsigned short int *array;
	struct seminfo *__buf;
};

int main()
{
	int fd;

	if ((fd = open(PATH_NAME, O_RDONLY)) < 0)
	{
		cout << "open file " << PATH_NAME << "failed.";
		cout << strerror(errno) << endl;
		return -1;
	}

	close(fd);

	key_t keyShm = ftok(PATH_NAME, 0);
	key_t keySem = ftok(PATH_NAME, 1);

	int shmID, semID;

	if ((shmID = shmget(keyShm, sizeof(int), 0)) < 0)
	{
		cout << "shmget failed..." << strerror(errno) << endl;
		return -1;
	}

	int *buf = (int *)shmat(shmID, 0, 0);

	if ((semID = semget(keySem, 1, 0)) < 0)
	{
		cout << "semget failed..." << strerror(errno) << endl;
		return -1;
	}

	struct sembuf buffer;
	buffer.sem_num = 0;
	buffer.sem_op = -1;
	buffer.sem_flg = 0;

        //获得信号量资源
	semop(semID, &buffer, 1);

	cout << "process 2:recv " << *buf << endl;

	return 0;
}

测试结果为:

[[email protected] csdnblog]# ./test1

process 1:send 12345678

[[email protected] csdnblog]# ./test2

process 2:recv 12345678

时间: 2024-09-29 08:42:44

进程间通信(9) - 共享内存(System V)的相关文章

进程间通信_05共享内存_System V实现

一 概念 内核分配的一块存储去,多个进程可以将物理内存映射到进程的虚拟地址空间,从而实现对内存的直接操作,是效率最高的IPC. 消息队列和管道都有在用户地址空间和内核空间相互复制产生的开销. 二 操作函数 1 创建共享内存 #include <sys/ipc.h> #include <sys/shm.h> int shmget( key_t key, //代表共享内存唯一性的key值 size_t size, //共享内存的大小 int shmflg); //共享内存的属性设置 /

Linux环境进程间通信:共享内存

共享内存简介 共享内存允许两个或多个进程共享一给定的存储区.因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC.共享内存的方式有两种:mmap()系统调用和系统V共享内存. mmap()系统调用 mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存.普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作. 注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的.它本身提供了不同于一般对普

进程间通信(8) - 共享内存(posix)

1.前言 本篇文章的所有例子,基于RHEL6.5平台(linux kernal: 2.6.32-431.el6.i686). 2.共享内存介绍 前面所讲述的Linux下面的各种进程间通信方式,例如:pipe(管道),FIFO(命名管道),message queue(消息队列),它们的共同点都是通过内核来进行通信(假设posix消息队列也是在内核中实现的,因为posix标准没有规定它的具体实现方式).向pipe,fifo,message queue写入数据时,需要把数据从用户空间(用户进程)复制到

Linux进程IPC浅析[进程间通信SystemV共享内存]

Linux进程IPC浅析[进程间通信SystemV共享内存] 共享内存概念,概述 共享内存的相关函数 共享内存概念,概述: 共享内存区域是被多个进程共享的一部分物理内存 多个进程都可把该共享内存映射到自己的虚拟内存空间,所有用户空间的进程若要操作共享内存,都要将其映射到自己的虚拟内存空间中,通过映射的虚拟内存空间地址去操作共享内存,从而达到进程间的数据通信 共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容 本身不提供同

Linux进程间通信—使用共享内存

Linux进程间通信-使用共享内存 转自: https://blog.csdn.net/ljianhui/article/details/10253345 下面将讲解进程间通信的另一种方式,使用共享内存. 一.什么是共享内存 顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式.不同进程之间共享的内存通常安排为同一段物理内存.进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像

从并发处理谈PHP进程间通信(二)System V IPC

.container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .container::before,.container::after { content: " "; display: table } .container::after { clear: both } .container::before,.container::after { content:

Linux进程间通信--mmap共享内存(一)

共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B共享内存的意思是,同一块物理内存被映射到进程A.B各自的进程地址空间.进程A可以即时看到进程B对共享内存中数据的更新,反之亦然.由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以. 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝.对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入

Linux进程间通信--shmget()共享内存(二)

共享内存区域是被多个进程共享的一部分物理内存.如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信.共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容.这块共享虚拟内存的页面,出现在每一个共享该页面的进程的页表中.但是它不需要在所有进程的虚拟内存中都有相同的虚拟地址. 图 共享内存映射图 象所有的 System V IPC对象一样,对于共享内存对象的获取

进程间通信(共享内存),五种通信方式简单总结

共享内存:它是system V版本中最高效的一种通信机制,可以使多个进程共享同一地址空间,若有一个进程修改该地址空间,则其它共享该地址空间的进程可以同时看到,但它不提供同步与互斥关系.一般结合信号量来达到进程间的同步于互斥. 创建共享内存:int shmget(key_t _key,size_t size,int shmflg);//size必须为4096的倍数,即页表大小的倍数. 挂接进程:void* shmat(int shmid,const void* shmaddr,int shmflg