参考资料:
《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!
$