IPC之共享内存

一、说明

内存共享:两个进程可以直接共享访问同一块内存区域,其最突出优点是效率高,是常用的IPC方式之一。

                                                           

                                 

两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。

共享内存段的一些基本概念:

1. 共享内存段在内存中不依赖于进程的存在而存在;

2. 共享内存段有自己的名字,称为关键字(key);

3. 关键字是一个整形数;

4.共享内存段有自己的拥有者以及权限位;

5.进程可以连接到某共享内存段,并且获得指向此段的指针。

效率分析: 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据: 一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,共享内存应用时并不(j经常)读写数据后就立马解除映射,然后有新的通信时再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止。这样,数据内容一直保存在共享内存中并没有写回文件。也就是说共享内存中的内容往往是在解除映射时才写回文件的。综合以上两点原因,我们说共享内存是效率最快的进程间通信方式。

特别提醒:

共享内存自身并未提供同步机制。也就是说在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以在实际中同步与互斥机制需要用户来完成。

 

实现共享内存的步骤如下:

(1) 创建内存共享区 

进程1通过操作系统提供的api从内存中申请一块共享区域,linux系统中可以通过shmget函数实现,生成的共享内存块与某个特定的key进行绑定。

(2) 映射共享内存到进程1中

在linux环境中,可以通过shmat实现。

(3)映射共享内存到进程2中

进程2通过进程1的shmget函数和同一个key值,然后执行shmat,将这个内存映射到进程2中。

(4)进程1与进程2中相互通信

共享内存实现两个映射后,可以利用该区域进行信息交换,由于没有同步机制,需要参与通信的进程自己协商处理。

(5)撤销内存映射关系

完成通信之后,需要撤销之前的映射操作,通过shmdt函数实现。

(6)删除共享内存区

在linux中通过shctl函数来实现。

二、相关函数详解。

1、shmget函数——得到共享内存段

该函数用来创建共享内存,它的原型为:

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

第一个参数:与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.

不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget函数的返回值)。注意:只有shmget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。

第二个参数:size以字节为单位指定需要共享的内存容量。所有的内存分配操作都是以页为单位的。所以如果一段进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE=4096字节)这样,新创建的共享内存的大小实际上是从size这个参数调整而来的页面大小。即如果size为1至4096,则实际申请到的共享内存大小为4K(一页);409

第三个参数:shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样。举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

IPC_CREAT:如果不存在与指定的key对应的段,那么就创建一个新段。

IPC_EXCL:如果同时指定了IPC_CREAT并且与指定的key对应的段已经存在,那么返回EXIST错误。

其他的可以参见man shmget帮助文档,或者《Linux/Unix编程手册》。

2、shmat函数——将进程连接到共享内存段

第一次创建完共享内存时,它还不能被任何进程访问。shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:

void *shmat(int shm_id, const void *shm_addr, int shmflg);

//举例如下,返回一个指向共享内存的指针ptr
ptr = shmat( seg_id, NULL, 0 );

第一个参数,shm_id是由shmget函数返回的共享内存标识。

第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

第三个参数,shm_flg是一组标志位,通常为0。

调用成功时返回一个指向共享内存(第一个字节)的指针,如果调用失败返回-1。

/*************************************与共享内存交互************************************/
利用shmat()函数返回的指向共享内存的指针ptr进行交互。

strcpy(ptr,"hello");

memcpy/ptr[i]及其他一些通用的指针操作。

/*******************************************************************************************/

3、shmdt函数——向共享内存和当前进程分离

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:

int shmdt(const void *shmaddr);

参数shmaddr是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1.

4、shmctl函数——控制共享内存(删除等)

与信号量的semctl函数一样,用来控制共享内存,它的原型如下:

int shmctl(int shm_id, int command, struct shmid_ds *buf);

第一个参数,shm_id是shmget函数返回的共享内存标识符。

第二个参数,command是要采取的操作,它可以取下面的三个值 :

  •     IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
  •     IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
  •     IPC_RMID:删除共享内存段

第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。

shmid_ds结构至少包括以下成员:

struct shmid_ds{
  uid_t shm_perm.uid;
  uid_t shm_perm.gid;
  mode_t shm_perm.mode;
};

 

三、使用共享内存段的例程:

service:

#include	<stdio.h>
#include	<sys/shm.h>
#include	<time.h>
 
#define	TIME_MEM_KEY	99			/* like a filename      */
#define	SEG_SIZE	((size_t)100)		/* size of segment	*/
#define oops(m,x)  { perror(m); exit(x); }
 
main()
{
	int	seg_id;
	char	*mem_ptr;//, *ctime();
	long	now;
	int	n;
 
	/* create a shared memory segment */
 //貌似都是类似这种,如shMId = shmget(674, getpagesize(), 0666|IPC_CREAT);
	seg_id = shmget( TIME_MEM_KEY, SEG_SIZE, IPC_CREAT|0777 );
	if ( seg_id == -1 )
		oops("shmget", 1);
 
	/* attach to it and get a pointer to where it attaches */
 
	mem_ptr = shmat( seg_id, NULL, 0 );
	if ( mem_ptr == ( void *) -1 )
		oops("shmat", 2);
 
	/* run for a minute */
	for(n=0; n<60; n++ ){
		time( &now );			/* get the time	*/
		strcpy(mem_ptr, ctime(&now));	/* write to mem */
/***************如果想要继续添加共享数据***************/
//      mem_ptr+=sizeof(ctime(&now));
//      strcpy(mem_ptr, "appending date");
		sleep(1);			/* wait a sec   */
	}
		
	/* now remove it */
	shmctl( seg_id, IPC_RMID, NULL );
}

注:

(1)此处mem_ptr所指向的位置是固定的,如果继续添加数据的话先前添加的位置重叠的数据就会被覆盖。所以如果想要继续添加共享数据的话大致如下才可以。

mem_ptr+=sizeof(ctime(&now));

strcpy(mem_ptr,"appending date");

(2)time函数的作用是获取当前时间相对于1970年1月1日00:00:00以来经历的秒数时间,原型是 time_t time(time_t *calptr);

结果既可以通过函数返回值获得也可以通过calptr指针来获得。即calptr是用于返回数据用的,而不是传入数据用的。

(3)ctime函数的作用格式转换。将秒数时间转换为人能够读懂的年月日时间,如Tue Dec 11 18:42:20 2018。

client

#include	<stdio.h>
#include	<sys/shm.h>
#include	<time.h>
 
#define	TIME_MEM_KEY	99		/* kind of like a port number */
#define	SEG_SIZE	((size_t)100)		/* size of segment	*/
#define oops(m,x)  { perror(m); exit(x); }
 
main()
{
	int	seg_id;
	char	*mem_ptr, *ctime();
	long	now;
 
	/* create a shared memory segment */
 
	seg_id = shmget( TIME_MEM_KEY, SEG_SIZE, 0777 );
	if ( seg_id == -1 )
		oops("shmget",1);
 
	/* attach to it and get a pointer to where it attaches */
 
	mem_ptr = shmat( seg_id, NULL, 0 );
	if ( mem_ptr == ( void *) -1 )
		oops("shmat",2);
 
	printf("The time, direct from memory: ..%s", mem_ptr);
 
	shmdt( mem_ptr );		/* detach, but not needed here */
}

 

四、使用共享内存的优缺点

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

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

五、避免竞态

首先,客户端必须有对共享内存段的访问读权限。共享内存段拥有一个权限系统,它的工作原理和文件权限系统类似。共享内存段有自己的拥有者并且为用户、组或者其他成员设置了权限位,来控制他们各自的访问权限。正因为有如此的特性,才可以让服务器只有写权限而客户只有读权限。而多个客户,也可以同时从共享内存段读数据。

但是,服务器通过调用一个运行在用户空间的库函数strcpy来更新共享的内存段。如果客户端正好在服务器写入共享内存的时候读取共享内存段,那么数据就出错了。我们自然想到了锁对资源的控制,内核提供了一种进程间加锁的机制-------信号量。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

焱齿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值