今天研读了2.6.26内核的kfifo代码,感觉实现得巧妙,队列的队头队尾下标不受队列长度的限制,就算队头下标大于队列长度,也一样可以使用,原理就在于,数据不是全部放在队头(fifo->out)和队尾(fifo->in)之间的内存空间,而是把超出队头队尾之间长度的数据放到整个队列buffer的开始处,如图:
蓝色部分为真实数据所在内存段,白色部分其实为逻辑上假定的数据所在地,也就是说,为了给用户一种真正的队列感觉——从尾部推进数据,从头部拉取数据,那么就必须让fifo->out和fifo->in只能一直往一个方向推进,但是由于fifo所分配的buffer是有限的一段连续内存,fifo->out和fifo->in迟早要“越界”,因此,代码中是这样来处理所谓的“越界”情况的:
首先是入队列的操作,保证fifo->out和fifo->in是一直往右推进的:
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);
/* 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);
fifo->in += len;
return len;
}
可以明显看到,如果fifo->in小于buffer->size,那么先放完buffer->size-fifo->in这段内存空间,剩下的部分,转移到buffer的可用空间开头存放;如果fifo->in大于buffer->size,那么直接把要入队列的数据放到buffer可用空间开头。
其次是出队列的操作:
unsigned int __kfifo_get(struct kfifo *fifo, unsigned char *buffer, unsigned int len)
{
unsigned int l;
len = min(len, fifo->in - fifo->out);
/* 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);
fifo->out += len;
return len;
}
情况1:fifo->in大于fifo->size而fifo->out小于fifo->size(即只有fifo->in“越界”),则先读取fifo->out到fifo->size-1的那一段,大小为l个byte,然后再读取剩下的从buffer开头,大小为len-l个byte的数据(如下图所示,即先读data A段, 再读出data B段);
情况2:fifo->in和fifo->out都“越界”了,那么l = min(len, fifo->size - (fifo->out & (fifo->size - 1))); 这一语句便起作用了,此时fifo->out&fifo->size-1的结果即实际要读的数据所在的内存地址相对于buffer起始地址的偏移值(如下图所示,左边为实际上存在于内存中的data A段, 而右边虚线框为逻辑上的data A段的位置);
最后我自己有个疑问,fifo->out和fifo->in是unsign int(32bit)类型,如果应用程序在使用kfifo的时候,不断偏移,最终导致fifo->out或者fifo->in溢出,那么这种情况如何处理呢?kfifo的代码中并没有提到这种问题,等待深究。