内存映射概念及相关函数介绍

大家好,许久不见,空闲时间除了娱乐,一直忙着学习注释内核代码,如果感兴趣可以访问https://github.com/aeneag/linux_0.11 一起学习,同时忙着更新上线新的网站,争取月底上线更新完,今天来介绍一下与内存映射相关的知识和函数吧,对于大文件读取相对来说比较友好的一种方式。

内存映射概念

简单来说,就是把磁盘中的某个文件的一块区域,与内核中虚拟地址空间的一块区域一一对应,如下图所示:

001.jpg

道理很简单吧,内存映射根据种类分为:

  • 文件映射
  • 匿名映射

文件映射

文件映射:将文件的特定部分直接映射到虚拟进程地址空间。这样的映射使得虚拟进程地址空间中的一部分与内存RAM中的相应分页相对应。简而言之,通过文件映射,进程可以直接访问文件的某一片区域,而无需通过传统的读取或写入文件的方式。其有共享文件映射和私有文件映射。

  • 共享文件映射是一种在同一个文件上创建多个映射,当其中一个映射发生变更时,这些变更对共享同一文件映射的其他进程是可见的。这是因为这些进程共享相同的物理内存分页,使得它们能够实时观察到其他进程对映射所做的修改。此外,共享文件映射还会将这些变更同步反映到磁盘文件上,以确保对文件的永久性更改。另外这是一种替代write和read的比较好的方案。
  • 私有文件映射是一种在同一个文件上创建多个映射的方式,其中一个映射发生变更时,对共享该文件映射的其他进程是不可见的。在进行对某个分页的修改时,私有文件映射使用写时复制技术,通过重新分配一个分页并将原始分页内容复制到新分页中,然后修改进程相应的页表内容。这确保了对该分页的修改不会对底层磁盘文件产生影响。这种机制提供了一种有效的方式,使得多个进程可以并行地修改文件映射,而不必担心彼此之间的冲突或干扰。

匿名映射

匿名映射是一种将虚拟文件(在实际文件系统中没有对应文件)映射到虚拟进程地址空间的技术。在这种映射中,没有实际的文件与之相关联,而是直接在内存中创建了一块虚拟空间,供进程使用。这种映射通常用于需要在进程之间共享数据,而不必使用实际文件作为中介的场景。匿名映射在创建时不需要指定文件路径,而是直接为虚拟地址空间分配相应的内存。由于没有对应的物理文件,对这些映射的修改通常只存在于内存中,而不会影响到磁盘上的文件。

  • 共享文件映射(与上文一样不再赘述)
  • 私有文件映射(与上文一样不再赘述)

创建映射

 1/**
 2 * @brief  : 系统调用在调用进程的虚拟地址空间创建一个映射
 3 * @param  : addr   指定了映射被放置的开始虚拟地址,一般设置为NULL
 4 *           表示内核自己选择一个分页边界的整倍数的地址,如果是一个非
 5 *           NULL值,内核会把他作为一个提示地址,选择相邻的分页边界的
 6 *           整倍数的地址返回,如果设置了MAP_FIXED标志,addr就必须为
 7 *           一个分页边界的整倍数的地址
 8 * @param  : length  映射的字节数大小,系统会自动选择一个分页整倍数的
 9 *           长度,所以实际大小可能比length大
10 * @param  : port  一个位掩码,指定了施加于映射之上的保护信息,可以选择多个
11 *                 PROT_WRITE  可写
12 *                 PROT_READ   可读
13 *                 PROT_NONE   区域无法访问
14 *                 PROT_EXEC   区域可执行
15 * @param  : flags  控制映射操作的选项位掩码,只能选一个
16 *                 MAP_SHARED:创建共享映射
17 *                 MAP_PRIVATE:创建私有映射
18 * @param  : fd       被映射的文件的描述符
19 * @param  : offset   被映射文件的起点值,也就是从文件开始到要映射的地方的偏移量 
20 * @return : 成功返回映射之后的对应的虚拟地址,错误返回MAP_FAILED((void *)-1)
21 * @time   : 2023/11/23 19:52:44
22 */
23void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

相关说明已在函数上方注释。

解除映射

1void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
2/**
3 * @brief  : 从调用进程的虚拟进程地址空间删除一个映射关系
4 * @param  : addr 映射被放置的开始虚拟地址
5 * @param  : length 映射的长度,以字节数为大小
6 * @return : 返回0成功,失败返回-1
7 * @time   : 2023/11/23 20:01:13
8 */
9int munmap(void *addr, size_t length);

相关说明已在函数上方注释。

同步映射

对映射区域进行修改后,并不保证内核会立即将数据从内核高速缓存刷新到磁盘。为了在需要时手动控制这个过程,可以使用 msync() 函数。

msync() 函数允许程序员显式地将映射区域的数据同步到磁盘,确保对文件的修改被及时写入到永久存储。这对于确保数据的持久性和一致性非常重要。通过调用 msync(),程序可以要求内核立即将对映射区域的更改刷新到磁盘,而不必等待操作系统自行处理。

因此,使用 msync() 提供了对数据同步过程更精细的控制,尤其是在需要确保关键数据不会因为系统延迟而丢失或不一致的情况下。

 1/**
 2 * @brief  :
 3 * @param  : addr    映射被放置的开始虚拟地址
 4 * @param  : length  映射的长度
 5 * @param  : flags:
 6 *              MS_SYNC:  执行同步操作,阻塞到所有被修改的内存分页写入底层文件为止。
 7 *                        内核高速缓存区域马上同步底层文件
 8 *              MS_ASYNC: 执行异步操作,后面某个时候写入磁盘。内核高速缓存区域后面
 9 *                        某个时刻同步底层文件
10 * @return : 成功返回0,失败返回-1
11 * @time   : 2023/11/23 20:05:56
12 */
13int msync(void *addr, size_t length, int flags);

样例

实现了一个大文件的mmap映射加速访问的样例,重点是func开头的函数,映射一次,可以多次对其修改,灵活控制读写指针,代码没有时间注释,相信也是比较有意思的代码,调试的时候遇到了一个bug,编译通过,运行通过,但是就是结果不对,后来发现是 mmap函数的参数用的不对。

  1#include <sys/mman.h>
  2#include <sys/types.h>
  3#include <sys/stat.h>
  4#include <fcntl.h>
  5#include <unistd.h>
  6#include <stdio.h>
  7#include <stdlib.h>
  8#include <string.h>
  9#include <assert.h>
 10
 11#define MAX_FILE_SIZE (100 * 8)
 12
 13typedef struct file_info_s
 14{
 15    int fd;
 16    unsigned long long file_size;
 17    void *start_pointer;
 18    void *cur_pointer;
 19} file_info_t;
 20
 21void func_close(file_info_t *file_info, const char *func, int line)
 22{
 23    if ((-1 == msync(file_info->start_pointer, file_info->file_size, MS_ASYNC)) ||
 24        (-1 == munmap(file_info->start_pointer, file_info->file_size)) ||
 25        (-1 == close(file_info->fd)))
 26    {
 27        printf("error: close fd %d func %s line %d \n", file_info->fd, func, line);
 28        assert(0);
 29    }
 30}
 31void func_lseek(file_info_t *file_info, off_t offset, int whence)
 32{
 33    switch (whence)
 34    {
 35    case SEEK_SET:
 36        file_info->cur_pointer = (void *)((off_t)file_info->start_pointer + offset);
 37        break;
 38    case SEEK_CUR:
 39        file_info->cur_pointer = (void *)((off_t)file_info->cur_pointer + offset);
 40        break;
 41    default:
 42        file_info->cur_pointer = ((void *)0);
 43        break;
 44    }
 45    if ((unsigned long long)file_info->cur_pointer > ((unsigned long long)file_info->start_pointer + file_info->file_size))
 46    {
 47        printf("lseek error");
 48        func_close(file_info, __func__, __LINE__);
 49        assert(0);
 50    }
 51}
 52
 53void func_read(file_info_t *file_info, void *buf, size_t count)
 54{
 55    void *dest = ((void *)0);
 56    if (((void *)0) == buf)
 57    {
 58        printf("buf error");
 59        assert(0);
 60    }
 61    dest = memcpy(buf, file_info->cur_pointer, count);
 62
 63    if (dest != buf)
 64    {
 65        printf("read error");
 66        func_close(file_info, __func__, __LINE__);
 67        assert(0);
 68    }
 69    func_lseek(file_info, count, SEEK_CUR);
 70}
 71void func_write(file_info_t *file_info, const void *buf, size_t count)
 72{
 73    void *dest = ((void *)0);
 74    if (((void *)0) == buf)
 75    {
 76        printf("buf error");
 77        assert(0);
 78    }
 79    dest = memcpy(file_info->cur_pointer, buf, count);
 80    if (dest != file_info->cur_pointer)
 81    {
 82        printf("write error");
 83        func_close(file_info, __func__, __LINE__);
 84        assert(0);
 85    }
 86    func_lseek(file_info, count, SEEK_CUR);
 87}
 88file_info_t func_open(const char *pathname, int flags, int mode)
 89{
 90    struct stat st_file;
 91    file_info_t file_info;
 92    if (-1 == (file_info.fd = open(pathname, flags, mode)))
 93    {
 94        printf("open error");
 95        assert(0);
 96    }
 97
 98    if ((0 == fstat(file_info.fd, &st_file)) && S_ISREG(st_file.st_mode))
 99    {
100        file_info.file_size = (unsigned long long)st_file.st_size;
101    }
102    file_info.start_pointer =
103        file_info.cur_pointer =
104            mmap(((void *)0), file_info.file_size,
105                 PROT_WRITE | PROT_READ, MAP_SHARED, file_info.fd, SEEK_SET);
106    return file_info;
107}
108void create_new_file(void)
109{
110    int i = 0;
111    unsigned char *buf;
112    int fi;
113    if (((void *)0) == (buf = malloc(MAX_FILE_SIZE)))
114    {
115        printf("malloc error");
116        assert(0);
117    }
118    for (i = 0; i < MAX_FILE_SIZE; i++)
119    {
120        buf[i] = 0x00;
121    }
122    fi = open("my_rw_file", O_RDWR | O_CREAT, 00666);
123
124    for (i = 0; i < 3; ++i)
125    {
126        write(fi, buf, MAX_FILE_SIZE);
127    }
128    close(fi);
129}
130int main(int argc, char **argv)
131{
132    int i;
133    create_new_file();
134    unsigned char *buf;
135    // unsigned char *;
136    file_info_t fi;
137    fi = func_open("my_rw_file", O_RDWR | O_CREAT, 00666);
138    buf = malloc(16);
139    for (i = 0; i < 16; i++)
140    {
141        buf[i] = 0xff;
142    }
143    unsigned char *read_buf;
144    read_buf = malloc(48);
145    for (i = 0; i < 48; i++)
146    {
147        read_buf[i] = 0x00;
148    }
149    func_write(&fi, buf, 16);
150    func_lseek(&fi, 0, SEEK_SET);
151    func_read(&fi, read_buf, 48);
152    func_lseek(&fi, 32, SEEK_SET);
153    func_write(&fi, buf, 16);
154    func_lseek(&fi, 0, SEEK_SET);
155    func_read(&fi, read_buf, 48);
156    func_close(&fi, __func__, __LINE__);
157    return 0;
158}

结果

002.jpg

最后

最近要做的事情真的蛮多的,写一些文档本没什么意义,可能最大的意义就是对自己来说吧。完结撒花。


    


公众号'艾恩凝'
个人公众号
个人微信
个人微信
    吾心信其可行,
          则移山填海之难,
                  终有成功之日!
                                  ——孙文
    评论
    0 评论
avatar

取消