Linux kernel kfifo分析【轉】


轉自:https://zohead.com/archives/linux-kernel-kfifo/

本文同步自(如瀏覽不正常請點擊跳轉):https://zohead.com/archives/linux-kernel-kfifo/

kfifo 是 Linux kernel 中的一個通用隊列實現,對於 kernel 中常見的 FIFO 隊列應用還是很有用的,本文主要簡單介紹分析下 Linux kernel kfifo。實際上 ChinaUnix 上有個 kfifo 的分析文章,但已經比較老(基於 Linux 2.6.10),而且我現在用的 2.6.34 版本 kernel 中 kfifo 實現有很多改動,故自己簡單寫下,ChinaUnix 上的 kfifo 介紹帖子在這里:

http://bbs.chinaunix.net/thread-1994832-1-1.html

kfifo 定義在 include/linux/kfifo.h 頭文件中,我們經常使用的就是 kfifo 結構,看看它的定義:

include/linux/kfifo.h
1
2
3
4
5
6
struct kfifo {
     unsigned char *buffer;  /* the buffer holding the data */
     unsigned int size;  /* the size of the allocated buffer */
     unsigned int in;    /* data is added at offset (in % size) */
     unsigned int out;   /* data is extracted from off. (out % size) */
};

kfifo 也像其它隊列那樣提供了兩個主要操作:入隊列(in) 和 出隊列(out),對應於上面結構中的 in 和 out 兩個偏移量,in 偏移量為下次入隊列的位置,out 為下次出隊列的位置,很容易也能想到 out 值必須小於等於 in 值,當 out 值等於 in 值時表示隊列為空,kfifo 中 buffer 為隊列的空間,size 為空間大小,必須為 2 的 k 次冪值(原因在下面說明)。當然如果 in 值等於隊列長度了,就表示隊列已經滿了。

先看看 kfifo 最簡單的一些操作實現,在 kernel/kfifo.c 文件中:

kernel/kfifo.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
static void _kfifo_init( struct kfifo *fifo, void *buffer,
         unsigned int size)
{
     fifo->buffer = buffer;
     fifo->size = size;
 
     kfifo_reset(fifo);
}
 
/**
  * kfifo_init - initialize a FIFO using a preallocated buffer
  * @fifo: the fifo to assign the buffer
  * @buffer: the preallocated buffer to be used.
  * @size: the size of the internal buffer, this has to be a power of 2.
  *
  */
void kfifo_init( struct kfifo *fifo, void *buffer, unsigned int size)
{
     /* size must be a power of 2 */
     BUG_ON(!is_power_of_2(size));
 
     _kfifo_init(fifo, buffer, size);
}
EXPORT_SYMBOL(kfifo_init);
 
/**
  * kfifo_alloc - allocates a new FIFO internal buffer
  * @fifo: the fifo to assign then new buffer
  * @size: the size of the buffer to be allocated, this have to be a power of 2.
  * @gfp_mask: get_free_pages mask, passed to kmalloc()
  *
  * This function dynamically allocates a new fifo internal buffer
  *
  * The size will be rounded-up to a power of 2.
  * The buffer will be release with kfifo_free().
  * Return 0 if no error, otherwise the an error code
  */
int kfifo_alloc( struct kfifo *fifo, unsigned int size, gfp_t gfp_mask)
{
     unsigned char *buffer;
 
     /*
      * round up to the next power of 2, since our 'let the indices
      * wrap' technique works only in this case.
      */
     if (!is_power_of_2(size)) {
         BUG_ON(size > 0x80000000);
         size = roundup_pow_of_two(size);
     }
 
     buffer = kmalloc(size, gfp_mask);
     if (!buffer) {
         _kfifo_init(fifo, NULL, 0);
         return -ENOMEM;
     }
 
     _kfifo_init(fifo, buffer, size);
 
     return 0;
}
EXPORT_SYMBOL(kfifo_alloc);
 
/**
  * kfifo_free - frees the FIFO internal buffer
  * @fifo: the fifo to be freed.
  */
void kfifo_free( struct kfifo *fifo)
{
     kfree(fifo->buffer);
     _kfifo_init(fifo, NULL, 0);
}
EXPORT_SYMBOL(kfifo_free);

調用 kfifo_alloc 可以自動分配空間並初始化,你也可以調用 kfifo_init 函數使用自己的空間來初始化隊列,可以看到這兩個函數中都用 is_power_of_2 做了檢查隊列空間的操作。kfifo_free 釋放隊列,它會調用 _kfifo_init 函數(參數為 NULL 和 0 清空隊列),調用 kfifo_reset 可以重置隊列(將 in 和 out 都設為 0)。你也可以用 DECLARE_KFIFO 和 INIT_KFIFO 靜態定義一個 kfifo 隊列,盡管這不太會被用到。

調用 kfifo_in 函數將數據加入隊列,kfifo_out 將數據從隊列中取出並從隊列中刪除(增加 out 值),Linux 還提供了 kfifo_out_peek 函數從隊列中取數據但並不刪除(不增加 out 值)。kfifo_in 中會先調用 __kfifo_in_data 將輸入加入隊列,然后調用 __kfifo_add_in 增加 in 的值,kfifo_out 相反則調用 __kfifo_out_data 和 __kfifo_add_out 函數取出數據並刪除。

kfifo 中同時提供了 kfifo_from_user 函數用戶將用戶空間的數據加入到隊列中,它會先調用 __kfifo_from_user_data 將用戶空間的數據加入隊列,再調用 __kfifo_add_in 增加 in 的值。看看 __kfifo_from_user_data 的實現:

kernel/kfifo.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static inline int __kfifo_from_user_data( struct kfifo *fifo,
      const void __user *from, unsigned int len, unsigned int off,
      unsigned *lenout)
{
     unsigned int l;
     int ret;
 
     /*
      * Ensure that we sample the fifo->out index -before- we
      * start putting bytes into the kfifo.
      */
 
     smp_mb();
 
     off = __kfifo_off(fifo, fifo->in + off);
 
     /* first put the data starting from fifo->in to buffer end */
     l = min(len, fifo->size - off);
     ret = copy_from_user(fifo->buffer + off, from, l);
     if (unlikely(ret)) {
         *lenout = ret;
         return -EFAULT;
     }
     *lenout = l;
 
     /* then put the rest (if any) at the beginning of the buffer */
     ret = copy_from_user(fifo->buffer, from + l, len - l);
     *lenout += ret ? ret : len - l;
     return ret ? -EFAULT : 0;
}

可以看到 __kfifo_from_user_data 中是直接調用 copy_from_user 將用戶空間的數據拷貝到 kfifo 隊列的空間中。相應的也有 kfifo_to_user 函數將隊列中的數據取出到用戶空間的地址,他就調用 copy_to_user 將隊列中數據拷貝到用戶空間。

需要注意的是 __kfifo_from_user_data 中用到的 __kfifo_off 函數:

include/linux/kfifo.h
1
2
3
4
static inline unsigned int __kfifo_off( struct kfifo *fifo, unsigned int off)
{
     return off & (fifo->size - 1);
}

__kfifo_off 是根據指定的偏移量得到索引值,由這里也可以看出為什么隊列的大小為什么必須是 2 的 k 次冪值,否則無法得到正確的值。而且從代碼中可以看到 __kfifo_from_user_data、__kfifo_in_n、__kfifo_in_rec 等函數中都用到了 __kfifo_off 函數指定加入隊列時的偏移量。

另外從 include/linux/kfifo.h 中你也可以看到新的 kfifo 實現中默認 EXPORT 了非常多的 API 函數給 kernel 開發者使用。

以上為個人分析結果,有任何問題歡迎指正哦 ^_^


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM