易错点
- 对新创建的文件(文件没有大小)进行映射,会收到bus error的错误
- mmap函数的最后一个参数 offset如果不是4K的整数倍,报错:Invalid argument
mmap(共享映射区)存储映射I/O
-
一个磁盘文件与存储空间中的一个缓冲区相映射,于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成I/O操作。
-
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset):创建映射区
addr :建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
length :欲创建映射区的大小(不能为0,不能给新创建的文件创建映射区,需要拓展新文件,
可以小于文件大小,可以大于文件的大小(但是毫无意义))
prot :映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE、PROT_NONE、PROT_EXEC
flags :
MAP_SHARED:会将映射区所做的操作反映到物理设备(磁盘)上
MAP_PRIVATE:映射区所做的修改不会反映到物理设备
fd :用来建立映射区的文件描述符
offset :映射文件的偏移(4k的整数倍):MMU帮助完成映射,MMU单位是4K
-
int munmap(void *addr, size_t length):回收映射区
addr :mmap的返回值,映射空间的首地址
length :映射区的大小
例子:给文件创建映射区,再往映射区中写内容,看是否写入磁盘空间
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
int fd;
char*p;
fd = open("./my.txt",O_RDWR|O_CREAT,0664);
ftruncate(fd,sizeof("julian\n"));
p = mmap(NULL,sizeof("julian\n"),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
memcpy(p,"julian\n",sizeof("julian\n"));
munmap(p,sizeof("julian\n"));
close(fd);
return 0;
}
结果:在当前目录下创建一个my.txt文件,里面有写入映射区的内容
结论
- (1) 创建映射区的权限 一定要小于等于 映射的文件的权限(如果指定flags为O_PRIVATE就无所谓)
- (2) 创建映射区的过程隐含了一次对文件的读操作
- (3)映射的大小大于0的值,可以小于或者大于文件大小,大于没有意义(后面的空间对应的不是文件内容)
- (4) 打开的文件的描述符可以先关闭,再释放映射区
文件的实名映射-父子进程间通信
- mmap函数的flags参数在父子进程间通信时的区别:
MAP_PRIVATE: (私有映射) 父子进程各自独占映射区
MAP_SHARED: (共享映射) 父子进程共享映射区
例子:父进程创建映射区,fork子进程,子进程修改映射区的内容,然后,父进程再读映射区,查看是否共享
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
int fd;
int*p;
pid_t pid;
int global_val=10;
fd = open("./my2.txt",O_RDWR|O_CREAT,0664); //临时文件,只为了创建映射区
ftruncate(fd,4);
unlink("./my2.txt"); //删除文件的目录项,使其进程介绍之后就被释放,删除
p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
close(fd);
pid=fork();
if(pid==0)
{
*p=10000;
global_val=9999;
printf("son ---*p:%d,global_val:%d\n",*p,global_val);
}
else
{
sleep(1);
printf("father---*p:%d,global_val:%d\n",*p,global_val);
wait(NULL);
munmap(p,4);
}
return 0;
}
结果:son 改了映射区*p的值,father读出来是修改过后的,但是global_val是各自进程独享的(0-3G用户空间),即使子进程改变了,父进程依然没改变
共享的条件是:mmap函数的Flags参数是shared,如果是private则结果如下:
匿名映射-父子进程间通信
- 通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。父子间通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。 可以直接使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。使用 MAP_ANONYMOUS (或MAP_ANON)
- 需要注意的是,以上的宏只能在linux使用,在类unix上没有此宏,但是也可以用如下方法:
① fd = open("/dev/zero", O_RDWR);
② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
int fd;
int*p;
pid_t pid;
int global_val=10;
fd = open("/dev/zero",O_RDWR);
p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);// 4 根据自己定义映射区大小
close(fd);
pid=fork();
if(pid==0)
{
*p=10000;
global_val=9999;
printf("son ---*p:%d,global_val:%d\n",*p,global_val);
}
else
{
sleep(1);
printf("father---*p:%d,global_val:%d\n",*p,global_val);
wait(NULL);
munmap(p,4);
}
return 0;
}
使用宏和/dev/zero文件两个结果是一样的
mmap无血缘关系进程间通信
- 两个进程打开同一个文件,再分别创建映射区,则映射区对应都是同一个文件,所以是同一个映射区,可以利用其来进程无血缘关系间的进程通信
例子:mmap_r.c:利用一个文件创建映射区,然后不断从中读数据(来自另一个进程写的结果)
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
struct student
{
int id;
char name[20];
};
int main(int argc,char* argv[])
{
int fd;
struct student student1;
struct student* p;
fd = open(argv[1],O_RDWR); //文件作为参数传进来
p = mmap(NULL,sizeof(student1),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
close(fd);
while(1)
{
printf("id=%d name=%s\n",p->id,p->name);
sleep(2);
}
munmap(p,sizeof(student1));
return 0;
};
mmap_w.c:利用一个文件创建映射区,然后不断往返回的地址 覆盖性的写数据
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
struct student
{
int id;
char name[20];
};
int main(int argc,char* argv[])
{
int fd;
struct student student1={1,"julian"};
struct student* p;
fd=open(argv[1],O_RDWR|O_CREAT,0644);
p = mmap(NULL,sizeof(student1),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//
close(fd);
while(1)
{
sleep(1);
memcpy(p,&student1,sizeof(student1));
student1.id++;
}
munmap(p,sizeof(student1));
return 0;
}
结果:一个进程不断的读出另一个进程写进映射区的数据