本文参考《嵌入式Linux开发教程》和《Linux/UNIX系统编程手册》。
共享内存概述
共享内存是允许两个不相关的进程访问同一个逻辑内存的进程间通信方法,是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。
不同进程之间的共享内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用 C语言 malloc()分配的内存一样。两个进程使用共享内存的通信机制如图所示。
POSIX 共享内存区涉及四个主要步骤:
1.指定一个名字参数调用 shm_open,以创建一个新的共享内存区对象(或打开一个已经存在的共享内存区对象);
2.调用 mmap 把这个共享内存区映射到调用进程的地址空间;
3.调用 munmap() 取消共享内存映射;
4.调用 shm_unlink()函数删除共享内存段。
在编译 POSIX 共享内存应用程序时需要加上-lrt 参数。
打开或创建一个共享内存区
shm_open()函数用来打开或者创建一个共享内存区,两个进程可以通过给 shm_open()函数传递相同的名字以达到操作同一共享内存的目的。
int shm_open(const char *name, int oflag, mode_t mode);
函数成功返回创建或打开的共享内存描述符,与文件描述符作用相同,供后续操作使用,失败则返回-1。
删除共享内存
当使用完共享内存后,需要将其删除,以便释放系统资源,可通过 shm_unlink()函数完成。
int shmunlink(const char *name);
函数成功返回 0,否则返回-1。参数 name 为共享内存的名字。
设置共享内存大小
创建一个共享内存后,其默认大小为 0,所以需要设置该共享内存的大小。
int ftruncate(int fd, off_t length);
函数成功返回 0,失败返回-1。参数 fd 为需要调整的共享内存或者文件的描述符,length 为需要调整的大小。
映射共享内存
创建共享内存后,需要将这块内存区域映射到调用进程的地址空间中,可通过 mmap()函数来完成。
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
函数成功返回映射后指向共享内存的虚拟地址,失败返回 MAP_FAILED 值。
取消共享内存映射
已经建立的共享内存映射,可通过 munmap()函数来取消。
int munmap(void *addr, size_t length);
参数 addr 为 mmap()函数返回的地址,length 是映射的字节数。取消映射后再对映射地址访问会导致调用进程收到 SIGSEGV 信号。
共享内存范例
程序清单write.c与程序清单read.c两个范例实现了两个无关进程间使用共享内存进行通信的功能。一个进程往共享内存起始地址写入一个整形数据12,另一个进程则检测该区域的数据,如果不是12,继续等待,直到数据变化为12。
程序清单write.c所示代码完成共享内存写操作,先创建共享内存,设置大小并完成映射,随后往共享内存起始地址写入一个值为12 的整形数据,最后取消和删除共享内存。
vmuser@vmuser-virtual-machine:~/workspace/test$ cat write.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#define SHMSIZE 10 //共享内存大小,10 字节
#define SHMNAME "shmtest" //共享内存名称
int main()
{
int fd;
char *ptr;
/* 创建共享内存 */
fd = shm_open(SHMNAME, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
if (fd<0) {
perror("shm_open error");
exit(-1);
}
/* 设置共享内存大小*/
ftruncate(fd, SHMSIZE); //设置大小为 SHMSIZE
/*映射共享内存*/
ptr = mmap(NULL, SHMSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap error");
exit(-1);
}
*ptr = 0x12; //往起始地址写入 12
munmap(ptr, SHMSIZE); //取消映射
shm_unlink(SHMNAME); //删除共享内存
return 0;
}
程序清单read.c所示范例为读共享内存,首先创建共享内存,然后设置大小并映射共享内存,最后检测共享内存首字节数据是否为12,如果不是,继续等待,否则打印显示,并取消和删除共享内存。
vmuser@vmuser-virtual-machine:~/workspace/test$ cat read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#define SHMSIZE 10 //共享内存大小,10 字节
#define SHMNAME "shmtest" //共享内存名称
int main()
{
int fd;
char *ptr;
/*创建共享内存 */
fd = shm_open(SHMNAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (fd < 0) {
perror("shm_open error");
exit(-1);
}
/*映射共享内存*/
ptr = mmap(NULL, SHMSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap error");
exit(-1);
}
/* 设置共享内存大小 */
ftruncate(fd, SHMSIZE);
while (*ptr != 0x12) { //读起始地址,判断值是否为12
sleep(1); //不是12,继续读取
}
printf("ptr : %d\n", *ptr); //数据是12,打印显示
munmap(ptr, SHMSIZE); //取消内存映射
shm_unlink(SHMNAME); //删除共享内存
return 0;
}
下图为运行结果截图,共享内存的读进程先在后台运行,它循环等待写进程对共享内存做修改。当写进程完成修改后,读进程将检测到共享内存单元的值发生了变化,然后打印出来并退出。
vmuser@vmuser-virtual-machine:~/workspace/test$ gcc read.c -o read -lrt
vmuser@vmuser-virtual-machine:~/workspace/test$ gcc write.c -o write -lrt
vmuser@vmuser-virtual-machine:~/workspace/test$ ./read &
[1] 3752
vmuser@vmuser-virtual-machine:~/workspace/test$ ./write
vmuser@vmuser-virtual-machine:~/workspace/test$ ptr : 18
[1]+ Done ./read
vmuser@vmuser-virtual-machine:~/workspace/test$