循环缓冲区(参考linux内核Kfifo)【转】

(转自:https://blog.csdn.net/jnu_kinke/article/details/7274651

1 循环缓冲区在一些竞争问题上提供了一种免锁的机制,免锁的前提是,生产者和消费
2 都只有一个的情况下,否则也要加锁。下面就内核中提取出来,而经过修改后的fifo进
3 行简要的分析。

5 先看其只要数据结构:
struct my_fifo {
unsignedchar *buffer;/* the buffer holding the data*/
unsignedint size;/* the size of the allocated buffer*/
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 int size)
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 在头文件里还有缓冲区置位及返回缓冲区中数据大小两个函数,很简单,不必解释。

猜你喜欢

转载自blog.csdn.net/biqioso/article/details/83995070
今日推荐