前面我们详细说明了下内核队列 Kfifo 的源码。详情请跳转内核循环队列 。
最后的 自我评价 现在看来,令人汗颜,大牛们给出的代码精华,我等小人猪油蒙了心,却认为是不和适宜的瑕疵。所以,我对 Kfifo 位置更新做了个整理。
问题提出
我们再来看看源码:
unsigned int __kfifo_put(struct kfifo *fifo,
const unsigned char *buffer, unsigned int len)
{
unsigned int l;
//buffer中空的长度
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 */
l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);
/* then put the rest (if any) at the beginning of the buffer */
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; //每次累加,到达最大值后溢出,自动转为0
return len;
}
我们最大的疑问来自于 23 行。有两个灵魂拷问:
- 我们更新了 fifo->in ,却不是和 fifo 的大小进行求余处理。而是进行累加操作。
- 我们是不是需要把队列的大小设置成无符号整型溢出值一样大?
- 每次累加到最大值溢出,固然是很不错的想法,但是,如果我的缓冲区大小为 16M ,难道也要到 unsigned int 最大值溢出时,才返回0吗?
- 我们这样累加, fifo->in 值会不断增大,但是我们设置的size比较小。要写入的数据咋办?
分析问题
我们提出了几个问题,这些个问题的关键点,是在于无符号整型的溢出值很大,我们设置的队列大小比较小,而写入读取数据的位置在不断累计。这二者之间出现了矛盾。所以,我们先来看看对于队列的操作。
memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);
/* then put the rest (if any) at the beginning of the buffer */
memcpy(fifo->buffer, buffer + l, len - l);
在对 buffer 实际操作时,我们使用到了两个变量, len 、l 。我们再看看他们的含义。
len = min(len, fifo->size - fifo->in + fifo->out);
l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
从代码中,我们可以看到:
- len的含义是实际插入数据的长度。数据大于剩余空间,则实际插入剩余空间大小,如果数据小于剩余空间大小,则实际插入剩余空间大小。
- l的意义参考图二 , tail free 和 实际插入数据的最小值。
我们发现,这里 len 和 l 数值来源,没有涉及到具体的位置使用。都是和相对于 fifo->in 、fifo->out。所有求相对值时,也都用到了 fifo->size .所以,这里有一个关键条件,那就是:
:::success fifo->in - fifo->out <= fifo->size
:::
只要满足了上述关系,不管你 in 和 out 怎么累计赋值,都是符合要求的。
在加上对无符号整型循环归零的特性,就完美解决了循环操作的数据处理。
总结
上面基本说明了 fido->in 和 fifo->out 可以累计,不对 fifo->size 做求余处理。其实基核心就是一点, in 和 out 之间的差值一定是小于等于队列大小的。无符号整型溢出归零的特性解决了循环问题,但是给理解整个问题带来了一定难度。