磁盘映射****MMAP
概述
存储映射 I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相
映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存
入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用 read 和 write 函数
的情况下,使用地址(指针)完成 I/O 操作。 使用存储映射这种方法,首先应通知内
核,将一个指定文件映射到存储区域中。这个映射工作可以通过 mmap 函数来实现。
与文件读取的区别(了解)
首先简单的回顾一下常规文件系统操作(调用 read/fread 等类函数)中,函数的调用
过程:
1 、进程发起读文件请求。
2 、内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此
文件的 inode( 存储文件元信息的区域 , 中文名索引节点 ) 。
3 、 inode 在 address_space( 地址空间 ) 上查找要请求的文件页是否已经缓存在页缓存
中。如果存在,则直接返回这片文件页的内容。
4 、如果不存在,则通过 inode 定位到文件磁盘地址,将数据从磁盘复制到页缓存。之后
再次发起读页面过程,进而将页缓存中的数据发给用户进程。
总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成
读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用
户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这
样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一
样,待写入的 buffer 在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,
再写回磁盘中(延迟写回),也是需要两次数据拷贝。
而使用 mmap 操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映
射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺
页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数
据传入内存的用户空间中,供进程使用。
总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而 mmap 操
控文件,只需要从磁盘到用户主存的一次数据拷贝过程。
mmap 效率更高
相关函数
mmap 函数
作用:
建立映射区
语法
所需头文件
#include <sys/mman.h>
函数
void *mmap(void *addr, size_t length, int prot, int flags,int fd,
off_t offset);
参数 :
addr 内核创建映射的地址,填 NULL, 由内核自己选取地址
length 长度 要申请的映射区的长度
prot 权限
PROT_READ 可读
PROT_WRITE 可写
flags 标志位
MAP_SHARED 共享的 -- 对映射区的修改会影响源文件
MAP_PRIVATE 私有的
fd 文件描述符 需要打开一个文件
offset 指定一个偏移位置 ,从该位置开始映射
返回值
成功 返回映射区的首地址
失败 返回 MAP_FAILED ((void *) -1)
munmap 函数
作用:
释放映射区
语法:
所需头文件
#include <sys/mman.h>
函数
int munmap(void *addr, size_t length)
参数 :
addr 映射区的首地址
length 映射区的长度
返回值
成功 返回 0
失败 返回 -1
truncate 函数
作用
会将参数 path 指定的文件大小改为参数 length 指定的大小 . 如果原来的文件大小比参数length 大 , 则超过的部分会被删去 .
函数
所需头文件
#include <unistd.h>
#include <sys/types.h>
函数
int truncate(const char *path, off_t length);
参数 :
path 要拓展的文件
length 要拓展的长度
使用步骤
1, 打开文件
2, 指定文件大小
3, 建立映射
4, 释放映射区
示例
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
// 1、通过open打开文件
int fd = open("tmp", O_RDWR | O_CREAT, 0666);
if (fd < 0)
{
perror("open");
return 0;
}
// 2、拓展文件大小
truncate("tmp", 16);
// 3、mmap建立映射
char *buf = (char *)mmap(NULL, 16, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, 0);
// 4、使用内存区域,读
printf("buf=%s\n", buf);
printf("buf=%s\n", buf);
// 4、使用内存区域,写
// strcpy(buf, "hello mmap");
// 5、断开映射
munmap(buf, 16);
close(fd);
return 0;
}
共享内存
概述
共享内存允许两个或者多个进程共享给定的存储区域。
物理内存 : 电脑物理内存就是指的内存条
虚拟内存 : 是系统默认在 C 盘划分的一部分磁盘空间临时存储数据用的
特点
1 、共享内存是进程间共享数据的一种最快的方法。 一个进程向共享的内存区域写入了
数据,共享这个内存区域的所有进程就可以立刻看到其中的内容 .
2 、使用共享内存要注意的是多个进程之间对一个给定存储区访问的互斥。 若一个进程
正在向共享内存区写数据,则在它做完这一步操作前,别的进程不应当去读、写这些数
据
在ubuntu部分版本中共享内存限制值如下
共享存储区的最小字节数: 1
共享存储区的最大字节数: 32M
共享存储区的最大个数: 4096
每个进程最多能映射的共享存储区的个数: 4096
共享内存命令
查看共享内存 ipcs -m
删除共享内存 ipcrm -m shmid
相关函数
shmget****函数
作用
创建或打开一块共享内存区 , 获得一个共享存储标识符
函数:
所需头文件
#include <sys/ipc.h>
#include <sys/shm.h>
函数
int shmget(key_t key, size_t size, int shmflg);
参数
key:IPC 键值 ( 进程间通讯键值 )
size:该共享存储段的长度 ( 字节 )
shmflg:标识函数的行为及共享内存的权限。
IPC_CREAT:创建, 如果不存在就创建
IPC_EXCL:如果已经存在则返回失败
位或权限位:共享内存位或权限位后可以设置共享内存的访问权限,格式和open函数的 mode_t 一样 , 但可执行权限未使用。
返回值 :
成功:返回共享内存标识符。
失败:返回-1
shmat 函数
作用:
建立进程和物理内存的映射
函数
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr,int shmflg);
参数 :
shmid:共享内存标识符。
shmaddr:共享内存映射地址 ( 若为 NULL 则由系 统自动指定 ) ,推荐使用NULL。
shmflg:共享内存段的访问权限和映射条件
0:共享内存具有可读可写权限。
SHM_RDONLY:只读。
SHM_RND:(shmaddr 非空时才有效)没有指定 SHM_RND 则此段连接到shmaddr 所指定的地址上 (shmaddr 必需页对齐 ) 。
指定了 SHM_RND 则此段连接到 shmaddr- shmaddr%SHMLBA 所表示的地址上。
返回值:
成功:返回共享内存段映射地址
失败:返回 -1
注意 :
shmat 函数使用的时候第二个和第三个参数一般设为 NULL 和 0, 即系统自动指定共享内存地址, 并且共享内存可读可写
shmdt 函数
作用
将共享内存和当前进程分离 ( 仅仅是断开本进程与共享内存的联系 , 并不删除共享内
存 ) 。
语法
所需头文件
#include <sys/types.h>
#include <sys/shm.h>
函数
int shmdt(const void *shmaddr);
参数:
shmaddr:共享内存映射地址。
返回值:
成功返回 0
失败返回 -1
shmctl 函数
作用
共享内存空间的控制。
函数
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:共享内存标识符。
cmd:函数功能的控制。
IPC_RMID:删除。
IPC_SET:设置 shmid_ds 参数。
IPC_STAT:保存 shmid_ds 参数。
SHM_LOCK:锁定共享内存段 ( 超级用户 ) 。
SHM_UNLOCK:解锁共享内存段。
buf : shmid_ds 数据类型的地址,用来存放或修改共享内存的属性。
返回值:
成功返回 0
失败返回 -1 。
注意:
SHM_LOCK 用于锁定内存,禁止内存交换。并不代表共享内存被锁定后禁止其它进程访问。其真正的意义是:被锁定的内存不允许被交换到虚拟内存中。这样做的优势在于让共享内存一直处于内存中,从而提高程序性能
使用步骤
1.获取唯一key值
2.获取共享内存标识符
3.建立共享内存映射
4.操作映射
1.读
2.写
5.释放映射
示例
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main(int argc, char const *argv[])
{
// 1、获取唯一的key值
key_t key = ftok("/", 2023);
// 2、根据唯一的key的共享内存标识(分配物理内存)
int shm_id = shmget(key, 32, IPC_CREAT | 0666);
printf("shm_id=%d\n", shm_id);
// 3、建立进程和物理内存的映射
char *p = (char *)shmat(shm_id, NULL, 0);
// 4,操作映射,写
strcpy(p, "hello shm");
// 4,操作映射,读
// printf("%s\n",p);
// 5、断开进程和物理内存的映射
shmdt(p);
return 0;
}