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使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

CentOS系统Maven安装教程分享

《CentOS系统Maven安装教程分享》本文介绍了如何在CentOS系统中安装Maven,并提供了一个简单的实际应用案例,安装Maven需要先安装Java和设置环境变量,Maven可以自动管理项目的... 目录准备工作下载并安装Maven常见问题及解决方法实际应用案例总结Maven是一个流行的项目管理工具

Java中基于注解的代码生成工具MapStruct映射使用详解

《Java中基于注解的代码生成工具MapStruct映射使用详解》MapStruct作为一个基于注解的代码生成工具,为我们提供了一种更加优雅、高效的解决方案,本文主要为大家介绍了它的具体使用,感兴趣... 目录介绍优缺点优点缺点核心注解及详细使用语法说明@Mapper@Mapping@Mappings@Co

Redis存储的列表分页和检索的实现方法

《Redis存储的列表分页和检索的实现方法》在Redis中,列表(List)是一种有序的数据结构,通常用于存储一系列元素,由于列表是有序的,可以通过索引来访问元素,因此可以很方便地实现分页和检索功能,... 目录一、Redis 列表的基本操作二、分页实现三、检索实现3.1 方法 1:客户端过滤3.2 方法

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程