list and hlist in kernel
Overview
Linux内核文件list.h 提供了一个简单双向链表list的实现和一个有链表头并通常用来作为hash表的双向链表hlist的实现。我们来看看如何正确的使用它们。
首先,这里的链表有别于平时使用的linked list ,一个linked list通常包含它需要链接的元素,如下:
struct the_list { void *the_item; struct the_list *prev; struct the_list *next; };
kernel实现的linked list给人感觉list自己被包含在它链接的元素内,如果我们要创建一个 kernel_list
,那么如下:
struct kernel_list { struct list_head list; /* list structure in the kernel */ void *the_item; };
List的基本特性如下:
- List在你需要链接数据元素的里面。
- 可以把
struct list_head
放在structure的任何地方那个。 - 可以吧
struct list_head
变量命名为任何名字。 - 可以有多个lists在一个structure中。
list
list structure
struct list_head { struct list_head *next, *prev; };
list init
list的初始化可以分为静态和动态:
dynamic
#define LIST_HEAD_INIT(name) { &(name), &(name) } /* or */ static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; } /* example */ struct list_head my_list = LIST_HEAD_INIT(my_list); struct list_head your_list; INIT_LIST_HEAD(&your_list);
static
声明并初始化
#define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) /* example */ LIST_HEAD(rds_sock_list); /* ==> */ struct rds_sock_list = { &(rds_sock_list), &(rds_sock_list) };
insert
static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } /* new 插入到head之后 */ static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } /* new 插入到head之前 */ static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); }
delete from the list
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); }
replace
/* list_replace - replace old entry by new one */ static inline void list_replace(struct list_head *old, struct list_head *new) { new->next = old->next; new->next->prev = new; new->prev = old->prev; new->prev->next = new; } static inline void list_replace_init(struct list_head *old, struct list_head *new) { list_replace(old, new); INIT_LIST_HEAD(old); }
misc operations
static inline int list_is_last(const struct list_head *list, const struct list_head *head) { return list->next == head; } static inline int list_empty(const struct list_head *head) { return head->next == head; } /* list_empty_careful - tests whether a list is empty and not being modified */ static inline int list_empty_careful(const struct list_head *head) { struct list_head *next = head->next; return (next == head) && (next == head->prev); } static inline int list_is_singular(const struct list_head *head) { return !list_empty(head) && (head->next == head->prev); }
splice
static inline void __list_splice(const struct list_head *list, struct list_head *prev, struct list_head *next) { struct list_head *first = list->next; struct list_head *last = list->prev; first->prev = prev; prev->next = first; last->next = next; next->prev = last; } /* list 加到 head 前面 */ static inline void list_splice(const struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head, head->next); } /* list 加到 head 后面 */ static inline void list_splice_tail(struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head->prev, head); }
entry operations
一般通过 struct list_head
维护链表,那么有一个指向 struct
list_head*
的指针 ptr
,如何获取它所在的structure的指针然后访问它的元素呢?
/* * @ptr: &struct list_head 指针。 * @type: ptr所在的元素结构体的类型。 * @member: list_head在type结构体中的名字。 */ #define list_entry(ptr, type, member) \ container_of(ptr, type, member) #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) /* 只是 ptr 变成 (ptr)->next */ #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member)
这里难点是通过0地址的指针强制转换成相应结构体,然后在获取其中元素所在位置,因为起始地址是0,这里得到的元素所在指针位置就是它相对于structure
的位置,也就是这里 offsetof
所得到的以字节单位的值。
有了这个值就好办了,list的指针地址是知道的,只要减去这个偏移值就能得到元素structure的地址,最后转换成其type就得到entry了。
主要是扩展成:
entry = (type *)( (char *)__mptr - offsetof(type,member);
example:
struct list_head *lh; struct inet_protosw *answer; struct inet_protosw { struct list_head list; unsigned short type; unsigned short protocol; } answer = list_entry(lh, struct inet_protosw, list);
iterate the list
safe的区别是多了另一个&struct listhead类型的临时变量n.变量n提前指向 pos下一个元素,确保在访问的当前节点被其他进程删除时,仍能正常遍历整个list.
/* @pos: &struct list_head类型的loop cursor. @head: list的head. */ #define list_for_each(pos, head) \ for (pos = (head)->next; prefetch(pos->next), pos != (head); \ pos = pos->next) #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ pos = pos->prev) /* @n: 另一个&struct list_head类型的临时变量. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) #define list_for_each_prev_safe(pos, n, head) \ for (pos = (head)->prev, n = pos->prev; \ prefetch(pos->prev), pos != (head); \ pos = n, n = pos->prev)
example
struct rfcomm_dlc { struct list_head list; u8 dlci; }; struct list_head dlc_list; struct list_head *p; struct rfcomm_dlc *d; list_for_each(p, &dlc_list) { d = list_entry(p, struct rfcomm_dlc, list); if (d->dlci == dlci) return d; }
iterate the entry
list_for_each
与 list_for_each_entry
的区别是,前者pos的类型是
&struct list_head
,只遍历并返回链表指针,而后者pos的类型是 type *
,
在遍历链表的同时,找出并返回list所在的元素指针.
/* * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #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)) #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)) #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_entry(pos->member.next, typeof(*pos), member); \ prefetch(pos->member.next), &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) #define list_for_each_entry_continue_reverse(pos, head, member) \ for (pos = list_entry(pos->member.prev, typeof(*pos), member); \ prefetch(pos->member.prev), &pos->member != (head); \ pos = list_entry(pos->member.prev, typeof(*pos), member)) #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)) #define list_for_each_entry_safe_continue(pos, n, head, member) \ for (pos = list_entry(pos->member.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))
example
struct atm_dev_addr { struct sockaddr_atmsvc addr; /* ATM address */ struct list_head entry; /* next address */ }; struct atm_dev_addr *this; struct list_head *head; /* list header */ int total = 0; list_for_each_entry(this, head, entry) total += sizeof(struct sockaddr_atmsvc);
example
从当前module节点,遍历系统所有module.
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/version.h> #include <linux/list.h> MODULE_LICENSE("GPL"); struct module *m = &__this_module; static void list_module_test(void) { struct module *mod; list_for_each_entry(mod, m->list.prev, list) printk ("%s\n", mod->name); } static int list_module_init (void) { list_module_test(); return 0; } static void list_module_exit (void) { printk ("unload listmodule.ko\n"); } module_init(list_module_init); module_exit(list_module_exit);
Makefile:
ifeq ($(KERNELRELEASE),) KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules modules_install: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions .PHONY: modules modules_install clean else obj-m := listmodule.o endif
hlist
拥有一个指针链表头的双向链表.它被分为 struct hlist_head
头节点和
struct hlist_node
元素节点.元素节点的 pprev
指向前一个节点的next指针.
hlist structure
struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next, **pprev; };
init head or node
#define HLIST_HEAD_INIT { .first = NULL } #define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) static inline void INIT_HLIST_NODE(struct hlist_node *h) { h->next = NULL; h->pprev = NULL; }
delete node
static inline void __hlist_del(struct hlist_node *n) { struct hlist_node *next = n->next; struct hlist_node **pprev = n->pprev; *pprev = next; if (next) next->pprev = pprev; } static inline void hlist_del(struct hlist_node *n) { __hlist_del(n); n->next = LIST_POISON1; n->pprev = LIST_POISON2; } static inline void hlist_del_init(struct hlist_node *n) { if (!hlist_unhashed(n)) { __hlist_del(n); INIT_HLIST_NODE(n); } }
add
add操作分为两种插入方式: 1)新插入的节点成为head节点(相当于把新插入的节点排在第一位,得到优先搜索到); 2)节点间的插入
add head
n成为新的head节点,原本head节点后的元素节点成为n的next节点.
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) { struct hlist_node *first = h->first; n->next = first; if (first) first->pprev = &n->next; h->first = n; n->pprev = &h->first; }
add node
/* 节点n插入到节点next之前 */ /* next must be != NULL */ static inline void hlist_add_before(struct hlist_node *n, struct hlist_node *next) { n->pprev = next->pprev; n->next = next; next->pprev = &n->next; *(n->pprev) = n; } /* 节点n插入到节点next之后 */ static inline void hlist_add_after(struct hlist_node *n, struct hlist_node *next) { next->next = n->next; n->next = next; next->pprev = &n->next; if(next->next) next->next->pprev = &next->next; }
move
把old头节点后的元素节点移动到new后面,old指向被置空.
static inline void hlist_move_list(struct hlist_head *old, struct hlist_head *new) { new->first = old->first; if (new->first) new->first->pprev = &new->first; old->first = NULL; }
iterate node
与iterate the list操作类似.
#define hlist_for_each(pos, head) \ for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \ pos = pos->next) #define hlist_for_each_safe(pos, n, head) \ for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ pos = n)
iterate the entry
与如上iterate list's entries 类似.
#define hlist_entry(ptr, type, member) container_of(ptr,type,member) #define hlist_for_each_entry(tpos, pos, head, member) \ for (pos = (head)->first; \ pos && ({ prefetch(pos->next); 1;}) && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = pos->next) #define hlist_for_each_entry_continue(tpos, pos, member) \ for (pos = (pos)->next; \ pos && ({ prefetch(pos->next); 1;}) && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = pos->next) #define hlist_for_each_entry_from(tpos, pos, member) \ for (; pos && ({ prefetch(pos->next); 1;}) && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = pos->next) #define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ for (pos = (head)->first; \ pos && ({ n = pos->next; 1; }) && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = n)
misc
static inline int hlist_unhashed(const struct hlist_node *h) { return !h->pprev; } static inline int hlist_empty(const struct hlist_head *h) { return !h->first; }
example
kernel中会注册很多net device的设备,之后上层或其他模块总会经常通过设备名来查找相应的设备,随着设备数的增加,加上名字字符串的比对有一定的开销, 把设备通过hash表存,能快速查找.如下就是简单实例的源码,源码包下载.
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/version.h> #include <linux/list.h> #include <linux/slab.h> MODULE_LICENSE("GPL"); #define init_name_hash() 0 #define IFNAMSIZ 16 #define NETDEV_HASHBITS 8 #define NETDEV_HASHENTRIES (1 << NETDEV_HASHBITS) /* partial hash update function. Assume roughly 4 bits per character */ static inline unsigned long partial_name_hash(unsigned long c, unsigned long prevhash) { return (prevhash + (c << 4) + (c >> 4)) * 11; } /* * Finally: cut down the number of bits to a int value (and try to avoid * losing bits) */ static inline unsigned long end_name_hash(unsigned long hash) { return (unsigned int) hash; } /* Compute the hash for a name string. */ static inline unsigned int full_name_hash(const unsigned char *name, unsigned int len) { unsigned long hash = init_name_hash(); while (len--) hash = partial_name_hash(*name++, hash); return end_name_hash(hash); } struct net { struct hlist_head *dev_name_head; }; static inline struct hlist_head *dev_name_hash(struct net *net, const char *name) { unsigned hash = full_name_hash(name, strnlen(name, IFNAMSIZ)); return &net->dev_name_head[hash & ((1 << NETDEV_HASHBITS) - 1)]; } static struct hlist_head *netdev_create_hash(void) { int i; struct hlist_head *hash; hash = kmalloc(sizeof(*hash) * NETDEV_HASHENTRIES, GFP_KERNEL); if (hash != NULL) for (i = 0; i < NETDEV_HASHENTRIES; i++) INIT_HLIST_HEAD(&hash[i]); return hash; } struct net_device { char name[IFNAMSIZ]; struct hlist_node name_hlist; int net_num; }; struct net_device *dev_get_by_name(struct net *net, const char *name) { struct hlist_node *p; hlist_for_each(p, dev_name_hash(net, name)) { struct net_device *dev = hlist_entry(p, struct net_device, name_hlist); if (!strncmp(dev->name, name, IFNAMSIZ)) return dev; } return NULL; } static struct net net_space; static struct net_device *devices; static int hlist_module_init(void) { const int dev_num = 10; int i; net_space.dev_name_head = netdev_create_hash(); if (net_space.dev_name_head == NULL) { goto err_name; } devices = kmalloc(sizeof(struct net_device) * dev_num, GFP_KERNEL); if (devices == NULL) { goto err_dev; } for (i = 0; i < dev_num; ++i) { snprintf(devices[i].name, IFNAMSIZ, "eth%d", i); devices[i].net_num = i; hlist_add_head(&devices[i].name_hlist, dev_name_hash(&net_space, devices[i].name)); } struct net_device *dev; dev = dev_get_by_name(&net_space, "eth1"); if (dev) { printk("%s, %d\n", dev->name, dev->net_num); } return 0; err_dev: kfree(net_space.dev_name_head); err_name: return -ENOMEM; } static void hlist_module_exit(void) { kfree(devices); kfree(net_space.dev_name_head); } module_init(hlist_module_init); module_exit(hlist_module_exit);