Linux内核中链表的使用
作者:Linux内核中链表的使用 发布时间:[ 2012/8/8 10:02:03 ] 推荐标签:
4、遍历链表
为什么不先讲删除节点,因为很多情况下都是先遍历链表,找到匹配的节点后再去删除的,所以先讲遍历链表,遍历链表分普通遍历和安全删除遍历,可见如果要删除链表结点我们需要使用安全删除遍历。
普通遍历,主要使用在当加入节点时判断是否有相同的结点存在,如果已存在不需要再往下操作了,如下判断是否有相同的IP地址存在。
#define list_for_each(pos, head)
for (pos = (head)->next; prefetch(pos->next), pos != (head);
pos = pos->next)
struct list_head *p;
struct ipstore *store;
list_for_each(p, &addr_list)
{
store = list_entry(p, struct ipstore, list);
if(store->addr[0] == ist->addr[0])
{
if(ist)
kfree(ist);
break;
}
}
INIT_LIST_HEAD(&ist->list);
list_add(&ist->list, &addr_list);
ist是上一步的链表结点指针,如果链表中有IP和新增节点IP地址相同的节点则释放刚分配的新节点空间,不进行任何操作,否则加入addr_list链表中。
内核提供了一个比较简单的接口,可以在遍历的同时取出节点,此函数内部封装了list_entry():
#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))
所以以上例子可改成如下,这样比较简洁了。
struct ipstore *store;
list_for_each_entry(store, &addr_list, list)
{
if(store->addr[0] == ist->addr[0])
{
if(ist)
kfree(ist);
return 0;
}
}
再来看安全删除遍历接口:
#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))
同样把上面的例子进行修改,这样删除时不会破坏原有链表的结构出现“使用在释放后”的程序崩溃问题:
struct ipstore *v, *n;
list_for_each_entry_safe(v, n, &addr_list, list)
{
if(iph->saddr == v->addr[0])
{
list_del(&v->list);//删除节点
kfree(v);//还需要注意的是list_del()只是把节点从链表中摘除,它并不释放其占用的内存,所以需要手动释放内存。
}
}
5、判空操作
判空操作也比较重要,如果为空咱什么都不操作,直接返回
if(list_empty(&addr_list))
{
return 0;
}
总结:到此为止对于内核链表的操作基本上可以满足日常需求了,当然内核还提供移动和合并链表操作、反向遍历链表操作的接口,具体可参考LKD3e.
对链表的操作中一不小心会出现“使用在释放后”的程序崩溃问题,请看如下使用普通遍历并删除的代码:
DEFINE_RWLOCK(v4_rwlock);
struct list_head *p;
struct ipstore *store;
list_for_each(p, &addr_list)
{
store = list_entry(p, struct ipstore, list);
if(iph->saddr == store->addr[0])
{
read_lock(&v4_rwlock);
list_del(&store->list);
read_unlock(&v4_rwlock);
kfree(store);
return 0;
}
}
这段代码虽然用了普通非安全遍历删除操作,但不会引起程序崩溃,但它已经埋下了安全隐患,不会崩溃是因为在删除链表并释放内存后直接return 0了,没再对目前的链表进行遍历移动操作,所以不会出现问题,万一哪天没return呢?。所以很多时间程序出现莫名崩溃问题都发现不了,我想这也许是个例子。
在操作写数据时的好习惯是加一把锁,前面例子为了突出链表操作没把加解锁的代码贴出,这个例子中有体现,首先定义了一把读写锁,在删除链表结点时加锁,删除完毕解锁。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11