当前位置:网站首页>DRM 驱动 mmap 详解:(一)预备知识
DRM 驱动 mmap 详解:(一)预备知识
2022-06-12 17:08:00 【_kerneler】
!!!声明!!!
本文章转自:何小龙
链接:https://blog.csdn.net/hexiaolong2009/article/details/87392266
转载只是为了学习备份。
视频:三种 mmap 驱动实现方法
前言
在上一篇《DRM GEM 驱动程序开发(dumb)》我们学习了如何编写一个最简单的 DRM GEM 驱动程序。该驱动程序只提供了 dumb buffer 的操作能力,允许应用程序对 dumb buffer 进行 create 和 mmap 操作。
dumb buffer 操作虽简单,但在 drm 驱动目录下,各个厂家的实现却是五花八门。因为 dumb buffer 只能通过 mmap 来访问,因此本系列文章将带着大家一起来深入学习下 DRM 中的 mmap 驱动开发。
在正式讲解 DRM mmap 之前,我觉得有必要先让大家了解一个普通的 mmap 驱动应该如何编写。本文并不打算讲解 mmap 系统调用的原理及其相关细节,因为这涉及到 linux 内存管理的诸多概念,大家只需要了解如何去写一个简单的 mmap 驱动程序就可以了,为后续 drm mmap 驱动的编写做准备。如果大家阅读完本文后,想再进一步学习 mmap 系统调用的相关知识,推荐大家阅读彭东林的博客《内存映射函数 remap_pfn_range 学习》和胡潇的博客《认真分析mmap:是什么 为什么 怎么用》。
正文
在 kernel 驱动中,实现 mmap 系统调用离不开两个关键步骤:(1)内存分配 (2) 建立映射关系。这刚好也对应了 DRM 中的 dumb_create 和 mmap 操作。
先说映射关系,在 linux 驱动中建立映射关系的方法主要有如下两种:
- 一次性映射 —— 在 mmap 回调函数中,一次性建立好整块内存的映射关系,通常以
remap_pfn_range()
为代表 。 - Page Fault —— mmap 先不建立映射关系,等上层触发缺页异常时,在 fault 中断处理函数中建立映射关系,缺哪块补哪块,通常以
vm_insert_page()
为代表。
而内存分配的时机也会影响驱动程序的设计,大致分为如下三种:
- 在 mmap 系统调用之前分配
- 在 mmap 系统调用过程中分配
- 在 fault 中断处理函数中分配
因此不同的分配时机 + 不同的映射机制,就会得到不同的 mmap 的实现策略。这也是为什么在 DRM 驱动中,各家的 dumb_create 和 mmap 实现代码差异很大的原因。
下面就以示例代码的形式为大家展示几种典型的 mmap 驱动实现方式。
示例一
mmap 之前分配 + 一次性映射:
描述:
- 驱动初始化时先分配好 3 个 PAGE。
- 上层执行 mmap 系统调用时,在底层 mmap 回调函数中通过 remap_pfn_range() 一次性建立好所有的映射关系,并将映射后的起始虚拟地址返回给应用程序。
- 应用程序使用返回的虚拟地址进行内存读写操作。
驱动代码:
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>
static void *kaddr;
static int my_mmap(struct file *file, struct vm_area_struct *vma)
{
return remap_pfn_range(vma, vma->vm_start,
(virt_to_phys(kaddr) >> PAGE_SHIFT) + vma->vm_pgoff,
vma->vm_end - vma->vm_start, vma->vm_page_prot);
}
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.mmap = my_mmap,
};
static struct miscdevice mdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "my_dev",
.fops = &my_fops,
};
static int __init my_init(void)
{
kaddr = kzalloc(PAGE_SIZE * 3, GFP_KERNEL);
return misc_register(&mdev);
}
module_init(my_init);
示例二
mmap 之前分配 + Page Fault:
描述:
- 驱动初始化时预先分配好 3 个 PAGE。
- 上层执行 mmap 系统调用,底层驱动在 mmap 回调函数中不建立映射关系,而是将本地实现的 vm_ops 挂接到进程的 vma->vm_ops 指针上,然后函数返回。
- 上层获取到一个未经映射的进程地址空间,并对其进行内存读写操作,导致触发缺页异常。缺页异常最终会调用前面挂接的 vm_ops->fault() 回调接口,在该接回调中通过 vm_insert_page() 建立物理内存与用户地址空间的映射关系。
- 异常返回后,应用程序就可以继续之前被中断的读写操作了。
注意:这种情况每次 Page Fault 中断只能映射一个 Page。
驱动代码:
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>
static void *kaddr;
static int my_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
int offset, ret;
offset = vmf->pgoff * PAGE_SIZE;
ret = vm_insert_page(vma, vmf->address, virt_to_page(kaddr + offset));
if (ret)
return VM_FAULT_SIGBUS;
return VM_FAULT_NOPAGE;
}
static const struct vm_operations_struct vm_ops = {
.fault = my_fault,
};
static int my_mmap(struct file *file, struct vm_area_struct *vma)
{
vma->vm_flags |= VM_MIXEDMAP;
vma->vm_ops = &vm_ops;
return 0;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.mmap = my_mmap,
};
static struct miscdevice mdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "my_dev",
.fops = &my_fops,
};
static int __init my_init(void)
{
kaddr = kzalloc(PAGE_SIZE * 3, GFP_KERNEL);
return misc_register(&mdev);
}
module_init(my_init);
DRM 驱动典型代表:tegra
、udl
示例三
Page Fault 中分配 + 映射:
描述:
映射的过程和示例二完全一样,只是内存分配的时机是在 page fault 中断处理函数中进行的。
驱动代码:
这里为了简化代码,总共只分配一个 page,多个 page 可通过 vmf->pgoff 来进行区分。
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>
static struct page *page;
static int my_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
int ret;
if (!page)
page = alloc_page(GFP_KERNEL);
ret = vm_insert_page(vma, vmf->address, page);
if (ret)
return VM_FAULT_SIGBUS;
return VM_FAULT_NOPAGE;
}
static const struct vm_operations_struct vm_ops = {
.fault = my_fault,
};
static int my_mmap(struct file *file, struct vm_area_struct *vma)
{
vma->vm_flags |= VM_MIXEDMAP;
vma->vm_ops = &vm_ops;
return 0;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.mmap = my_mmap,
};
static struct miscdevice mdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "my_dev",
.fops = &my_fops,
};
static int __init my_init(void)
{
return misc_register(&mdev);
}
module_init(my_init);
DRM 驱动典型代表:vkms
、vgem
示例四
同示例二(mmap 之前分配 + Page Fault 映射),区别在于 fault 中断处理函数,不再使用 vm_insert_page() 来映射,而是直接将物理 page 返回给 vmf->page 指针。
驱动代码:
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>
static void *kaddr;
static int my_fault(struct vm_fault *vmf)
{
vmf->page = virt_to_page(kaddr + vmf->pgoff * PAGE_SIZE);
get_page(vmf->page);
return 0;
}
static const struct vm_operations_struct vm_ops = {
.fault = my_fault,
};
static int my_mmap(struct file *file, struct vm_area_struct *vma)
{
vma->vm_flags |= VM_MIXEDMAP;
vma->vm_ops = &vm_ops;
return 0;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.mmap = my_mmap,
};
static struct miscdevice mdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "my_dev",
.fops = &my_fops,
};
static int __init my_init(void)
{
kaddr = kzalloc(PAGE_SIZE * 3, GFP_KERNEL);
return misc_register(&mdev);
}
module_init(my_init);
注意:使用 vmf->page 方式映射时,fault 中断函数返回值应该是 0。而使用 vm_insert_page() 方式映射时,返回值应该是 VM_FAULT_NOPAGE
。
DRM 驱动典型代表:
vkms
、vgem
示例五
mmap 中分配 + Page Fault 映射: 代码其实和示例三(Page Fault 中分配 + 映射)差异不大,只是在 mmap 回调中进行内存分配,这里就不演示了。
DRM 驱动典型代表:
shmem
(linux 4.19)
测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd;
fd = open("/dev/my_dev", O_RDWR);
char *str = mmap(NULL, 4096 * 3, PROT_WRITE, MAP_SHARED, fd, 0);
strcpy(str, "hello world\n");
munmap(str, 4096 * 3);
str = mmap(NULL, 4096 * 3, PROT_READ, MAP_SHARED, fd, 0);
printf("%s\n", str);
munmap(str, 4096 * 3);
close(fd);
return 0;
}
描述:
先后执行 2 次 mmap/munmap 操作,第一次为写操作,第二次为读操作。
运行结果:
# ./a.out
hello world
结语
不管是做 DRM 驱动开发,还是其它设备驱动开发,上面的示例都值得参考。希望通过本文,能让初学者对 mmap 驱动的实现有个大致的认识,这样我们在后续的 DRM 驱动讲解中才能做到心中有数。
源码下载
Github: sample-code/mmap
测试平台:QEMU vexpress-a9
参考资料
扩展阅读:《dma-buf 由浅入深(四) —— mmap》
文章汇总:DRM (Direct Rendering Manager) 学习简介
边栏推荐
- 布局管理中的sizePolicy的策略问题
- 怎么在公司里面做好测试工作(做好测试工作)
- 5-5 configuring MySQL replication log point based replication
- 记录使用yolov5进行旋转目标的检测
- Kill program errors in the cradle with spotbugs
- Alibaba cloud image station supports IPv6!
- Pat class a 1142 largest regiment
- 薛定谔的日语学习小程序源码
- 初识GO语言
- R language uses the sum function of epidisplay package to calculate the descriptive statistical summary information of the specified variables in dataframe under different grouped variables and visual
猜你喜欢
Microsoft Office MSDT Code Execution Vulnerability (cve-2022-30190) vulnerability recurrence
su直接切换到超级管理员模式,这样很多报错都可以避免了
Qt开发高级进阶:初探qt + opengl
Cicada mother talks to rainbow couple: 1.3 billion goods a year, from e-commerce beginners to super goods anchor
Introduction to several common functions of fiddler packet capturing (stop packet capturing, clear session window contents, filter requests, decode, set breakpoints...)
JVM内存模型与本地内存
使用ubantu时,遇见的一些小毛病和解决方法
Unit sshd. service could not be found
Compilation optimization of performance optimization
MySQL transaction introduction and transaction isolation level
随机推荐
R language arma-garch-copula model and financial time series case
(七)循环语句for
云开发坤坤鸡乐盒微信小程序源码
Kill program errors in the cradle with spotbugs
PAT甲级 1139 第一次接触
R language uses ggplot2 to visualize the density graph (curve) of specific data columns in dataframe data, and uses Xlim parameter to specify the range of X axis
Google浏览器调试技巧
Sizepolicy policy in layout management
JVM memory model and local memory
R语言使用pdf函数将可视化图像结果保存到pdf文件中、使用pdf函数打开图像设备、使用dev.off函数关闭图像设备、自定义width参数和height参数指定图像的宽度和高度
5-5 configuring MySQL replication log point based replication
Flink 维表异步查询的实现以及问题排查
Qiushengchang: Practice of oppo commercial data system construction
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
启牛开的证券账户安全吗?合法吗?
PAT甲级 1142 最大团
Microsoft Office MSDT代码执行漏洞(CVE-2022-30190)漏洞复现
R语言使用epiDisplay包的tableStack函数基于分组变量生成统计分析表(包含描述性统计分析、假设检验、不同数据使用不同的统计量和假设检验方法)、自定义配置是否显示统计检验内容
怎么在公司里面做好测试工作(做好测试工作)
Volcano engine held a video cloud technology force summit and released a new experience oriented video cloud product matrix