链表
链表是Linux内核中最简单、最普通的数据结构
Linux内核中的实现
在2.1内核开发系列中,首次引入了官方内核链表实现
在<linux/list.h>中声明
c
struct list_head {
struct list_head *next, *prev;
};
使用宏container_of()可以方便地从链表指针找到父结构中包含的任何变量
c
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
使用container_of()宏,定义一个简单的函数可返回包含list_head的父类型结构体
c
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
依靠list_entry()方法,内核提供了创建、操作以及其他链表管理的各种方法,所有这些方法都不需要知道list_head锁嵌入对象的数据结构
操作链表
内核中提供了一组函数来操作链表,这些函数都要使用一个list_head结构体指针做参数,函数都是用C语言以内联函数实现的
所有这些函数的复杂度是O(1)
- 向链表中添加一个节点
c
static inline void list_add(struct list_head *new, struct list_head *head)
该函数向指定链表的head节点后插入new节点
- 从链表中删除节点
c
static inline void list_del(struct list_head *entry)
该函数从链表中删除entry()元素,该操作并不会释放entry 或释放包含entry的数据结构体所占用的内存,只是仅仅将entry元素从链表中移走
c
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
从链表中删除一个节点并对其重新初始化
c
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
虽然链表不再需要entry项,但是还可以再次使用包含entry的数据结构体
- 移动和合并链表节点
把一个节点从一个链表移到另一个链表
c
static inline void list_move(struct list_head *list, struct list_head *head)
该函数从一个链表中移除list项,然后将其加入到另一个链表的head节点后面
把节点从一个链表移到另一个链表的末尾
c
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
该函数和list_move()函数一样,唯一不同的是将list项插入到head项前
检查链表是否为空
c
static inline int list_empty(const struct list_head *head)
将两个未连接的链表合并在一起
c
static inline void list_splice(const struct list_head *list,
struct list_head *head)
它将list指向的链表插入到指定链表的head元素后面
把两个未连接的链表合并在一起,并重新初始化原来的链表
c
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
遍历链表
- 基本方法
使用list_for_each()宏,该宏使用两个list_head类型的参数
第一个参数指向当前项
第二个参数是需要遍历的链表的以头节点形式存在的list_head
每次遍历中,第一个参数在链表中不断移动指向下一个元素,直到所有元素都被访问为止
c
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
- 可用的方法
多数内核代码采用list_for_each_entry()宏来遍历链表
这里的pos是一个包含list_head节点对象的指针,可将它看作是list_entry宏的返回值,head是一个指向头节点的指针
c
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
prefetch(pos->member.next), &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
内核中的例子:
来自inotify:内核文件系统的更新通知机制
在inode结构串联起来的inotify_watches链表中,查找inotify_handle与所提供的句柄相匹配的inotify_watche项
c
/*
* inotify_find_handle - find the watch associated with the given inode and
* handle
*
* Callers must hold inode->inotify_mutex.
*/
static struct inotify_watch *inode_find_handle(struct inode *inode,
struct inotify_handle *ih)
{
struct inotify_watch *watch;
list_for_each_entry(watch, &inode->inotify_watches, i_list) {
if (watch->ih == ih)
return watch;
}
return NULL;
}
- 反向遍历链表
沿着prev指针向后遍历
c
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member); \
prefetch(pos->member.prev), &pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
- 遍历的同时删除
如果当前项在遍历循环中被删除,那么接下来的遍历就无法获取next/prev指针了
因此开发人员在潜在的删除操作之前保存next/prev指针到一个临时变量中,以便安全删除当前项
c
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
例子:
c
/**
* inotify_inode_is_dead - an inode has been deleted, cleanup any watches
* @inode: inode that is about to be removed
*/
void inotify_inode_is_dead(struct inode *inode)
{
struct inotify_watch *watch, *next;
mutex_lock(&inode->inotify_mutex);
list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
struct inotify_handle *ih = watch->ih;
mutex_lock(&ih->mutex);
inotify_remove_watch_locked(ih, watch);
mutex_unlock(&ih->mutex);
}
mutex_unlock(&inode->inotify_mutex);
}
反向遍历链表的同时删除
c
#define list_for_each_entry_safe_reverse(pos, n, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member), \
n = list_entry(pos->member.prev, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.prev, typeof(*n), member))
队列
Linux内核通用队列实现称为kfifo,它实现在文件<kernel/kfifo.c>中
kfifo
主要提供了两个操作:enqueue和dequeue
维护了两个偏移量:入口偏移量和出口偏移
创建队列
c
int kfifo_alloc(struct kfifo *fifo, unsigned int size, gfp_t gfp_mask)
该函数创建并初始化一个大小为size的kfifo,内核使用gfp_mask标识分配队列
成功返回0,错误则返回一个负数错误码
自己分配缓冲,可以调用
c
void kfifo_init(struct kfifo *fifo, void *buffer, unsigned int size)
该函数创建并初始化一个kfifo对象,它使用buffer指向size字节大小的内存
对于kfifo_allor()和kfifo_init(),size()必须是2的幂
静态声明kfifo更简单,但不大常用
c
DECLARE_KFIFO(name, size);
INIT_KFIFO(name)
推入队列数据
推入队列数据需要通过kfifo_in()方法完成
c
unsigned int kfifo_in(struct kfifo *fifo, const void *from,
unsigned int len)
该函数把from指针所指的len字节数据拷贝到fifo所指的队列中
成功返回推入字节的大小,如果队列中空闲字节小于len,则该函数值最多可拷贝队列可用空间那么多的数据,这样的话,返回值可能小于len,也有可能返回0