一、mmap 函数
1、函数原型:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
2、返回值:
若执行成功: 返回创建的映射区的首地址;
若执行失败: 返回 MAP_FAILED 宏
3、参数:
addr : 建立映射区的首地址,由 Linux 内核指定。使用时直接传递 NULL。
length: 欲创建的映射区大小。
prot : 映射权限 PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags : 标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
MAP_SHARED:会将映射区所做的操作反映到物理设备上(磁盘上)
MAP_PRIVATE:映射区所做的修改不会反映到物理设备上。
fd : 用来建立映射区的文件描述符
offset : 映射文件的偏移(4k的整数倍)
二、munmap 函数
同 malloc 函数申请内存空间类似,mmap 建立的映射区在使用结束后也应调用类似的 free 的函数来解释。
int munmap(void *addr, size_t length);
返回值:
若成功,返回 0 ; 若失败,返回 -1。
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
void sys_error(char *str)
{
perror(str);
exit(1);
}
int main(void)
{
char *mem;
int len = 0;
int fd = open("hello123",O_RDONLY|O_CREAT|O_TRUNC,0644);
if( fd == -1 )
sys_error("open error");
ftruncate(fd,20);
/*
len = lseek(fd,3,SEEK_SET); // 获取文件大小,根据文件大小建立映射区
write(fd,"e",1); // 实质性完成文件拓展
printf("The length of file = %d\n",len);
*/
mem = mmap(NULL,20,PROT_READ,MAP_SHARED,fd,0);
if( mem == MAP_FAILED ) // 出错判断
sys_error("mmap error");
mem++;
close(fd);
strcpy(mem,"xxx");
printf("%s\n",mem);
if( munmap(mem,4)<0 )
sys_error("munmap error");
return 0;
}
三、mmap 的使用注意事项
1、创建映射区时,隐含着对映射文件的一次读操作。
2、当 MAP_SHARED时,要求映射区的权限应 <= 文件的打开权限(出于对映射区的保护)。而 MAP_PRIVATE则无所谓,因为 mmap 中的权限是对内存的限制。
3、映射区的释放与文件关闭无关。只要映射建立成功,则文件可以立即关闭。
4、特别注意:当映射文件大小为 0 时,不能创建映射区。所以,用于映射的文件必须有实际大小!mmap 使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
5、munmap 传入的地址一定是 mmap 的返回地址。杜绝指针 ++ ,-- 操作。
6、文件偏移量必须为 4k 的整数倍。
7、mmap 创建映射区出错概率非常高,一定要检查返回值,确保映射区成功建立,并进行后续操作。
四、mmap 父子进程间通信
父子等有血缘关系的进程之间也可以通过 mmap 建立的映射区来完成数据通信。但是相应的要在创建映射区的时候指定对应的标志位参数 flags:
MAP_PROVATE(私有映射):父子进程各自独占映射区
MAP_SHARED(共享进程) :父子进程共享映射区
结论:
父子进程共享:
(1)、打开的文件
(2)、mmap 建立的映射区(但必须使用 MAP_SHARED)
#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int var = 100;
int main(void)
{
int *p;
pid_t pid;
int fd;
fd = open("temp",O_RDWR|O_CREAT|O_TRUNC,0644);
if( fd == -1 )
{
perror("open error");
exit(1);
}
// 这里程序在运行期间 temp 文件还存在,运行完成后,推出进程后,temp文件被删除掉
unlink("temp"); // 删除临时文件目录项,使之具备被释放的条件
ftruncate(fd,4);
p =(int*)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if( p == MAP_FAILED )
{
perror("mmap error");
exit(1);
}
close(fd); // 映射区建立完毕,即可关闭文件
pid = fork(); // 创建子进程
if( pid == 0 )
{
var = 1000;
*p = 2000;
printf("child,*p = %d,var = %d\n",*p,var);
}
else
{
sleep(1);
printf("parent,*p = %d,var = %d\n",*p,var);
wait(NULL);
int ret = munmap(p,4);
if( ret == -1 )
{
perror("munmao error");
exit(1);
}
}
return 0;
}
五、匿名映射
使用 MAP_ANOYYMOUS(MAP_ANON),如:
int *p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAXP_SHARED|MAP_ANONYMOUS,-1,0);
#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int var = 100;
int main(void)
{
int *p;
pid_t pid;
p =(int*)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
if( p == MAP_FAILED )
{
perror("mmap error");
exit(1);
}
pid = fork(); // 创建子进程
if( pid == 0 )
{
var = 1000;
*p = 2000;
printf("child,*p = %d,var = %d\n",*p,var);
}
else
{
sleep(1);
printf("parent,*p = %d,var = %d\n",*p,var);
wait(NULL);
int ret = munmap(p,4);
if( ret == -1 )
{
perror("munmao error");
exit(1);
}
}
return 0;
}
注意:
MAP_ANONYMOUS 和 MAP_ANON 这两个宏是 Linux操作系统特有的宏,在类 UNIX 系统中无该宏定义,可使用如下两步来完成匿名映射区的建立。
(1)、fd = open("/dev/zero",O_RDWR);
(2)、p = mmap(NULL,size,PROT_READ|PROT_WRTIE,MAP_SHARED,fd,0);
六、mmap 无血缘关系进程间通信
实质上 mmap 是内核借助帮助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据的传递。由于内核控件多进共享,因此,无血缘关系的进程间也可以使用 mmap 来完成通信。只要设置相应的标志位参数 flags 即可。若要实现共享,当然使用 MAP_SHARED 了。
非血缘关系写进程:
#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
struct STU
{
int id;
char name[20];
char sex;
};
void sys_err(char *error)
{
perror(error);
exit(1);
}
int main(int argc,char *argv[])
{
int fd;
struct STU student = { 10,"xiaoming",'m'};
char *mm;
if( argc < 2 )
{
printf("./a.out file_shared\n");
exit(-1);
}
fd = open(argv[1],O_RDWR|O_CREAT,0644);
if( fd == -1 )
sys_err("open error");
ftruncate(fd,sizeof(student));
mm = mmap(NULL,sizeof(student),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if( mm == MAP_FAILED )
sys_err("mmap error");
close(fd);
while(1)
{
memcpy(mm,&student,sizeof(student));
student.id++;
sleep(1);
}
int ret = munmap(mm,sizeof(student));
if( ret == -1 )
sys_err("munmap error");
return 0;
}
非血缘关系读进程:
#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
struct STU
{
int id;
char name[20];
char sex;
};
void sys_err(char *error)
{
perror(error);
exit(1);
}
int main(int argc,char *argv[])
{
int fd;
struct STU student;
struct STU *mm;
if( argc < 2 )
{
printf("./a.out file_shared\n");
exit(-1);
}
fd = open(argv[1],O_RDONLY);
if( fd == -1 )
sys_err("open error");
mm = mmap(NULL,sizeof(student),PROT_READ,MAP_SHARED,fd,0);
if( mm == MAP_FAILED )
sys_err("mmap error");
close(fd);
while(1)
{
printf("id = %d\tname=%s\t%c\n",mm->id,mm->name,mm->sex);
sleep(2);
}
int ret = munmap(mm,sizeof(student));
if( ret == -1 )
sys_err("munmap error");
return 0;
}
七、文件通信
两个非血缘关系的进程可以通过文件的方式来进行通信。
strace + 文件名 能够追踪到当前程序执行过程中用到的系统调用。
注意:文件的一些操作都是用 mmap 来实现的。
/*
先执行,将数据写入文件 test.txt
*/
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#define N 5
void sys_err(char *error)
{
perror(error);
exit(1);
}
int main(void)
{
char buf[1024];
char *str= "-------------successs--------------\n";
int ret;
int fd = open("test.txt",O_RDWR|O_TRUNC|O_CREAT,0644);
if( fd == -1 )
sys_err("open error");
// 直接打开文件写数据
write(fd,str,strlen(str));
printf("test1 write into test.txt finish\n");
sleep(N);
lseek(fd,0,SEEK_SET);
ret = read(fd,buf,sizeof(buf));
if( ret <=0 )
sys_err("read error");
ret = write(STDOUT_FILENO,buf,ret);
if( ret == -1 )
sys_err("write error");
close(fd);
return 0;
}
/*
先执行,将数据写入文件 test.txt
*/
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#define N 5
void sys_err(char *error)
{
perror(error);
exit(1);
}
int main(void)
{
char buf[1024];
char *str= "-------------test2 write success--------------\n";
int ret;
sleep(2); // 睡两秒,保证 test1 将数据写入 test.txt中
int fd = open("test.txt",O_RDWR);
if( fd == -1 )
sys_err("open error");
// 尝试读取 test.txt文件中 test1 写入数据
ret = read(fd,buf,sizeof(buf));
if( ret <= 0 )
sys_err("read error");
// 将读到的数据打入屏幕
write(STDOUT_FILENO,buf,ret);
// 写入数据到 test.txt 中,未修改读写位置
write(fd,str,strlen(str));
printf("test2 read/write finish\n");
close(fd);
return 0;
}