mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存地址,对文件的读写可以直接用指针来操做而不需要read/write函数。
#include
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
如果addr参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映射。如果addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择addr之上的某个合适的地址开始映射。建立映射后,真正的映射首地址通过返回值可以得到。len参数是需要映射的那一部分文件的长度。off参数是从文件的什么位置开始映射,必须是页大小的整数倍(在32位体系统结构上通常是4K)。filedes是代表该文件的描述符。
prot参数有四种取值:
1)PROT_EXEC 表示映射的这一段可执行,例如映射共享库
2)PROT_READ 表示映射的这一段可读
3)PROT_WRITE 表示映射的这一段可写
4) PROT_NONE 表示映射的这一段不可访问
flag参数有很多种取值,这里只讲两种常用的,其它取值可查看mmap(2)
* 如果mmap成功则返回映射首地址,如果出错则返回常数MAP_FAILED。当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射。munmap成功返回0,出错返回-1。
举例:
/* 运行前准备 */
book@ubuntu:~$ vi hello
book@ubuntu:~$ cat hello
helloworld
book@ubuntu:~$ od -tx1 -tc hello
0000000 68 65 6c 6c 6f 77 6f 72 6c 64 0a
h e l l o w o r l d \n
0000013
#include
#include
#include
#include
#include
#include
#include
int main(void)
{
int fd, *p, len;
fd = open("hello",O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
//获取文件长度
len = lseek(fd, 0, SEEK_END);
//建立映射
p = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED)
{//如果失败,记住这种检测方式
perror("mmap");
exit(1);
}
//即使关闭文件也不会释放映射
close(fd);
//由于是共享映射,所以映射源也会被修改
p[0] = 0x30313233;
munmap(p, len);
return 0;
}
/* 运行后结果(小端存储的原因) */
book@ubuntu:~$ cat hello
3210oworld
book@ubuntu:~$ od -tx1 -tc hello
0000000 33 32 31 30 6f 77 6f 72 6c 64 0a
3 2 1 0 o w o r l d \n
0000013
利用内存映射的特性,也可以用于进程间的通信,但是要注意的是:
1、用于进程间通信时,一般设计成结构体,来传输通信的数据
2、进程间通信的文件,应该设计成临时文件(即创建文件,使用文件,删除文件)
3、 当报总线错误时,优先查看共享文件是否有存储空间
例子:
/* mmap-proc_r.c */
#include
#include
#include
#include
#include
#include
#include
#define MAPLEN 0x1000
void sys_err(char *error,int exitno)
{
perror("error");
exit(exitno);
}
int main(int argc, char *argv[])
{
char *mm;
int fd, i = 0;
if(argc < 2)
{
printf("./app \n" );
exit(1);
}
//打开一个文件
fd = open(argv[1], O_RDWR);
if(fd < 0)
sys_err("open", 1);
/*
*第一个NULL: 内核在进程地址空间中选择合适的地址建立映射
* 其返回映射的首地址
*第二个MAPLEN: 映射的长度是MAPLEN
*第三个参数: 表示映射的这一页可读,可写
*第四个MAP_SHARED: 以共享方式打开
*第五个fd: 将fd映射到该进程中
*第六个0: 从fd的偏移量为0的位置开始映射
*/
mm = mmap(NULL, MAPLEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(mm == MAP_FAILED)
sys_err("mmap", 2);
//读数据
while(1)
{
printf("%s\n", mm);
sleep(3);
}
close(fd);
munmap(mm, MAPLEN);
return 0;
}
/* mmap-proc_w.c */
#include
#include
#include
#include
#include
#include
#include
#define MAPLEN 0x1000
void sys_err(char *error,int exitno)
{
perror("error");
exit(exitno);
}
int main(int argc, char *argv[])
{
char *mm;
int fd, i = 0;
if(argc < 2)
{
printf("./app \n" );
exit(1);
}
//创建一个文件
fd = open(argv[1], O_RDWR|O_CREAT,0777);
if(fd < 0)
sys_err("open", 1);
//下面两句话,使创建的文件长度变为4k,这是为了映射4k
lseek(fd,MAPLEN - 1, SEEK_SET);
write(fd, "\0", 1);
/*
*第一个NULL: 内核在进程地址空间中选择合适的地址建立映射
* 其返回映射的首地址
*第二个MAPLEN: 映射的长度是MAPLEN
*第三个参数: 表示映射的这一页可读,可写
*第四个MAP_SHARED: 以共享方式打开
*第五个fd: 将fd映射到该进程中
*第六个0: 从fd的偏移量为0的位置开始映射
*/
mm = mmap(NULL, MAPLEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(mm == MAP_FAILED)
sys_err("mmap", 2);
//写数据
while(1)
{
sprintf(mm, "hello %d\n", i++);
sleep(3);
}
close(fd);
munmap(mm, MAPLEN);
return 0;
}
运行方式:
分别编译为mmap-proc_r 和 mmap-proc_ww 可执行文件
在一个文件中输入
$ ./mmap-proc_w share
在另一个终端中输入
$ ./mmap-proc_r share
但是在实际的开发过程中常常是用一下的方式进行进程间通信的:
/* process_mmap_w.c */
#include
#include
#include
#include
#include
#include
#include
#define MAPLEN 0x1000
struct STU {
int id;
char name[20];
char sex;
};
void sys_err(char *str, int exitno)
{
perror(str);
exit(exitno);
}
int main(int argc, char *argv[])
{
struct STU *mm;
int fd, i = 0;
if (argc < 2) {
printf("./a.out filename\n");
exit(1);
}
fd = open(argv[1], O_RDWR | O_CREAT, 0777);
if (fd < 0)
sys_err("open", 1);
if (lseek(fd, MAPLEN-1, SEEK_SET) < 0)
sys_err("lseek", 3);
if (write(fd, "\0", 1) < 0)
sys_err("write", 4);
mm = mmap(NULL, MAPLEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (mm == MAP_FAILED)
sys_err("mmap", 2);
close(fd);
while (1) {
mm->id = i;
sprintf(mm->name, "zhang-%d", i);
if (i % 2 == 0)
mm->sex = 'm';
else
mm->sex = 'w';
i++;
sleep(1);
}
munmap(mm, MAPLEN);
return 0;
}
/* process_mmap_r.c */
#include
#include
#include
#include
#include
#include
#include
#define MAPLEN 0x1000
struct STU {
int id;
char name[20];
char sex;
};
void sys_err(char *str, int exitno)
{
perror(str);
exit(exitno);
}
int main(int argc, char *argv[])
{
struct STU *mm;
int fd, i = 0;
if (argc < 2) {
printf("./a.out filename\n");
exit(1);
}
fd = open(argv[1], O_RDWR);
if (fd < 0)
sys_err("open", 1);
mm = mmap(NULL, MAPLEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (mm == MAP_FAILED)
sys_err("mmap", 2);
close(fd);
unlink(argv[1]); //删除该文件,收尾工作
while (1) {
printf("%d\n", mm->id);
printf("%s\n", mm->name);
printf("%c\n", mm->sex);
sleep(1);
}
munmap(mm, MAPLEN);
return 0;
}