Linux内核中队列的实现详解

任何操作系统内核都少不了一个编程模型:生产者和消费者。在该模式中,生产者创造数据(比如说需要读取的错误信息或者需要处理的网络包),而消费者则反过来,读取消息和处理包,或者以其他方式消费这些数据。实现该模型的最简单的方式无非是使用队列。生产者将数据推进队列,然后消费者从队列中摘取数据。消费者获取数据的顺序和推入队列的顺序一致。也就是说,第一个进入队列的数据一定是第一个离开队列的。也正是这个原因,队列也称为FIFO。顾名思义,FIFO是先进先出的缩写。下图是标准队列的例子。

                                            队列(FIFO)

Linux内核通用队列实现称为kfifo。它实现在文件kernel/kfifo.c中,声明在文件<linux/kfifo.h>中。本节讨论的是自2.6.33以后刚更新的API,使用方法和2.6.33前的内核稍有不同,所以在使用前请仔细检查文件<linux/kfifo.h>。

Linux的kfifo和多数其他队列实现类型,提供了两个主要操作:enqueue(入队列)和dequeue(出队列)。kfifo对象维护了两个偏移量:入口偏移量和出口偏移量。入口偏移量是指下一次入队列时的位置,出口偏移是指下一次出队列时的位置。出口偏移总是小于等于入口偏移,否则无意义,因为那样说明要出队列的元素根本还没有入队列。
enqueue操作拷贝数据到队列中的入口偏移位置。当上述动作完成后,入口偏移随之加上推入的元素数目。dequeue操作从队列中出口偏移处拷贝数据,当上述动作完成后,出口偏移随之减去摘取的元素数据。当出口偏移等于入口偏移时,说明队列空了;在新数据被推入前,不可再摘取任何数据了。当入口偏移等于队列长度时,说明队列重置前,不可再有新数据推入队列。

1.创建队列:
使用kfifo前,首先必须对它进行定义和初始化。和多数内核对象一样,有动态或者静态方法供你选择。动态方法更为普遍:

int kfifo_alloc(struct kfifo *fifo, unsigned int size, gfp_t gfp_mask);

该函数创建并初始化一个大小为size的kfifo。内核使用gfp_mask标识分配队列。如果成功kfifo_alloc()返回0;错误则返回一个负的错误码。下面便是一个例子:

struct kfifo fifo;
int ret;
ret = kfifo_alloc(&fifo,PAGE_SIZE,GFP_KERNEL);
if(ret)
	return ret;
/* fifo现在代表一个大小为PAGE_SIZE的队列..... */

你想要自己分配缓冲区,可以调用:

void kfifo_init(struct kfifo *fifo, void *buffer, unsigned int size);

该函数创建并初始化一个kfifo对象,它将使用由buffer指向的size字节大小的内存。对于kfifo_alloc()和kfifo_init(),size必须是2的幂。

静态声明kfifo更简单,但不大常用:

DECLARE_KFIFO(name, size);
INIT_KFIFO(name);

上述方法会创建一个名称为name、大小为size的kfifo对象。和前面一样,size必须是2的幂。

2.推入队列数据:
当你的kfifo对象创建和初始化后,推入数据到队列需要通过kfifo_in()方法完成:

void kfifo_init(struct kfifo *fifo, void *buffer, unsigned int size);

该函数把from指针所指的len字节数据包括到kfifo所指的队列中,如果成功,则返回推入数据的字节大小。如果队列中的空闲字节小于len,则该函数最多可拷贝队列可用空间那么多的数据,这样的话,返回值可能小于len,甚至会返回0,这时意味着没有任何数据被推入。

3.摘取队列数据
推入数据使用kfifo_in(),摘取数据则需要通过函数kfifo_out()完成:

unsigned int kfifo_out(struct kfifo *fifo, void *to, unsigned int len);

该函数从fifo所指向的队列中拷贝处长度为len字节的数据到to所指的缓冲中。如果成功,该函数返回拷贝的数据长度。如果队列中数据大小小于len,则该函数拷贝出的数据必然小于需要的数据大小。

当数据被摘取后,数据就不再存在于队列之中。这是队列操作的常用方式。不过如果仅仅想"偷窥"队列中的数据,而不真想删除它,你可以使用kfifo_out_peek()方法:

unsigned int kfifo_out_peek(struct kfifo *fifo, void *to, unsigned int len,unsigned offset);

该函数和kfifo_out()类似,但出口偏移不增加,而且摘取的数据任然可被下次kfifo_out()获得。参数offset指向队列中的索引位置,如果该参数为0,则读队列头,这和kfifo_out()无异。

4.获取队列长度
若想获得用于存储kfifo队列的空间的总体大小(以字节为单位),可调用方法kfifo_size();

static inline __must_check unsigned int kfifo_size(struct kfifo *fifo);

另一个内核命名不佳的例子来了——kfifo_len()方法返回kfifo队列中推入的数据大小;

static inline unsigned int kfifo_len(struct kfifo *fifo);

如果想得到kfifo队列中还有多少可用空间,则要调用方法:

static inline __must_check unsigned int kfifo_avail(struct kfifo *fifo);

最后两个方法是kfifo_is_empty()和kfifo_is_full()。如果给定的kfifo分别是空或者满,它们返回非0值。如果返回0,则相反:

static inline __must_check int kfifo_is_empty(struct kfifo *fifo);
static inline __must_check int kfifo_is_full(struct kfifo *fifo);

5.重置或撤销队列
如果重置kfifo,意味着抛弃所有队列中的内容,调用kfifo_reset():

static inline void kfifo_reset(struct kfifo *fifo);

撤销一个使用kfifo_alloc()分配的队列,调用kfifo_free():

void kfifo_free(struct kfifo *fifo);

如果你是使用kfifo_init()方法创建的队列,那么你需要负责释放相关的缓冲区,具体方法取决于你是如果创建它的。

猜你喜欢

转载自blog.csdn.net/caihaitao2000/article/details/81367048