转载自https://blog.csdn.net/hexiaolong2009/article/details/102596761
1. 前言
在上一篇《最简单的 dma-buf 驱动程序》中,我们学习了编写 dma-buf 驱动程序的三个基本步骤,即 dma_buf_ops
、 dma_buf_export_info
、 dma_buf_export()
。在本篇中,我们将在 exporter-dummy 驱动的基础上,对其 dma_buf_ops
的 kmap / vmap 接口进行扩展,以此来演示这两个接口的使用方法。
2. dma-buf只能用于DMA硬件访问吗?
在内核代码中,我们见得最多的 dma-buf API 莫过于 dma_buf_attach()
、dma_buf_map_attachment()
,以至于有些小伙伴会问:dma-buf 难道只能给 DMA 硬件来访问吗?当然不是!在上一篇的 概念 小节中就曾讲过,dma-buf 本质上是 buffer 与 file 的结合,因此它仍然是一块 buffer。不要看它带了 dma 字样就被迷惑了,dma-buf 不仅能用于 DMA 硬件访问,也同样适用于 CPU 软件访问,这也是 dma-buf 在内核中大受欢迎的一个重要原因。
正因如此,我才决定将 dma_buf_kmap()
/ dma_buf_vmap()
作为 dma-buf 系列教程的第二篇文章来讲解,因为这两个接口使用起来实在是比 DMA 操作接口简单太多了!
3. dma-buf 只能分配离散 buffer 吗?
当然不是!就和内核中 dma-mapping 接口一样,dma-buf 既可以是物理连续的 buffer,也可以是离散的 buffer,这最终取决于 exporter 驱动采用何种方式来分配 buffer。
因此为了尽量让读者易于理解,本篇特意使用了内核中最简单、最常见的 kzalloc() 函数来分配 dma-buf,自然,这块 buffer 就是物理连续的了。
4. cpu access
从 linux-3.4 开始,dma-buf 引入了 CPU 操作接口,使得开发人员可以在内核空间里直接使用 CPU 来访问 dma-buf 的物理内存。
- dma-buf: add support for kernel cpu access
如下 dma-buf API 实现了 CPU 在内核空间对 dma-buf 内存的访问:
dma_buf_kmap()
dma_buf_kmap_atomic()
dma_buf_vmap()
(它们的反向操作分别对应各自的 unmap 接口)
通过 `dma_buf_kmap() / dma_buf_vmap() 操作,就可以把实际的物理内存,映射到 kernel 空间,并转化成 CPU 可以连续访问的虚拟地址,方便后续软件直接读写这块物理内存。因此,无论这块 buffer 在物理上是否连续,在经过 kmap / vmap 映射后的虚拟地址一定是连续的。
上述的3个接口分别和 linux 内存管理子系统(MM)中的 kmap()、 kmap_atomic() 和 vmap() 函数一一对应,三者的区别如下:
函数 | 说明 |
---|---|
kmap() | 一次只能映射1个page,可能会睡眠,只能在进程上下文中调用 |
kmap_atomic() | 一次只能映射1个page,不会睡眠,可在中断上下文中调用 |
vmap() | 一次可以映射多个pages,且这些pages物理上可以不连续,只能在进程上下文中调用 |
从 linux-4.19 开始,dma_buf_kmap_atomic()
不再被支持。
dma_buf_ops
中的 map / map_atomic
接口名,其实原本就叫 kmap / kmap_atomic
,只是后来发现与 highmem.h 中的宏定义重名了,为了避免开发人员在自己的驱动中引用 highmem.h 而带来的命名冲突问题,于是去掉了前面的“k”字。
想了解更多关于 kmap() 、vmap() 的信息,推荐阅读参考资料中的《Linux内核内存管理架构》一文。
5. 示例程序
本示例分为 exporter 和 importer 两个驱动。
首先是 exporter 驱动,我们基于上一篇的 exporter-dummy.c,对其 exporter_kmap() 和 exporter_vmap() 进行扩展,具体如下:
exporter-kmap.c
#include <linux/dma-buf.h>
#include <linux/module.h>
#include <linux/slab.h>
struct dma_buf *dmabuf_exported;
EXPORT_SYMBOL(dmabuf_exported);
static void *exporter_kmap(struct dma_buf *dmabuf, unsigned long page_num)
{
return dmabuf->priv;
}
static void *exporter_kmap_atomic(struct dma_buf *dmabuf, unsigned long page_num)
{
return dmabuf->priv;
}
static void *exporter_vmap(struct dma_buf *dmabuf)
{
return dmabuf->priv;
}
static void exporter_release(struct dma_buf *dmabuf)
{
kfree(dmabuf->priv);
}
...
static const struct dma_buf_ops exp_dmabuf_ops = {
.map = exporter_kmap,
.map_atomic = exporter_kmap_atomic,
.vmap = exporter_vmap,
.release = exporter_release,
...
};
static struct dma_buf *exporter_alloc_page(void)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
struct dma_buf *dmabuf;
void *vaddr;
vaddr = kzalloc(PAGE_SIZE, GFP_KERNEL);
exp_info.ops = &exp_dmabuf_ops;
exp_info.size = PAGE_SIZE;
exp_info.flags = O_CLOEXEC;
exp_info.priv = vaddr;
dmabuf = dma_buf_export(&exp_info);
sprintf(vaddr, "hello world!");
return dmabuf;
}
static int __init exporter_init(void)
{
dmabuf_exported = exporter_alloc_page();
return 0;
}
module_init(exporter_init);
然后我们再编写一个 importer 驱动,用于演示如何在 kernel 空间,通过 dma_buf_kmap() / dma_buf_vmap() 接口操作 exporter 驱动导出的 dma-buf。
importer-kmap.c
#include <linux/dma-buf.h>
#include <linux/module.h>
#include <linux/slab.h>
extern struct dma_buf *dmabuf_exported;
static int importer_test(struct dma_buf *dmabuf)
{
void *vaddr;
vaddr = dma_buf_kmap(dmabuf, 0);
pr_info("read from dmabuf kmap: %s\n", (char *)vaddr);
dma_buf_kunmap(dmabuf, 0, vaddr);
vaddr = dma_buf_vmap(dmabuf);
pr_info("read from dmabuf vmap: %s\n", (char *)vaddr);
dma_buf_vunmap(dmabuf, vaddr);
return 0;
}
static int __init importer_init(void)
{
return importer_test(dmabuf_exported);
}
module_init(importer_init);
示例描述:
exporter 通过 kzalloc 分配了一个 PAGE 大小的物理连续 buffer,并向该 buffer 写入了“hello world!” 字符串;
importer 驱动通过 extern 关键字导入了 exporter 的 dma-buf,并通过 dma_buf_kmap()
、dma_buf_vmap()
函数读取该 buffer 的内容并输出到终端显示。
6. 运行
在 my-qemu 仿真环境中执行如下命令:
# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/exporter-kmap.ko
# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/importer-kmap.ko
将看到如下打印结果:
read from dmabuf kmap: hello world!
read from dmabuf vmap: hello world!
注意:执行 insmod 命令时,必须先加载 exporter-kmap.ko,后加载 importer-kmap.ko,否则将出现符号依赖错误。
或者直接使用“modprobe importer_kmap”命令来自动解决模块依赖问题。
7. 总结
通过本篇,我们学习了 dma_buf_kmap()
、dma_buf_vmap()
函数的底层实现,以及如何使用这两个 API,它们是 CPU 在 kernel 空间访问 dma-buf 的典型代表。在下一篇,我们将一起来学习如何通过 DMA 硬件来访问 dma-buf 的物理内存。
评论