Linux内核笔记之DMA_BUF
内存管理始终是底层软件的核心部分,尤其是对于音视频的解码显示功能。本文将通过编写一个实例驱动程序,同内核中的i915显卡驱动进行内存方面的交互来剖析 Linux内核中的通用子系统DMA_BUF。
DMA_BUF
需求背景
考虑这样一种场景,摄像头采集的视频数据需要送到GPU中进行编码、显示。负责数据采集和编码的模块是Linux下不同的驱动设备,将采集设备中的数据送到编码设备中 需要一种方法。最简单的方法可能就是进行一次内存拷贝,但是我们这里需要寻求一种免拷贝的通用方法。
实际的硬件环境是,采集设备是一个pciv驱动,编码设备就是i915驱动。现在就是要编写一个驱动程序,让i915驱动可以直接访问pciv中管理的视频数据内存。
概述
引入dma-buf机制的原因
之前内核中缺少一个可以让不同设备、子系统之间进行内存共享的统一机制。
混乱的共享方法:
V4L2(video for Linux)使用
USERPTR
的机制来处理访问来自其他设备内存的问题,这个机制需要借助于以后空间的mmap方法。类似的,wayland和x11各自定义了客户端和主进程之间的内存共享机制,而且都没有实现不同设备间内存共享的机制。
内核中各种soc厂商的驱动、各种框架和子系统都各自实现各自的内存共享机制。
之前共享方式存在问题:
使用用户层的mmap机制实现内存共享方式太过简单粗暴,难以移植。
没有统一的内存共享的API接口。
dma_buf是一种怎样的存在
dma_buf是内核中一个独立的子系统,提供了一个让不同设备、子系统之间进行共享缓存的统一框架,这里说的缓存通常是指通过DMA方式访问的和硬件交互的内存。 比如,来自摄像头采集的通过pciv驱动传输的内存、gpu内部管理的内存等等。
其实一开始,dma_buf机制在内核中的主要运用场景是支持GPU驱动中的prime
机制,但是作为内核中的通用模块,它的适用范围很广。
dma_buf子系统包含三个主要组成:
dma-buf对象,它代表的后端是一个sg_table,它暴露给应用层的接口是一个文件描述符,通过传递描述符达到了交互访问dma-buf对象,进而最终达成了 共享访问sg_table的目的。
fence对象, which provides a mechanism to signal when one device as finished access.
reservation对象, 它负责管理缓存的分享和互斥访问。.
dma-buf实现
整体构架
DMA_BUF框架下主要有两个角色对象,一个是exporter
,相当于是buffer的生产者,相对应的是importer
或者是user
,即buffer的消费使用者。
假设驱动A想使用由驱动B产生的内存,那么我们称B为exporter,A为importer.
The exporter
实现struct dma_buf_ops中的buffer管理回调函数。
允许其他使用者通过dma_buf的sharing APIS来共享buffer。
通过struct dma_buf结构体管理buffer的分配、包装等细节工作。
决策buffer的实际后端内存的来源。
管理好scatterlist的迁移工作。
The buffer-usr
是共享buffer的使用者之一。
无需关心所用buffer是哪里以及如何产生的。
通过struct dma_buf_attachment结构体访问用于构建buffer的scatterlist,并且提供将buffer映射到自己地址空间的机制。
数据结构
struct dma_buf{size_t size;struct file *file; /* file pointer used for sharing buffers across,and for refcounting */struct list_head attachments; /* list of dma_buf_attachment that denotes all devices attached */const struct dma_buf_ops *ops;struct mutex lock;unsigned vmapping_counter;void *vmap_ptr;const char *exp_name; /* name of the exporter; useful for debugging */struct module *owner;struct list_head list_node; /* node for dma_buf accounting and debugging */void *priv; /* exporter specific private data for this buffer object */struct reservation_object *resv; /* reservation object linked to this dma-buf */wait_queue_head_t poll;struct dma_buf_poll_cb_t{struct fence_cb cb;wait_queue_head_t *poll;unsigned long active;}cb_excl, cb_shared;
};
dma_buf对象中最重要的成员变量是ops方法集,dma_buf本身是一个通用的框架,正是依靠这里的ops回调函数集来实现dma_buf对象的重载功能
,所谓重载就是 说dma_buf框架可以用于不同的运用场景。所以ops定义的回调函数是我们编写dma_buf框架下exporter驱动的主要实现代码。
ops中定义的回调函数都对应着dma_buf模块外部头文件dma_buf.h中的API,比如其他驱动调用dma_buf.h中的dma_buf_attach()API时,实际最终调用的就是 我们实现的ops中的int (*attach)(struct dma_buf *, struct device *, struct dma_buf_attachment *)
方法;调用dma_buf_map_attachment() API 实际就是调用ops中的struct sg_table * (*map_dma_buf)(struct dma_buf_attachment *, enmu dma_data_direction)
方法。
dma_buf对象中更加重要的一个成员变量是file,我们知道一切皆文件
是unix的核心思想。dma_buf子系统之所以可以使不同的驱动设备可以共享访问内存,就 是借助于文件系统是全局的这个特征。另外,因为Unix操作系统都是通过Unix domain域的socket使用SCM_RIGHTS语义来实现文件描述符传递,所以安全性很高。和dma_buf对象中file成员变量对应的API接口有,dma_buf_export()、dma_buf_fd()。
/*** dma_buf_export - Create a new dma_buf, and associates an anon file with this buffer,* so it can be exported.*/
struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
{struct dma_buf *dma_buf;struct file *file;...dmabuf = kzalloc(alloc_size, GFP_KERNEL);if(!dmabuf){module_put(exp_info->owner);return ERR_PTR(-ENOMEM);}...dmabuf->ops = exp_info->ops; //[0]...file = anon_inode_getfile("dmabuf", &dma_buf_fops, dmabuf, exp_info->flags); //[1]file->f_mode |= FMODE_LSEEK;dmabuf->file = file;...return dmabuf;
}
上面[0]出传入的ops方法集就是上面提到的我们编写驱动应该实现的回调函数集,dmabuf通过anon_inode_getfile()函数挂载到了file对象上的 priv指针上,而dma_buf_fops回调函数集挂载在file对象上的ops上,最后dma_buf_fops函数集中的回调函数实现都会通过file->priv拿到dma_buf对象, 然后直接调用dma_buf中的ops方法。这样的函数重载实现是file作为驱动程序接口功能实现的常规操作
.
dma_buf_fd()函数的实现很简单,就是根据传入的dma_buf对象,生成全局可见的文件描述符fd。后面正是通过这个fd作为媒介来实现各个驱动设备间的 交互。
运作流程
Exporter驱动申请或者引用导入的待共享访问的内存。
Exporter驱动调用dma_buf_export()创建dma_buf对象,同时将自定义的struct dma_buf_ops方法集和步骤1中的内存挂载到dma_buf对象中。
Exporter驱动调用dma_buf_fd()将步骤2中创建的dam_buf对象关联到全局可见的文件描述符fd,同时通过ioctl方法将fd传递给应用层。
应用层将fd传递给importer驱动程序。
importer驱动通过调用dma_buf_get(fd)获取dma_buf对象。
importer驱动调用dma_buf_attach()和dma_buf_map_attachment()获取共享缓存的信息。
Importer驱动实例剖析
Linux内核中的DRM子系统中实现了importer功能,这样我们可以通过实现exporter驱动来将某个内存传递进DRM子系统中,让DRM进行访问。
上面描述的运作流程中的4~6步骤都是importer需要实现的代码,其中对应于第4点,drm驱动中通过ioctl的DRM_IOCTL_PRIME_FD_TO_HANDLE
命令来将应用层 传递的fd转换为对应的dma_buf对象。不过要注意的是,drm中对应这个命令的函数不仅是将fd转换为了dma_buf对象,同时还将这个dma_buf对象通过idr机制将dmd_buf 索引为handle,方便drm驱动中进行内存的管理。具体函数实现如下:
int drm_gem_prime_fd_to_handle(struct drm_device *dev, struct drm_file *file_priv, int prime_fd, uint32_t *handle)
{struct dma_buf *dma_buf;struct drm_gem_object *obj;int ret;dma_buf = dma_buf_get(prime_fd); //[0]...ret = drm_prime_lookup_buf_handle(&file_priv->prime, dma_buf, handle); //[1]if(ret == 0){...return 0;}...obj = dev->driver->gem_prime_import(dev, dma_buf); //[2]...drm_gem_handle_create_tail(file_priv, obj, handle); //[3]...drm_prime_add_buf_handle(&file_priv->prime, dma_buf, *handle); //[4]...dma_buf_put(dma_buf); //[5]return 0;
}
上面代码中的[0]处就是实现了运作流程中的第5点。
从drm_gem_prime_fd_to_handle()函数的实现的[1]处可见,当prime_fd对应的内存对象已经通过dma_buf机制获取过,那么prime的机制和drm中的flink机制 一样,用于将bo在多个上下文下共享。也就是说上面代码中的[1]、[3]、[4]处和dma_buf机制没有关系,而是drm中的bo对象管理机制,基于的是idr机制。所以下面 重点分析[1]处的代码实现,其回调实现如下:
struct drm_gem_object *i915_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf)
{struct dma_buf_attachment *attach;struct drm_gem_object *obj;...attach = dma_buf_attach(dma_buf, dev->dev); //[0]get_dma_buf(dma_buf);obj = i915_gem_object_alloc(dev); //[1]...drm_gem_private_object_init(dev, &obj->base, dma_buf->size);i915_gem_object_init(obj, &i915_gem_object_dmabuf_ops); //[2]obj->base.import_attach = attach;return &obj->base;
}
从上面代码看,i915_gem_prime_import()貌似只是完成了运作流程步骤中第6点的一半工作,即[0]处调用的dma_buf_attach(),并没有调用 dma_buf_map_attachment()方法。其实i915驱动是将dma_buf_map_attachment()函数的调用lazy到了obj->ops中去了,即上面代码中[2]处 注册的方法集i915_gem_object_dmabuf_ops。i915驱动中调用obj->ops中方法的流程如下:
通过上面的具体流程可以看出,当i915驱动需要实际使用内存时,会调用obj->pos中的get_pages()方法。而这个方法的具体实现如下:
static int i915_gem_object_get_pages_dmabuf(struct drm_i915_gem_object *obj)
{struct sg_table *sg;sg = dma_buf_map_attachment(obj->base.import_attach, DMA_BIDIRECTIONAL);...obj->pages = sg;return 0;
}static void i915_gem_object_put_pages_dmabuf(struct drm_i915_gem_object *obj)
{dma_buf_unmap_attachment(obj->base.import_attach, obj->pages, DMA_BIDIRECTIONAL);
}static const struct drm_i915_gem_object_ops i915_gem_object_dmabuf_ops = {.get_pages = i915_gem_object_get_pages_dmabuf,.put_pages = i915_gem_object_put_pages_dmabuf,
};
至此,作为linux内核中一个dma_buf的importer实例,即i915驱动中的importer运作流程分析完成了。
Linux内核笔记之DMA_BUF相关推荐
- Linux内核笔记--内存管理之用户态进程内存分配
内核版本:linux-2.6.11 Linux在加载一个可执行程序的时候做了种种复杂的工作,内存分配是其中非常重要的一环,作为一个linux程序员必然会想要知道这个过程到底是怎么样的,内核源码会告诉你 ...
- linux内核自旋锁解释,LINUX内核笔记:自旋锁
目录 1.自旋锁作用与基本使用方法? 与其他锁一样,自旋锁也用于保护临界区,但是自旋锁主要是用于在SMP上保护临界区.在SMP上,自旋锁最多只能被一个可执行线程持有,如果一个线程尝试获得一个被争用的自 ...
- Linux内核笔记006 - 交换分区
本文转自网络文章,内容均为非盈利,版权归原作者所有. 转载此文章仅为个人收藏,分享知识,如有侵权,马上删除. 原文作者:jmpcall 专栏地址:https://zhuanlan.kanxue.com ...
- linux 2.6.36代码构架,Linux 内核笔记(2.6.36)(二)
C语言基础 链表 linux内核代码中大量使用链表,为了提高效率,内核采用了一套通用的,一般的,可以用到各种不同数据结构的队列操作.在include/linux/ list.h中,有如下申明: 点击( ...
- linux内核笔记-内核同步
linux内核就相当于不断对请求进行响应的服务器,这些请求可能来自CPU,可能来自发出中断的外部设备.我们将内核看作两种请求的侍者. (1)老板提出请求,侍者如果空闲,为老板服务.(系统调用或异常) ...
- Linux内核笔记--软中断
Linux软中断 1.软中断介绍 2.软中断的使用 2.1.注册软中断处理函数 2.2.触发软中断 1.软中断介绍 Linux 内核使用结构体 softirq_action 表示软中断, softir ...
- linux 内核笔记之watchdog
一.概要 watchdog简而言之,watchdog是为了保证系统正常运行,或者从死循环,死锁等一场状态退出的一种机制. 看门狗分硬件看门狗和软件看门狗.硬件看门狗是利用一个定时器电路,其定时输出连接 ...
- 深入理解LINUX内核 笔记 第四章 中断和异常
中断和异常处理程序的嵌套执行 https://blog.csdn.net/denglin12315/article/details/121703669 一.历史 早前的Linux内核版本,中断分为两种 ...
- 【Linux 内核笔记】进程管理
文章目录 进程创建 进程终结 孤儿进程 小结 clone()-fork()-exec()-exit() 子进程结束ZOMBIE 父进程wait4() 进程描述符task_struct进程所有信息 由t ...
最新文章
- shell中字符串截取的几种方法
- python 类的绑定方法和非绑定方法
- 剑指 Offer 面试题45:把数组排成最小的数——Python内置函数 map()、__lt__()、join()、sorted()
- Android Java包
- rcp rapido_Rapido使用数据改善乘车调度
- 多个域名向主域名自动跳转的Nginx配置
- Sql中如何将数据表的两个字段的值如何互换?
- 互联网行业,再卷就卷没了…
- Re0:DP学习之路 01背包如何打印路径?
- Google AI 博客:Hum to Search 项目,使用机器学习来识别随口哼唱的旋律
- Excel文档中字符型数据转化为数字类型
- Ruby read JSON file
- Android计算器心得体会,计算器编程设计心得体会
- 【MySQL数据库笔记 - 进阶篇】(四)视图/存储过程/触发器
- 关于ArrayList和LinkedList的插入,遍历,删除时间比照
- win10常用电脑快捷操作;gif工具推荐
- Android Studio 连接网易MuMu模拟器教程
- 绩优公司成主流 多家公司获政府补贴
- 北斗一号、北斗二号、北斗三号的区别
- Unittest框架介绍