我们来简单了解下文件共享映射的定义:通过映射文件,使用映射机制,实现资源共享,完成进程通信
具体是如何实现的呢?跟随着这篇博客,我们来看一看
- 由某一进程创建映射文件
- 该进程通过使用frtuncate函数将文件截断至我们想要的大小(关于frtuncate函数的介绍请看注释①)
- 在进程内申请与映射文件大小相同的映射内存,并将映射文件中的内容通过共享映射的方式加载到被申请的映射内存当中,该步骤通过mmap函数实现(关于mmap函数的介绍请看注释②)。要注意的是,mmap函数的映射方式有两种,分别是私有映射和共享映射(关于私有映射和共享映射的区别请看注释③)
- 其余进程使用被创建出的映射文件描述符,通过mmap函数也和该映射文件进行共享映射,这样就实现了进程之间的通信
- 通信完成后就要释放映射,释放映射通过munmap函数实现(关于munmap函数的介绍请看注释④)
PS:映射内存申请在进程用户层的库内存中,因为库内存的大小会改变
头文件 | #include |
功能 | 如果文件初始大小小于我们所要求的大小,便在后面不断填充'\0',直到文件截断至我们要求的大小; 如果文件初始大小大于我们所要求的大小,便在文件目标大小处加入'\0'来截断文件并删除后面的内容,使文件达到我们要求的大小 |
语法 | int ftruncate(文件描述符 , 想要截断到多大) |
返回值 | 成功则返回 0,失败返回 -1, 错误原因存于errno |
参数介绍:
参数 | 变量类型 | 解释 |
文件描述符 | int | 有效文件描述符,一般由open函数返回 |
想要截断到多大 | off_t | 将指定的文件的大小改为指定的大小 |
头文件 | #include |
功能 | 在进程内申请映射内存,并将映射文件中的内容加载到系统申请的映射内存当中 |
语法 | void *mmap(起始地址 , 映射内存大小 , 内存权限 , 映射方式 , 映射文件描述符 , 偏移量); |
返回值 | 成功执行时,mmap函数返回映射内存的起始地址 失败时,mmap函数返回MAP_FAILED [ 其值为(void *) -1 ] |
参数介绍:
参数 | 变量类型 | 解释 |
起始地址 | void* | 我们希望映射内存从哪里开始被创建,一般传NULL,表示让系统自主申请空间 |
映射内存大小 | size_t | 一般直接传入映射文件的大小,注意这个地方不能填0,否则直接失败 |
内存权限 | int | PROT_EXEC //页内容可以被执行 PROT_READ //页内容只读PROT_WRITE //页可以只写 PROT_NONE //页不可访问 |
映射方式 | int | MAP_PRIVATE//私有映射 MAP_SHARED//共享映射(具体请看注释③) |
映射文件描述符 | int | 有效文件描述符,一般是由open函数返回 |
偏移量 | off_t | 被映射对象内容的起点,需要注意的是,内存是以页为单位的,所以偏移量必须是一页或者一页的整数倍 |
PS:映射成功后这个映射文件描述符就没用了,因为后续的修改是通过sync同步机制,而不是通过read,write函数来实现,所以映射成功后就可以直接close(映射文件描述符)
映射方式 | 功能 |
私有映射 MAP_PRIVATE | 私有映射又叫只读映射,是指仅将映射文件中的内容拷贝到进程的映射内存之中,后续进程对映射内存中内容的修改不会影响到映射文件中的内容 |
共享映射 MAP_SHARED | 共享映射中有一种机制叫做sync同步机制,对一端的修改会实时同步到另一端,这也是通过文件共享映射实现进程通信的基础 |
头文件 | #include |
功能 | 内存映射完毕后,调用munmap函数解除内存映射,释放相关资源。 |
语法 | int munmap(映射内存起始地址, 映射内存的大小); |
返回值 | 成功返回0,失败返回-1 |
参数介绍:
参数 | 变量类型 | 解释 |
映射内存起始地址 | void* | mmap函数的返回值 |
映射内存的大小 | size_t | 其实也就是映射文件的大小 |
我来做一个情景假设,假设我提前创建了一个映射文件,大小为0,文件描述符为fd
然后我写下面这样一行代码,直接申请一个大小为4096的映射内存,大家猜一下,这行代码能不能通过
void* ptr = mmap(NULL , 4096 , PROT_WRITE , MAP_SHARED , fd , 0);
这段代码是可以通过的,你确实申请出来了这样大小的一块映射内存,但是你想要使用时就会报错,因为操作系统后面发现你骗了他,你映射文件大小为0,你用个屁的映射内存啊。
就像你拿 “给哥们过生日” 当理由找老婆要钱的时候,你老婆一开始确实会划出一部分钱来专门给你用来给哥们过生日,但后面你老婆一个电话打过去问你哥们,结果发现你哥们生日还有大半年的时候,她就会直接就把这部分钱收回来不让你用,并且给你几个最爱吃的大逼兜作为警告,这是一个道理
你映射文件大小是多少才能使用多大的映射内存,你映射文件大小为1,你即使申请出来100大小的映射内存,你也只能用1的大小,你用其他99大小的映射内存就属于访问越界,这就是总线错误,系统会发出SIGBUS信号来直接干掉你这个进程
这里需要两个进程,一个读一个写,我们创建两个.c文件,分别为map_write.c和map_read.c
代码及讲解如下所示:
//map_write.c
#include
#include
#include
#include
#include
#include
#include
/*设置一个定时器*/
#define timeout 1
/*定义一个结构体,映射文件里存放的就是结构体消息*/
typedef struct
{
int code;
char msg[100];
}msg_t;
int main(void)
{
/*1.创建映射文件,读写打开*/
int fd = open("MAP" , O_RDWR|O_CREAT , 0664);
/*2.由于是往文件里写结构体类型的消息,所以要将文件拓展至一个结构体的大小*/
ftruncate(fd , sizeof(msg_t));
/*3.建立映射关系*/
/*由于映射文件里是结构体类型的消息,所以要定义一个结构体类型的指针来接取返回值*/
msg_t* ptr = NULL;
//如果映射创建失败,程序直接退出
if((ptr = mmap(NULL , sizeof(msg_t) , PROT_READ|PROT_WRITE , MAP_SHARED , fd , 0)) == MAP_FAILED)
{
perror("映射关系建立失败!\n");
exit(0);
}
/*映射关系建立成功,关闭文件*/
close(fd);
/*初始化结构体里面的内容*/
ptr->code = 0;
bzero(ptr->msg , sizeof(ptr->msg));
/*循环向映射文件内写入内容*/
while(1)
{
ptr->code++;
sprintf(ptr->msg , "this is a test message , code = %d\n" , ptr->code);
sleep(timeout);
}
/*关闭映射*/
munmap(ptr , sizeof(msg_t));
exit(0);
}
//map_read.c
#include
#include
#include
#include
#include
#include
#include
/*设置一个定时器*/
#define timeout 1
/*定义一个结构体,映射文件里存放的就是结构体消息*/
typedef struct
{
int code;
char msg[100];
}msg_t;
int main(void)
{
/*先打开文件*/
int fd = open("MAP" , O_RDWR);
/*由于该进程是读取内容,所以不能扩展映射文件的大小,否则会覆盖文件里的内容*/
/*建立映射关系*/
/*由于映射文件里是结构体类型的消息,所以要定义一个结构体类型的指针来接取返回值*/
msg_t* ptr = NULL;
if((ptr = mmap(NULL , sizeof(msg_t) , PROT_READ|PROT_WRITE , MAP_SHARED , fd , 0)) == MAP_FAILED)
{
perror("读进程映射关系建立失败!\n");
exit(0);
}
/*映射关系建立成功,关闭文件*/
close(fd);
/*循环向映射文件内读取内容*/
while(1)
{
printf("%s\n" , ptr->msg);
sleep(timeout);
}
/*关闭映射*/
munmap(ptr , sizeof(msg_t));
exit(0);
}
以上就是本篇博客的全部内容了,大家有什么地方没有看懂的话,可以在评论区留言给我,咱要力所能及的话就帮大家解答解答
今天的学习记录到此结束啦,咱们下篇文章见,ByeBye!