1) ring buffer位首尾相接的buffer,即相似生活中的圆形跑道;linux
2) 空闲空间+数据空间=ring buffer大小编程
3) ring buffer的读写,相似生活中在圆形跑道上的追赶游戏,领跑者位write,追赶着为read函数
4) 若是read跑的太快,追上write,追赶者read要停下来,不然游戏结束。即保证没有数据空间时,再也不从ring buffer中读取数据;spa
5) 若是write跑的太快,反过来套圈要超过read,此时领跑者write也要停下来。即保证没有空闲空间时,再也不往ring buffer中写入数据;.net
6) 因此,read和write之间的距离老是介于开区间(0, buffer大小)设计
假设buffer的大小为size,读指针为in,写指针为outunix
1) 在计算机中,整型数据加到最大值后溢出会回卷到0,从头开始)指针
2)buffer的长度必须是2的n次幂rest
3) buffer空闲空间和数据空间的大小orm
1> 空闲空间的大小=size-in+out
2> 空闲空间的大小=in-out
本设计总能保证in前out前面的,in跑出unsigned int边界溢出后回卷。
由于buffer的大小是2的n次幂,而unsigned int也是2的n次幂(32位机器上,n=32),通常buffer大小不会超过unsigned int大小,即unsigned int被分红m个整块(m>=1)
空闲空间=size-数据控件=size-(in-out)=size-in+out
第2种状况:(in跑到unsigned int的边界后,溢出了)
out+数据空间=in,这个等式仍然成立。
因此:空闲空间=size-in+out,亦成立
设落在ring buffer内写指针为__in,读指针为__out,须要写入的空间大小为len, 其中
1. __in = fifo->in % (fifo->size - 1) (读写指针都是从0开始算起)
2. __out = fifo->out % (fifo->size - 1)
3. __size = fifo->size
4. len <= 空闲空间大小
这种状况下,须要写两块buffer,作两次拷贝动做,设须要写入的大小为len,第一块空闲空间大小为left1,第二块为left2,须要第一次拷贝的大小为len1,第二次拷贝的大小为len2,len1 + len2 = len:
1. left1 = _size-__in;
2. len1 = min(len, left1) = min(len, _size-__in);
3. left2 = __out;
4. len2 = len - len1
这种状况下,须要写一块buffer,作一次拷贝动做:
1. left1 = __out - __in <= __size - __in;
2. 而写入长度len <= 空闲空间大小,因此len <= left1 <= __size - __in,因此len1 = len, len1 = min(len, __size - __in)仍然成立
3. left2 = 0;
4. len2 = 0 = len -len1
总结以上两种情形,第一块空闲空间大小为left1,第二块为left2,须要第一次拷贝的大小为len1,第二次拷贝的大小为len2,len1 + len2 = len,则通用状况以下:
1. len <= 空闲空间大小
2. len1 = min(len, _size-__in);
3. len2 = len -len1
unsigned int __kfifo_put(struct kfifo *fifo,
unsigned char *buffer, unsigned int len)
{
unsigned int l;
len = min(len, fifo->size - fifo->in + fifo->out);
/* 前提条件写入大小len不超过空闲空间大小 */
smp_mb();
/* 第一块写入空闲空间,大小为min(len, size-in) */
l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);
/* 第二块写入空闲空间,大小为len-min(len, size-in) */
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;
}
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;
}
buffer大小是2的K次方:size = (size & (size - 1)
将SIZE 调整到最近的2^k:
思想很简单,就是找出当前数的二级制中最大位为1位的位置,而后用1左移位数便可。
好比数据5,它的二进制形式为101,最高位为1的位置是3,而后左移3位,等于1000,即数字8。也就是数字8是5的接近的2的整数次幂。
思想很简单,最主要的任务就是找到最高位为1的位置。这个有专门的AT&T汇编指令bsrl。这个指令是个32位指令,位置范围是0到31。
若是bsrl %eax 5的范围结果是2,因此咱们在用它的时候加1便可。固然0是须要考虑的,咱们把它的值赋值成-1,这样函数结果的范围就编程1到32。具体实现以下:
static inline int fls(int x)
{
int r;
__asm__("bsrl %1,%0\n\t"
"jnz 1f\n\t"
"movl $-1,%0\n"
"1:" : "=r" (r) : "rm" (x));
return r+1;
}
固然,若是不用汇编也是能够实现的,咱们用C语言来实现它。就是不断左移,直到数值为0,记下它左移的次数,既是它最高位为1的位置。
static inline int fls(int x)
{
int position;
int i;
if(0 != x)
{
for (i = (x >> 1), position = 0; i != 0; ++position)
i >>= 1;
}
else
{
position = -1;
}
return position+1;
}
最后把要获得的数字用1左移那么屡次数,便可。考虑到0的特殊性,咱们把数字都减1,其余都不会受影响,这样0会取值成-1,而后取到的位置为32,1左移32位后仍是为0。
实现代码以下:
static inline unsigned int roundup_pow_of_two(unsigned int x)
{
return 1UL << fls(x - 1);
}
版权