Linux下进程间通讯方式——共享内存

1.什么是共享内存?

共享内存就是容许两个或多个进程共享必定的存储区。就如同 malloc() 函数向不一样进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。由于数据不须要在客户机和服务器端之间复制,数据直接写到内存,不用若干次数据拷贝,因此这是最快的一种IPC。
注:共享内存没有任何的同步与互斥机制,因此要使用信号量来实现对共享内存的存取的同步。
共享内存特色和优点
 
当中共享内存的大体原理相信咱们能够看明白了,就是让两个进程地址经过页表映射到同一片物理地址以便于通讯,你能够给一个区域里面写入数据,理所固然你就能够从中拿取数据,这也就构成了进程间的双向通讯,并且共享内存是IPC通讯当中 传输速度最快的通讯方式没有之一,理由很简单,客户进程和服务进程传递的数据直接从内存里存取、放入,数据不须要在两进程间复制,没有什么操做比这简单了。再者用共享内存进行数据通讯,它对数据也没啥限制。
 
最后就是共享内存的生命周期随内核。即全部访问共享内存区域对象的进程都已经正常结束,共享内存区域对象仍然在内核中存在(除非显式删除共享内存区域对象),在内核从新引导以前,对该共享内存区域对象的任何改写操做都将一直保留;简单地说,共享内存区域对象的生命周期跟系统内核的生命周期是一致的,并且共享内存区域对象的做用域范围就是在整个系统内核的生命周期以内。
 
 
缺陷
 
可是,共享内存也并不完美,共享内存并未提供同步机制,也就是说,在一个服务进程结束对共享内存的写操做以前,并无自动机制能够阻止另外一个进程(客户进程)开始对它进行读取。这明显还达不到咱们想要的,咱们不单是在两进程间交互数据,还想实现多个进程对共享内存的同步访问,这也正是使用共享内存的窍门所在。基于此,咱们一般会用平时常谈到和用到  信号量来实现对共享内存同步访问控制。

 

与共享内存有关的函数

全部的函数共用头文件安全

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

  建立共享内存——>shmget() 函数服务器

int shmget(key_t key, size_t size, int shmflg);
				//成功返回共享内存的ID,出错返回-1       

  

(1)第一个参数key是长整型(惟一非零),系统创建IPC通信 ( 消息队列、 信号量和 共享内存) 时必须指定一个ID值。一般状况下,该id值经过ftok函数获得,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就能够。
 
 (2)第二个参数size指定共享内存的大小,它的值通常为一页大小的整数倍(未到一页,操做系统向上对齐到一页,可是用户实际能使用只有本身所申请的大小)。
 
 (3)第三个参数shmflg是一组标志,建立一个新的共享内存,将shmflg 设置了IPC_CREAT标志后,共享内存存在就打开。而IPC_CREAT | IPC_EXCL则能够建立一个新的,惟一的共享内存,若是共享内存已存在,返回一个错误。通常咱们会还或上一个文件权限
 
3.2操做共享内存———>shmctl()函数
int shmctl(int shm_id, int cmd, struct shmid_ds *buf); 
				//成功返回0,出错返回-1

  

(1)第一个参数,shm_id是shmget函数返回的共享内存标识符。
 
(2)第二个参数,cmd是要采起的操做,它能够取下面的三个值 :    
 
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。    
 
IPC_SET:若是进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值    
 
IPC_RMID:删除共享内存段
 
(3)第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。 shmid_ds结构至少包括如下成员 
struct shmid_ds  
{  
    uid_t shm_perm.uid;  
    uid_t shm_perm.gid;  
    mode_t shm_perm.mode;  
}; 

  

挂接操做———>shmat()函数

建立共享存储段以后,将进程链接到它的地址空间数据结构

void *shmat(int shm_id, const void *shm_addr, int shmflg); 
					//成功返回指向共享存储段的指针,出错返回-1 

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

(2)第二个参数,shm_addr指定共享内存链接到当前进程中的地址位置,一般为空,表示让系统来选择共享内存的地址。ui

(3)第三个参数,shm_flg是一组标志位,一般为0操作系统

3.4分离操做———>shmdt()函数

该操做不从系统中删除标识符和其数据结构,要显示调用shmctl(带命令IPC_RMID)才能删除它
int shmdt(const void *shmaddr); 
			//成功返回0,出错返回-1
(1)addr参数是之前调用shmat时的返回值
 

4.模拟实现进程间的通讯方式———>共享内存

3、使用共享内存进行进程间通讯
说了这么多,又到了实战的时候了。下面就以两个不相关的进程来讲明进程间如何经过共享内存来进行通讯。其中一个文件shmread.c建立共享内存,并读取其中的信息,另外一个文件shmwrite.c向共享内存中写入数据。为了方便操做和数据结构的统一,为这两个文件定义了相同的数据结构,定义在文件shmdata.c中。结构shared_use_st中的written做为一个可读或可写的标志,非0:表示可读,0表示可写,text则是内存中的文件。

shmdata.h的源代码以下:

#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER 
#define TEXT_SZ 2048 
struct shared_use_st
{	
	int written;//做为一个标志,非0:表示可读,0表示可写	
	char text[TEXT_SZ];//记录写入和读取的文本
}; 
#endif

  源文件shmread.c的源代码以下:指针

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include "shmdata.h" 
int main()
{	
	int running = 1;//程序是否继续运行的标志	
	void *shm = NULL;//分配的共享内存的原始首地址	
	struct shared_use_st *shared;//指向shm	
	int shmid;//共享内存标识符	//建立共享内存	
	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
	if(shmid == -1)	
	{		
		fprintf(stderr, "shmget failed\n");	
		exit(EXIT_FAILURE);
	}	//将共享内存链接到当前进程的地址空间	
	shm = shmat(shmid, 0, 0);
	if(shm == (void*)-1)	
	{	
		fprintf(stderr, "shmat failed\n");	
		exit(EXIT_FAILURE);	
	}	
	printf("\nMemory attached at %X\n", (int)shm);	//设置共享内存	
	shared = (struct shared_use_st*)shm;	
	shared->written = 0;	
	while(running)//读取共享内存中的数据	
	{		//没有进程向共享内存定数据有数据可读取		
		if(shared->written != 0)	
		{		
			printf("You wrote: %s", shared->text);		
			sleep(rand() % 3);			//读取完数据,设置written使共享内存段可写
			shared->written = 0;			//输入了end,退出循环(程序)	
			if(strncmp(shared->text, "end", 3) == 0)		
				running = 0;		
		}		
		else//有其余进程在写数据,不能读取数据		
			sleep(1);	
	}	//把共享内存从当前进程中分离	
	if(shmdt(shm) == -1)	
	{		
		fprintf(stderr, "shmdt failed\n");		
		exit(EXIT_FAILURE);
	}	//删除共享内存	
	if(shmctl(shmid, IPC_RMID, 0) == -1)	
	{	
		fprintf(stderr, "shmctl(IPC_RMID) failed\n");	
		exit(EXIT_FAILURE);
	}	
	exit(EXIT_SUCCESS);
}

  源文件shmwrite.c的源代码以下:对象

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shmdata.h" 
int main()
{	
	int running = 1;	
	void *shm = NULL;	
	struct shared_use_st *shared = NULL;
	char buffer[BUFSIZ + 1];//用于保存输入的文本
	int shmid;	//建立共享内存
	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
	if(shmid == -1)	
	{	
		fprintf(stderr, "shmget failed\n");	
		exit(EXIT_FAILURE);	
	}	//将共享内存链接到当前进程的地址空间	
	shm = shmat(shmid, (void*)0, 0);	
	if(shm == (void*)-1)
	{	
		fprintf(stderr, "shmat failed\n");		
		exit(EXIT_FAILURE);	
	}	
	printf("Memory attached at %X\n", (int)shm);	//设置共享内存	
	shared = (struct shared_use_st*)shm;	
	while(running)//向共享内存中写数据	
	{		//数据尚未被读取,则等待数据被读取,不能向共享内存中写入文本		
		while(shared->written == 1)		
		{			
			sleep(1);		
			printf("Waiting...\n");	
		}		//向共享内存中写入数据		
		printf("Enter some text: ");		
		fgets(buffer, BUFSIZ, stdin);		
		strncpy(shared->text, buffer, TEXT_SZ);		//写完数据,设置written使共享内存段可读		
		shared->written = 1;		//输入了end,退出循环(程序)	
		if(strncmp(buffer, "end", 3) == 0)			
			running = 0;	
	}	//把共享内存从当前进程中分离	
	if(shmdt(shm) == -1)	
	{		
		fprintf(stderr, "shmdt failed\n");		
		exit(EXIT_FAILURE);	
	}	
	sleep(2);	
	exit(EXIT_SUCCESS);
}

  结果截图以下:blog

 

分析:
一、程序shmread建立共享内存,而后将它链接到本身的地址空间。在共享内存的开始处使用了一个结构struct_use_st。该结构中有个标志written,当共享内存中有其余进程向它写入数据时,共享内存中的written被设置为0,程序等待。当它不为0时,表示没有进程对共享内存写入数据,程序就从共享内存中读取数据并输出,而后重置设置共享内存中的written为0,即让其可被shmwrite进程写入数据。
 
二、程序shmwrite取得共享内存并链接到本身的地址空间中。检查共享内存中的written,是否为0,若不是,表示共享内存中的数据尚未被完,则等待其余进程读取完成,并提示用户等待。若共享内存的written为0,表示没有其余进程对共享内存进行读取,则提示用户输入文本,并再次设置共享内存中的written为1,表示写完成,其余进程可对共享内存进行读操做。
 
4、关于前面的例子的安全性讨论
这个程序是不安全的,当有多个程序同时向共享内存中读写数据时,问题就会出现。可能你会认为,能够改变一下written的使用方式,例如,只有当written为0时进程才能够向共享内存写入数据,而当一个进程只有在written不为0时才能对其进行读取,同时把written进行加1操做,读取完后进行减1操做。这就有点像文件锁中的读写锁的功能。咋看之下,它彷佛能行得通。可是这都不是原子操做,因此这种作法是行不能的。试想当written为0时,若是有两个进程同时访问共享内存,它们就会发现written为0,因而两个进程都对其进行写操做,显然不行。当written为1时,有两个进程同时对共享内存进行读操做时也是如些,当这两个进程都读取完是,written就变成了-1.
 
要想让程序安全地执行,就要有一种进程同步的进制,保证在进入临界区的操做是原子操做。例如,可使用前面所讲的信号量来进行进程的同步。由于信号量的操做都是原子性的。
 
5、使用共享内存的优缺点
一、优势:咱们能够看到使用共享内存进行进程间的通讯真的是很是方便,并且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通讯的进程有必定的父子关系。
 
二、缺点:共享内存没有提供同步的机制,这使得咱们在使用共享内存进行进程间通讯时,每每要借助其余的手段来进行进程间的同步工做。
相关文章
相关标签/搜索