链表
Linux内核中的链表是一个比较通用的,所以刚开始我看到时候不是特别习惯,和教科书上的都不一样,教科书上的是正向思维,就是有链表,然后怎么用。内核中的是有需求了,我们怎么构造链表,构造的链表怎么通用。这就是工作和学习的不同,最近一直找一些比较贴近实现的书,例如0bug,虽然很多人对其评价不太好,但我知道是很多c语言的书,评价很好,但是现实用的时候又有多少能用上?后面我会专门写一下0bug书上的东西。
<linux/list.h>
struct list_head {
struct list_head *next, *prev;
};
用法是将list_head嵌套到结构体中去,例如
struct foo {
struct list_head list;
void data;
};
问题就来了,如何通过list求得整个结构体?
/**
* 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) );})
这个宏定义写的很高超,用于求得整个结构体的地址。
有人就会问了,为何需要从嵌套求得嵌套结构体地址呢?原因是,作为通用的链表,那么所有的函数应该是和具体用处无关的,所以例如list_add函数的参数就是一个list_head,也就是保存的是list。
#define list_entry(ptr, type, member) /
container_of(ptr, type, member)
struct foo *ifoo;
....
INIT_LIST_HEAD(&ifoo->list);
以前不太清楚规矩,不知道内核链表是怎么用的,所以看很多函数都比较蒙,特别是splice一类的函数,因为用法都有一个链表头。
定义一个链表头,用于指向链表,后面的所有都可以根据这个链表头来索引。
static LIST_HEAD(foo_list);
这里foo_list就可以当作是一个链表头,其实也完全是一个链表节点一样的结构体。
链表的一些操作
1、Add
参数new表示一个新节点,head则是链表头。
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
向链表头的prev新增。
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
2、Del
从链表除去
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
除去并初始化
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
3、Move和splicing 链表节点
从下面函数的实现可以看出,任何设计和实现都要从最基本的开始,上层应用基本函数来实现复杂功能。
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del(list->prev, list->next);
list_add(list, head);
}
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del(list->prev, list->next);
list_add_tail(list, head);
}
判断是否为空函数
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
传入的一定是链表头
将两个链表合并,这两个函数第一个参数是一个链表头,head是将要合并的链表头,最后head访问,list则变成空的链表头了。
static inline void list_splice(const struct list_head *list,
struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head, head->next);
}
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
}
4、遍历链表
基本便利函数
#define list_for_each(pos, head) /
for (pos = (head)->next; prefetch(pos->next), pos != (head); /
pos = pos->next)
struct list_head *p;
struct fox *f;
list_for_each(p, &fox_list) {
/* f points to the structure in which the list is embedded */
f = list_entry(p, struct fox, list);
}
将上面合并一下就成了
#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))
遍历过程中删除,则需要safe函数。
#define list_for_each_safe(pos, n, head) /
for (pos = (head)->next, n = pos->next; pos != (head); /
pos = n, n = pos->next)
n先保存了next的指针,这样,及时pos被free了无所谓了
在Linux内核中,还有一个叫做hlist,用于hash表表项,而节点和前面讲到的list一样,目的是减少hash表项占用内存。
RCU,用于读取远多于写操作。
http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html
下面介绍一下hlist
http://www.cnitblog.com/luofuchong/archive/2008/01/17/38986.html
队列 kfifo
1、创建队列
int kfifo_alloc(struct kfifo *fifo, unsigned int size, gfp_t gfp_mask);
void kfifo_init(struct kfifo *fifo, void *buffer, unsigned int size);
2、入列
unsigned int kfifo_in(struct kfifo *fifo, const void *from, unsigned int len);
3、出列
unsigned int kfifo_out(struct kfifo *fifo, void *to, unsigned int len);
unsigned int kfifo_out_peek(struct kfifo *fifo, void *to, unsigned int len, unsigned offset);
4、重置
static inline void kfifo_reset(struct kfifo *fifo);
5、销毁
void kfifo_free(struct kfifo *fifo);