模仿Linux核心kfifo實現的迴圈快取

2022-07-23 20:54:22 字數 4228 閱讀 5221

想實現個迴圈緩衝區(circular buffer),搜了些資料多數是基於迴圈佇列的實現方式。使用乙個變數存放緩衝區中的資料長度或者空出來乙個空間來判斷緩衝區是否滿了。偶然間看到分析linux核心的迴圈緩衝佇列kfifo的實現,確實極其巧妙。kfifo主要有以下特點:

關於記憶體屏障的本文不作過多分析,可以參考wikimemory barrier。另外,本文所涉及的整數都預設為無符號整數,不再做一一說明。

linux核心中kfifo實現技巧,主要集中在放入資料的put方法和取資料的get方法。**如下:

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

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

put返回實際儲存到緩衝區中的資料長度,get返回的是實際取到的資料長度。在上面**中,需要注意到在寫入、取出時候的兩次min運算。關於kfifo的分析,已有很多資料了,也可參考眉目傳情之匠心獨運的kfifo 。

linux核心實現的kfifo的有以下特點:

優點:實現單消費者和單生產者的無鎖併發訪問。多消費者和多生產者的時候還是需要加鎖的。

使用與運算in & (size-1)代替模運算

在更新in或者out的值時不做模運算,而是讓其自動溢位。這應該是kfifo實現最牛叉的地方了,利用溢位後的值參與運算,並且能夠保證結果的正確。溢位運算保證了以下幾點:

主要是模仿其無符號溢位的運算方法,並沒有利用記憶體屏障實現單生產者和單消費者的無鎖併發訪問。初始化及輸入輸出的**如下:

struct kfifo

// 返回實際寫入緩衝區中的資料

uint32_t put(const uint8_t *data, uint32_t len)

// 返回實際讀取的資料長度

uint32_t get(uint8_t *data, uint32_t len)

在初始化緩衝空間的時候要驗證size是否為2的次冪,如果不是則向上取整為2的次冪。下面著重分析下在放入取出資料時對指標inout的處理,以及在溢位後怎麼能夠保證in - out仍然為緩衝區中的已有的資料長度。

put和get方法詳解

在向緩衝區中put資料的時候,需要兩個引數:要put的資料指標data和期望能夠put的資料長度len,返回值是實際存放到緩衝區中的資料長度(當緩衝區中空間不足時該值小於len)。下面詳細的解釋下put中每個語句的作用。

因為是迴圈緩衝區,所以其空閒空間有兩部分:從in到緩衝空間的末尾->[in,buffer end]和緩衝空間的起始位置到out->[buffer start,out]。

get和put很類似,首先判斷是否有足夠的資料取出;在取資料時首先從out取到buffer的末尾,如果不夠則從buffer的開始位置取;最後更新out時也是不做模運算,讓其溢位。看參看上面put的語句解釋,這裡就不再多說。

無符號溢位運算

kfifo之所以如次的簡潔,很大一部分要歸功於其in和out的溢位運算。這裡就解釋下在溢位的情況下,如何保證in - out仍然為緩衝區中的資料長度。首先來看圖:

以上引用自linux核心資料結構之kfifo,其對kfifo的分析也很詳細。

前三種情況下從圖中可以很清晰的看出in - out為緩衝區中的已有的資料長度,但是最後一種發現in跑到了out的前面,這時候in - out不是應該為負的麼,怎麼能是資料長度?這正是kfifo的高明之處,in和out都是無符號整數,那麼在in < out 時in - out就是負數,把這個負數當作無符號來看時,其值仍然是緩衝區中的資料長度。這和in累加到溢位的情況基本一致,這裡放在一起說。

這裡使用8位無符號整數來儲存in和out,方便溢位。這裡假設out = 100,in = 255,size = 256,如下圖

/*

--------------------------------------

| | | |

--------------------------------------

out = 100 in = 250

這時緩衝區中已有的資料為:in - out = 150,空閒空間為:size - (in - out) = 106

向緩衝區中put10個資料後

--------------------------------------

| | | |

--------------------------------------

in out

這時候 in + 10 = 260 溢位變為in = 4;這是 in - out = 4 - 100 = -96,仍然溢位-96十六進製制為`0xa0`,將其直接轉換為有符號數`0xa0 = 160`,在沒put之前的資料為150,put10個後,緩衝區中的資料剛好為160,剛好為溢位計算結果。

*/

進行上述運算的前提是,size必須為2的次冪。假如size = 257,則上述的執行就不會成功。

測試例項

int main()

; uint8_t data[256] = ;

for (int i = 0; i < 256; i++)

data[i] = i;

kfifo fifo(128);

fifo.put(data, 100);

fifo.get(output, 50);

fifo.put(data, 30);

auto c = fifo.put(data + 10, 92);

cout << "empty:" << fifo.isempty() << endl;

cout << "left space:" << fifo.left() << endl;

cout << "length:" << fifo.length() << endl;

uint8_t a = fifo.size - fifo.in + fifo.out;

uint8_t b = fifo.in - fifo.out;

cout << "***********************************====" << endl;

fifo.get(output, 128);

cout << "empty:" << fifo.isempty() << endl;

cout << "left space:" << fifo.left() << endl;

cout << "length:" << fifo.length() << endl;

cout << "***********************************===" << endl;

fifo.put(output, 100);

cout << "empty:" << fifo.isempty() << endl;

auto d = static_cast(fifo.left());

auto e = static_cast(fifo.length());

printf("left space:%d\n", d);

printf("length:%d\n", e);

getchar();

return 0;

}

執行結果:

秋實評論 模仿者必然是落後者!

導引 秋實評論認為,卡薩帝的成功不是偶然的,她是一種歷史必然,也是一種客觀存在。這是品牌基因決定的。卡薩帝已經進入乙個全新的賽道 高品質的生活方式已根植人心,持續引領全球家電產業發展趨勢,推動行業高質量發展。近一段時間以來,眾多 密集關注卡薩帝現象,並有各種不同形式的解讀,有關創新與模仿的話題再次進...

模仿核心快取之who命令的快取

1.核心緩衝 核心操作磁碟時,核心會將磁碟上的資料塊複製到核心緩衝區,當乙個使用者空間中的程序要從磁碟中讀取資料時,核心一般不直接讀取磁碟,而是將核心緩衝區中的資料複製到程序緩衝區。當程序要求的資料快不再核心緩衝區時,核心會把相應的資料快加入到請求資料列表中,然後把該程序掛起,接著為其他程序服務。一...

Linux0 11核心 8086中的實模式

8086 8088cpu中暫存器為16位,16位子長的機器可以訪問的最大儲存位址空間為64k,但是8086 8088cpu的位址線有20根,能夠定址1m的位址空間。為了解決這種衝突,採用儲存器分段的辦法,實際位址 段位址左移4位 偏移位址。按照這樣計算,每個段的大小最大可達64k,如果所有的段都按這...