无锁环形队列—linux内核数据结构

一、队列的定义

队列(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、总结

内核编写者的作品真的是雅,太雅!!!

  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值