- 論壇徽章:
- 0
|
從2.6.10開始,Linux內(nèi)核提供了一個通用的環(huán)形緩存(我喜歡稱為環(huán)形隊列);它的頭文件是<linux/kfifo.h>,kfifo.c是實現(xiàn)代碼。
在設(shè)備驅(qū)動中環(huán)形緩存出現(xiàn)相當(dāng)多. 網(wǎng)絡(luò)適配器, 特別地, 常常使用環(huán)形緩存來與處理器交換數(shù)據(jù)(報文)[LDD3]。
見下面的圖“LDD3中描述的隊列”。
我們來看下kfifo的數(shù)據(jù)結(jié)構(gòu):
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) */
spinlock_t *lock; /* protects concurrent modifications */
}; |
如E文注釋所解,buffer指向隊列空間,對于隊列的操作,一般會涉及到讀寫,如果是多線程的話,就有可能涉及到生產(chǎn)者/消費者,也稱讀者/寫者問題;
size表示空間大小,必須為2^k,如果不是2^k大小,kfifo將會幫助用戶擴展到一個合適的大小;
in表示寫者指向的索引,在kfifo體統(tǒng)的put函數(shù)中,被處理為一個虛擬索引(我暫這么稱呼)。之所以稱之為虛擬索引,是因為該索引并不一定真正指向有效的地址空間,而是要通過一定運算才能得到真實的索引。下面的put/get代碼分析將會看到內(nèi)核是kfifo是怎么運算的。
out表示讀者指向的索引,out的更新與in一樣,都是使用了虛擬索引的概念,out總是小于等于in。
我們直接來看代碼(保留了原來的注釋,//后面的內(nèi)容為筆者的解釋)
/**
* kfifo_init - allocates a new FIFO using a preallocated buffer
* @buffer: the preallocated buffer to be used.
* @size: the size of the internal buffer, this have to be a power of 2.
* @gfp_mask: get_free_pages mask, passed to kmalloc()
* @lock: the lock to be used to protect the fifo buffer
*
* Do NOT pass the kfifo to kfifo_free() after use! Simply free the
* &struct kfifo with kfree().
*/
struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size,
gfp_t gfp_mask, spinlock_t *lock)
{
struct kfifo *fifo;
/* size must be a power of 2 */
BUG_ON(size & (size - 1)); //大小必須為2的k次方(k>0)的目的在于put/get中從虛擬索引計算真實索引,size & (size - 1)是常用判斷技巧
fifo = kmalloc(sizeof(struct kfifo), gfp_mask); //分配kfifo數(shù)據(jù)結(jié)構(gòu)
if (!fifo)
return ERR_PTR(-ENOMEM);
fifo->buffer = buffer;
fifo->size = size;
fifo->in = fifo->out = 0; //當(dāng)fifo->in == fifo->out 時,表示空隊列
fifo->lock = lock;
return fifo;
}
/**
* kfifo_alloc - allocates a new FIFO and its internal buffer
* @size: the size of the internal buffer to be allocated.
* @gfp_mask: get_free_pages mask, passed to kmalloc()
* @lock: the lock to be used to protect the fifo buffer
*
* The size will be rounded-up to a power of 2.
*/
//通過調(diào)用kfifo_alloc分配隊列空間,該函數(shù)會調(diào)用kfifo_init初始化kfifo結(jié)構(gòu)體,并調(diào)整size的大小以適應(yīng)運算
struct kfifo *kfifo_alloc(unsigned int size, gfp_t gfp_mask, spinlock_t *lock)
{
unsigned char *buffer;
struct kfifo *ret;
/*
* round up to the next power of 2, since our 'let the indices
* wrap' tachnique works only in this case.
*/
if (size & (size - 1)) { //如果size不是2的k次方,代碼將size調(diào)整最近的2^k次方附近
BUG_ON(size > 0x80000000);
size = roundup_pow_of_two(size);
}
buffer = kmalloc(size, gfp_mask);
if (!buffer)
return ERR_PTR(-ENOMEM);
ret = kfifo_init(buffer, size, gfp_mask, lock);
if (IS_ERR(ret))
kfree(buffer);
return ret;
}
/**
* __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,
unsigned char *buffer, unsigned int len)
{
unsigned int l;
//fifo->size - fifo->in + fifo->out,這段代碼計算空閑的空間
//in是寫索引,out是讀索引,而且put與get操作都是分別增加in與out的值來重新計算虛擬索引
//注意,out 始終不會大于 in,(in - out)是有效數(shù)據(jù)空間大小,size是總空間的大小
//那么空閑的空間大小就是 size - (int - out)
//如果請求的len大于空閑空間,就使len = size - (int - out)
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 */
//(fifo->in & (fifo->size - 1))這段代碼計算真實的寫索引偏移,筆者假設(shè)為real_in
//這是因為in在每次調(diào)用put之后都會增加一個len的長度
//由于fifo->size必定是2的k次方,而(fifo->size - 1)就是類似0x00FFFFF的值
//(fifo->in & (fifo->size - 1))的操作從數(shù)學(xué)角度將就是對長度fifo->size的取模運算
//這里能用AND運算代替取模運算得益于前面申請的空間大小為2^k次方
//l = min(空閑空間大小,從real_in開始到緩沖區(qū)結(jié)尾的空間)
l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
//先從buffer中拷貝l字節(jié)到緩沖區(qū)剩余空間,l<=len,也<=從real_in開始到緩沖區(qū)結(jié)尾的空間
//所以這個copy可能沒拷貝完,但是不會造成緩沖區(qū)越界
memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);
/* then put the rest (if any) at the beginning of the buffer */
//當(dāng)len > l時,拷貝buffer中剩余的內(nèi)容,其實地址當(dāng)然為buffer + l,而剩余的大小為len - l
//當(dāng)len == l時,下面的memcpy啥都不干,絕對精妙的算法
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();
//更新in(寫者)的邏輯索引
fifo->in += len;
return 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;
//讀取的大小不能超過有效空間長度
//經(jīng)過min運算后len <= 請求的空間len, len <= size
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 */
//同理,fifo->out & (fifo->size - 1)等于out(讀者)的虛擬索引計算出來真實索引real_out
//fifo->size - real_out就等于該索引到緩沖區(qū)尾部的空間大小
//經(jīng)過min運算后,l<=len,l<=real_out至緩沖區(qū)尾部的空間大小
l = min(len, fifo->size - (fifo->out & (fifo->size - 1)));
//從real_out開始拷貝l字節(jié)內(nèi)容到buffer中
memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l);
/* then get the rest (if any) from the beginning of the buffer */
//如果l<len,那么從fifo->buffer的首部開始繼續(xù)拷貝剩下的內(nèi)容
//如果l == len,memcpy啥都不干
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();
//更新out(讀者)的虛擬索引
fifo->out += len;
return len;
}
|
從上面幾個重要的函數(shù)可以看出一些特性,就是put函數(shù)能放入的數(shù)據(jù)長度永遠不會大于緩沖區(qū)的長度,而fifo->in - fifo->out永遠小于等于size。
get函數(shù)得到的數(shù)據(jù)永遠小于等于size(這個是必然的)。
在讀者/寫者問題中,發(fā)生互斥問題時,使用環(huán)形隊列是很好的解決方案。
fifo不太好的地方在于,想寫完整的話,必須保證空間足夠大,否則不能保證一次寫完,特別是在中斷調(diào)用過程中,如果阻塞了,然后讓讀者讀取內(nèi)容后騰出空間喚醒繼續(xù)寫,將會出現(xiàn)問題,唯一我能想到的方法就是申請大空間,以避免這種“沒寫完”的情況發(fā)生。不知道還有沒有其它方法,寫的不對的地方,大家討論討論,我是菜鳥,寫得也倉促,剛寫內(nèi)核代碼不久,請多指教。
[ 本帖最后由 springtty 于 2009-3-24 00:04 編輯 ] |
評分
-
查看全部評分
|