1、mmap设备操作
映射一个设备意味着将用户空间的一段内存与设备内存关联起来。无论何时当程序在分配的地址范围内读写时,实际上访问的就是设备。不是所有的设备都能进行mmap抽象。比如像串口和其他面向流的设备就不能。mmap的另一个限制是:必须以PAGE_SIZE为单位进行映射。
mmap方法是file_operation结构的一部分,并且执行mmap系统调用时将调用该方法。为了执行mmap,驱动程序只需要为该地址范围建立合适的页表,并将vma->vm_ops替换为一系列的新操作即可。
有两种建立页表的方法:使用remap_pfn_range函数一次全部建立,或者通过nopage方法每次建立一个页。
2、执行直接IO访问
如果需要传递的数据量非常大,直接进行数据传输,而不需要额外的从内核空间拷贝数据操作的参与,这将会大大提高速度。使用直接IO并不在任何情况下都能提高性能。设置直接IO的开销很大,而又没有使用IO缓存的优势。比如,使用直接IO需要write系统调用同步执行;否则应用程序将会不知道什么时候能再次使用他的IO缓冲区。在每个写操作完成之前不能停止应用程序,这样会导致关闭程序缓慢,这就是使用直接IO的应用程序也使用异步IO的原因。
在字符设备中执行直接IO是不行的,也是有害的。只有确定设置缓冲IO的开销特别大,才使用直接IO。块设备和网络设备不担心实现直接IO的问题,内核高层代码设置和使用了直接IO,而驱动程序级的代码甚至不需要知道已经执行了直接IO。
2.6内核中,实现直接IO的关键名为get_user_pages函数。如果函数调用成功调用者就会拥有一个指向用户空间缓冲区的页数组,它将被锁在内存中。为了能直接操作缓冲区,内核空间代码必须用kmap或kmap_atomic函数将每个page结构指针转换为内核虚拟地址。使用直接IO的设备通常使用DMA操作,因此驱动程序要从page结构指针数组中创建一个分散/聚集链表。
一旦直接IO操作完成则需要释放用户内存页。在释放前,如果改变了这些页中的内容,则必须通知内核,否则内核会认为这些页是干净的,也就是说,内核会认为他们与交换设备中的拷贝是匹配的,因此,无需回存就能释放他们。
3、异步IO
aio_read和aio_write函数的目的是初始化读和写操作,在这两个函数完成时,读写操作可能已经完成。如果操作立刻完成,函数将返回常规状态:传输的字节数或者错误码。
如何允许异步操作:如果驱动程序可以开始操作,必须记住与操作相关的所有信息,并且返回-EIOCBQUEUE给调用者。记住操作的信息包括了安排对用户空间缓冲区的访问;一旦返回,因为要运行在调用进程的上下文中,所以将不能再访问这个缓冲区。通常这意味着建立直接的内核映射(get_user_pages)或者DMA映射。-EIOCBQUEUE表示操作没有完成,它的最终状态将在未来某个时刻公布。当未来某个时刻到来时,驱动程序必须通知内核操作已经完成。使用aio_complete函数。
4、直接内存访问DMA
DMA是一种硬件机制,他允许外围设备和主内存之间直接传输他们的IO数据,而不需要系统处理器的参与。使用这种机制可以提高与设备通信的吞吐量,因为免除了大量的计算开销。
DMA需要设备驱动程序分配一个或者多个适合执行DMA的特殊缓冲区。
必须谨慎的为DMA操作分配正确的内存类型,因为并不是所有内存区间都适合DMA操作。在实际操作中,一些设备和一些系统中的高端内存不能用于DMA,这是因为外围设备不能使用高端内存的地址。在现代总线上的大多数设备能够处理32位地址,这意味着常用的内存分配机制能很好的工作。一些PCI设备没能实现全部的PCI标准,因此不能使用32位地址,而一些ISA设备还局限在使用24位地址的阶段。
对于有这些限制的设备,应使用GFP_DMA标志调用kmalloc或者get_free_pages从DMA区间分配内存。当设置了该标志时,只有使用24位寻址方式的内存才能被分配。另外韩可以使用通用DMA层来分配缓冲区,这样也恩那个满足对设备限制的需要。
如果需要为DMA缓冲区分配一大块内存,最好考虑一下是否有替代方法。如果设备支持分散/聚合IO,则可以将缓冲区分配成多个小块,设备会很好的处理它们。当用户空间中执行直接IO的时候,也可以用分散/聚合IO。
基于DMA的硬件使用总线地址,而非物理地址。
int dma_set_mask(struct device *dev,u64 mask);//如果使用指定的mask时DMA能正常工作,则返回非零值。
void *dma_alloc_coherent(struct device *dev,size_t size,dma_addr_t *dma_handle,int flag);//分配一致性DMA缓冲区;函数的返回值是缓冲区的内核虚拟地址,可以被驱动程序使用;而与其相关的总线地址,返回时保存在dma_handle中。
void dma_free_alloc_coherent(struct device *dev,size_t size,void *addr,dma_addr_t dma_handle);//一致性DMA缓冲区释放
struct dma_pool *dma_pool_create(const char *name ,struct device *dev,size_t size,size_t align,size_t allocation);//创建DMA池
void dma_pool_destroy(struct dma_pool *pool);//销毁DMA池
void *dma_pool_alloc(struct dma_pool *pool,int mem_flags,dma_addr_t *handle);//从DMA池中分配DMA缓冲区
void dma_pool_free(struct dma_pool *pool,void *vaddr,dma_addr_t addr);//释放缓冲区到DMA池
dma_addr_t dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction);
void dma_unmap_single(struct device *dev,dma_addr_t dma_addr,size_t size,enmu dma_data_direction direction);
流式DMA原则:
1、缓冲区的传送方向必须匹配与映射时给定的方向值
2、一旦缓冲区被映射,他将属于设备,而不是处理器。直到缓冲区被撤销映射前,驱动程序不能以任何方式访问其中的内容
3、在DMA处于活动期间,不能撤销对缓冲区的映射,否则会严重破坏系统的稳定性。
有时候,驱动程序需要不经过撤销映射就访问流式DMA缓冲区,内核提供了一下方法:
void dma_sync_single_for_cpu(struct device *dev,dma_handle_t bus_addr,size_t size,enmu dma_data_direction direction);//应该在处理器访问流式DMA缓冲区前调用该函数。一旦调用,则处理器将“拥有”DMA缓冲区,并可根据需要对他进行访问。
void dam_sync_single_for_device(...)//在设备访问缓冲区前,应该调用该函数将缓冲区所有权还给设备。
dma_addr_t dma_map_page(struct device *dev,struct page *page,unsigned long offset,size_t size,enum dma_data_direction direction);//单页流式映射
void dma_unmap_page(struct device *dev,dma_addr_t dma_address,size_t size,enmu dma_data_direction);//撤销单页映射
int dma_map_sg(struct device *dev,struct scatterlist *sg,int nents,enum dma_data_direction direction);//分散/聚集映射建立
dma_addr_t sg_dma_address(struct scatterlist *sg);//从该分散表的入口返回总线地址
unsigned int sg_dma_len(struct scatterlist *sg);//返回缓冲区的长度
来源:https://www.cnblogs.com/shuying1234/archive/2013/05/20/3087951.html