第6章内核数据结构之队列

6.2 队列

任何操作系统内核都少不了一种编程模型:生产者和消费者。在该模式中,生产者创造数据,而消费者则反过来,读取消息和处理包,或者以其他方式消费这些数据。实现该模型的最简单的方式是使用队列。生成者将数据推进队列,消费者从队列中摘取数据。消费者获取数据的顺序和推入队列的顺序一致。第一个进入队列的数据一定是第一个离开队列的。队列也称为FIFO(先进先出)。图6-5是一个标准队列的例子。

Linux内核通用队列实现称为kfifo。其实现在lib/kfifo.c中,声明在文件<linux/kfifo.h>。

6.2.1 kfifo

Linux的kfifo和多数其他队列实现类似,提供两个主要操作:enqueue(入队列)和dequeue(出队列)。kfifo对象维护了两个偏移量:入口偏移和出口偏移。入口偏移是指下一次入队列时的位置,出口偏移是指下一次出队列时的位置。出口偏移总是小于等于入口偏移,否则无意义,因为那样说明要出队列的元素根本还没有入队列。

enqueue操作拷贝数据到队列中的入口偏移位置。当上述动作完成后,入口偏移随之加上推入的元素数目。dequeue操作从队列中出口偏移处拷贝数据,当上述动作完成后,出口偏移随之减去摘取的元素数目。当出口偏移等于入口偏移时,说明队列空了:在新数据被推入前,不可再摘取任何数据了。当入口偏移等于队列长度时,说明在队列重置前,不可再有新数据推入队列。

6.2.2 创建队列

使用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 !=0)

         return ret;

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

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的幂。

6.2.3 推入队列数据

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

unsigned int kfifo_in(struct kfifo *fifo, const void *from, unsigned int len);

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

6.2.4 摘取队列数据

摘取队列数据通过函数kfifo_out()完成:

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

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

当数据被摘取后,数据就不再存在于队列之中。如果仅仅想偷窥队列中的数据,而不想删除,可以使用kfifo_out_peek()方法:

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

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

6.2.5 获取队列长度

获得用于存储kfifo队列的空间的总体大小(以字节为单位),可调用方法kfifo_size():

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

kfifo_len()方法返回kfifo队列中已推入的数据大小:

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

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

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

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

static inline int kfifo_is_empty(struct kfifo *fifo);

static inline int kfifo_is_full(struct kfifo *fifo);

6.2.6 重置和撤销队列

如果重置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/xiezhi123456/article/details/81709873
今日推荐