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.