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呢?。所以很多时间程序出现莫名崩溃问题都发现不了,我想这也许是个例子。

  在操作写数据时的好习惯是加一把锁,前面例子为了突出链表操作没把加解锁的代码贴出,这个例子中有体现,首先定义了一把读写锁,在删除链表结点时加锁,删除完毕解锁。