Linux笔记_kfifo

1.介绍

kfifo是内核里的一个First In First Out数据结构,它采用环形循环队列的数据结构来实现,提供无边界的字节流服务(kfifo_rec 适用于块数据的数据缓存, 一个线程进行一块块的数据入队, 另一个线程进行每次取一块数据进行处理),并且当且仅当只有一个生产者(入队线程)和一个消费者(出队线程)的场情时,两个线程可以并发操作,而不需要加锁,就可以保证kfifo的线程安全。

2.实现

首先看一下kfifo的结构的示意图:

|<---------------------------size---------------------------->|

+--------------------------------------------------------------+ 
|           |<----------data---------->|                            | 
+--------------------------------------------------------------+ 
            ^                                  ^                           ^  
            |                                   |                            |
           in                                out                      mask

struct __kfifo {
	unsigned int	in;
	unsigned int	out;
	unsigned int	mask;
	unsigned int	esize;
	void		*data;
};
/*
 * Note about locking: There is no locking required until only one reader
 * and one writer is using the fifo and no kfifo_reset() will be called.
 * kfifo_reset_out() can be safely used, until it will be only called
 * in the reader thread.
 * For multiple writer and one reader there is only a need to lock the writer.
 * And vice versa for only one writer and multiple reader there is only a need
 * to lock the reader.
 */

in, out,  和data一起构成一个循环队列。 in指向data头,而且out指向data尾

mask,   指向最后一个元素

esize,      元数据的大小

data, 用于存放数据的缓存

3.内存分配/初始化

int __kfifo_alloc(struct __kfifo *fifo, unsigned int size,
		size_t esize, gfp_t gfp_mask)
{
	/*
	 * round up 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_array(esize, size, gfp_mask);

	if (!fifo->data) {
		fifo->mask = 0;
		return -ENOMEM;
	}
	fifo->mask = size - 1;

	return 0;
}
EXPORT_SYMBOL(__kfifo_alloc);

void __kfifo_free(struct __kfifo *fifo)
{
	kfree(fifo->data);
	fifo->in = 0;
	fifo->out = 0;
	fifo->esize = 0;
	fifo->data = NULL;
	fifo->mask = 0;
}
EXPORT_SYMBOL(__kfifo_free);

int __kfifo_init(struct __kfifo *fifo, void *buffer,
		unsigned int size, size_t esize)
{
	size /= esize;

	if (!is_power_of_2(size))
		size = rounddown_pow_of_two(size);

	fifo->in = 0;
	fifo->out = 0;
	fifo->esize = esize;
	fifo->data = buffer;

	if (size < 2) {
		fifo->mask = 0;
		return -EINVAL;
	}
	fifo->mask = size - 1;

	return 0;
}
EXPORT_SYMBOL(__kfifo_init);

 初始化后的结构示意图如下:

|<---------------------------size---------------------------->|

+--------------------------------------------------------------+ 
data=空
+--------------------------------------------------------------+ 

 ^                                  
 |                                   
in、out

kfifo_alloc() 申请内存用的是 kmalloc(), 其申请的内存大小最小为32或64字节, 不能超过128KB,所以当需要 kfifo 大小大于128KB时, 最好就是通过 vmalloc() 申请内存, 然后通过 kfifo_init() 来初始化 kfifo,这相当于驱动自身管理 kfifo 的内存 buffer 的申请与释放,需要注意的是, kfifo_init() 要求 buffer 大小为2的幂(即2^n), 所以使用 vmlloc() 申请内存时要注意这点。

4.in/out

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;//*esize为了后续计算空闲位置的起始位置fifo->data + off
		size *= esize;
		len *= esize;
	}
	l = min(len, size - off);

	memcpy(fifo->data + off, src, l);
	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);
	if (len > l)
		len = l;

	kfifo_copy_in(fifo, buf, len, fifo->in);
	fifo->in += len;
	return len;
}
EXPORT_SYMBOL(__kfifo_in);

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();
}

值得思考的问题:

  • __kfifo_in() 函数在数据拷贝完成后, 直接对 fifo->in 的值加上入队长度, 而 fifo->in 是 unsigned int 类型的, 所以 fifo->in 超过 2^32 之后, 会从0开始计数
  • kfifo_unused() 函数计算内存 buffer 未使用的内存大小时, 用 (fifo->in - fifo->out) 获取已使用内存的长度, 在 fifo->in 超过 2^32 溢出后能否成立? 答案是肯定的, 因为 (fifo->out + len = fifo->in), 所以使用 (fifo->in - fifo->out = len) 获取已使用内存的长度是成立的
  • kfifo_copy_in() 函数进行数据拷贝时, 会用 fifo->in 与 fifo->mask 进行’与’运算求模, 从而获取到 fifo->in 在内存 buffer 中的对应位置, 使用’与’运算比直接使用 % 求模快, 提高了效率

5.参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值