Linux内核 mmap内存映射的实现原理

在Linux内核以及Linux系统编程的时候,经常会碰到mmap内存映射,mmap函数是实现高性能编程的一个关键点。本文详细介绍一下mmap实现原理。

虚拟地址映射物理地址

虚拟地址映射物理地址采用的是页表机制,64位CPU采用的是4级页表。 64位CPU虚拟地址长度为64位,但实际只用48位就已满足虚拟地址映射物理内存的要求,如下图:

在这里插入图片描述
用户空间和内核空间共256T,2的48次方刚好为256T,所以48位地址空间能映射所有的虚拟地址。

48位虚拟地址由五部分组成:

  • pgd表偏移,四级表,9位
  • pud表偏移,三级表,9位
  • pmd表偏移,二级表,9位
  • ptl表偏移,一级表,9位
  • 物理页偏移,12位

pgd,pud,pmd,ptl表实现原理都相同,我们以pgd来讲解。

一张pgd表对应一个物理页,一个物理页的大小为4KB,一个pgd_t表项为8个字节,一张pgd表能存储4*1024/8=512个表项。

2的9次方等于512,所以采用9位的表偏移就能索引整张表的表项。

在这里插入图片描述
虚拟地址映射物理地址需要依次索引pgd,pud,pmd,ptl表,具体过程如下:

  • 查询pgd表:查询pgd表,首先需要找到pgd表物理页首地址,pgd表物理页首地址由task_struct->mm_struct->pgd成员保存,每个进程的task_struct->mm_struct->pgd成员数值不同,所以不同的进程即使使用相同的虚拟地址也不会访问相同到物理地址。通过pgd表首地址+虚拟地址pgd表偏移索引到pgd_t表项完成pgd表查询。

  • 查询pud表:pgd_t表项存储的是pud表物理页首地址。通过pud表首地址+虚拟地址pud表偏移索引到pud_t表项完成pud表查询。

  • 查询pmd表:pud_t表项存储的是pmd表物理页首地址。通过pmd表首地址+虚拟地址pmd表偏移索引到pmd_t表项完成pmd表查询。

  • 查询ptl表:pmd_t表项存储的是ptl表物理页首地址。通过ptl表首地址+虚拟地址ptl表偏移索引到pte表项完成ptl表查询。

  • 步骤5:映射物理地址:pte表项存储的是物理页首地址,pte+虚拟地址物理页偏移就能定位到物理地址。

定位到物理地址后,虚拟地址映射物理地址的过程就已完成。

在这里插入图片描述

mmap实现原理

mmap函数是一种内存映射文件的方法,它可以将一个文件或设备映射到进程的地址空间中,使得进程可以像访问内存一样访问文件或设备。

mmap可以分为:文件映射和匿名映射。

mmap函数主要工作就是创建VMA。

VMA简介

VMA(Virtual Memory Area,虚拟内存区域)是Linux内核中用于管理进程虚拟内存的数据结构。每个进程都有一个VMA链表,用于描述进程的虚拟地址空间的不同区域。

VMA包含了一段连续的虚拟地址空间,它定义了该区域的起始地址、结束地址以及一些属性信息。VMA可以表示进程的代码段、数据段、堆、栈等不同的内存区域。

VMA对应Linux内核struct vm_area_struct对象。

struct vm_area_struct {  /* The first cache line has the info for VMA tree walking. */  unsigned long vm_start;         /* Our start address within vm_mm. */  unsigned long vm_end;           /* The first byte after our end address within vm_mm. */  /* linked list of VM areas per task, sorted by address */  struct vm_area_struct *vm_next, *vm_prev;  struct rb_node vm_rb;  unsigned long rb_subtree_gap;  struct mm_struct *vm_mm;        /* The address space we belong to. */  pgprot_t vm_page_prot;          /* Access permissions of this VMA. */  unsigned long vm_flags;         /* Flags, see mm.h. */  struct {  struct rb_node rb;  unsigned long rb_subtree_last;  } shared;  struct list_head anon_vma_chain; /* Serialized by mmap_sem & * page_table_lock */  struct anon_vma *anon_vma;      /* Serialized by page_table_lock */  /* Function pointers to deal with this struct. */  const struct vm_operations_struct *vm_ops;  /* Information about our backing store: */  unsigned long vm_pgoff;         /* Offset (within vm_file) in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */  struct file * vm_file;          /* File we map to (can be NULL). */  void * vm_private_data;         /* was vm_pte (shared mem) */  ...  
};  

struct vm_area_struct结构体主要成员如下:

  • vm_start:虚拟内存区域起始地址。

  • vm_end:虚拟内存区域结束地址,vm_end减去vm_start为映射区域长度。

  • vm_page_prot:虚拟内存访问权限,PROT_READ:可读,PROT_WRITE:可写,PROT_EXEC:可执行

  • vm_page_flags:内存映射标志,MAP_SHARED:共享映射,MAP_PRIVATE:私有映射

  • vm_ops:文件映射操作集合,匿名映射为NULL。

  • vm_pgoff:文件映射文件偏移量,匿名映射无效。

  • vm_file:映射文件,匿名映射为NULL。

注意:VMA用于指导虚拟内存映射物理内存,没有VMA指导无法完成虚拟地址和物理地址映射。

其中需要重点关注的是vm_ops变量,它指向的是一组函数指针,定义如下:

struct vm_operations_struct {  void (*open)(struct vm_area_struct * area);  void (*close)(struct vm_area_struct * area);  int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);  void (*map_pages)(struct vm_area_struct *vma, struct vm_fault *vmf);  int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);  int (*pfn_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);  int (*access)(struct vm_area_struct *vma, unsigned long addr,  void *buf, int len, int write);  const char *(*name)(struct vm_area_struct *vma);  struct page *(*find_special_page)(struct vm_area_struct *vma,  unsigned long addr);  
};  

当进程在申请的内存的时候,linux内核其实只分配一块虚拟内存地址,并没有分配实际的物理内存,相当于操作系统只给进程这一块地址的使用权。只有当程序真正使用这块内存时,会产生一个缺页异常,这时内核去真正为进程分配物理页,并建立对应的页表,从而将虚拟内存和物理内存建立一个映射关系,这样可以做到充分利用到物理内存。

mmap系统调用

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

参数如下:

  • start:映射空间的起始地址,一般设置为 NULL;
  • length:映射空间的长度;
  • prot:内存保护标志,包括PROT_EXEC(可执行)、PROT_READ(可读)、PROT_WRITE(可写)、PROT_NONE(不可访问) ;
  • flags:映射类型,通常用来标记共享内存(MAP_SHARED)、匿名映射(MAP_ANONYMOUS)等。
  • fd:真正要映射的文件描述符;
  • offset:映射文件的偏移量。

一个简单的demo如下:

int main(int argc, char **argv)  
{  char *filename = "/tmp/foo.data";  struct stat stat;  int fd = open(filename, O_RDWR, 0);  fstat(fd, &stat);  void *bufp = mmap(NULL, stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);  memcpy(bufp, "Linuxdd", 7); munmap(bufp, stat.st_size); close(fd);return 0;  
}  

从demo中可以看出,mmap是将一个文件直接映射到进程的地址空间,进程可以像操作内存一样去读写磁盘上的文件内容,而不需要再调用read/write等系统调用。

源码分析

基于3.10.0-514

SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,unsigned long, prot, unsigned long, flags,unsigned long, fd, unsigned long, off)
{long error;error = -EINVAL;if (off & ~PAGE_MASK) //判断off是不是按页対齐的goto out;error = sys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
out:return error;
}

内部直接调用的是sys_mmap_pgoff函数:

SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,unsigned long, prot, unsigned long, flags,unsigned long, fd, unsigned long, pgoff)
{struct file *file = NULL;unsigned long retval = -EBADF;if (!(flags & MAP_ANONYMOUS)) {//有名文件映射audit_mmap_fd(fd, flags);if (unlikely(flags & MAP_HUGETLB))return -EINVAL;file = fget(fd);//根据fd得到对应file结构if (!file)goto out;if (is_file_hugepages(file))//如果是hugetlbfs文件系统文件,将文件大小对齐到页面大小len = ALIGN(len, huge_page_size(hstate_file(file)));} else if (flags & MAP_HUGETLB) {struct user_struct *user = NULL;struct hstate *hs = hstate_sizelog((flags >> MAP_HUGE_SHIFT) &SHM_HUGE_MASK);if (!hs)return -EINVAL;len = ALIGN(len, huge_page_size(hs));/** VM_NORESERVE is used because the reservations will be* taken when vm_ops->mmap() is called* A dummy user value is used because we are not locking* memory so no accounting is necessary*/file = hugetlb_file_setup(HUGETLB_ANON_FILE, len,VM_NORESERVE,&user, HUGETLB_ANONHUGE_INODE,(flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK);if (IS_ERR(file))return PTR_ERR(file);}flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);if (file)fput(file);
out:return retval;
}

sys_mmap_pgoff只是做了一些准备,其通过调用位于mm/util.c的vm_mmap_pgoff进行地址映射,部分源码如下:

unsigned long do_mmap_pgoff(struct file *file,  unsigned long addr,  unsigned long len,  unsigned long prot,  unsigned long flags,  unsigned long pgoff,  unsigned long *populate)  
{  // 申请一个vm_area_struct结构体  struct vm_area_struct *vma;  // ...  // 为vma分配内存  vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);  if (!vma)  goto error_getting_vma;  // ... 初始化相关  // 如果是文件映射,给文件添加一个引用计数  if (file) {  region->vm_file = get_file(file);  vma->vm_file = get_file(file);  }  down_write(&nommu_region_sem);  // ...  // 真正去做文件映射  if (file && vma->vm_flags & VM_SHARED)  ret = do_mmap_shared_file(vma);  else  ret = do_mmap_private(vma, region, len, capabilities);  // ...  // 将vma插入到链表中  add_vma_to_mm(current->mm, vma);  // ...  
}  

在做文件映射时,如果不是共享的文件,则调用的是do_mmap_private函数,此函数流程如下:

static int do_mmap_private(struct vm_area_struct *vma,  struct vm_region *region,  unsigned long len,  unsigned long capabilities)  
{  // ...  if (capabilities & NOMMU_MAP_DIRECT) {  // 调用文件映射的方法  ret = vma->vm_file->f_op->mmap(vma->vm_file, vma);  // ...  }  // ...  
}  

此处f_op->mmap指向的是generic_file_mmap:

int generic_file_mmap(struct file * file, struct vm_area_struct * vma)  
{  struct address_space *mapping = file->f_mapping;  if (!mapping->a_ops->readpage)  return -ENOEXEC;  file_accessed(file);  vma->vm_ops = &generic_file_vm_ops;  return 0;  
}  

内部就是给前面提到的vm_ops函数指针的集合赋值,generic_file_vm_ops指向的是针对文件操作的一系列函数:

const struct vm_operations_struct generic_file_vm_ops = {  .fault      = filemap_fault,  .map_pages  = filemap_map_pages,  .page_mkwrite   = filemap_page_mkwrite,  
}; 

其中包括缺页处理,映射页,置为可写三个操作;其中缺页异常的处理逻辑如下:

int filemap_fault(struct vm_area_struct *vma, struct vm_fault *vmf)  
{  // ...  // 先判断当前页有没有被cache  page = find_get_page(mapping, offset);  if (likely(page) && !(vmf->flags & FAULT_FLAG_TRIED)) {  // 预读机制,从cache中拿到数据  do_async_mmap_readahead(vma, ra, file, page, offset);  } else if (!page) {  // 未cache到,直接同步读取  do_sync_mmap_readahead(vma, ra, file, offset);  count_vm_event(PGMAJFAULT);  mem_cgroup_count_vm_event(vma->vm_mm, PGMAJFAULT);  ret = VM_FAULT_MAJOR;  
retry_find:  page = find_get_page(mapping, offset);  if (!page)  goto no_cached_page;  }  // ...  // 找到对应页将其赋值给vmf,并返回  vmf->page = page;  return ret | VM_FAULT_LOCKED;  // ...  
}  

总结mmap文件映射过程:

  • 用户在进程中触发mmap操作
  • 内核对参数做基本的校验,并针对映射长度做一些内存对齐
  • 分配vm_area_struct结构,并对其进行初始化; 调用文件系统的mmap映射,将缺页异常等函数指针赋于vm_ops
  • 将新建的vm_area_struct结构插入到mm链表中; 当进程访问这片内存时,引发缺页异常,从而调用filemap_fault
  • 缺页异常查找cache中有无请求的页,如果没有,内核发起请求将数据从磁盘装入内存

与read/write的区别

在这里插入图片描述

用户进程发起read操作,内核会做一些基本的page cache判断,从磁盘中读取数据到kernel buffer中;,然后内核将buffer的数据再拷贝至用户态的user buffer,唤醒用户进程继续执行。

在这里插入图片描述

内核直接将内存暴露给用户态,用户态对内存的修改也直接反映到内核态,少了一次的内核态至用户态的内存拷贝,速度上会有一定的提升。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/876695.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙 HarmonyOS NEXT端云一体化开发-认证服务篇

一、开通认证服务 地址:AppGallery Connect (huawei.com) 步骤: 1 进入到项目设置页面中,并点击左侧菜单中的认证服务 2 选择需要开通的服务并开通二、端侧项目环境配置 添加依赖 entry目录下的oh-package.json5 // 添加:主要前…

《python程序语言设计》第6章14题 估算派值 类似莱布尼茨函数。但是我看不明白

这个题提供的公式我没看明白,后来在网上找到了莱布尼茨函数 c 0 for i in range(1, 902, 100):a (-1) ** (i 1)b 2 * i - 1c a / bprint(i, round(4 / c, 3))结果 #按题里的信息,但是结果不对,莱布尼茨函数到底怎么算呀。

PyTorch深度学习快速入门(上)

PyTorch深度学习快速入门(上) 一、前言(一)PyTorch环境配置(二)Python编译器的选择(三)Python学习中的两大法宝函数 二、如何加载数据(一)Dataset与Dataloade…

轻松学EntityFramework Core--模型创建

一、使用代码优先(Code-First)创建模型 Code-First 方法是 EF Core 提供的一种用于定义模型的方式,它允许开发人员通过编写 C# 类来定义数据库模式,再通过迁移命令生成数据库表。下面我们来一起看一下代码优先如何使用。 1.1、创…

lua 游戏架构 之 游戏 AI (六)ai_auto_skill

定义一个为ai_auto_skill的类,继承自ai_base类。ai_auto_skill类的目的是在AI自动战斗模式下,根据配置和条件自动选择并使用技能。 lua 游戏架构 之 游戏 AI (一)ai_base-CSDN博客文章浏览阅读379次。定义了一套接口和属性&#…

【原创】使用keepalived虚拟IP(VIP)实现MySQL的高可用故障转移

1. 背景 A、B服务器均部署有MySQL数据库,且互为主主。此处为A、B服务器部署MySQL数据库实现高可用的部署,当其中一台MySQL宕机后,VIP可自动切换至另一台MySQL提供服务,实现故障的自动迁移,实现高可用的目的。具体流程…

快速安装torch-gpu和Tensorflow-gpu(自用,Ubuntu)

要更详细的教程可以参考Tensorflow PyTorch 安装(CPU GPU 版本),这里是有基础之后的快速安装。 一、Pytorch 安装 conda create -n torch_env python3.10.13 conda activate torch_env conda install cudatoolkit11.8 -c nvidia pip ins…

mstc远程连接不锁屏

连接不锁屏 方法一 方法二 win10 解决多用户同时远程连接教程(超详细图文)_win10多用户登录-CSDN博客 win7软件 logout.bat for /f "skip1 tokens3" %%s in (query user %USERNAME%) do (%windir%\System32\tscon.exe %%s /dest:console) …

Datawhale AI 夏令营——AI+逻辑推理——Task1

# Datawhale AI 夏令营 夏令营手册:从零入门 AI 逻辑推理 比赛:第二届世界科学智能大赛逻辑推理赛道:复杂推理能力评估 代码运行平台:魔搭社区 比赛任务 本次比赛提供基于自然语言的逻辑推理问题,涉及多样的场景&…

React Native 与 Flutter:你的应用该如何选择?

Flutter 和 React Native 都被认为是混合应用程序开发中的热门技术。然而,当谈到为你的项目使用框架时,你必须考虑哪一个是最好的:Flutter 还是 React Native? 本篇文章包含 Flutter 和 React Native 在各个方面的差异。因此&…

正则表达式与文本处理

目录 一、正则表达式 1、正则表达式定义 1.1正则表达式的概念及作用 1.2、正则表达式的工具 1.3、正则表达式的组成 2、基础正则表达式 3、扩展正则表达式 4、元字符操作 4.1、查找特定字符 4.2、利用中括号“[]”来查找集合字符 4.3、查找行首“^”与行尾字符“$”…

Lesson 52 What nationality are they? Where do they come from?

Lesson 52 What nationality are they? Where do they come from? 词汇部分 the U.S. 美国 全称:The United States of America    美利坚合众国 其他称呼:the States      the U.S.A.      Uncle Sam Brazil n. 巴西 Brazilian a. 巴…

LeetCode算法——滑动窗口矩阵篇

1、长度最小的子数组 题目描述&#xff1a; 解法&#xff1a; 设一个 for 循环来改变指向窗口末尾的指针&#xff0c;再不断抛弃当前窗口内的首元素 最终确定满足条件的最小长度 class Solution { public:int minSubArrayLen(int target, vector<int>& nums) {int …

duilib中设置窗口透明度的接口CPaintManagerUI::SetTransparent有问题导致使用duilib窗口实现异形窗口无效的排查

目录 1、duilib框架中设置窗口透明度的代码说明 2、UpdateLayeredWindow调用失败,发现添加的WS_EX_LAYERED风格被删除了 3、窗口有WS_EX_LAYERED风格了,但UpdateLayeredWindow调用依旧失败 4、如何知道SetLayeredWindowAttributes函数调用之后再调用UpdateLayeredWindow…

苹果电脑暂存盘已满怎么清理 Mac系统如何清理磁盘空间 清理MacBook

Mac电脑用户在长时间使用电脑之后&#xff0c;时常会看到“暂存盘已满”的提示&#xff0c;这无疑会给后续的电脑使用带来烦恼&#xff0c;那么苹果电脑暂存盘已满怎么清理呢&#xff0c;下面将给大家带来一些干货帮你更好地解决这个问题。 首先我们要搞明白为什么暂存盘会满&…

c++ 智能指针shared_ptr与make_shared

shared_ptr是C11引入的一种智能指针&#xff0c;‌它允许多个shared_ptr实例共享同一个对象&#xff0c;‌通过引用计数来管理对象的生命周期。‌当最后一个持有对象的shared_ptr被销毁时&#xff0c;‌它会自动删除所指向的对象。‌这种智能指针主要用于解决资源管理问题&…

【运维自动化-配置平台】模型及模型关联最小化实践

蓝鲸智云配置平台&#xff0c;以下简称配置平台 我们知道主机是配置平台最常见的管控资源对象&#xff0c;在业务拓扑里可以通过划分模块来清晰的可视化管理&#xff1b;那其他资源如何通过配置平台来纳管呢&#xff0c;比如网络设备交换机。场景需求&#xff1a;如何把交换机…

【前端 10】初探BOM

初探BOM&#xff1a;浏览器对象模型 在JavaScript的广阔世界中&#xff0c;BOM&#xff08;Browser Object Model&#xff0c;浏览器对象模型&#xff09;扮演着举足轻重的角色。它为我们提供了一套操作浏览器窗口及其组成部分的接口&#xff0c;让我们能够通过编写JavaScript…

QT--线程

一、线程QThread QThread 类提供不依赖平台的管理线程的方法&#xff0c;如果要设计多线程程序&#xff0c;一般是从 QThread继承定义一个线程类&#xff0c;在自定义线程类里进行任务处理。qt拥有一个GUI线程,该线程阻塞式监控窗体,来自任何用户的操作都会被gui捕获到,并处理…