Linux系统 mmap 存储映射

2024-04-26 03:08
文章标签 linux 系统 存储 映射 mmap

本文主要是介绍Linux系统 mmap 存储映射,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

设备缓存与设备内存

设备缓存位于主内存中,是驱动程序管理的一段内存区域,比如framebuffer, driver工作需要的scratch pad buffer等等。

而设备内存是位于设备上的,和设备是分不开的,比如某些设备上的FIFO,SRAM,或者显卡上的DOORBELL区等,都属于设备内存。

不同的内存映射给用户态或者内核态的时候,需要使用不同的API。驱动开发者需要保证在正确的状态下使用正确的接口将虚拟地址映射到正确的存储类型上,不能混用。

mmap函数原型: 

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

void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
addr:推荐映射定义,如果为NULL,内核自动分配,如果flags设置了MAP_FIXED且addr合法,则映射addr开始的地址并返回。
length: length是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起,一般为PAGE整数倍,不足一内存页按一内存页处理.
prot:映射读写属性等等,期望的内存保护标志,不能与文件的打开模式冲突.
flags:私有,共享,匿名等等MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。MAP_FIXED:映射addr开始的地址,如果事先存在VMA,则删除原来的在做映射
fd:对于file backend映射,需要指定文件fd,如果为匿名映射,则为-1.
offset:文件内字节从偏移处开始映射

根据内核代码,offset参数的单位是字节,内核在调用ksys_mmap_pgoff前会将其单位转换为page。内核中mmap函数的vm_pgoff来源于off >> PAGE_SHIFT,在mmap实现中可以根据此映射fd文件的不同区域。

映射属性:

映射建立后,会在进程的地址空间中创建一个struct vm_area_struct对象,来描述虚拟地址空间的信息,信息包括起始地址,区域大小,映射属性FLAG等等,FLAG包括:

VM_IO: Memory Maped I/O or similary.(MMIO).

VM_DONTCOPY: 在fork子进程的时候不要拷贝这个部分。

VM_DONTEXPAND: 不能用mremap 函数扩展映射区域。

VM_DONTDUMP:在coredump时不保存这个部分。

VM_PFNMAP: 这个比较重要,它表示这个映射针对的是PURE PFN,纯物理空间,没有struct page 对其进行管理。所以有些情况下,使用受到限制,通常设备的IO映射以及PCI BAR空间的映射,都会带有这个标志。比如AMD KFD GPU驱动中在IO映射时就设置了这个标志:

/dev/mem设备节点可以将物理内存全部映射到用户态,这里实践一把。

配置:

为了启用/dev/mem设备节点,并且BYPASS调如下的检查逻辑,需要设置内核如下的配置单,CONFIG_STRICT_DEVMEM主要是对映射范围进行安全性检查,避免将一些限制区域映射到用户态。

CONFIG_DEVMEM=y
# CONFIG_STRICT_DEVMEM is not set
# CONFIG_X86_PAT is not set

来自于LDD3的测试代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <limits.h>int main(int argc, char **argv)
{char *fname;FILE *f;unsigned long offset, len;void *address;if (argc !=4|| sscanf(argv[2],"%li", &offset) != 1|| sscanf(argv[3],"%li", &len) != 1) {fprintf(stderr, "%s: Usage \"%s <file> <offset> <len>\"\n", argv[0],argv[0]);exit(1);}/* the offset might be big (e.g., PCI devices), but conversion trims it */if (offset == INT_MAX) {if (argv[2][1]=='x')sscanf(argv[2]+2, "%lx", &offset);elsesscanf(argv[2], "%lu", &offset);}fname=argv[1];if (!(f=fopen(fname,"r"))) {fprintf(stderr, "%s: %s: %s\n", argv[0], fname, strerror(errno));exit(1);}address=mmap(0, len, PROT_READ, MAP_FILE | MAP_PRIVATE, fileno(f), offset);if (address == (void *)-1) {fprintf(stderr,"%s: mmap(): %s\n",argv[0],strerror(errno));exit(1);}fclose(f);fprintf(stderr, "mapped \"%s\" from %lu (0x%08lx) to %lu (0x%08lx)\n",fname, offset, offset, offset+len, offset+len);fwrite(address, 1, len, stdout);return 0;
}
$ sudo ./a.out /dev/mem  0x10000 0x1000|od -Ax -t x1

这段程序非常好用,基本上可以映射/dev/mem背后的所有物理内存,上面的例子不够特殊,我们以物理地址0内存为例,首先看到物理内存确实是从0开始,0物理地址映射的确实是RAM,通过内核模块获取page_address(pfn_to_page(0))地址,打印其中内容:

然后xxd直接获取设备内容如下:

ldd程序获取内容如下:

可见三种方法得到的数据完全一致。

devmem2

开源工具devmem2是一个LINUX命令行工具,用于读写物理内存地址,通过将/dev/mem映射到进程地址空间,devmem2允许用户直接访问物理内存。项目源码在如下地址:

GitHub - radii/devmem2: devmem2 - simple program to read/write from/to any location in memory.

CONFIG_X86_PAT

根据menuconfig的解释,PAT是页面属性表的简称(Page Attribute Table),功能是传统的MTRR的(Memory Type Range Register)扩展和增强,目的是对页面的CACHE访问属性进行配置,比如可用于为不同的用途(例如,可缓存,不可更改,写合并等)指定物理地址空间的不同部分。

PAT(Page Attribute Table) 是在线性地址空间中对映射的物理内存的一种缓存策略的描述,物理内存中的一个物理页框采用什么样的缓存策略,是由两方面决定的,一个是在物理地址空间进行描述的MTRR,一个是线性空间的PAT。

Write Combine 和 Cache 是什么关系 - 知乎

配置CONFIG_X86_PAT的目的是DISABLE如下的检查。

remap_pfn_range->track_pfn_remap(vma, &prot, remap_pfn, addr, PAGE_ALIGN(size));

如果映射区有设置CACHE 属性,则调用lookup_memtyp寻找:

设置文件在arch/x86/mm/pat.c这里。

PCIE 空间的配置方式:

PCIe架构定义了4种地址空间:配置空间、Memory空间、IO空间和message空间:

每个PCIe Function都有4KB的配置空间(Configuration Space)。前256 Bytes 是和PCI 兼容的配置空间(64B是HEADER),剩余的是PCIe扩展配置空间(Extended Configuration Space)。

PCIe设备声明的两种不同类型的MMIO:可预取MMIO(Prefetchable MMIO,P-MMIO)和不可预取MMIO(Non-Prefetchable MMIO,NP-MMIO)。可预取空间有两个意义十分明确的属性:

  • 读操作不存在副作用。(Reads do not have side effects)(读操作不会出发设备逻辑操作)
  • 允许写合并(Write merging is allowed)(Write Conbined.)

X86采用独立编址的方式,将memory操作与外设IO操作分开了,才有了memory空间和IO空间的区分。ARM采用统一编码的方式。

登记物理内存的CACHE属性

包括系统内存和设备内存在内,按照归属分成不同的区段,内核驱动模块可以调用reserve_memtype,传入物理地址和SIZE大小,将期望的CACH属性传递进去,函数会维护建立i一个红黑树,维护所有这些区段的CACHE属性,当驱动进行实际的映射时,会调用track_pfn_remap->lookup_memtype寻找对应的属性,将其设置在MMU的PTE页表中生效,运行机制如此。

调试PAT属性

debugfs中存在pat_memtype_list文件节点,可以调试系统中记录的物理内存区的CACHE属性:,使用如下方式查看

cat /sys/kernel/debug/x86/pat_memtype_list

可以看到,显存的CACHE属性为WC,表示可以将零散数据BUST起来,增加传输效率。

以我的集成显卡为例,BAR地址是0xb0000000,属性也是WC:

这篇关于Linux系统 mmap 存储映射的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/936549

相关文章

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信

Linux命令rm如何删除名字以“-”开头的文件

《Linux命令rm如何删除名字以“-”开头的文件》Linux中,命令的解析机制非常灵活,它会根据命令的开头字符来判断是否需要执行命令选项,对于文件操作命令(如rm、ls等),系统默认会将命令开头的某... 目录先搞懂:为啥“-”开头的文件删不掉?两种超简单的删除方法(小白也能学会)方法1:用“--”分隔命

使用MapStruct实现Java对象映射的示例代码

《使用MapStruct实现Java对象映射的示例代码》本文主要介绍了使用MapStruct实现Java对象映射的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、什么是 MapStruct?二、实战演练:三步集成 MapStruct第一步:添加 Mave