驱动开发系列06 - 内存映射和DMA

news2024/11/27 12:15:26

目录

一:概述

二:Linux中的内存管理

 地址类型

 物理地址与页

 高内存和低内存

 内存映射和页结构体

 页表

 虚拟内存区域

 vm_area_struct 结构

 进程内存映射

三:mmap 设备操作

使用 remap_pfn_range

一个简单的实现

添加 VMA 操作

使用 nopage 映射内存

重新映射特定 I/O 区域

四:直接I/O操作

五:异步I/O操作

六:直接内存访问(DMA)

分配 DMA 缓冲区      

总线地址

通用 DMA 层

设备DMA寻址范围

DMA 映射

设置一致性 DMA 映射

DMA pools

设置流式 DMA 映射

单页流式映射

分散/聚集映射

PCI 64位地址映射

 一个 PCI DMA 示例

        


一:概述

        本文深入探讨 Linux 内存管理子系统,重点介绍对设备驱动程序编写者有用的技术。许多类型的驱动程序编程都需要对虚拟内存子系统的工作原理有一定的了解;当我们编写一些更复杂、对性能要求更高的驱动时,本文所涉及的内容就会派上用场。虚拟内存子系统也是 Linux 核心内核中非常有趣的一部分,因此值得一看。

        本文内容分为三节:
        第一节涉及 mmap 系统调用的实现,该调用允许将设备内存直接映射到用户进程的地址空间。将设备内存直接映射到用户进程的地址空间。虽然并非所有设备并都需要 mmap 支持,但对于某些设备,映射设备内存可以显著提高性能。

        第二节我们将讨论直接从内核态访问用户态内存页面的问题,需要这种功能的驱动程序相对较少;在一般情况下,内核会自动执行这种映射,而驱动程序不会感知到这一点。不过,了解如何将用户空间内存映射到内核(使用 get_user_pages)还是很有用的。

        第三节我们讨论直接内存访问(DMA)I/O 操作,它为外部设备提供了直接访问系统内存的能力。

        当然,所有这些技术都需要了解 Linux 内存管理的工作原理,因此,我们首先对该子系统进行概述。

二:Linux中的内存管理

        本节不描述操作系统中的内存管理理论,而是试图指出 Linux 实现的主要特点。尽管你不需要成为一个 Linux 虚拟内存大师来实现 mmap,但对其工作原理有一个基本的了解还是很有用的。接下来将对内核用于管理内存的数据结构进行相当长篇幅的描述。在介绍了必要的背景知识后,我们就可以开始使用这些数据结构了。

        地址类型

        当然,Linux是一个虚拟内存系统,这意味着用户程序看到的地址并不直接是硬件使用的物理地址。虚拟内存引入了一个中间层,可以做很多事情。有了虚拟内存,在系统上运行的程序可以分配比实际可用内存多得多的内存; 事实上,即使一个进程的虚拟地址空间也可能大于系统的物理内存。虚拟内存还允许程序对进程的地址空间进行一些操作,包括将程序的虚拟内存映射到设备内存上面。

        到目前为止,我们已经讨论了虚拟地址和物理地址,但忽略了其中的一些细节。Linux 系统可以处理多种类型的地址,每种地址类型都有自己的含义。

        下面列出了 Linux 中使用的地址类型。

        用户虚拟地址
        是用户空间程序使用的地址。用户地址长度为 32 或 64 位,具体取决于底层硬件架构,每个进程都有自己的虚拟地址空间。

        物理地址
        是处理器访问系统内存使用的地址。物理地址为 32 位或 64 位;在某些情况下,即使是 32 位系统也可以使用更大的物理地址。

        总线地址
        是总线访问外设内存使用的地址。总线地址在很大程度上取决于体系结构,通常,它们与处理器使用的物理地址相同。但也不一定, 有些架构可以提供 I/O 内存管理单元(IOMMU),它可以将总线地址映射到系统内存。 IOMMU 简化了内存操作。

        内核逻辑地址
        构成内核地址空间,是内核程序使用的地址。这些地址映射了部分或全部的系统内存内存,通常被当作物理地址处理。在大多数体系结构中,逻辑地址与相关物理地址之间只差一个偏移量。逻辑地址使用硬件的本地指针大小,因此在 32 位系统上可能无法寻址所有物理内存。逻辑地址通常使用无符号 long 或 void * 类型的变量存储。从 kmalloc 返回地址是内核逻辑地址。

        内核虚拟地址
        也是内核程序使用的地址,内核虚拟地址与逻辑地址类似,都是从内核空间地址到物理地址的映射。内核虚拟地址不一定对应实际的物理地址,所以它与逻辑地址不一定具有一对一的映射关系。但是,所有逻辑地址都是内核虚拟地址。所有逻辑地址都是内核虚拟地址,但许多内核虚拟地址并不是逻辑地址。例如,由 vmalloc 分配的内存是虚拟地址(但没有直接映射物理地址),而 kmap 函数(本章稍后描述)也会返回虚拟地址。虚拟地址通常存储在指针变量中。

        下面的图中显示了这些地址类型与物理内存的关系。

        如果有一个内核逻辑地址,宏 __pa( )(定义在 <asm/page.h> 中)会返回其对应的的内核物理地址。内核物理地址可以用 __va( ) 映射回内核逻辑地址,但只适用于low-memory 页面, low-memory会在后面介绍。 

        不同的内核函数需要不同类型的地址。如果定义了不同的 C 语言类型就好了,这样所需的地址类型就明确了,但我们没有这样的运气。内核没有为每种地址类型都定义不同的C语言类型,所以在本章中,我们将尝试明确在哪些情况下使用哪种类型的地址。 

        物理地址与页

        物理内存被划分为一个个离散的单元称为页。系统对内存内部的处理都是按页进行的。虽然目前大多数系统都使用 4096 字节的页面,但页面大小因体系结构而异。常量 PAGE_SIZE(定义在 <asm/page.h> 中)给出了任何给定架构上的页面大小。

        如果查看内存地址--虚拟地址或物理地址--它可分为页码和页内偏移量。例如,如果使用的是 4096 字节的页面,那么就用低12位表示偏移量,其余高位表示页码。如果舍弃偏移量,将偏移量的其余部分向右移动,得到的结果称为页码(PFN)。通过位移在页码和地址之间进行转换是一种相当常见的操作; 使用宏 PAGE_SHIFT可以完成这种转换。

        高内存和低内存

        逻辑虚拟地址和内核虚拟地址之间的区别在配备大量内存的 32 位系统上表现得尤为突出。32 位系统可以寻址 4 GB 的内存。不过在32 位系统上, Linux 所能容纳的内存还要小于这个数字、因为Linux配置虚拟地址空间还要占一部分内存。

        内核(在 x86 架构上,默认配置)将 4GB 虚拟地址空间划分成用户空间和内核空间;这两种空间都使用使用同一套内存映射机制。一个典型的划分是将 3 GB 用于用户空间,1 GB 用于内核空间。内核的代码和数据结构必须在内核空间上、内核无法直接操作未映射到内核地址空间的内存。所以,减去内核代码本身所需的空间。因此,基于 x86 的 Linux 系统最多只能使用不到 1 GB 的物理内存。

        为了应对商业压力,支持内核访问更多物理内存,在不破坏原有 32 位应用程序兼容性的同时,处理器制造商在其产品中增加了 “地址扩展 ”功能。 因此,在许多情况下,即使是 32 位处理器也能寻址超过 4 GB 的物理内存。

        内核逻辑地址映射的物理内存容量是 1GB 或 2GB(具体取决于硬件和内核配置),我们将这部分物理内存称为低内存,只有这部分物理内存有对应的逻辑地址; 将其余部分的物理内存称为高内存,这部分物理内存没有对应的逻辑地址。在访问特定的高内存页之前,内核必须设置一个显式虚拟映射,以使该内存页在内核地址空间中可用。因此,许多内核数据结构必须放在低内存中;而高内存往往是为用户空间进程页预留的。

        有些人对 “高内存 ”一词感到困惑,尤其是因为它在个人电脑领域还有其他含义。因此,为了让大家更清楚,我们将在这里对这些术语进行定义:

        低内存
        在内核空间中,有逻辑地址对应的内存。几乎所有系统的内核空间中,所有的内存都是低内存。

        高内存
        在内核空间中,没有逻辑地址对应的内存,因为它属于内核虚拟地址映射的内存。

        在 i386 系统上,低内存和高内存之间的界限通常设置在 1 GB 以下,不过这个界限可以在内核配置时更改。这个界限与最初 PC 上的 640 KB 限制没有任何关系,它也不是由硬件决定的。相反,它是内核在划分内核和用户空间的 32 位地址空间时设置的限制。

        内存映射和页结构体

        内核一直使用逻辑地址来关联物理内存页。然而,增加高内存支持后,这种方法暴露出一个明显的问题,逻辑地址无法用于高内存。因此,内核处理这种内存时越来越多地使用指向 sturct page(定义于 <linux/mm.h>)的指针来代替逻辑地址。该数据结构用于跟踪内核需要了解的有关物理内存的所有信息。

        系统中的每个物理页面都有一个struct page(结构页)。该结构的部分字段如下:

        atomic_t count;
        页面引用计数,表示该页面被引用的次数。当计数降为 0 时,该页面将返回空闲列表。

        void *virtual;
        表示页面对应的内核虚拟地址(如果已映射);否则为 NULL。低内存页面总是被映射;高内存页面通常不映射。这个字段并不是在所有的体系结构上都会出现;通常只有在无法轻松计算页面的内核虚拟地址时才会编译这个字段。如果要查看该字段,正确的方法是使用 page_address 宏,这个在后面会有讲到。

        无符号 long flags;
        一组描述页面状态的标志位。其中包括 PG_locked(表示该页面已被锁定在内存中)和 PG_reserved(表示内存管理系统完全不能使用该页面)。

        结构页中还有更多信息,但这些信息属于内存管理的深层黑魔法,与驱动程序编写者无关。

        内核维护着一个或多个struct page数组,用于跟踪系统中的所有物理内存。在某些系统中,一个struct page数组称为内存映射数组(mem_map)。但在另外某些系统中,情况更为复杂一些。非统一内存访问(NUMA)系统中物理内存不连续,系统可能有不止一个内存映射数组,因此,旨在实现可移植性的代码应尽可能避免直接访问数组。幸运的是,使用struct page 指针通常是非常简单的,可以避免使用struct page数组。

        Linux中定义了一些函数,用于在 struct page 指针和虚拟地址之间进行转换:

        struct page *virt_to_page(void *kaddr);
        该函数定义在 <asm/page.h> 中,参数接收一个内核逻辑地址并返回其对应的struct page指针。由于它的参数只接受一个内核逻辑地址,因此对来自 vmalloc 或高内存的地址是无效的。

        struct page *pfn_too_page(int pfn);
        返回给定页码对应的struct page指针。如有必要,在传递给 pfn_to_page 之前,可以使用 pfn_valid 检查页面码的有效性。

        void *page_address(struct page *page);
        如果存在page对应的内核虚拟地址,则返回该页面的内核虚拟地址。对于高内存,只有在页面已被映射的情况下才存在该地址。该函数在 <linux/mm.h> 中定义。在大多数情况下,你需要使用 kmap 而不是 page_address。

        #include <linux/highmem.h>
        void *kmap(struct page *page);
        void kunmap(struct page *page);
        kmap 返回系统中任何页面对应的内核虚拟地址。对于低内存页面,它只返回页面对应的内核逻辑地址;对于高内存页面,kmap 会在内核地址空间里创建一个高内存页映射。使用 kmap 创建的映射,应始终使用 kunmap 释放;此类映射的可用数量有限,因此最好不要保留太久。kmap 调用会保留一个计数器,因此如果两个或多个函数同时在同一页面上调用 kmap,就会使得计数器增加。还请注意,如果没有可用的映射,kmap 可以休眠。

        #include <linux/highmem.h>
        #include <asm/kmap_types.h>
        void *kmap_atomic(struct page *page, enum km_type type);
        void kunmap_atomic(void *addr, enum km_type type);
        kmap_atomic 是 kmap 的一种高性能形式。每个体系结构都为 kmap_atomic 维护一个小的插槽列表(专用页表项); kmap_atomic 的调用者必须告诉系统在类型参数中使用这些槽中的哪一个。对于驱动程序而言,唯一有意义的槽位是 KM_USER0 和 KM_USER1(用于直接从用户空间调用的代码)、和 KM_IRQ0(用于中断处理程序)。请注意代码在调用 kmap_atomic 时不能休眠。

        在本文后面部分,我们将在示例代码中看到这些函数的一些用法。

        页表

        在任何现代系统中,处理器都必须有一种将虚拟地址转换为相应物理地址的机制。这种机制被称为页表(pagetable);它本质上是一个多级树状结构数组,包含虚拟地址到物理地址的映射和一些相关标志。即使在不直接使用页表的体系结构上,Linux 内核也会维护一组页表。

        设备驱动程序通常执行的许多操作都涉及页表操作。对于驱动程序作者来说,幸运的是 2.6 内核已经不再需要直接处理页表。因此,我们不再详细描述;好奇的读者不妨看看 Daniel P. Bovet 和 Marco Cesati(O'Reilly 出版社)合著的《理解 Linux 内核》(Understanding The Linux Kernel)一书,了解全部内容。

        虚拟内存区域

        虚拟内存区域(VMA)是内核数据结构,用于管理进程地址空间的不同区域。虚拟内存区代表进程虚拟内存中的一个区域: 一个连续的虚拟地址范围,这些地址具有相同的权限标志,并关联到一个对象(如文件或交换空间)。它们通常称为 “段 ”,进程的内存映射(至少)由以下区域组成:

        - 程序代码区域(通常称为文本区)
        - 多个数据区域,包括初始化数据(开始执行时有明确分配值的数据)、未初始化数据(BSS) 和程序堆栈。
        - 文件或内存映射区域

        进程的内存区域可以通过 /proc/<pid/maps>(当然,其中的pid 已被进程 ID 代替)查看。/proc/self是/proc/pid的特例,因为它总是指当前进程。下面是几个内存映射的例子:

        /proc/*/maps 中的每个字段(图像名称除外)都对应 structvm_area_struct 中的一个字段: 

        start
        end
        表示该内存区域的开始和结束地址(虚拟内存地址)。

        perm
        内存区域读取、写入和执行权限的位掩码。该字段描述了允许进程对属于该区域的页面做什么。字段中的最后一个字符是表示 “私有 ”的 p 或表示 “共享 ”的 s。

        offset
        内存区域在映射文件中的起始位置。偏移量为 0 表示内存区域的起点与文件的起点相对应。

        major
        minor
        已映射设备文件所在设备的主次号。对于设备映射,主次号指的是存放用户打开的设备特殊文件,而不是设备本身。

        inode
        映射文件的 inode 编号。

        image
        已映射的文件的名称

        vm_area_struct 结构


        当用户空间进程调用 mmap 将设备内存映射到其地址空间时,系统会响应创建一个新的 VMA 来表示该映射。支持 mmap 的驱动程序(也就是实现 mmap 方法的驱动程序)需要帮助该进程完成 VMA 的初始化。因此,要支持 mmap,驱动程序编写者至少要对 VMA 有最基本的了解。

        让我们来看看 struct vm_area_struct(定义于 <linux/mm.h>)中最重要的字段。设备驱动程序在实现 mmap 时可能会用到这些字段。请注意,内核维护 VMA 的列表和树,以优化映射区域查找,vm_area_struct 的几个字段用于维护这种数据结构。因此,驱动程序不能随意创建 VMA,否则会破坏该结构。VMA 的主要字段如下(注意这些字段与我们刚才看到的 /proc 输出之间的相似性):

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

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

相关文章

QT5.9.9+Android开发环境搭建

文章目录 1.安装准备1.1 下载地址1.2 安装前准备2.安装过程2.1 JDK安装2.1.1 安装2.1.2 环境变量配置2.2 SDK配置2.2.1 安装2.2.2 环境变量配置2.2.3 adb 错误解决2.2.4 其他SDK安装2.2.5 AVD虚拟机配置2.3 NDK配置2.4 QT 5.9.9安装配置2.4.1 QT安装2.4.2 配置安卓环境3.QT工程…

Android Kotlin:协程

目录&#xff1a; 1&#xff09;协程是什么&#xff1f; 2&#xff09;协程和线程的关系&#xff1f; 3&#xff09;协程如何使用&#xff1f;切线程是什么 4&#xff09;挂起函数是什么&#xff1f; 5&#xff09;withContext和lanuch的区别在哪里&#xff1f; 6&#xff09;…

某矿webpack逆向

免责声明&#xff1a; 本篇博文的初衷是分享自己学习逆向分析时的个人感悟&#xff0c;所涉及的内容仅供学习、交流&#xff0c;请勿将其用于非法用途&#xff01;&#xff01;&#xff01;任何由此引发的法律纠纷均与作者本人无关&#xff0c;请自行负责&#xff01;&#xf…

华媒舍:6个媒体宣发套餐,快速突破传播界限

在当今信息爆炸的社会中&#xff0c;有效地传播自己的信息变得愈发困难。特别是对于媒体宣发来说&#xff0c;如何在市场竞争激烈的情况下突破传播界限&#xff0c;让自己的消息传达给更多的人&#xff0c;这是每个企业和个人都面临的难题。 为了解决这个问题&#xff0c;我们推…

图片变更检测

20240723 By wdhuag 目录 前言&#xff1a; 参考&#xff1a; 文件监控&#xff1a; 图片占用问题&#xff1a; 源码&#xff1a; 前言&#xff1a; 由于第三方图像处理软件不能回传图片&#xff08;正常都能做&#xff0c;这里只是不想做&#xff09;&#xff0c;只能在…

有什么好用的AI工具推荐吗?

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 所有打工人集合&#xff01;根据你问题中的描述&#xff0c;本小白正好都有涉及过相关领域的AI工具。 今天一次性讲清能处理所有办公场景的AI工具…

Harmony学习(二)------ArkUI(2)

1.主轴对齐方式.justifyContent build() {Column(){Text().width(200).height(100).backgroundColor(Color.Yellow).border({width:2})Text().width(200).height(100).backgroundColor(Color.Yellow).border({width:2}).margin(10)Text().width(200).height(100).backgroundCol…

现货黄金布林线怎么看?又应如何使用?

在现货黄金投资中&#xff0c;技术指标是很多人做交易分析和决策时所需要的工具。当然&#xff0c;由于电脑技术的发达&#xff0c;现在各种技术指标已经有成千上万种&#xff0c;其中一些经典的指标还是很受市场欢迎&#xff0c;比方说布林线。下面我们就来简单地介绍布林线在…

【调试笔记-20240723-Linux-gitee 仓库同步 github 仓库,并保持所有访问链接调整为指向 gitee 仓库的 URL】

调试笔记-系列文章目录 调试笔记-20240723-Linux-gitee 仓库同步 github 仓库&#xff0c;并保持所有访问链接调整为指向 gitee 仓库的 URL 文章目录 调试笔记-系列文章目录调试笔记-20240723-Linux-gitee 仓库同步 github 仓库&#xff0c;并保持所有访问链接调整为指向 gite…

Python爬虫(5) --爬取网页视频

文章目录 爬虫爬取视频指定url发送请求UA伪装请求页面 获取想要的数据解析定位定位音视频位置 存放视频完整代码实现总结 爬虫 Python 爬虫是一种自动化工具&#xff0c;用于从互联网上抓取网页数据并提取有用的信息。Python 因其简洁的语法和丰富的库支持&#xff08;如 requ…

typora激活问题

不使用激活码解决。 1.右键桌面图标&#xff0c;打开文件位置 2.按照 Typora路径到 —> resources —> page-dist —> static —> js 这个路径找到这两个文件 LicenseIndex.180dd4c7.xxxxxxx.chunk.js LicenseIndex.180dd4c7.xxxxxxx.chunk.js &#xff08;也可…

MySQL简介(超详细)

课程目标 • 了解数据库基本概念 • 熟悉MySQL数据库的常用操作 • 掌握Insert、Delete、Update、Select等常用SQL语句 • 理解MySQL数据库的事务&#xff0c;索引以及函数 • 了解MySQL数据库的存储过程和触发器 一、什么是数据库&#xff1f; 概念&#xff1a;数据库(D…

立创梁山派--移植开源的SFUD和FATFS实现SPI-FLASH文件系统

本文主要是在sfud的基础上进行fatfs文件系统的移植&#xff0c;并不对sfud的移植再进行过多的讲解了哦&#xff0c;所以如果想了解sfud的移植过程&#xff0c;请参考我的另外一篇文章&#xff1a;传送门 正文开始咯 首先我们需要先准备资料准备好&#xff0c;这里对于fatfs的…

Windows图形界面(GUI)-MFC-C/C++ - MFC项目工程框架解析

公开视频 -> 链接点击跳转公开课程博客首页 -> e​​​​​​链接点击跳转博客主页 目录 MFC项目 项目选择 配置安装 程序引导 MFC框架 环境设置 程序框架 代码编写 MFC解析 程序入口 执行流程 代码结构 应用程序类 窗口框架类 消息处理 消息类型 消息…

探索扫描二维码登录的奥秘:从前端到后端的无缝连接

&#x1f389; 博客主页&#xff1a;【剑九 六千里-CSDN博客】 &#x1f3a8; 上一篇文章&#xff1a;【React中的无状态组件&#xff1a;简约之美】 &#x1f3a0; 系列专栏&#xff1a;【面试题-八股系列】 &#x1f496; 感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1、…

[Jenkins]jenkins-cli.jar调用用户token启动任务

背景&#xff1a;项目入了一群od伙伴&#xff0c;但是od伙伴有单独的构建工程需要提交&#xff0c;由于jenkins的版本太拉闸&#xff0c;不能配置根据role和项目分权限&#xff0c;插件安装失败&#xff0c;不得已想到一个办法。让OD伙伴&#xff0c;在本地&#xff0c;用java&…

音频剪辑里的几种基础操作

音频对于视频的重要性&#xff0c;怎么强调都不为过&#xff0c;它在视频里扮演着举足轻重的角色&#xff0c;对观众有着极为深远的影响。下面为您阐述音频在视频中的关键意义&#xff1a; ① 情感传递&#xff1a;音频有强大的情感传达能力&#xff0c;借助声音的起伏变化、音…

windows网络应急排查

一、系统排查 msinfo32 #GUI显示的系统信息systeminfo #简单了解系统信息用户信息排查 排查恶意账号&#xff1a; 黑客喜欢建立相关账号用作远控: 1.建立新账号2.激活默认账号3.建立隐藏账号(windows中账号名$)cmd方法 net user #打印用户账号信息 ---看不到$结尾的隐藏账…

postgresql 使用navicat 导出报 gs_package 关系不存在问题解决。

1. 问题描述 临时接手的项目&#xff0c;使用的数据库是postgresql&#xff0c;使用navicat 17 Lite 免费版&#xff0c;导出就会报如下图所示的错误&#xff1a;2. 尝试的办法&#xff1a; 1) 换navicat 17 和navicat 17 for postgresql 试用版本 还是一样的错误。 2) 换pos…

大数据-43 Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…