文章目录
1.Posix 共享内存概念
Posix 表示可移植操作系统接口(Portable Operating System Interface ,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准,是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称,其正式称呼为IEEE 1003,而国际标准名称为ISO/IEC 9945。
Posix提供了两种在无亲缘关系进程间共享内存区的方法,内存映射文件和共享内存区对象,这两种共享内存的区别在于共享的数据的载体(底层支撑对象)不一样:
- 内存映射文件(memory-mapped file),由open函数打开,由mmap函数把所得到的描述符映射到当前进程空间地址中的一个文件。
- 共享内存区对象(shared-memory object),由shm_open函数打开一个Posix IPC名字,所返回的描述符由mmap函数映射到当前进程的地址空间
ps:经常说的Posix共享内存,一般是指共享内存区对象,也就是共享物理内存
2.Posix 共享内存关键函数
Posix共享内存区对象主要涉及下面两个步骤:
- 指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个已存在的共享内存区对象。
- 调用mmap把这个共享内存区映射到调用进程的地址空间
shm_open利用tmpfs技术将一段物理内存区域模拟成磁盘文件。
2.1 shm_open()函数
shm_open最主要的操作也是默认的操作就是在/dev/shm/下面打开或创建一个共享内存区。
SHM_OPEN(3) Linux Programmer's Manual SHM_OPEN(3)
NAME
shm_open, shm_unlink - create/open or unlink POSIX shared memory
objects
SYNOPSIS
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
int shm_open(const char *name, int oflag, mode_t mode);
Link with -lrt.
**参数**:
1. name:共享内存区的名字
2. oflag:标志位,参数必须含有O_RDONLY和O_RDWR标志,还可以指定如下标志:O_CREAT,O_EXCL或O_TRUNC.
3. mode:权限位,它指定O_CREAT标志的前提下使用
**返回值:**shm_open的返回值是一个整数描述字,它随后用作mmap的第五个参数。
相关函数:
- int shm_unlink(const char *name);//删除共享内存
- int ftruncate(int fd, off_t length);//重置共享内存文件大小
2.2 mmap函数
mmap函数把一个文件或者一个Posix共享内存区对象映射至调用进程的地址空间
MMAP(2) Linux Programmer's Manual MMAP(2)
NAME
mmap, munmap - map or unmap files or devices into memory
SYNOPSIS
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
See NOTES for information on feature test macro requirements.
**参数**:
1. addr:可以指定描述符fd应被映射到进程内空间的起始地址,它通常被指定为一个空指针,这样告诉内核自己去选择起始 地址,无论哪种情况下,该函数的返回值都是描述符fd所映射到内存区的其实地址。这里需要注意的是,文件需要 初始化长度,否则对内存操作时会产生SIGBUS信息(硬件错误)。
2. length:映射到调用进程地址空间中字节数,它从被映射文件开头offset个字节出开始算。offset通常设置为0
3. prot:内存映射区的保护由port参数指定,通常设置为PROT_READ | PROT_WRITE(可读与可写)
PORT_READ -> 可读
PORT_WRITE -> 可写
PORT_EXEC -> 可执行
PORT_NONE -> 数据不可访问
4.用于设置内存映射区的数据被修改时,是否改变其底层支撑对象(这里的对象是文件),MAP_SHARED和MAP_PRIVATE必须指
定一个。
MAP_SHARED -> 变动是共享的
MAP_PRIVATE -> 变动是私自的
MAP_FIXED -> 准确的解析addr参数
**返回值:**若成功则为被映射区的起始地址,若出错则为MAP_FAILED。
**相关函数:**munmap:用于释放mmap所映射的内存区域
ps:
- mmap成功返回后,fd参数可以关闭。该操作对由于mmap建立的映射关系没有影响。
- fd参数对于共享内存文件(tmpfs),用shm_open函数创建或打开文件,fd参数是对于磁盘文件则用open函数打开,通常Posix 共享内存使用的共享区域是内存区域(用tmpfs在内存中模拟文件,/dev/shm)
3.Posix实例
3.1 父子进程间Posix共享内存通信
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/wait.h>
#define MAXSIZE 1024*1024*24 /*共享内存的大小,建议设置成内存页的整数倍*/
#define FILENAME "myshm"
int main()
{
/* 创建共享对象,可以查看/dev/shm目录 */
int fd = shm_open(FILENAME, O_CREAT | O_TRUNC | O_RDWR, 0777);
if (fd == -1) {
perror("open failed:");
exit(1);
}
/* 调整大小 */
if (ftruncate(fd, MAXSIZE) == -1) {
perror("ftruncate failed:");
exit(1);
}
/* 获取属性 */
struct stat buf;
if (fstat(fd, &buf) == -1) {
perror("fstat failed:");
exit(1);
}
printf("the shm object size is %ld\n", buf.st_size);
void *ptr = mmap(0, MAXSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED)
error_out("MMAP");
close(fd);
pid_t pid = fork();
if (pid == 0)
{
u_long *d = (u_long *)ptr;
*d = 0xdeadbeef;
exit(0);
}
else
{
int status;
waitpid(pid, &status, 0);
printf("child wrote %#lx\n", *(u_long *)ptr);
}
sleep(50);
if (munmap(ptr, MAXSIZE) != 0)
error_out("munmap");
/* 如果引用计数为0,系统释放内存对象 */
if (shm_unlink(FILENAME) == -1) {
perror("shm_unlink failed:");
exit(1);
}
printf("shm_unlink %s success\n", FILENAME);
return 0;
}
程序解析:
- 执行shm_open函数创建了共享内存区域,此时会在/dev/shm/创建myshm文件
- 通过ftruncate函数改变shm_open创建共享内存的大小,如果不执行ftruncate函数的话,会报Bus error的错误. (其实大小指定成多少都可以,1024也行,2048也行(page size的倍数?),但是一定要用ftruncate来将文件改成指定的大小,后面mmap要用的)
- 通过mmap函数将创建的myshm文件映射到内存.
- 通过fork派生出子进程,而共享区域映射通过fork调用而被继承.
- 程序通过wait系统调用来保持父进程与子进程的同步.
- 在非父子进程也可以通过共享内存区域的方式进行通讯.
3.2 非血缘关系进程间Posix共享内存通信
写数据进程:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define MAXSIZE 1024*4 /*共享内存的大小,建议设置成内存页的整数倍*/
#define FILENAME "shm.test"
int main()
{
/* 创建共享对象,可以查看/dev/shm目录 */
int fd = shm_open(FILENAME, O_CREAT | O_TRUNC | O_RDWR, 0777);
if (fd == -1) {
perror("open failed:");
exit(1);
}
/* 调整大小 */
if (ftruncate(fd, MAXSIZE) == -1) {
perror("ftruncate failed:");
exit(1);
}
/* 获取属性 */
struct stat buf;
if (fstat(fd, &buf) == -1) {
perror("fstat failed:");
exit(1);
}
printf("the shm object size is %ld\n", buf.st_size);
/* 建立映射关系 */
char *ptr = (char*)mmap(NULL, MAXSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap failed:");
exit(1);
}
printf("mmap %s success\n", FILENAME);
close(fd); /* 关闭套接字 */
/* 写入数据 */
char *content = "hello readprocess!!!";
strncpy(ptr, content, strlen(content));
sleep(30);
return 0;
}
读进程:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define FILENAME "shm.test"
int main()
{
/* 创建共享对象,可以查看/dev/shm目录 */
int fd = shm_open(FILENAME, O_RDONLY, 0);
if (fd == -1) {
perror("open failed:");
exit(1);
}
/* 获取属性 */
struct stat buf;
if (fstat(fd, &buf) == -1) {
perror("fstat failed:");
exit(1);
}
printf("the shm object size is %ld\n", buf.st_size);
/* 建立映射关系 */
char *ptr = (char*)mmap(NULL, buf.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap failed:");
exit(1);
}
printf("mmap %s success\n", FILENAME);
close(fd); /* 关闭套接字 */
printf("the read msg is:%s\n", ptr);
sleep(30);
return 0;
}
先执行写进程,再执行读进程:读进程就会打印出"the read msg is hello readprocess!!!"
注意上面读写进程是先写后读,因此不用进行进程同步控制,若两进程同步执行需要加一个读写锁或其他方式来进行进程同步控制