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

参考资料:

《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命令的意思是“取整”。除非只计划在一种硬件上运行应用程序,否则不应该指定共享段所连接到的地址。所以一般指定addr为0,以便由内核选择地址;
shmflg:决定以什么方式来确定共享内存映射的地址。若指定了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);
	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! 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值