共享内存机制,就是在物理内存中划分一段内存,多个进程间可以共同访问这段内存,也可对这段内存做修改操作,从而达到进程通讯的效果!
共享内存机制是允许两个或多个进程(不相关或有亲缘关系)访问同一个逻辑内存的机制。它是共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。
两种常用共享内存方式
- System V版本的共享内存 shm
1. 多个进程直接共享内存
- 文件映射 mmap
1. 文件进行频繁读写,将一个普通文件映射到内存中
2. 将特殊文件进行匿名内存映射,为关联进程提供共享内存空间
3. 为无关联的进程提供共享内存空间,将一个普通文件映射到内存中
目录
一、System V版本
原理:利用共享内存完成进程通讯,两个进程都通过虚拟地址空间到用户页表,然后通过用户级页表映射到物理内存的相同一块内存区域。
1. ftok
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
描述:生成key标识符返回;
参数:
pathname
文件路径名;
proj_id
id是子序号;虽然是int类型,但是只使用8bits(1-255);
返回值:
成功:返回key标识符;
失败:返回-1,并设置错误标志errno;
例:
key_t key = ftok("a.txt", 1);
if ((key_t)-1 == key) {
fprintf(stderr, "ftok failed. reason: %s\n", strerror(errno));
exit(1);
}
2. shmid
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
描述:创建一个共享内存块,返回这个共享内存块的标识符shmid;
参数:
key
共享内存的key,可随机取;但互相通信的进程需一致;
size
申请的共享内存的大小,为4k(4096)的整数倍;
shmflg
IPC_CREAT,创建新的共享内存,存在则返回共享内存标识符;
IPC_EXCL,不存在就创建,存在则报错;
返回值:
成功:返回标识符;
失败:返回-1,并设置错误标志errno;
例:
int shmid = shmget((key_t)1234, 4096, 0666|IPC_CREAT);
if (-1 == shmid) {
fprintf(stderr, "shmget failed!\n");
exit(1);
}
3. shmat
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
描述:挂接共享内存(将进程地址空间挂接到物理空间,可以有多个挂接);
参数:
shmid
挂接的共享内存id;
shmatddr
一般为0,表示连接到由内核选择的第一个可用地址上;
shmflg
一般为0;
返回值:
成功:返回共享内存段的地址;
失败:返回(void *)-1,并设置错误标志errno;
例:
void *shm = shmat(shmid, (void*)0, 0);
if ((void*)-1 == shm) {
fprintf(stderr, "shmat failed!\n");
exit(1);
}
4. shmdt
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
描述:取消共享内存映射;
参数:
shmaddr
共享内存段的地址;
返回值:
成功:返回0;
失败:返回-1,并设置错误标志errno;
例:
int ret = shmdt(shm);
if (-1 == ret) {
fprintf(stderr, "shmdt failed!\n");
exit(1);
}
5. shmctl
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
描述:控制共享内存,可用于删除内存共享等操作;
参数:
shmid
由shmget返回的共享内存标识码;
cmd
将要采取的动作(可取值:IPC_STAT、IPC_SET、IPC_RMID);
buf
指向一个保存着共享内存的模式状态和访问权限的数据结构;
返回值:
成功:IPC_INFO、SHM_INFO、SHM_STAT 返回其他一些东东;其他操作返回0;
失败:返回-1,并设置错误标志errno;
例:
int ret = shmctl(shmid, IPC_RMID, 0);
if (-1 == ret) {
fprintf(stderr, "shmctl(IPC_RMID) failed, reason:%s\n", strerror(errno));
exit(1);
}
6. 示例
1). 例一
shmwrite.cpp
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
typedef struct MSG {
char name[32];
int age;
char sex[32];
}Msg;
int main(int argc, char **argv) {
void *shm = NULL;
int shmid = 0; // 共享内存标识符
const key_t key = (key_t)2023; // 共享内存的key
Msg msg;
sprintf(msg.name, "%s", "老王");
msg.age = 25;
sprintf(msg.sex, "%s", "男");
// 创建共享内存
shmid = shmget(key, sizeof(Msg), 0666|IPC_CREAT);
if (-1 == shmid) {
fprintf(stderr, "shmget failed!\n");
exit(1);
}
// 将共享内存连接到当前进程的地址空间
shm = shmat(shmid, (void*)0, 0);
if ((void*)-1 == shm) {
fprintf(stderr, "shmat failed!\n");
exit(2);
}
printf("Memory attached at %p \n", shm);
// 设置共享内存
Msg *p = (Msg*)shm;
memcpy(p, &msg, sizeof(Msg));
sleep(3);
printf("name = %s\n", p->name);
printf("age = %d\n", p->age);
printf("sex = %s\n", p->sex);
// 把共享内存从当前进程中分离
/*int ret = shmdt(shm);
if (-1 == ret) {
fprintf(stderr, "shmdt failed!\n");
exit(3);
}*/
return 0;
}
shmread.cpp
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include <errno.h>
typedef struct MSG {
char name[32];
int age;
char sex[32];
}Msg;
int main(int argc, char **argv) {
void *shm = NULL;
int shmid = 0; // 共享内存标识符
const key_t key = (key_t)2023; // 共享内存的key
Msg msg; // 指向shm
// 创建共享内存
shmid = shmget(key, sizeof(Msg), 0666|IPC_CREAT);
if (-1 == shmid) {
fprintf(stderr, "shmget failed!\n");
exit(1);
}
// 将共享内存连接到当前进程的地址空间
shm = shmat(shmid, (void*)0, 0);
if ((void*)-1 == shm) {
fprintf(stderr, "shmat failed!\n");
exit(2);
}
printf("Memory attached at %p \n", shm);
// 设置共享内存
memcpy(&msg, shm, sizeof(Msg));
printf("name = %s\n", msg.name);
printf("age = %d\n", msg.age);
printf("sex = %s\n", msg.sex);
sprintf(msg.name, "%s", "小红");
msg.age = 24;
sprintf(msg.sex, "%s", "女");
memcpy(shm, &msg, sizeof(Msg));
// 把共享内存从当前进程中分离
int ret = shmdt(shm);
if (-1 == ret) {
fprintf(stderr, "shmdt failed!\n");
exit(3);
}
// 删除共享内存
ret = shmctl(shmid, IPC_RMID, 0);
if (-1 == ret) {
fprintf(stderr, "shmctl(IPC_RMID) failed, reason:%s\n", strerror(errno));
exit(4);
}
return 0;
}
2). 例二
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
int main(int argc, char **argv) {
void *shm = NULL;
int shmid = 0; // 共享内存标识符
const key_t key = (key_t)2023; // 共享内存的key
pid_t fpid; // 进程唯一标识符
char msg[1024] = { '\0' };
// 创建共享内存
shmid = shmget(key, sizeof(msg), 0666|IPC_CREAT|IPC_EXCL);
if (-1 == shmid) {
fprintf(stderr, "shmget failed!\n");
exit(1);
}
// 将共享内存连接到当前进程的地址空间
shm = shmat(shmid, (void*)0, 0);
if ((void*)-1 == shm) {
fprintf(stderr, "shmat failed!\n");
exit(2);
}
printf("Memory attached at %p \n", shm);
// 设置共享内存
char *p = (char*)shm;
memcpy(p, &msg, sizeof(shm));
// 创建进程
fpid = fork();
if (fpid < 0) {
printf("error in fork!\n");
exit(3);
} else if (0 == fpid) { // 子进程的操作
printf("Child:p = %s\n", p);
sprintf(p, "%s", "Hello World!");
sleep(1);
} else { // 父进程的操作
sprintf(p, "%s", "this is message!"); // 修改内存中的值
sleep (1);
printf("Parent:p = %s\n", p);
wait(NULL);
// 把共享内存从当前进程中分离
int ret = shmdt(shm);
if (-1 == ret) {
fprintf(stderr, "shmdt failed!\n");
exit(3);
}
// 删除共享内存
ret = shmctl(shmid, IPC_RMID, 0);
if (-1 == ret) {
fprintf(stderr, "shmctl(IPC_RMID) failed, reason:%s\n", strerror(errno));
exit(4);
}
}
return 0;
}
二、mmap文件映射
原理:将一个文件或者其他对象映射进内存;
1. 使用普通文件提供的内存映射;
2. 使用特殊文件提供匿名内存映射。
1. mmap
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
描述:在调用的虚拟地址空间中创建一个新的映射;
参数:
addr
指向欲映射的内存起始地址,通常设为NULL,让系统自动选定地址,映射成功后返回该地址;
length
代表将文件中多大的部分映射到内存;(4k的整数倍)
prot
映射区域的保护方式,可以是以下几种反射光hi的组合:
PROT_EXEC 执行
PROT_READ 读取
PROT_WRITE 写入
PROT_NONE 不能存取
flags
影响映射区域的各种特性;必须要指定MAP_SHARED 或 MAP_PRIVATE;
MAP_SHARED 映射区域数据与文件对应,允许其他进程共享;
MAP_PRIVATE 映射区域生成文件的copy,修改不同步文件;
MAP_ANONYMOUS 建立匿名映射;此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享;
MAP_DENYWRITE 允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝;
MAP_LOCKED 将映射区域锁定住,这表示该区域不会被设置swap;
fd
要映射到内存中的文件描述符;如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设置为-1;有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero 文件,然后对该文件进行映射,可以同样达到匿名映射的效果;
offset
文件映射的偏移量,通常设置为0,代表从文件最前方开始映射,offset必须是分页大小的整数倍,即4k的整数倍;
返回值:
成功:返回一个指向映射区域的指针;
失败:返回MAP_FAILED(即(void *) -1),并设置错误标志errno;
例:
void *mem = mmap(NULL, FILE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 映射失败
if (MAP_FAILED == mem) {
fprintf(stderr, "map file failed: %s", strerror(errno));
exit(1);
}
2. munmap
#include <sys/mman.h>
int munmap(void *addr, size_t length);
描述:解除映射;
参数:
addr
文件映射到进程空间的地址;
length
映射空间的大小;
返回值:
成功:返回0;
失败:返回-1,并设置错误标志errno;
例:
int ret = munmap(p, sizeof(p));
if(ret < 0) {
perror("munmap error. reason:");
exit(1);
}
3. msync
#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);
描述:实现磁盘文件内容与共享内存区中的内容一致,即同步操作;
参数:
addr
文件映射到进程空间的地址;
length
映射空间的大小;
flags
MS_ASYNC 异步,调用会立即返回,不等更新完成;
MS_SYNC 同步,调用后会等到更新完成之后才返回;
返回值:
成功:返回0;
失败:返回-1,并设置错误标志errno;
例:
int ret = msync(p, FILE_SIZE, MS_ASYNC);
if (MAP_FAILED == p) {
fprintf(stderr, "msync failed. reason:%s\n", strerror(errno));
exit(1);
}
4. mremap
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/mman.h>
void *mremap(void *old_address, size_t old_size, size_t new_size, int flags);
描述:扩大(或缩小)现有的内存映射;
参数:
old_address
旧的文件映射到进程空间的地址;
old_size
旧的映射空间的大小;
new_size
重新映射指定的新空间大小;
flags
取值0或者MREMAP_MAYMOVE,0代表不允许内核移动映射区域,MREMAP_MAYMOVE则表示内核可以根据实际情况移动映射区域以找到一个符合new_size大小要求的内存区域;
返回值:
成功:返回0;
失败:返回MAP_FAILED即(void*)-1,并设置错误标志errno;
例:
p = (char*)mremap(p, FILE_SIZE, 2*FILE_SIZE, MREMAP_MAYMOVE);
if (MAP_FAILED == p) {
fprintf(stderr, "mremap failed. reason:%s\n", strerror(errno));
exit(1);
}
5. 示例
1). 例一
mmap_write.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
typedef struct MSG {
char name[32];
int age;
char sex[32];
}Msg;
int main(int argc, char **argv) {
if (2 > argc) {
printf("usage: %s file.\n", argv[0]);
exit(1);
}
Msg msg;
sprintf(msg.name, "%s", "小明");
msg.age = 25;
sprintf(msg.sex, "%s", "男");
// 打开一个文件
int fd = open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0664);
if (fd < 0) {
perror("open error. reason:");
exit(2);
}
// 修改文件的大小
ftruncate(fd, sizeof(Msg));
// 将文件映射到内存
void *mem = mmap(NULL, sizeof(Msg), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 映射失败
if (MAP_FAILED == mem) {
fprintf(stderr, "map file failed: %s", strerror(errno));
exit(3);
}
close(fd); //关闭不用的文件描述符
Msg *p = (Msg*)mem;
memcpy(p, &msg, sizeof(Msg)); // 将内容拷贝映射的内存
sleep(3);
printf("name = %s\n", p->name);
printf("age = %d\n", p->age);
printf("sex = %s\n", p->sex);
// 结束映射
int ret = munmap(p, sizeof(Msg));
if(ret < 0)
{
perror("munmap error. reason:");
exit(4);
}
return 0;
}
mmap_read.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
typedef struct MSG {
char name[32];
int age;
char sex[32];
}Msg;
int main(int argc, char **argv) {
if (2 > argc) {
printf("usage: %s file.\n", argv[0]);
exit(1);
}
Msg msg;
int fd = open(argv[1], O_RDWR, 0664);
if (fd < 0) {
perror("open error. reason:");
exit(2);
}
// 将文件映射到内存
void *mem = mmap(NULL, sizeof(Msg), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 映射失败
if (MAP_FAILED == mem) {
fprintf(stderr, "map file failed: %s", strerror(errno));
exit(3);
}
close(fd); //关闭不用的文件描述符
Msg *p = (Msg*)mem;
memcpy(&msg, p, sizeof(Msg)); // 将内容拷贝映射的内存
printf("name = %s\n", msg.name);
printf("age = %d\n", msg.age);
printf("sex = %s\n", msg.sex);
sprintf(p->name, "%s", "小红");
p->age = 24;
sprintf(p->sex, "%s", "女");
// 结束映射
int ret = munmap(mem, sizeof(Msg));
if(ret < 0)
{
perror("munmap error. reason:");
exit(4);
}
return 0;
}
2). 例二
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#define FILE_SIZE 32
int main(int argc, char **argv) {
void *mem = NULL;
pid_t fpid; // 进程唯一标识符
int ret = -1;
if (2 > argc) {
printf("usage: %s file.\n", argv[0]);
exit(1);
}
// open file
int fd = open(argv[1], O_RDWR|O_CREAT, 0664);
if (fd < 0) {
perror("open error. reason:");
exit(2);
}
// 修改文件的大小
ftruncate(fd, FILE_SIZE);
// 将文件映射到内存
mem = mmap(NULL, FILE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 映射失败
if (MAP_FAILED == mem) {
fprintf(stderr, "map file failed: %s", strerror(errno));
exit(3);
}
close(fd); //关闭不用的文件描述符
char *p = (char*)mem;
// 创建进程
fpid = fork();
if (fpid < 0) {
printf("error in fork!\n");
exit(3);
} else if (0 == fpid) { // 子进程的操作
printf("Child:p = %s, strlen(p) = %ld\n", p, strlen(p));
// 扩大现有的内存映射
p = (char*)mremap(p, strlen(p), 2*FILE_SIZE, MREMAP_MAYMOVE);
if (MAP_FAILED == p) {
fprintf(stderr, "mremap failed. reason:%s\n", strerror(errno));
exit(4);
}
ftruncate(fd, 2*FILE_SIZE);
sprintf(p, "%s", "Hello World!");
} else { // 父进程的操作
printf("Parent:p = %s\n", p);
sprintf(p, "%s", "this is message!this is message!"); // 修改内存中的值
sleep (1);
printf("Parent:p = %s\n", p);
wait(NULL);
// 同步内存中的数据到文件
ret = msync(p, 2*FILE_SIZE, MS_SYNC);
if (MAP_FAILED == p) {
fprintf(stderr, "msync failed. reason:%s\n", strerror(errno));
exit(4);
}
// 结束映射
int ret = munmap(p, sizeof(p));
if(ret < 0) {
perror("munmap error. reason:");
exit(5);
}
}
return 0;
}
3). 例三,实际意义的项目用法
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
typedef struct MSG {
char name[32];
int age;
char sex[32];
}Msg;
int main(int argc, char **argv) {
void *mem = NULL;
pid_t fpid; // 进程唯一标识符
int ret = -1;
int dataCount = 0; // 记录结构体数据在文件中的个数
if (2 > argc) {
printf("usage: %s file.\n", argv[0]);
exit(1);
}
// open file
int fd = open(argv[1], O_RDWR|O_CREAT, 0664);
if (fd < 0) {
perror("open error. reason:");
exit(2);
}
// 将文件映射到内存
mem = mmap(NULL, sizeof(Msg), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 映射失败
if (MAP_FAILED == mem) {
fprintf(stderr, "map file failed: %s", strerror(errno));
exit(3);
}
// 修改文件的大小
ftruncate(fd, sizeof(Msg));
//close(fd); //关闭不用的文件描述符
// 创建进程
fpid = fork();
if (fpid < 0) {
printf("error in fork!\n");
exit(3);
} else if (0 == fpid) { // 子进程的操作
printf("子进程读取共享内存中的数据打印:\n");
// 循环读取内存中的所有数据打印出来
int i = 0;
for (; i <= 10; i++) { // 共享内存中一个共有是一个结构体数据,依次读取出来
Msg m;
// 读取
memcpy(&m, (Msg*)((char*)mem + (sizeof(Msg) * i)), sizeof(Msg));
printf("name = %s\n", m.name);
printf("age = %d\n", m.age);
printf("sex = %s\n", m.sex);
}
} else { // 父进程的操作
int i = 0;
for (; i <= 10; i++) {
Msg msg;
sprintf(msg.name, "%s%d", "张某", i);
msg.age = 20 + i;
sprintf(msg.sex, "%s%d", "男", i);
dataCount++; // 统计个数
// 扩大现有的内存映射
mem = mremap(mem, sizeof(Msg) * i, sizeof(Msg) * dataCount, MREMAP_MAYMOVE);
if (MAP_FAILED == mem) {
fprintf(stderr, "mremap failed. reason:%s\n", strerror(errno));
exit(4);
}
// 将数据依次拷贝到共享内存中
memcpy((Msg*)((char*)mem + (sizeof(Msg) * i)), &msg, sizeof(Msg));
}
wait(NULL);
printf("end---\n");
printf("sizeof(Msg) = %ld\n", sizeof(Msg));
// 修改文件的大小
ftruncate(fd, sizeof(Msg) * dataCount);
// 同步内存中的数据到文件
ret = msync(mem, sizeof(Msg) * dataCount, MS_SYNC);
if (MAP_FAILED == mem) {
fprintf(stderr, "msync failed. reason:%s\n", strerror(errno));
exit(5);
}
// 结束映射
int ret = munmap(mem, sizeof(Msg) * dataCount);
if(ret < 0) {
perror("munmap error. reason:");
exit(6);
}
}
return 0;
}
三、总结
共享内存机制在项目中是常用的,且读取数据也是非常高效的,学好它也许日后对做项目会有实在用处!
淘宝分布式文件系统,用的就是mmap!