使用linux内核链节时有时会有这样的使用场景:链表节点会被频繁的换入换出链表,这时操作链表节点时就需要判断节点是否归属于链表,比较容易想到的是判断指针是否为空来进行判断,实现如下:
static inline int node_in_list(const struct list_head *node)
{
ASSERT(node);
return (node->next != NULL && node->prev!= NULL && node->next != node);
}
然而,上面这段代码犯了一个致命错误:内核链表的删除操作list_del并不会将前驱和后继指针置空,而是将它们指向了特殊值:
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
这两个值定义如下:
/*
* These are non-NULL pointers that will result in page faults
* under normal circumstances, used to verify that nobody uses
* non-initialized list entries.
*/
#define LIST_POISON1 ((void *) 0x00100100)
#define LIST_POISON2 ((void *) 0x00200200)
如注释所述,内核链表这样设计正是为了防止我们使用那些未初始化的链表节点,当上面那段代码运行时,我们直到在堆栈中发现一个0x00200200或是0x00100100的数字,才恍然大悟,这样做判断是不行的。当然,你能将上面的那段代码改成:
static inline int node_in_list(const struct list_head *node)
{
ASSERT(node);
return (node->next != NULL && node->prev!= NULL && node->next != node
&&node->next != LIST_POISON1 && node->prev!=
LIST_POISON2 );
}
但是,这样修改复杂不说,你就能保证LIST_POISON1不会被什么宏包起来出现重复定义么?定义的值也未必相同吧……所以还是想想其他的办法吧。经高人指点,发现了这个函数:
/**
* list_del_init - deletes entry from list and reinitialize it.
* @entry: the element to delete from the list.
*/
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
聪明的朋友已经知道怎么做了吧,:)
1、申请节点时调用INIT_LIST_HEAD将其自环;
2、删除时调用list_del_init进行删除;
3、判断时根据节点是否自环判断是否在链表中。
实现如下:
static inline int node_in_list(const struct list_head *node)
{
if(list_empty(node))
return 0;
else
return 1;
}
另外,所谓自环就是节点前驱与后继都指向自己,即INIT_LIST_HEAD的实现:
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
阅读(2225) | 评论(2) | 转发(0) |