进程间通信(一)——共享内存

参考资料:

《Linux驱动开发入门与实战》
https://blog.csdn.net/ypt523/article/details/79958188

一、概念


       共享内存允许两个或更多进程共享一给定的存储区。因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。使用共享内存时要掌握的唯一窍门时多个进程之前对一给定存储区的同步访问。若服务器进程正在将数据放入共享存储区,则在它做完这一操作之前,客户进程不应当去取这些数据。通常,信号量被用来实现对共享内存访问的同步。

 

二、共享内存相关函数


1、创建共享内存
头文件:#include <sys/shm.h>
函数原型:int shmget(key_t key, size_t size, int shmflg);
参数:
key:标识共享内存的键值:0/IPC_PRIVATE,当key的取值IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;
size:申请的共享内存的大小。在操作系统中,申请内存的最小单位为页,一页是4K字节,为了避免内存碎片,一般申请的内存大小为页的整数倍。
shmflg:如果要创建新的共享内存,则需要使用IPC_CTEAT,IPC_EXCL,如果已经存在的,可以使用IPC_CREAT或直接传0;
返回值:成功时返回一个新建或已经存在的共享内存标识符,取决于shmflg参数;失败返回-1并置错误码。
函数作用:创建共享内存。


2、映射共享内存
头文件:#include <sys/shm.h>
函数原型:int *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid:shmget函数返回的共享内存标识符;
addr:共享内存连接到调用进程的哪个地址与addr参数及在flag中是否指定SHM_RND位有关。
    如果addr为0,则此段连接到由内核选择的第一个可用地址上。这是推荐使用的方式。
    如果addr非0,并且没有指定SHM_RND,则此段连接到addr所指定的地址上。
    如果addr非0,并且指定了SHM_RND,则此段连接到(addr-(addr mod ulus SHMLBA))所指定的地址上。SHM_RND命令的意思是“取整”。SHMLBA的意思是“低边界地址倍数”,它总是2的乘方。该算式是将地址向下取近1个SHMLBA的倍数。除非只计划在一种硬件上运行应用程序,否则不应该指定共享段所连接到的地址。所以一般指定addr为0,以便由内核选择地址;
flag:决定以什么方式来确定共享内存映射的地址。若指定了SHM_RDONLY位,则以只读的方式连接此段,否则以读写方式连接此段。通常设置为0;
返回值:若成功,返回指向共享内存的指针即共享内存映射到进程中的地址。并且内核将使其与该共享内存相关的shmid_ds结构中的shm_nattch计数器加1;若失败返回-1;
函数作用:映射共享内存;

3、解除共享内存映射
头文件:#include <sys/shm.h>
函数原型:int shmdt(void *addr);
参数:
addr:共享内存连接到调用进程的地址;
返回值:若成功,返回0并将shmid_ds结构中的shm_nattch计数器减1,若失败返回-1;
函数作用:当对共享内存的操作已经结束时,调用shmdt脱接共享内存。即把它从进程地址空间中脱离。注意,这并不从系统中删除其标识符以及其数据结构。该标识符仍然存在,直至某个进程(一般是服务器进程)调用shmcl (带命令IPC_RMID)特地删除它。

4、销毁共享内存
头文件:#include <sys/shm.h>
函数原型:int shmctl(int shmid, int cmd; struct shmid_ds *buf);
参数:
shmid:shmget函数返回的共享内存标识符;
cmd:指定的执行操作,设置为IPC_RMID时表示可以删除共享内存。
buf:设置为NULL即可。
返回值:若成功返回0,若失败返回-1;
函数作用:销毁共享内存。

5、ftok系统IPC键值的格式转换函数
在IPC中,我们经常用用key_t的值来创建或者打开信号量,共享内存和消息队列。
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
函数原型:key_t ftok( const char * fname, int id )
fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:key = ftok(".", 1); 这样就是将fname设为当前目录。
id:子序号。虽然是int类型,但是只使用8bits(1-255)。
说明:在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。


三、总结

1)优点:我们可以看到使用共享内存进行进程之间的通信是非常方便的,而且函数的接口也比较简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,加快了程序的效率。

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


四、代码

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/stat.h>

#define SHM_SIZE 4096

int main(int argc, char *argv[])
{
	int shmid;
	char *p_addr = NULL;
	char *c_addr = NULL;
	
	if(argc != 2)
	{
		printf("Usage : %s \n", argv[0]);
		return -1;
	}
	printf("[%s] %s %d argv : %s \n", __FILE__, __func__, __LINE__, argv[1]);
	/* 创建共享内存 */
	/*
	使用如下方式定义创建共享内存,PROJ_ID的取值很关键,取错了就会出现段错误
	#define PATHNAME "."
	#define PROJ_ID 0x6666
	key_t key = ftok(PATHNAME, PROJ_ID);
	shmid = shmget(key, SHM_SIZE, 0640 | IPC_CREAT | IPC_EXCL);
	*/
	shmid = shmget(IPC_PRIVATE, SHM_SIZE, S_IRUSR | S_IWUSR);
	// shmid = shmget(0, SHM_SIZE, IPC_PRIVATE);//这样定义会导致段错误
	if(shmid < 0)
	{
		printf("[%s] %s %d Creat share memory failed : %s \n", 
			__FILE__, __func__, __LINE__, strerror(errno));
		exit(1);
	}
	
	/* 创建子进程 */
	if(fork())
	{
		printf("[%s] %s %d shmid : %d \n", __FILE__, __func__, __LINE__, shmid);
		/* 父进程写共享内存 */
		/* 映射共享内存 */
		p_addr = shmat(shmid, 0, 0);
		memset(p_addr, 0, SHM_SIZE);
		strncpy(p_addr, argv[1], SHM_SIZE);
		wait(NULL); //释放资源不关心终止状态

		/* 解除共享内存映射 */
		if(shmdt(p_addr) < 0)
		{
			printf("[%s] %s %d shmdt failed : %s \n", 
			__FILE__, __func__, __LINE__, strerror(errno));
			
			exit(1);
		}
		else
		{
			printf("[%s] %s %d server shmdt sucess! \n", __FILE__, __func__, __LINE__);
		}
		
		/* 销毁共享内存 */
		if(shmctl(shmid, IPC_RMID, NULL) < 0)
		{
			printf("[%s] %s %d shmctl failed : %s \n", 
			__FILE__, __func__, __LINE__, strerror(errno));
			
			exit(1);
		}
		else
		{
			printf("[%s] %s %d server shmctl sucess! \n", __FILE__, __func__, __LINE__);
		}
		exit(0);
	}
	else
	{
		printf("[%s] %s %d shmid : %d \n", __FILE__, __func__, __LINE__, shmid);
		/* 子进程读共享内存 */
		sleep(5); //让父进程先行
		/* 映射共享内存 */
		c_addr = shmat(shmid, 0, 0);
		printf("Client get %s \n", c_addr);
		
		/* 解除共享内存映射 */
		if(shmdt(c_addr) < 0)
		{
			printf("[%s] %s %d shmdt failed : %s \n", 
			__FILE__, __func__, __LINE__, strerror(errno));
			
			exit(1);
		}
		else
		{
			printf("[%s] %s %d client shmdt sucess! \n", __FILE__, __func__, __LINE__);
		}
		exit(0);
	}
	
}

输出结果:
$ gcc -g shm_test.c  -o shm
$ ./shm 11111
[shm_test.c] main 25 argv : 11111 
[shm_test.c] main 45 shmid : 753680 
[shm_test.c] main 82 shmid : 753680 
Client get 11111 
[shm_test.c] main 99 client shmdt sucess! 
[shm_test.c] main 63 server shmdt sucess! 
[shm_test.c] main 76 server shmctl sucess! 

 

 

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页