一、队列的定义
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表;也是一种先进先出的线性表,简称FIFO,如下图所示(an为元素)。
队头(Front):允许删除的一端,又称队首
队尾(Rear):允许插入的一端。
空队列:不包含任何元素。
二、无锁环形队列—KFIFO
1、KFIFO结构体
KFIFO是Linux内核对队列功能的实现实例,被成为无锁环形队列。无锁体现在当生产者(IN)和消费者(OUT)各只有一个时,操作KFIFO不需要加锁,这是因为KFIFO出队和入队索引变量不同,如下是KFIFO的结构。
- in:入队的索引,代表入队数据的个数
- out:出队的索引,代表出队数据的个数
- mask:入队/出队数据个数的掩码
- esize:每个数据的大小
- data:FIFO数据区首地址
2、KFIFO的使用规则
- KFIFO中的数据个数必须为2的大于0的整数次方,数据大小没有限制
- out不允许超过in,out等于in时表示KFIFO为空
- 入队n个数据时,in变量+n;出队n个数据时,out变量+n
- in和out之间的差值不能超过KFIFO的数据个数
3、KFIFO工作原理
现以8个大小为1字节大小的KFIFO进行讲解,如下图所示:
KFIFO工作原理的关键:它是如何完成循环存储的?in和out变量超出了KFIFO个数时,会减去8重新开始吗?
不会!in和out索引值会一直往前加,不会回头,但是in一旦超过了8,如何完成数据地址中的索引呢(入队和出队的数据从哪儿里开始存储/读取呢),我们肯定会想到将in/out值对KFIFO大小size进行取余就行,但取余的方式比较耗费资源,内核开发者怎么会轻易采用呢?采用的方式是将in/out值和mask(KFIFO大小减1)相”与“,这个操作比取余快很多!!!这样KFIFO就是实现了无锁环形队列,该无锁只针对于单生产者和单消费者。如果有多消费者需要进行加锁操作!
4、KFIFO的API
1.初始化FIFO空间
int __kfifo_alloc(struct __kfifo *fifo, unsigned int size,
size_t esize, gfp_t gfp_mask)
{
/*
* round down to the next power of 2, since our 'let the indices
* wrap' technique works only in this case.
*/
size = roundup_pow_of_two(size);
fifo->in = 0;
fifo->out = 0;
fifo->esize = esize;
if (size < 2) {
fifo->data = NULL;
fifo->mask = 0;
return -EINVAL;
}
fifo->data = kmalloc(size * esize, gfp_mask);
if (!fifo->data) {
fifo->mask = 0;
return -ENOMEM;
}
fifo->mask = size - 1;
return 0;
}
会将你申请的size向上取2的整数次方
2.释放FIFO空间
void __kfifo_free(struct __kfifo *fifo)
{
kfree(fifo->data);
fifo->in = 0;
fifo->out = 0;
fifo->esize = 0;
fifo->data = NULL;
fifo->mask = 0;
}
3.查询FIFO中剩余空间
static inline unsigned int kfifo_unused(struct __kfifo *fifo)
{
return (fifo->mask + 1) - (fifo->in - fifo->out);
}
4.拷贝入队
static void kfifo_copy_in(struct __kfifo *fifo, const void *src,
unsigned int len, unsigned int off)
{
unsigned int size = fifo->mask + 1;
unsigned int esize = fifo->esize;
unsigned int l;
off &= fifo->mask;
if (esize != 1) {
off *= esize;
size *= esize;
len *= esize;
}
l = min(len, size - off);
memcpy(fifo->data + off, src, l);//将需入fifo的数据拷贝进fifo中剩余空间内
memcpy(fifo->data, src + l, len - l);//若拷贝内容超出,则将剩下内容从开头覆盖
/*
* make sure that the data in the fifo is up to date before
* incrementing the fifo->in index counter
*/
smp_wmb();//内存屏障
}
unsigned int __kfifo_in(struct __kfifo *fifo,
const void *buf, unsigned int len)
{
unsigned int l;
l = kfifo_unused(fifo);//计算出fifo中剩余空间
if (len > l)//如果入fifo元素长度大于剩余空间
len = l;//将入fifo长度修改成剩余空间大小
kfifo_copy_in(fifo, buf, len, fifo->in);
fifo->in += len;
return len;
}
这里会进行入队长度检查,不能超过剩余空间大小,且在memcpy拷贝过后加入smp_wmb()内存屏障,保证在运行时拷贝完成后,再改变in索引值!!
5.拷贝出队
static void kfifo_copy_out(struct __kfifo *fifo, void *dst,
unsigned int len, unsigned int off)
{
unsigned int size = fifo->mask + 1;
unsigned int esize = fifo->esize;
unsigned int l;
off &= fifo->mask;
if (esize != 1) {
off *= esize;
size *= esize;
len *= esize;
}
l = min(len, size - off);
memcpy(dst, fifo->data + off, l);//将数据拷贝出
memcpy(dst + l, fifo->data, len - l);//若剩余空间不够,则重头开始拷贝
/*
* make sure that the data is copied before
* incrementing the fifo->out index counter
*/
smp_wmb();
}
unsigned int __kfifo_out_peek(struct __kfifo *fifo,
void *buf, unsigned int len)
{
unsigned int l;
l = fifo->in - fifo->out;
if (len > l)
len = l;
kfifo_copy_out(fifo, buf, len, fifo->out);
return len;
}
unsigned int __kfifo_out(struct __kfifo *fifo,
void *buf, unsigned int len)
{
len = __kfifo_out_peek(fifo, buf, len);
fifo->out += len;
return len;
}
同样在memcpy拷贝出队后面加入内存屏障,再对out索引值进行改变。
5、总结
内核编写者的作品真的是雅,太雅!!!