1. 程式人生 > >Linux核心中的迴圈緩衝區【轉】

Linux核心中的迴圈緩衝區【轉】

 

(轉自:https://blog.csdn.net/heanyu/article/details/6291825

迴圈緩衝區定義在include/linux/kfifo.h中,如下:

struct kfifo { 
    unsigned char *buffer; // buffer指向存放資料的緩衝區 
    unsigned int size;        // size是緩衝區的大小 
    unsigned int in;           // in是寫指標下標 
    unsigned int out;        // out是讀指標下標 
    spinlock_t *lock;         // lock是加到struct kfifo上的自旋鎖 
};  

(上面說的免鎖不是免這裡的鎖,這個鎖是必須的),防止多個程序併發訪問此資料結構。當in==out時,說明緩衝區為空;當(in-out)==size時,說明緩衝區已滿。 
       為kfifo提供的介面可以分為兩類: 
       一類是滿足上述情況下使用的,以雙下劃線開頭,沒有加鎖的; 
       另一類是在不滿足的條件下,即需要額外加鎖的情況下使用的。 
       其實後一類只是在前一類的基礎上進行加鎖後的包裝(也有一處進行了小的改進),實現中所加的鎖是spin_lock_irqsave。


清空緩衝區的函式: 
static inline void __kfifo_reset(struct kfifo *fifo); 
static inline void kfifo_reset(struct kfifo *fifo); 
這很簡單,直接把讀寫指標都置為0即可。

 

向緩衝區裡放入資料的介面是: 
static inline unsigned int kfifo_put(struct kfifo *fifo, unsigned char *buffer, unsigned int len); 
unsigned int __kfifo_put(struct kfifo *fifo, unsigned char *buffer, unsigned int len);

後者是在kernel/kfifo.c中定義的。這個介面是經過精心構造的,可以小心地避免一些邊界情況。我們有必要一起來看一下它的具體實現。
 

   /**
    * __kfifo_put - puts some data into the FIFO, no locking version
    * @fifo: the fifo to be used.
    * @buffer: the data to be added.
    * @len: the length of the data to be added.
    *
    * This function copies at most @len bytes from the @buffer into
    * the FIFO depending on the free space, and returns the number of
    * bytes copied.
    *
    * Note that with only one concurrent reader and one concurrent
    * writer, you don't need extra locking to use these functions.
    */

    unsigned int __kfifo_put(struct kfifo *fifo,
               const unsigned char *buffer, unsigned int len)
    {
       unsigned int l;
    
       len = min(len, fifo->size - fifo->in + fifo->out);
    
       /*
        * Ensure that we sample the fifo->out index -before- we
        * start putting bytes into the kfifo.
        */
    
       smp_mb();
    
       /* first put the data starting from fifo->in to buffer end */
       l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
       memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);
    
       /* then put the rest (if any) at the beginning of the buffer */
       memcpy(fifo->buffer, buffer + l, len - l);
    
       /*
        * Ensure that we add the bytes to the kfifo -before-
        * we update the fifo->in index.
        */
    
       smp_wmb();
   
       fifo->in += len;
    
       return len;
    }
    EXPORT_SYMBOL(__kfifo_put);
   /**
    * kfifo_put - puts some data into the FIFO
    * @fifo: the fifo to be used.
    * @buffer: the data to be added.
    * @len: the length of the data to be added.
    *
    * This function copies at most @len bytes from the @buffer into
    * the FIFO depending on the free space, and returns the number of
    * bytes copied.
    */
   static inline unsigned int kfifo_put(struct kfifo *fifo,
                   const unsigned char *buffer, unsigned int len)
   {
       unsigned long flags;
       unsigned int ret;
    
       spin_lock_irqsave(fifo->lock, flags);
    
       ret = __kfifo_put(fifo, buffer, len);
    
       spin_unlock_irqrestore(fifo->lock, flags);
    
       return ret;
   }

len = min(len, fifo->size - fifo->in + fifo->out); 
      在 len(fifo->size - fifo->in + fifo->out) 之間取一個較小的值賦給len。注意,當 (fifo->in == fifo->out+fifo->size) 時,表示緩衝區已滿,此時得到的較小值一定是0,後面實際寫入的位元組數也全為0。 
      另一種邊界情況是當 len 很大時(因為len是無符號的,負數對它來說也是一個很大的正數),這一句也能保證len取到一個較小的值,因為     fifo->in 總是大於等於 fifo->out ,所以後面的那個表示式 l = min(len, fifo->size - (fifo->in & (fifo->size - 1))); 的值不會超過fifo->size的大小。 
      smp_mb();  smp_wmb(); 是加記憶體屏障,這裡不是我們討論的範圍,你可以忽略它。 


      l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));    是把上一步決定的要寫入的位元組數 len “切開”,這裡又使用了一個技巧。注意:實際分配給 fifo->buffer 的位元組數 fifo->size ,必須是2的冪,否則這裡就會出錯。既然 fifo->size 是2的冪,那麼 (fifo->size-1) 也就是一個後面幾位全為1的數,也就能保證 (fifo->in & (fifo->size - 1)) 總為不超過 (fifo->size - 1) 的那一部分,和 (fifo->in)% (fifo->size - 1) 的效果一樣。 
      這樣後面的程式碼就不難理解了,它先向  fifo->in  到緩衝區末端這一塊寫資料,如果還沒寫完,在從緩衝區頭開始寫入剩下的,從而實現了迴圈緩衝。最後,把寫指標後移 len 個位元組,並返回len。 
       從上面可以看出,fifo->in的值可以從0變化到超過fifo->size的數值,fifo->out也如此,但它們的差不會超過fifo->size。

從kfifo向外讀資料的函式是: 
static inline unsigned int kfifo_get(struct kfifo *fifo, unsigned char *buffer, unsigned int len); 
unsigned int __kfifo_get(struct kfifo *fifo, unsigned char *buffer, unsigned int len);

    /**
    * __kfifo_get - gets some data from the FIFO, no locking version
    * @fifo: the fifo to be used.
    * @buffer: where the data must be copied.
    * @len: the size of the destination buffer.
    *
    * This function copies at most @len bytes from the FIFO into the
    * @buffer and returns the number of copied bytes.
    *
    * Note that with only one concurrent reader and one concurrent
    * writer, you don't need extra locking to use these functions.
    */
   unsigned int __kfifo_get(struct kfifo *fifo,
                unsigned char *buffer, unsigned int len)
   {
       unsigned int l;
    
       len = min(len, fifo->in - fifo->out);
    
       /*
        * Ensure that we sample the fifo->in index -before- we
        * start removing bytes from the kfifo.
        */
    
       smp_rmb();
    
       /* first get the data from fifo->out until the end of the buffer */
       l = min(len, fifo->size - (fifo->out & (fifo->size - 1)));
       memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l);
    
       /* then get the rest (if any) from the beginning of the buffer */
       memcpy(buffer + l, fifo->buffer, len - l);
    
       /*
        * Ensure that we remove the bytes from the kfifo -before-
        * we update the fifo->out index.
        */
    
       smp_mb();
    
       fifo->out += len;
    
       return len;
   }
   EXPORT_SYMBOL(__kfifo_get);
    /**
    * kfifo_get - gets some data from the FIFO
    * @fifo: the fifo to be used.
    * @buffer: where the data must be copied.
    * @len: the size of the destination buffer.
    *
    * This function copies at most @len bytes from the FIFO into the
    * @buffer and returns the number of copied bytes.
    */
   static inline unsigned int kfifo_get(struct kfifo *fifo,
                        unsigned char *buffer, unsigned int len)
   {
       unsigned long flags;
       unsigned int ret;
    
       spin_lock_irqsave(fifo->lock, flags);
    
       ret = __kfifo_get(fifo, buffer, len);
    
       /*
        * optimization: if the FIFO is empty, set the indices to 0
        * so we don't wrap the next time
        */
       if (fifo->in == fifo->out)
           fifo->in = fifo->out = 0;
    
       spin_unlock_irqrestore(fifo->lock, flags);
    
       return ret;
   }

和上面的__kfifo_put類似,不難分析。

static inline unsigned int __kfifo_len(struct kfifo *fifo); 
static inline unsigned int kfifo_len(struct kfifo *fifo);
 

    /**
    * __kfifo_len - returns the number of bytes available in the FIFO, no locking version
    * @fifo: the fifo to be used.
    */
   static inline unsigned int __kfifo_len(struct kfifo *fifo)
   {
       return fifo->in - fifo->out;
   }
    
   /**
    * kfifo_len - returns the number of bytes available in the FIFO
    * @fifo: the fifo to be used.
    */
   static inline unsigned int kfifo_len(struct kfifo *fifo)
   {
       unsigned long flags;
       unsigned int ret;
    
       spin_lock_irqsave(fifo->lock, flags);
    
       ret = __kfifo_len(fifo);
    
       spin_unlock_irqrestore(fifo->lock, flags);
    
       return ret;
   }

這兩個函式返回緩衝區中實際的位元組數,只要用fifo->in減去fifo->out即可。

kernel/kfifo.c中還提供了初始化kfifo,分配和釋放kfifo的介面:

struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size, gfp_t gfp_mask, spinlock_t *lock); 
struct kfifo *kfifo_alloc(unsigned int size, gfp_t gfp_mask, spinlock_t *lock);

void kfifo_free(struct kfifo *fifo);

再一次強調,呼叫kfifo_init必須保證size是2的冪,而kfifo_alloc不必,它內部會把size向上圓到2的冪。kfifo_alloc和kfifo_free搭配使用,因為這兩個函式會為fifo->buffer分配/釋放記憶體空間。而kfifo_init只會接受一個已分配好空間的fifo->buffer,不能和kfifo->free搭配,用kfifo_init分配的kfifo只能用kfree釋放。

迴圈緩衝區在驅動程式中使用較多,尤其是網路介面卡。但這種免鎖的方式在核心互斥中使用較少,取而代之的是另一種高階的互斥機制──RCU。

 

參考資料:

1. Linux Device Drivers, 3rd Edition, Jonathan Corbet, Alessandro Rubini and Greg Kroah-Hartman, O'Reilly.

2. Linux Kernel 2.6.19 source code.