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()方法创建的队列,那么需要负责释放相关的缓冲区。具体的方法取决于如何创建它的。