迴圈緩衝區(參考linux核心Kfifo)【轉】
(轉自:https://blog.csdn.net/jnu_kinke/article/details/7274651)
1 迴圈緩衝區在一些競爭問題上提供了一種免鎖的機制,免鎖的前提是,生產者和消費
2 都只有一個的情況下,否則也要加鎖。下面就核心中提取出來,而經過修改後的fifo進
3 行簡要的分析。
4
5 先看其只要資料結構:
6 struct my_fifo {
7 unsignedchar *buffer;/* the buffer holding the data*/
8 unsigned
9 unsignedint in;/* data is added at offset (in % size)*/
10 unsignedint out;/* data is extracted from off. (out % size)*/
11 };
12 也不用多說,一看就明白。size, in, out 都設成無符號型的,因為都不存在負值的情
13 型。
14
15 /*
16 form kernel/kfifo.c
17 */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <fifo.h>
22
23 #define min(a,b) ((a) < (b) ? (a):(b))
24 /*
25 my_fifo_init
26 */
27 struct my_fifo *my_fifo_init(unsignedchar *buffer,unsigned
28 {
29 struct my_fifo *fifo;
30
31
32 fifo = malloc(sizeof(struct my_fifo));
33 if (!fifo)
34 returnNULL;
35
36 fifo->buffer = buffer;
37 fifo->size = size;
38 fifo->in = fifo->out = 0;
39
40 return fifo;
41 }
42 這個初始化fifo結構的函式一般也不會在應用層裡進行呼叫,而是被下面的fifo_alloc
43 呼叫。依我的觀點來看,這兩個函式合成一個函式會更加的清晰,但是這一情況只針對
44 /*
45 my_fifo_alloc
46 */
47 struct my_fifo *my_fifo_alloc(unsignedint size)
48 {
49 unsignedchar *buffer;
50 struct my_fifo *ret;
51
52 /*
53 * round up to the next power of 2, since our 'let the indices
54 * wrap' tachnique works only in this case.
55 */
56
57 buffer = malloc(size);
58 if (!buffer)
59 returnNULL;
60
61 ret = my_fifo_init(buffer, size);
62
63 if (ret ==NULL)
64 free(buffer);
65
66 return ret;
67 }
68 /*
69 * my_fifo_free
70 */
71 void my_fifo_free(struct my_fifo *fifo)
72 {
73 free(fifo->buffer);
74 free(fifo);
75 }
76
77 這兩個函式也不作過多的分析,都很清晰。
78 /*
79 my_fifo_put()
80 */
81 unsignedint my_fifo_put(struct my_fifo *fifo,
82 unsignedchar *buffer, unsigned int len)
83 {
84 unsignedint l;
85
86 len = min(len, fifo->size - fifo->in + fifo->out);/*可寫資料長度*/
/*fifo->size - fifo->in + fifo->out可理解為fifo->size - (fifo->in - fifo->out)*/
/*len取len表示 要寫的資料量 比 緩衝區的空閒區 小,足夠寫入 要寫入的資料量(len)*/
/*len取fifo->size - fifo->in + fifo->out表示 緩衝區的空閒區 比 要寫的資料量 小,容納不了想要寫入的資料量,所以寫滿空閒區即可*/
87
88 /* first put the data starting from fifo->in to buffer end*/
89 l = min(len, fifo->size - (fifo->in & (fifo->size -1)));
/* I取len表示 要寫入的資料量 比 緩衝區尾部的空閒區 小,所以將資料一次性寫入緩衝區尾部的空閒區就ok了*/
/* I取fifo->size - (fifo->in & (fifo->size -1))表示 緩衝區尾部的空閒區 比 要寫入的資料量 小,所以要分兩次寫入,先將資料寫入緩衝區尾部的空閒區,再將剩下的資料(len - l)寫到緩衝區頭部的空閒區*/
90 memcpy(fifo->buffer + (fifo->in & (fifo->size -1)), buffer, l);/*要寫入的資料從緩衝區尾部的空閒區開始寫*/
91
92 /* then put the rest (if any) at the beginning of the buffer*/
93 memcpy(fifo->buffer, buffer + l, len - l);/*上面還沒寫完的資料,從緩衝區頭部的空閒區開始寫*/
94
95 fifo->in += len;
96
97 return len;/*返回實際寫入的資料長度*/
98 }
99
100 /*
101 my_fifo_get
102 */
103 unsignedint my_fifo_get(struct my_fifo *fifo,
104 unsignedchar *buffer, unsigned int len)
105 {
106 unsignedint l;
107
108 len = min(len, fifo->in - fifo->out); /*可讀資料長度,這個地方是為了判斷足不足夠讀取想要讀取的資料長度*/
/*len取len表示 要讀取的資料量 比 緩衝區的可讀資料量 小,足夠讀取到 想讀取資料量(len)*/
/*len取fifo->in - fifo->out表示 緩衝區的可讀資料量 比 要讀取的資料量 小,不夠讀取到想讀取的量,所以將緩衝區中能讀取的全部讀取出來就ok了*/
109
110 /* first get the data from fifo->out until the end of the buffer*/
111 l = min(len, fifo->size - (fifo->out & (fifo->size -1)));/*這個地方是為了判斷需不需要分兩次讀取(尾部一次,頭部一次)*/
/* I取len表示 要讀取的資料量 比 從緩衝區out開始到末尾之間的區域 小,所以只需要一次性讀取緩衝區從out開始的資料就ok了*/
/* I取len表示 從緩衝區out開始到末尾之間的區域 比 要讀取的資料量 小,所以需要分兩次讀取,先讀取緩衝區尾部的資料,再讀取緩衝區頭部的 剩餘資料(len - l)*/
112 memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size -1)), l);/*要讀取的資料從緩衝區的out 開始讀取*/
113
114 /* then get the rest (if any) from the beginning of the buffer*/
115 memcpy(buffer + l, fifo->buffer, len - l);/*上面還沒讀取完的資料,從緩衝區頭部開始讀取*/
116
117 fifo->out += len;
118
119 return len;/*返回實際讀取的資料長度*/
120 }
121 這兩個讀寫結構才是迴圈緩衝區的重點。在fifo結構中,size是緩衝區的大小,是由用
122 戶自己定義的,但是在這個設計當中要求它的大小必須是2的冪次。
123 當in==out時,表明緩衝區為空的,當(in-out)==size 時,說明緩衝區已滿。
124
125 我們看下具體實現,在86行處如果size-in+out ==0,也即獲得的len值會0,而沒有數
126 據寫入到緩衝區中。所以在設計緩衝區的大小的時候要恰當,讀出的速度要比定入的速
127 度要快,否則緩衝區滿了會使資料丟失,可以通過成功寫入的反回值來做判斷嘗試再次
128 寫入.
129 另一種情況則是緩衝區有足夠的空間給要寫入的資料,但是試想一下,如果空閒的空間
130 在緩衝的首尾兩次,這又是如何實現呢?這部分程式碼實現得非常巧妙。
131 我們看fifo->in &(fifo->size-1) 這個表示式是什麼意思呢?我們知道size是2的冪次
132 項,那麼它減1即表示其值的二進位制所有位都為1,與in相與的最終結果是in%size,比
133 size要小,所以看in及out的值都是不斷地增加,但再相與操作後,它們即是以size為
134 週期的一個迴圈。89行就是比較要寫入的資料應該是多少,如果緩衝區後面的還有足夠
135 的空間可寫,那麼把全部的值寫到後面,否則寫滿後面,再寫到前面去93行。
136 讀資料也可以作類似的分析,108行表示請求的資料要比緩衝區的資料要大時,只
137 讀取緩衝區中可用的資料。
138
139 static inline void my_fifo_reset(struct my_fifo *fifo)
140 {
141 fifo->in = fifo->out = 0;
142 }
143
144 static inline unsigned int my_fifo_len(struct my_fifo *fifo)
145 {
146 return fifo->in - fifo->out;
147 }
148
149 在標頭檔案裡還有緩衝區置位及返回緩衝區中資料大小兩個函式,很簡單,不必解釋。