linux內核中的循環緩衝去的設計與實現

    今天在看linux內核時候,發現內核中的循環緩衝區的設計與實現非常的完美。讓後就想自己仿照着也寫一個,用於加深理解。

     linux內核中對struct kfifo結構體的各種操作的源代碼存放在:

         /kernel/kfifo.c 和 /include/linux/kfifo.h這兩個文件之中。

    struct kfifo

    {

         unsigned char *buffer;

         unsigned int size;  // 用於記錄緩存區的大小;

         unsigned int in;    // 用於記錄下一到達緩存區的數據的存放位置;

         unsigned int out;   // 用於記錄下一次從緩存區提取的數據的位置;

         spinlock_t   *lock; // 用於防止併發的對這個緩存區進行讀操作或寫操作;

    };

    通過這個struct kfifo結構體來對一個循環緩存區進行各種信息的統計;


    先介紹幾個在對這個緩存區進行各種操作時,需要用到的幾個非常簡單的函數,當然這幾個函數是我自己在寫循環緩存隊列時用到的。

  1. 測試一個數是否是 2的冪數

      int my_is_power_of_2(int n)

      {

         return ( n!=0  &&  ( n & (n - 1) ) == 0 );

      }

  2. 如果一個數不是2的冪數的話,找出大於這個數的最近的2的冪數

       int my_power_of_2(int n)

       {

           unsigned int i = 0;

           if(n)

           {

              while(n != 0)

              {

                  n = n >> 1;

                  i++;

              }

              return (1 << i);

           }

       }

      此函數的用法: 如果 n = 12 ,不是2的冪數,my_power_of_2(n)返回 16;

                          n = 17 ,也不是2的冪數, my_power_of_2(n)返回 32;


  3.這是一個宏定義,用於取兩個數中最小的數;

     #define min(m, n) (m) < (n) ? (m) : (n)

      min(2,3) min()返回的是 2;

      min(10, 4) min()返回的是 4;



開始對循環緩存隊列進行各種操作的函數:

   1.循環緩存隊列的初始化:

    struct kfifo * fifo_init(unsigned char *buffer, gfp_t gfp_mask, unsigned int len,

                              spinlock_t *spinlock)

    {

          struct kfifo *fifo = NULL;

          fifo = kmalloc(sizeof(struct kfifo), gfp_mask);

          if( fifo == NULL )

            return -ENOMEM;

          fifo->buffer = buffer;

          fifo->size = len;

          fifo->in = 0;

          fifo->out = 0;

          fifo->lock = spinlock;

          return fifo;

    }

    kfifo_init()函數主要做的工作是:分配了一個struct kfifo結構體,然後對其進行初始賦值;


    2.給struct kfifo結構體分配內存,用於存取緩存的數據

      struct kfifo * kfifo_alloc(gfp_t gfp_mask, unsigned int len, spinlock_t *spinlock)

      {

           struct kfifo *ret = NULL;

           unsigned char *buffer = NULL;

           // 用於判斷len是否是 2的冪數,如果是的話直接使用就可以了

           //     如果不是的話,使用my_power_of_2()將其調整爲2的冪數

           if(!my_is_power_of_2(len))

             len = my_power_of_2(len);


           buffer = kmalloc(len, gfp_mask);

           if( buffer == NULL )

              return -ENOMEM;

           memset(buffer, 0, len);

           ret = kfifio_init(buffer, gfp_mask, len, spinlock);

           if( IS_ERR(ret) ) // IS_ERR()函數主要用於判斷返回值是否是像-ENOMEM這類的返回值;

               kfree(buffer);

           return ret;

      }

    

     3.釋放循環緩存隊列中的緩存;

      void kfifo_free(struct kfifo *fifo)

      {

            kfree(fifo->buffer);

            kfree(fifo);

      }


     4.給循環緩存隊列中添加數據的操作(未加鎖的版本)

       int __kfifo_put(unsigned char *buffer, unsigned int len, struct kfifo *fifo)

       {

           unsigned int length ;

           //fifo->size - fifo->in + fifo->out 這個計算出來的結果值是循環緩存隊列中的未用的

           //空間字節數;

           len = min(len, fifo->size - fifo->in + fifo->out);

          

           // fifo->size - (fifo->in & (fifo->size -1))計算出來的結果是fifo->in到緩存數組

           //末尾的空間大小

           length = min(len, fifo->size - ( filo->in & (fifo->size - 1) ) );


           memcpy(fifo->buffer + (fifo->in & (fifo->size -1)),buffer, length );


           memcpy(fifo->buffer, buffer+length, len-length);


           fifo->in +=len;

           return len;

       }

       __kfifo_put()函數返回的是寫入循環隊列中的數據的字節數;因爲有可能存在,當要寫入的字節數大於循環隊列中的空閒的空間大小。


      5.從循環緩存隊列中取走數據的操作(爲加鎖版本)


        int __kfifo_get(unsigned char *buffer, int len, struct kfifo *fifo)

        {

              unsigned int length;


              // fifo->in - fifo->out 計算出來的是緩存隊列中存放的字節數;

              len = min(len, fifo->in - fifo->out);


              length = min(len, fifo->size - (fifo->out & (fifo->size - 1))) ;

             

              memcpy( buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), length );

              memcpy( buffer+length,  fifo->buffer,  len - length);


               fifo->out += len;

               return len;       

        }

        __kfifo_get()返回的是從循環隊列中提取的數據的字節數;


        6.循環緩存隊列的重置(爲加鎖):

         inline void __kfifo_reset(struct kfifo *fifo)

         {

               fifo->in = 0;

               fifo->out = 0;

         }


        7. 循環緩存隊列中的數據長度(爲加鎖):

          inline int  __kfifo_len(struct kfifo *fifo)

          {

               return fifo->in - fifo->out;

          }


    以上的這幾個函數涉及到了對循環緩存隊列的讀寫操作,而在系統中如果同時存在多個進程對這個緩存隊列進行讀寫操作的話,會存在數據的紊亂問題。所以這個循環緩存隊列可以看成是一個臨界區資源,每次只能有一個進程對其進行讀寫操作,不允許併發的讀寫操作。

    所以就有了以上幾個函數的加鎖版本了:


     1. int kfifo_put(unsigned char *buffer, unsigned int len, struct kfifo *fifo)

        {

            unsigned int flags;

            unsigned int ret;


            spin_lock_irqsave(fifo->lock, flags);

            ret = __kfifo_put(buffer, len, fifo);

            spin_unlock_irqstore(fifo->lock, flags);

            return ret;

        }


     2. int kfifo_get(unsigned char *buffer, unsigned int len, struct kfifo *fifo)

        {

             unsigned int flags ;

             unsigned int ret;

             

             spin_lock_irqsave(fifo->lock, flags);

             ret = __kfifo_get(buffer, len, fifo);

             spin_unlock_irqstore(fifo->lock, flags);

             return ret;

        }


     3.int kfifo_len(struct kfifo *fifo)

       {

            unsigned  int flags;

            unsigned  int ret;

           

            spin_lock_irqsave(fifo->lock, flags);

            ret = __kfifo_len(fifo);

            spin_unlock_irqstore(fifo->lock, flags);

            return ret;

       }


       4. void kfifo_reset(struct kfifo *fifo)

          {

             unsigned int flags;

             unsigned int ret;


             spin_lock_irqsave(fifo->lock, flags);

             __kfifo_reset(fifo);

             spin_unlock_irqstore(fifo->lock, flags);

          }


    看完內核對struct kfifo 結構體的各種操作,感覺在內核代中,每個函數只完成一個功能,並且對循環緩存隊列的讀操作、寫操作、重置操作都非常的簡單,代碼的邏輯也是非常的清晰。

    以上代碼是我對模仿內核代碼寫的,當然處理的肯定沒有內核代碼那麼完美,比如在內核代碼中對循環隊列緩存區的讀寫操作是要加入內存屏障的。

    多看、多想內核代碼,對編程水平幫助很大!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章