分类: LINUX
2010-11-10 16:25:48
先来点概念:
http://blog.csdn.net/fudan_abc/archive/2007/07/28/1713976.aspx
无聊的it玩家们创建了有一个词,叫做scatter/gather,她是一种用于高性能IO的标准技术.她通常意味着一种DMA传输方式,对于一个给定的数据块,她老人家可能在内存中存在于一些离散的缓冲区,换言之,就是说一些不连续的内存缓冲区一起保存一个数据块,如果没有scatter/gather呢,那么当我们要建立一个从内存到磁盘的传输,那么操作系统通常会为每一个buffer做一次传输,或者干脆就是把这些不连续的buffer里边的冬冬全都移动到另一个很大的buffer里边,然后再开始传输.那么这两种方法显然都是效率不高的.毫无疑问,如果 操作系统/驱动程序/硬件 能够把这些来自内存中离散位置的数据收集起来(gather up)并转移她们到适当位置整个这个步骤是一个单一的操作的话,效率肯定就会更高.反之,如果要从磁盘向内存中传输,而有一个单一的操作能够把数据块直接分散开来(scatter)到达内存中需要的位置,而不再需要中间的那个块移动,或者别的方法,那么显然,效率总会更高.
在struct scsi_cmnd中,有一个成员unsigned short use_sg,上头传下来的scsi_cmnd,其use_sg是设好了的,咱们判断一下,如果她为0,那么说明没有使用scatter/gather.struct scsi_cmnd中还有两个成员,unsigned request_bufflen和void *request_buffer,她们和use_sg是什么关系呢?
事实上,要玩scatter/gather,就需要一个scatterlist数组,有人管她叫散列表数组.对于不同的硬件平台,定义了不同的struct scatterlist结构体,她们来自include/asm/scatterlist.h中,(如果是硬件平台i386的,那么就是include/asm-i386/scatterlist.h,如果是x86_64的平台,那么就在include/asm-x86_64/scatterlist.h中),然后所谓的scatter/gather就是一次把整个scatterlist数组给传送掉.而use_sg为0就表示没有scatter gather list,或者说scatterlist,对于这种情况,数据将直接传送给request_buffer或者直接从request_buffer中取得数据.而如果use_sg大于0,那么表示scatter gather list这么一个数组就在request_buffer中,而数组元素个数正是use_sg个.也就是说,srb->request_buffer里边的冬冬有两种可能,一种是包含了数据本身,另一种是包含了scatter gather list.具体是哪种情况通过判断use_sg来决定.而接下来即将要讲到的srb->request_bufflen顾名思义,就是buffer的长度,但对于use_sg大于0的情况,换言之,对于使用scatter gather list的情况,request_bufflen没有意义,将被忽略.
对这些原理有了基本的了解之后,我们可以从下节开始看代码了.这里先提醒一下,要注意我们这个函数虽然看似是传输数据,可它实际上并没有和usb真正发生关系,我们只是从软件上来fix一个硬件的bug,这个bug就是我们已经说过了的,不能响应基本的SCSI命令INQUIRY,你问她,她完全不予理睬.至于为什么不能响应我就不用说了吧?人的冷漠,不是因为天生就如此,只是暗淡的心境早已将所有通向阳光的窗户关闭了.而设备的冷漠,显然是制造商做的傻事了.(我没有讽刺Sony的意思,事实上这种情况并非只有Sony他们家才出现了,别的厂家也有这样的产品.只是,Device有bug,可以让程序去补,我们80后的梦碎了,拿什么去补?)
再来点(LDD3的):
15.4.4.7. 发散/汇聚映射
发散/汇聚映射是一个特殊类型的流 DMA 映射. 假设你有几个缓冲, 都需要传送数据到或者从设备. 这个情况可来自几个方式, 包括从一个 readv 或者 writev 系统调用, 一个成簇的磁盘 I/O 请求, 或者一个页链表在一个被映射的内核 I/O 缓冲. 你可简单地映射每个缓冲, 轮流的, 并且进行要求的操作, 但是有几个优点来一次映射整个链表.
许多设备可以接收一个散布表数组指针和长度, 并且传送它们全部在一个 DMA 操作中; 例如, "零拷贝"网络是更轻松如果报文在多个片中建立. 另一个映射发散列表为一个整体的理由是利用在总线硬件上有映射寄存器的系统. 在这样的系统上, 物理上不连续的页从设备的观点看可被汇集为一个单个的, 连续的数组. 这个技术只当散布表中的项在长度上等于页大小(除了第一个和最后一个), 但是当它做这个工作时, 它可转换多个操作到一个单个的 DMA, 和有针对性的加速事情.
最后, 如果一个反弹缓冲必须被使用, 应该连接整个列表为一个单个缓冲(因为它在被以任何方式拷贝).
因此现在你确信散布表的映射在某些情况下是值得的. 映射一个散布表的第一步是创建和填充一个 struct scatterlist 数组, 它描述被传输的缓冲. 这个结构是体系依赖的, 并且在
struct page *page;
struct page 指针, 对应在发散/汇聚操作中使用的缓冲.
unsigned int length;
unsigned int offset;
缓冲的长度和它的页内偏移.
为映射一个发散/汇聚 DMA 操作, 你的驱动应当设置 page, offset, 和 length 成员在一个 struct scatterlist 项给每个要被发送的缓冲. 接着调用:
int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)
这里 nents 是传入的散布表项的数目. 返回值是要发送的 DMA 缓冲的数目. 它可能小于 nents.
对于输入散布表中的每个缓冲, dma_map_sg 决定了正确的给设备的总线地址. 作为任务的一部分, 它也连接在内存中相近的缓冲. 如果你的驱动运行的系统有一个 I/O 内存管理单元, dma_map_sg 也编程这个单元的映射寄存器, 可能的结果是, 从你的驱动的观点, 你能够传输一个单个的, 连续的缓冲. 你将不会知道传送的结果将看来如何, 但是, 直到在调用之后.
你的驱动应当传送由 pci_map_sg 返回的每个缓冲. 总线地址和每个缓冲的长度存储于 struct scatterlist 项, 但是它们在结构中的位置每个体系不同. 2 个宏定义已被定义来使得可能编写可移植的代码:
dma_addr_t sg_dma_address(struct scatterlist *sg);
从这个散布表入口返回总线( DMA )地址.
unsigned int sg_dma_len(struct scatterlist *sg);
返回这个缓冲的长度.
再次, 记住要传送的缓冲的地址和长度可能和传递给 dma_map_sg 的不同.
一旦传送完成, 一个 发散/汇聚 映射被使用 dma_unmap_sg 去映射:
void dma_unmap_sg(struct device *dev, struct scatterlist *list, int nents, enum dma_data_direction direction);
注意 nents 必须是你起初传递给 dma_map_sg 的入口项的数目, 并且不是这个函数返回给你的 DMA 缓冲的数目.
发散/汇聚映射是流 DMA 映射, 并且同样的存取规则如同单一映射一样适用. 如果你必须存取一个被映射的发散/汇聚列表, 你必须首先同步它:
void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction direction);
void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction direction);
//------------------------------------------------------------------------------------------------------
一般在块设备会使用到,比如sd卡驱动中,使用方法如下:
1、定义一个结构体struct has_sa{unsigned int sg_len; struct scatterlist *sg;}
其中,sg_len表示有一个“散开”的sg数组,而*sg就是表示有几个数组。这个相当于:
unsigned int len; unsigned int buffer[len];只不过他是用指针来表示数组。
2、给结构体成员赋值:
sg_len=1;
sg = &mysg;
这就表示有一个“散开”的序列。如此类推。
3、使用:
struct has_sa *has_sa;
1)取得当前的散布表:struct scatterlist *cur_sg = &has_sa->sg[index];
2)然后给此集散序列分配一段内存以便使用dma之类的:
dma_address = dma_map_page(NULL, sg_page(sg), sg->offset, sg->length, DMA_FROM_DEVICE);
3)使用完后释放该段内存(一般在dma中断里面):
dma_unmap_page(NULL, sg->dma_address, sg->length, DMA_FROM_DEVICE);
4、还有dma_map_sg(),一次性把所有的sg全部映射完。