mmap 可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对
应的内存地址,对文件的读写可以直接用指针来做而不需要read/write 函数。
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
int munmap(void *addr, size_t len);
参数:addr 一般为NULL,内核自动分配
len 映射区大小
prot 创建映射区的权限
flag
MAP_SHARED 修改会更新到物理层上,即修改内容,物理磁盘也会跟着被修改
MAP_PRIVATE 不会修改到物理层
filedes 文件描述符等
off 偏移量的起始位置,以页为单位
如果addr 参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映
射。如果addr 不是NULL,则给内核一个提示,应该从什么地址开始映射,内
核会选择addr 之上的某个合适的地址开始映射。建立映射后,真正的映射首地
址通过返回值可以得到。len 参数是需要映射的那一部分文件的长度。off 参数
是从文件的什么位置开始映射,必须是页大小的整数倍(在32 位体系统结构上
通常是4K)。filedes 是代表该文件的描述符。
prot 参数有四种取值:
• PROT_EXEC 表示映射的这一段可执行,例如映射共享库
• PROT_READ 表示映射的这一段可读
• PROT_WRITE 表示映射的这一段可写
• PROT_NONE 表示映射的这一段不可访问
flag 参数有很多种取值,这里只讲两种,其它取值可查看mmap(2)
• MAP_SHARED 多个进程对同一个文件的映射是共享的,一个进程对映射的
内存做了修改,另一个进程也会看到这种变化。
• MAP_PRIVATE 多个进程对同一个文件的映射不是共享的,一个进程对映
射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到
文件中去。
如果mmap 成功则返回映射首地址,如果出错则返回常数MAP_FAILED。当进程
终止时,该进程的映射内存会自动解除,也可以调用munmap 解除映射。munmap
成功返回0,出错返回-1。
/*
*使用mmap,将一个内容为“hello”的文件内容,改为“abcdo”,不使用read/write做
*/
//创建一个文件,内容为hello
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
int *p;
int fd;
fd = open("hello.txt", O_RDWR);
if(fd < 0){
perror("open hello.txt");
exit(1);
}
int len = lseek(fd, 0, SEEK_END);
printf("lseek:%d\n", len);
/*
*NULL :让内核自动制定映射区首地址
*len :制定映射区的大小
*PROT_WRITE:表示映射的这一段可写
*MAP_SHARED:多进程共享,修改会更新到物理设备上
*fd :要映射的文件的描述符
*0 :从文件的0号偏移位置开始映射
*/
p = mmap(NULL, len, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
if(p == MAP_FAILED){ //映射出错
perror("mmap");
exit(1);
}
close(fd); //只要映射成功文件即可关闭
// p[0] = 0x64636261;
strcpy(p, "abdco");
munmap(p, len); //解除映射
return 0;
}
/*
akaedu@akaedu-G41MT-D3:~/T74_system/0819_chp1_lseek_ctl_mmap_dup2$ ./mmap
lseek:6
*/
//打开文件,内容已修改为abcdo
/*
*./mmap_cp src dst 使用mmap实现的命令行参数cp命令
*mmap的好处,把文件当成数组来看待,简化程序逻辑,提高程序速度
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
int main(int argc, char *argv[])
{
int in, out; //在两个映射区之间进行复制
char *s, *d;
in = open(argv[1], O_RDONLY);
if(in == -1){ //检查返回值
perror("open src error");
exit(1);
}
/*
*映射区权限必须小于等于文件的权限,建立映射区的过程隐含着一次文件的
*读操作,所以这里应该是“读写”权限,而不是“只写”权限
*使用O_WRONLY建立映射区会报错
*/
out = open(argv[2], O_RDWR|O_TRUNC|O_CREAT, 0644);
if(out == -1){
perror("open dst error");
exit(1);
}
int len = lseek(in, 0, SEEK_END); //获取文件长度
/*
*NULL :由内核指定映射区首地址
*len :映射区的大小
*PROT_READ :读写权限为只读
*MAP_PRIVATE:进程间不共享映射区,映射区被关闭后,
* 修改消失,不会更新到物理设备上
*in :文件描述符
*0 :映射文件的偏移位置
*/
s = (char *)mmap(NULL, len, PROT_READ, MAP_PRIVATE, in, 0);
if(s == MAP_FAILED){
perror("mmap src error");
exit(1);
}
/*
*由于映射区的大小不允许超过原始文件的大小
*而目的文件是使用O_CREAT|O_TRUNC创建的,所以建立映射区的时候
*目的文件的大小是0个字节,所以应该先拓展目的文件的大小,
*然后在创建跟目的文件对应的映射区, 如不拓展,将来在对映射区进行
*写操作的时候就会出现“总线错误”
*
*所以:建立目的映射区之前,应该先拓展目的文件
*/
lseek(out, len-1, SEEK_SET);//不拓展,会出"总线错误"
//lseek只是修改记录,并不真正拓展文件,真正拓展是写(IO)操作做的。
write(out, "a", 1); //len-1 +'a'= len
//目的映射区权限只写,映射区选项是MAP_SHARED,才能更新到磁盘上
d = (char *)mmap(NULL, len, PROT_WRITE, MAP_SHARED, out, 0);
if(d == MAP_FAILED){
perror("mmap dst error");
exit(1);
}
close(in);close(out); //映射区建立完毕,文件即可关闭了。
memcpy(d, s, len);
#if 0
for(i = 0; i < len; i++)
*d++ = *s++;
#endif
return 0;
}