目录
内存映射mmap总介
内存映射mmap是Linux内核的一个重要机制,它和虚拟内存管理以及文件IO都有直接的关系,这篇细说一下mmap的一些要点。Linux的虚拟内存管理是基于mmap来实现的。vm_area_struct是在mmap的时候创建的,vm_area_strcut代表了一段连续的虚拟地址,这些虚拟地址相应地映射到一个后备文件或者一个匿名文件的虚拟页。一个vm_area_struct映射到一组连续的页表项。页表项又指向物理内存page,这样就把一个文件和物理内存页相映射。
一、用户进程访问内存分析
用户态进程独占虚拟地址空间,两个进程的虚拟地址可相同
在访问用户态虚拟地址空间时,如果没有映射物理地址,通过系统调用发出缺页异常
缺页异常陷入内核,分配物理地址空间,与用户态虚拟地址建立映射
二、dma_buf关键代码解读
struct dma_buf *
dma_buf_export(void *priv, struct dma_buf_ops *ops, size_t size, int flags)
⽤户空间获取⽂件句柄并传递给潜在消费者
⽤户程序请求⼀个⽂件描述符(fd),该⽂件描述符指向和缓冲区关联的匿名⽂件。⽤户程序可以将⽂件描述符共享给驱动程序或者⽤户进
程程序。
int dma_buf_fd(struct dma_buf *dmabuf)
该函数创建为匿名⽂件创建⼀个⽂件描述符,返回"fd"或者错误。
3. 消费者将其绑定在缓冲区上
现在每个消费者可以通过⽂件描述符fd获取共享缓冲区的引⽤。
struct dma_buf * dma_buf_get(int fd)
该函数返回⼀个dma_buf的引⽤,同时增加它的refcount(该值记录着dma_buf被多少消费者引⽤)。
获取缓冲区应⽤后,消费者需要将它的设备附着在该缓冲区上,这样可以让⽣产者知道设备的寻址限制。
struct dma_buf_attachment *
dma_buf_attach(struct dma_buf *dmabuf, struct device *dev)
该函数返回⼀个attachment的数据结构,该结构会⽤于scatterlist的操作。
dma-buf共享框架有⼀个记录位图,⽤于管理附着在该共享缓冲区上的消费者。
到这步为⽌,⽣产者可以选择不在实际的存储设备上分配该缓冲区,⽽是等待第⼀个消费者申请共享内存。
4. 如果需要,消费者发出访问该缓冲区的请求
当消费者想要使⽤共享内存进⾏DMA操作,那么它就会通通过接⼝dma_buf_map_attachment来访问缓冲区。在调⽤map_dma_buf前⾄少有⼀个消费者与之关联。
struct sg_table *
dma_buf_map_attachment(struct dma_buf_attachment *, enum dma_data_direction);
该函数是dma_buf->ops->map_dma_buf的⼀个封装,它可以对使⽤该接⼝的对象隐藏"dma_buf->ops->"
struct sg_table *
(*map_dma_buf)(struct dma_buf_attachment *, enum dma_data_direction);
⽣产者必须实现该函数。它返回⼀个映射到调⽤者地址空间的sg_table,该数据结构包含了缓冲区的scatterlist。
如果第⼀次调⽤该函数,⽣产者现在可以扫描附着在共享缓冲区上的消费者,核实附着设备的请求,为缓冲区选择⼀个合适的物理存储空
间。
基于枚举类型dma_data_direction,多个消费者可能同时访问共享内存(⽐如读操作)。如果被⼀个信号中断,map_dma_buf()可能返回-EINTR。
5. 当使⽤完成,消费者通知⽣成者DMA传输结束
当消费者完成DMA操作,它可以通过接⼝函数dma_buf_unmap_attachment发送“end-of-DMA”给⽣产者。
void
dma_buf_unmap_attachment(struct dma_buf_attachment *, struct sg_table *);
该函数是dma_buf->ops->unmap_dma_buf()的封装,对使⽤该接⼝的对象隐藏"dma_buf-
当消费者不再使⽤该共享内存,则脱离该缓冲区;
当消费者对该共享缓冲区没有任何兴趣后,它应该断开和该缓冲区的连接。
a. ⾸先将其从缓冲区中分离出来。
void
dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *dmabuf_attach);
此函数从dmabuf的attachment链表中移除了该对象,如果消费者实现了dma_buf->ops->detach(),那么它会调⽤该函数。
b. 然后消费者返回缓冲区的引⽤给⽣产者。
void dma_buf_put(struct dma_buf *dmabuf);
该函数减⼩缓冲区的refcount。
如果调⽤该函数后refcount变成0,该⽂件描述符的"release"函数将会被调⽤。它会调⽤dmabuf->ops->release(),企图释放⽣产者为dmabuf申请的内存。
注意事项:
a. attach-detach及{map,unmap}_dma_buf成对执⾏⾮常重要。
他消费者⽽⾔这个是全透明的。⽐如其他⽤户空间消费者注意不到⼀个 dma-buf是否做过⼀次撤销/回退操作。
三、内核处理器访问dma-buf缓冲区对象
在内核上下⽂访问dma_buf需要下⾯三个步骤:
1. 访问前的准备⼯作,包括使相关cache⽆效,使处理器可以访问缓冲区对象;
2. 通过dma_buf map接⼝函数以页为单位访问对象;
3. 完成访问时,需要刷新必要的处理器cache,释放占⽤的资源;
1. 访问前的准备⼯作
处理器在内核空间打算访问dma_buf对象前,需要通知⽣产者。
int dma_buf_begin_cpu_access(struct dma_buf *dmabuf, size_t start, size_t len, enum dma_data_direction direction)
⽣产者可以确保处理器可以访问这些内存缓冲区,⽣产者也需要确定处理器在指定区域及指定⽅向的访问是⼀致性的。⽣产者可以使⽤访问
区域及访问⽅向来优化cache flushing。⽐如访问指定范围外的区域或者不同的⽅向(⽤读操作替换写操作)会导致陈旧的或者不正确的数
据(⽐如⽣产者需要将数据拷贝到零时缓冲区)。
该函数调⽤可能会失败,⽐如在OOM(内存紧缺)的情况下。
2. 访问缓冲区
为了⽀持处理器可以访问到驻留在⾼端内存中的dma_buf对象,需要调⽤⼀个和kmap类似的接⼝函数。访问dma_buf需要页对齐。在访问对象前需要先做映射⼯作,及需要得到⼀个内核虚拟地址。操作完后,需要取消该对象的映射
void * dma_buf_kmap(struct dma_buf *, unsigned long);
void dma_buf_kunmap(struct dma_buf *, unsigned long, void *);
该函数有对应的原⼦操作函数,如下所⽰。在调⽤原⼦操作函数时,⽣产者和消费者都不能被阻塞。
void * dma_buf_kmap_atomic(struct dma_buf *, unsigned long);
void dma_buf_kunmap_atomic(struct dma_buf *, unsigned long, void *);
⽣产者在同⼀时间不能同时调⽤原⼦操作函数(在任何进程空间)。
如果访问缓冲区区域不是页对齐的,虽然kmap对应的区域数据得到了更新,但是在这个区域附近的区域数据也相应得到了更新,这个不是
我们所希望的。也就是说kmap更新了⾃⼰关⼼的区域外,还更新了其他区域,对于那些区域的使⽤者来说,数据就已经失效了。
下图给出了⼀个例⼦,⼀共有四个连续的页,其中kmap没有页对齐获取部分缓冲区,即红⾊部分,由于会同步⽤户空间通过mmap直接访问缓冲区。
在⽤户空间映射⼀个dma-buf对象,主要有两个原因:
处理器回退/撤销操作;
⽀持消费者程序中已经存在的mmap接⼝;
1. 处理器在⼀个pipeline中回退/撤销操作
在处理pipeline过程中,有时处理器需要访问dma-buf中的数据(⽐如创建thumbnail, snapshots等等)。⽤户空间程序通过使⽤dma-buf的⽂
件描述符fd调⽤mmap来访问dma-buf中的数据是⼀个好办法,这样可以避免⽤户空间程序对共享内存做⼀些特殊处理。
进⼀步说Android的ION框架已经实现了该功能(从⽤户空间消费者来说它实现了⼀个和dma-buf很像的东西,使⽤fds⽤作⽂件句柄)。因此
实现该功能对于Android⽤户空间来说是有意义的。
没有特别的接⼝,⽤户程序可以直接基于dma-buf的fd调⽤mmp。
2. ⽀持消费者程序中已经存在的mmap接⼝
与处理器在内核空间访问dma-buf对象⽬的⼀样,⽤户空间消费者可以将⽣产者的dma-buf缓冲区对象当做本地缓冲区对象⼀样使⽤。这对drm特别重要,其Opengl,X的⽤户空间及驱动代码⾮常巨⼤,重写这部分代码让他们⽤其他⽅式的mmap,⼯作量会很⼤。
int dma_buf_mmap(struct dma_buf *, struct vm_area_struct *, unsigned lon
四、引入dma-buf机制的原因
之前内核中缺少一个可以让不同设备、子系统之间进行内存共享的统一机制。
dma_buf是内核中一个独立的子系统,提供了一个让不同设备、子系统之间进行共享缓存的统一框架,这里说的缓存通常是指通过DMA方式访问的和硬件交互的内存。 比如,来自摄像头采集的通过pciv驱动传输的内存、gpu内部管理的内存等等。
其实一开始,dma_buf机制在内核中的主要运用场景是支持GPU驱动中的prime机制,但是作为内核中的通用模块,它的适用范围很广。
dma_buf子系统包含三个主要组成:
1.dma-buf对象,它代表的后端是一个sg_table,它暴露给应用层的接口是一个文件描述符,通过传递描述符达到了交互访问dma-buf对象,进而最终达成了 共享访问sg_table的目的。
2. fence对象, which provides a mechanism to signal when one device as finished access.
3.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映射到自己地址空间的机制。
dma_buf对象中更加重要的一个成员变量是file,我们知道一切皆文件是unix的核心思想。dma_buf子系统之所以可以使不同的驱动设备可以共享访问内存,就 是借助于文件系统是全局的这个特征。另外,因为Unix操作系统都是通过Unix domain域的socket使用SCM_RIGHTS语义来实现文件描述符传递,所以安全性很高。和dma_buf对象中file成员变量对应的API接口有,dma_buf_export()、dma_buf_fd()。