scatterlist分析

2070阅读 0评论2014-08-14 wangbaolin719
分类:嵌入式

        使用scatterlist的原因就是系统在运行的时候内存会产生很多碎片,比如4k,100k的,1M的,有时候对应磁盘碎片,总之就是碎片。而在网络和磁盘操作中很多时候需要传送大块的数据,尤其是使用DMA的时候,因为DMA操作的物理地址必须是连续的。假设要1M内存,此时可以分配一个整的1M内存, 也可以把10个10K的和9个100K的组成一块1M的内存,当然这19个块可能是不连续的,也可能其中某些或全部是连续的,总之情况不定,为了描述这种情况,就引入了scatterlist,其实看成一个关于内存块构成的链表就OK了。
        在SD/MMC代码中,在发起request的时候,都是通过scatterlist来发送数据的,定义在mmc_data里面,(MMC core就是这么设计的,跟具体的S3C2410还是PXA就没有关系了)
struct mmc_data {
。。。。。。。。。。。。。。
。。。。。。。。。。。。。。
。。。。。。。。。。。。。。
 unsigned int  sg_len;  /* size of scatter list */
 struct scatterlist *sg;  /* I/O scatter list */
}
        其中struct scatterlist *sg;就是指向scatter list的指针,可以理解为数组的头指针,这个数组的作用就是保存各个scatterlist结构的地址,sg_len表示有几个 scatterlist结构,相当于数组元素个数。比如前面提到的那个例子,sg_len就应该是19了,sg组成的内存块就是 sg_mem0--->sg_mem1--->........->sg_mem18这样的内存链。所以通过sg就可以遍历19块中的任意一块内存的情况,比如位置和大小。以下是scatterlist的定义:
struct scatterlist {
#ifdef CONFIG_DEBUG_SG
 unsigned long sg_magic;
#endif
 unsigned long page_link;
 unsigned int offset;
 unsigned int length;
 dma_addr_t dma_address;
 unsigned int dma_length;
};
        下面以dw_mmc.c里面的scatter操作来分析,其实pxamci.c里面也有,不过pxamci.c里面只使用了DMA模式,相对要简单一点,dw_mmc.c里面还使用pio模式(其实就是cpu模式),要复杂一些,所以分析起来更有意义。
1.DMA模式下的使用
        在使用DMA操作这些scatterlist之前,先要对scatterlist进行一下map:
        int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
                                  enum dma_data_direction dir)
        其中的nents就是scatterlist的块数,sg是指针数组的首地址。返回值是map以后这些地址块被合并为多少个适合DMA搬运的块的数量,假设其中一块的结束地址和另一块的起始地址挨到一起了,这两块是会合二为一的,这就是为什么说返回的值可能会小于 nents的原因。比如上面的例子传进去的nents=19, 函数的返回值肯定是小于等于19的,当然肯定大于0,同时sg的值也被改写成了新的块链表。此时就可以把这些块放入DMA队列一个一个的进行搬运了。 s3cmci.c中的代码是这样的。
 dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
        rw ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
。。。。。。。。。。。。。。。。。。
 for (i = 0; i < dma_len; i++) {
。。。。。。。。。。。。。。。。。。。。。
      sg_dma_address(&data->sg[i]),
      sg_dma_len(&data->sg[i]));
      res = dw_dma_enqueue(host->dma, host,
       sg_dma_address(&data->sg[i]),
       sg_dma_len(&data->sg[i]));
。。。。。。。。。。。。。。。。。。
 }
        for循环其实就是遍历内存块了,不过据下面这个网址上说的,这种用for的方式已经out了,现在要用for_each_sg, 其实是一样的:
        /* Fill in list and pass it to dma_map_sg().  Then... */
    for_each_sg(i, list, sgentry, nentries) {
             program_hw(device, sg_dma_address(sgentry), sg_dma_len(sgentry));
    }

2.CPU方式
        CPU方式就不用调用map了,这些list本来就是CPU自己产生自己消化。在dw_mmc.c中是通过函数:
        static inline int get_data_buffer(struct dw
_host *host,
                                                      u32 *bytes, u32 **pointer)
来实现的,它使用了一个计数host->pio_sgptr 来记录现在使用的是内存链中的第几块,这个值在每次request后prepare_pio的时候被清零,每次调用get_data_buffer就加 一,直到等于sg_len,表示所有的内存块都用过了。对于一个scatterlist指针sg,是如下这样获取它代表的内存块的大小和位置的:
 *bytes = sg->length;/*内存块长度*/
 *pointer = sg_virt(sg);/*内存块起始地址*/
        在dw_mmc.c中使用了一点点小技巧来操作scatterlist和SD/MMC FIFO发送/接收,比如在do_pio_write中
  while ((fifo = fifo_free(host)) > 3) {
  if (!host->pio_bytes) {
   res = get_data_buffer(host, &host->pio_bytes,
       &host->pio_ptr);
   if (res) {
    dbg(host, dbg_pio,
        "pio_write(): complete (no more data).\n");
    host->pio_active = XFER_NONE;
    return;
   }
。。。。。。。。。。。。。。。。。。。。。。。。
  if (fifo >= host->pio_bytes)
   fifo = host->pio_bytes;
  else
   fifo -= fifo & 3;
  host->pio_bytes -= fifo;
  host->pio_count += fifo;
  fifo = (fifo + 3) >> 2;
  ptr = host->pio_ptr;
  while (fifo--)
   writel(*ptr++, to_ptr);
  host->pio_ptr = ptr;
 }
        它通过host->pio_bytes来记录当前的内存块还有多少数据没有发,如果FIFO里面的空间够用,那就直接都发了,如果不够呢,则先把 FIFO填满,然后等着下一次中断的时候再发。如果这个内存块的数据都发完了,则host->pio_bytes为0,此时调用 get_data_buffer来获取内存链中的下一块内存数据,在get_data_bu中host->pio_bytes会被置为新块的长度:
 *bytes = sg->length
        其中的*bytes就是指向host->pio_bytes的。
        fifo-=fifo&3 是因为2410每次必须发四个字节,所以要把零头去掉,EVB也有这个问题。
        其实scatterlist这个东东蛮有意思的,俺们的Nucleus上其实也可以借鉴的,由于内存太少,在解JPEG文件时不一定能分到那么大的一块连续内存,可以通过scatterlist来把文件分块读取,然后解码的时候DMA再一块一块的搬,总比分不到内存就返回失败来的强。不过对于应用来讲如果没有MMU支持,还是有点杯具的,如果有MMU支持,让应用层看到的是一整块的内存,估计要爽的多,甚至在文件系统层也是这样的,只要到DMA搬数之前把scatterlist的内存链分清楚就OK了。

上一篇:内核内存分配API
下一篇:arm linux启动流程二