所以以上例子可改成如下,这样就比较简洁了。
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); kfree(store); return 0; |
这段代码虽然用了普通非安全遍历删除操作,但不会引起程序崩溃,但它已经埋下了安全隐患,不会崩溃是因为在删除链表并释放内存后直接return 0了,没再对目前的链表进行遍历移动操作,所以不会出现问题,万一哪天没return呢?。所以很多时间程序出现莫名崩溃问题都发现不了,我想这也许是个例子。
在操作写数据时的好习惯是加一把锁,前面例子为了突出链表操作没把加解锁的代码贴出,这个例子中就有体现,首先定义了一把读写锁,在删除链表结点时加锁,删除完毕解锁。