目录
1、函数定义
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
1.1、mmap说明
mmap()用来将某个文件的内容映射到内存中,对该区域的存取即直接对该文件内容的读写
addr:
要映射的内存起始地址 通常传NULL,让内核自己选,映射成功会返回该地址length:
将文件中多大的部分映射到内存。
对于小文件,可以直接设置为文件的大小,然后将 offset 设置为 0prot: 设置映射区域的访问方式。有以下组合
PROT_EXEC 可执行
PROT_READ 可读
PROT_WRITE 可写
PROT_NONE 不可存取flages:设置映射区域的特性
MAP_SHARED :
对映射区域写入数据会复制回文件,允许其他映射该文件的进程共享
MAP_PRIVATE :
设置为私有,对此区域的修改不会影响原来的文件内容
MAP_ANONYMOUS :
建立匿名映射,会忽略参数fd,不涉及文件,映射区域不和其他进程共享
(还有其他参数,这里只是初步了解mmp,就不过多介绍)fd:
open返回的文件描述符offset :
文件的偏移量,必须是sysconf(_SC_PAGESIZE)返回的页面大小的整数数。
可以为 0,代表从文件最前方开始映射
- 返回值;
成功 返回一个映射区域的内存起始地址。
失败 返回值MAP_FAILED(即(void *) -1)- 错误代码:
EBADF:参数fd无效。
EACCES:存取权限有误:
MAP_PRIVATE情况下文件必须可读;MAP_SHARED则要用PROT_WRITE,并且文件能写。
EINVAL:参数addr、length、offset、有一个不合法
EAGAIN:文件被锁住,或者太多内存被锁住 ENOMEM:内存不足
1.2、mumap说明
- 删除指定地址的范围映射,当进程终止时,也会自动取消映射
- 关闭文件描述符不会取消该区域的映射
- 成功返回0。失败返回-1
1.3、简单使用
int fd = open("./a.txt",O_CREAT | O_RDWR|O_TRUNC, 0666);
write(fd,"12345678\n",10);
char* p = (char*)mmap(NULL,10,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
2、利用共享内存实现进程间通信
2.1、普通文件映射(无亲缘关系的两个进程)
- 程序1往内存中写
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
typedef struct{
int age;
char name[4];
int flag;
} STU;
int main() {
int fd;
char tmp[] = "hh";
STU *p;
//生成一个5倍STU结构大小的空文件
fd = open("a.txt",O_CREAT|O_TRUNC|O_RDWR,0777);
lseek(fd, sizeof(STU)*5 - 1,SEEK_SET);
write(fd," ",1);
p = (STU *) mmap(NULL, sizeof(STU)*10,
PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
close(fd);
sleep(4);//等程序2先读
printf("开始映射\n");
//写入10倍STU结构大小的内存
for (int i = 0; i < 10 ; i++) {
(p+i)->age = i+20;
(p+i)->flag = 1;
memcpy(&((p+i)->name),tmp,2);
printf("name: %s, age: %d flag: %d\n",p[i].name,p[i].age,p[i].flag);
sleep(1);
}
sleep(6);//等待读程序
munmap(p, sizeof(STU)*10);
printf("映射结束\n");
return 0;
}
- 程序2读
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
typedef struct{
int age;
char name[4];
int flag;
} STU;
int main() {
int fd,i = 0;
STU *p_map,*p;
fd = open("a.txt",O_CREAT|O_RDWR,0777);
p_map = (STU *) mmap(NULL, sizeof(STU)*10,
PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
close(fd);
printf("开始读取\n");
p = p_map;
while (i<10){
if(p->flag == 1){
printf("name: %s, age: %d\n",p->name,p->age);
p++;
i++;
sleep(1);
}
else
sleep(1);
}
munmap(p_map, sizeof(STU)*10);
p = NULL;
printf("读取结束\n");
return 0;
}
- 开两个终端分别执行程序1,程序2,下图是程序2的两次执行结果
- 第一次是先执行程序2先读,再执行程序1写
当读到age = 24后,程序2会等待,直到程序1写入后面的内容 - 第二次是先执行程序1写,并等其结束后再执行程序2读
程序2会一直等待
2.2、匿名映射(父子进程)
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <stdlib.h>
typedef struct{
int count;
char data[10];
} Info;
int main() {
int i = 0;
pid_t pid;
Info *p;
p = (Info *) mmap(NULL, sizeof(Info)*3,
PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_ANONYMOUS,0,0);
pid = fork();
if(pid == 0){
sleep(2);//等待父进程写完
while (i<3){
printf("child read:count=%d message=%s\n",p[i].count,p[i].data);
i++;
}
//修改共享内存的内容
i=i-1;
memcpy(&((p+i)->data),"child ",6);
munmap(p, sizeof(Info)*3);
exit(0);
}
else if (pid > 0){
for (i = 0; i < 3 ; i++) {
(p+i)->count = i+1;
memcpy(&((p+i)->data),"parent",6);
}
sleep(3);//等待子进程读完
i=i-1;
printf("parent read:count=%d message=%s \n\n",p[i].count,p[i].data);
wait(NULL);
munmap(p, sizeof(Info)*3);
}
return 0;
}
2.3、 结论
1、映射的内存可以同时读写
2、文件一旦被映射,调用mmap的进程对返回地址的操作时作用在内存上
3、只有调用munmmap时才会写回文件,且所写的内容不会超过文件的大小
4、mmap()返回的地址可用于父子进程共享
3、地址越界问题
映射时可用的内存空间大小受限于被映射文件的大小,但不完全受限于文件大小
即使被映射的文件只有一个字节,内核也会为其分配一个页面大小的内存,对这个页面的任意位置访问不会出错,但对该页面以外的地址访问就会出错。
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
typedef struct{
int count;
char data[10];
} Info;
int main(int argc,char **argv) {
int fd,pagesize,offset = 0;
Info *p;
//设置偏移量
pagesize = (int)sysconf(_SC_PAGESIZE);//页面大小
printf("页面大小 = %d\n",pagesize);
if(argc == 1){
printf("偏移量 = %d\n",offset);
}
else {
offset = pagesize;
printf("偏移量 = %d\n",offset);
}
//文件大小介于两个页面之间
fd = open("a.txt",O_CREAT|O_TRUNC|O_RDWR,0644);
lseek(fd,pagesize*2-100,SEEK_SET);
write(fd," ",1);
p = (Info *) mmap(NULL, pagesize*4,
PROT_READ|PROT_WRITE,
MAP_SHARED,fd,offset);
close(fd);
printf("开始访问\n");
for(int i = 1;i < 5;i ++){
//p + x 的实际大小是 p + x*sizeof(Info)
(p+pagesize/ sizeof(Info)*i-2)->count = 100;
printf("访问页面 %d \n",i);
(p+pagesize/ sizeof(Info)*i-1)->count = 100;
printf("访问页面 %d 的边界\n",i);
}
munmap(p,pagesize*3);
return 0;
}
参考:《高质量嵌入式LinuC编程》 梁康 陈明 马小陆 编著 第281页–289页