更强的链表klist

来源:互联网 发布:百度推广如何优化 编辑:程序博客网 时间:2024/04/29 23:51
前面我们说到过list_head,这是linux中通用的链表形式,双向循环链表,功能强大,实现简单优雅。可如果您认为list_head就是链表的极致,应该在linux链表界一统天下,那可就错了。据我所知,linux内核代码中至少还有两种链表能占有一席之地。一种就是hlist,一种就是本节要介绍的klist。虽然三者不同,但hlist和klist都可以看成是从list_head中发展出来的,用于特殊的链表使用情景。hlist是用于哈希表中。众所周知,哈希表主要就是一个哈希数组,为了解决映射冲突的问题,常常把哈希数组的每一项做成一个链表,这样有多少重复的都可以链进去。但哈希数组的项很多,list_head的话每个链表头都需要两个指针的空间,在稀疏的哈希表中实在是一种浪费,于是就发明了hlist。hlist有两大特点,一是它的链表头只需要一个指针,二是它的每一项都可以找到自己的前一节点,也就是说它不再循环,但仍是双向。令人不解的是,hlist的实现太绕了,比如它明明可以直接指向前一节点,却偏偏指向指针地址,还是前一节点中指向后一节点的指针地址。即使这种设计在实现时占便宜,但它理解上带来的不便已经远远超过实现上带来的小小便利。 同hlist一样,klist也是为了适应某类特殊情形的要求。考虑一个被简化的情形,假设一些设备被链接在设备链表中,一个线程命令卸载某设备,即将其从设备链表中删除,但这时该设备正在使用中,这时就出现了冲突。当前可以设置临界区并加锁,但因为使用一个设备而锁住整个设备链表显然是不对的;又或者可以从设备本身做文章,让线程阻塞,这当然也可以。但我们上节了解了kref,就该知道linux对待这种情况的风格,给它一个引用计数kref,等计数为零就删除。klist就是这么干的,它把kref直接保存在了链表节点上。之前说到有线程要求删除设备,之前的使用仍存在,所以不能实际删除,但不应该有新的应用访问到该设备。klist就提供了一种让节点在链表上隐身的方法。下面还是来看实际代码吧。klist的头文件是include/linux/klist.h,实现在lib/klist.c。struct klist_node;   struct klist {       spinlock_t      k_lock;       struct list_head    k_list;       void            (*get)(struct klist_node *);       void            (*put)(struct klist_node *);   } __attribute__ ((aligned (4)));      #define KLIST_INIT(_name, _get, _put)                   \        { .k_lock   = __SPIN_LOCK_UNLOCKED(_name.k_lock),       \         .k_list   = LIST_HEAD_INIT(_name.k_list),         \         .get      = _get,                     \         .put      = _put, }      #define DEFINE_KLIST(_name, _get, _put)                 \        struct klist _name = KLIST_INIT(_name, _get, _put)      extern void klist_init(struct klist *k, void (*get)(struct klist_node *),                  void (*put)(struct klist_node *));      struct klist_node {       void            *n_klist;   /* never access directly */       struct list_head    n_node;       struct kref     n_ref;   };  可以看到,klist的链表头是struct klist结构,链表节点是struct klist_node结构。先看struct klist,除了包含链表需要的k_list,还有用于加锁的k_lock。剩余的get()和put()函数是用于struct klist_node嵌入在更大的结构中,这样在节点初始时调用get(),在节点删除时调用put(),以表示链表中存在对结构的引用。再看struct klist_node,除了链表需要的n_node,还有一个引用计数n_ref。还有一个比较特殊的指针n_klist,n_klist是指向链表头struct klist的,但它的第0位用来表示是否该节点已被请求删除,如果已被请求删除则在链表循环时是看不到这一节点的,循环函数将其略过。现在你明白为什么非要在struct klist的定义后加上__attribute__((aligned(4)))。不过说实话这样在x86下仍然不太保险,但linux选择了相信gcc,毕竟是多年的战友和兄弟了,相互知根知底。看过这两个结构,想必大家已经较为清楚了,下面就来看看它们的实现。/*   * Use the lowest bit of n_klist to mark deleted nodes and exclude   * dead ones from iteration.   */   #define KNODE_DEAD      1LU    #define KNODE_KLIST_MASK    ~KNODE_DEAD       static struct klist *knode_klist(struct klist_node *knode)   {       return (struct klist *)           ((unsigned long)knode->n_klist & KNODE_KLIST_MASK);   }      static bool knode_dead(struct klist_node *knode)   {       return (unsigned long)knode->n_klist & KNODE_DEAD;   }      static void knode_set_klist(struct klist_node *knode, struct klist *klist)   {       knode->n_klist = klist;       /* no knode deserves to start its life dead */       WARN_ON(knode_dead(knode));   }      static void knode_kill(struct klist_node *knode)   {       /* and no knode should die twice ever either, see we're very humane */       WARN_ON(knode_dead(knode));       *(unsigned long *)&knode->n_klist |= KNODE_DEAD;   }  前面的四个函数都是内部静态函数,帮助API实现的。knode_klist()是从节点找到链表头。knode_dead()是检查该节点是否已被请求删除。knode_set_klist设置节点的链表头。knode_kill将该节点请求删除。细心的话大家会发现这四个函数是对称的,而且都是操作节点的内部函数。void klist_init(struct klist *k, void (*get)(struct klist_node *),           void (*put)(struct klist_node *))   {       INIT_LIST_HEAD(&k->k_list);       spin_lock_init(&k->k_lock);       k->get = get;       k->put = put;   }  klist_init,初始化klist。static void add_head(struct klist *k, struct klist_node *n)   {       spin_lock(&k->k_lock);       list_add(&n->n_node, &k->k_list);       spin_unlock(&k->k_lock);   }      static void add_tail(struct klist *k, struct klist_node *n)   {       spin_lock(&k->k_lock);       list_add_tail(&n->n_node, &k->k_list);       spin_unlock(&k->k_lock);   }      static void klist_node_init(struct klist *k, struct klist_node *n)   {       INIT_LIST_HEAD(&n->n_node);       kref_init(&n->n_ref);       knode_set_klist(n, k);       if (k->get)           k->get(n);   }  又是三个内部函数,add_head()将节点加入链表头,add_tail()将节点加入链表尾,klist_node_init()是初始化节点。注意在节点的引用计数初始化时,因为引用计数变为1,所以也要调用相应的get()函数。void klist_add_head(struct klist_node *n, struct klist *k)   {       klist_node_init(k, n);       add_head(k, n);   }      void klist_add_tail(struct klist_node *n, struct klist *k)   {       klist_node_init(k, n);       add_tail(k, n);   }  klist_add_head()将节点初始化,并加入链表头。klist_add_tail()将节点初始化,并加入链表尾。它们正是用上面的三个内部函数实现的,可见linux内核中对函数复用有很强的执念,其实这里add_tail和add_head是不用的,纵观整个文件,也只有klist_add_head()和klist_add_tail()对它们进行了调用。void klist_add_after(struct klist_node *n, struct klist_node *pos)   {       struct klist *k = knode_klist(pos);          klist_node_init(k, n);       spin_lock(&k->k_lock);       list_add(&n->n_node, &pos->n_node);       spin_unlock(&k->k_lock);   }      void klist_add_before(struct klist_node *n, struct klist_node *pos)   {       struct klist *k = knode_klist(pos);          klist_node_init(k, n);       spin_lock(&k->k_lock);       list_add_tail(&n->n_node, &pos->n_node);       spin_unlock(&k->k_lock);   }  klist_add_after()将节点加到指定节点后面。klist_add_before()将节点加到指定节点前面。这两个函数都是对外提供的API。在list_head中都没有看到有这种API,所以说需求决定了接口。虽说只有一步之遥,klist也不愿让外界介入它的内部实现。之前出现的API都太常见了,既没有使用引用计数,又没有跳过请求删除的节点。所以klist的亮点在下面,klist链表的遍历。struct klist_iter {       struct klist    *i_klist;       struct klist_node   *i_cur;   };         extern void klist_iter_init(struct klist *k, struct klist_iter *i);   extern void klist_iter_init_node(struct klist *k, struct klist_iter *i,                    struct klist_node *n);   extern void klist_iter_exit(struct klist_iter *i);   extern struct klist_node *klist_next(struct klist_iter *i);  以上就是链表遍历需要的辅助结构struct klist_iter,和遍历用到的四个函数。struct klist_waiter {       struct list_head list;       struct klist_node *node;       struct task_struct *process;       int woken;   };      static DEFINE_SPINLOCK(klist_remove_lock);   static LIST_HEAD(klist_remove_waiters);      static void klist_release(struct kref *kref)   {       struct klist_waiter *waiter, *tmp;       struct klist_node *n = container_of(kref, struct klist_node, n_ref);          WARN_ON(!knode_dead(n));       list_del(&n->n_node);       spin_lock(&klist_remove_lock);       list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) {           if (waiter->node != n)               continue;              waiter->woken = 1;           mb();           wake_up_process(waiter->process);           list_del(&waiter->list);       }       spin_unlock(&klist_remove_lock);       knode_set_klist(n, NULL);   }      static int klist_dec_and_del(struct klist_node *n)   {       return kref_put(&n->n_ref, klist_release);   }      static void klist_put(struct klist_node *n, bool kill)   {       struct klist *k = knode_klist(n);       void (*put)(struct klist_node *) = k->put;          spin_lock(&k->k_lock);       if (kill)           knode_kill(n);       if (!klist_dec_and_del(n))           put = NULL;       spin_unlock(&k->k_lock);       if (put)           put(n);   }      /**   * klist_del - Decrement the reference count of node and try to remove.   * @n: node we're deleting.   */   void klist_del(struct klist_node *n)   {       klist_put(n, true);   }  以上的内容乍一看很难理解,其实都是klist实现必须的。因为使用kref动态删除,自然需要一个计数降为零时调用的函数klist_release。klist_dec_and_del()就是对kref_put()的包装,起到减少节点引用计数的功能。至于为什么会出现一个新的结构struct klist_waiter,也很简单。之前说有线程申请删除某节点,但节点的引用计数仍在,所以只能把请求删除的线程阻塞,就是用struct klist_waiter阻塞在klist_remove_waiters上。所以在klist_release()调用时还要将阻塞的线程唤醒。knode_kill()将节点设为已请求删除。而且还会调用put()函数。释放引用计数是调用klist_del(),它通过内部函数klist_put()完成所需操作:用knode_kill()设置节点为已请求删除,用klist_dec_and_del()释放引用,调用可能的put()函数。/**   * klist_remove - Decrement the refcount of node and wait for it to go away.   * @n: node we're removing.   */   void klist_remove(struct klist_node *n)   {       struct klist_waiter waiter;          waiter.node = n;       waiter.process = current;       waiter.woken = 0;       spin_lock(&klist_remove_lock);       list_add(&waiter.list, &klist_remove_waiters);       spin_unlock(&klist_remove_lock);          klist_del(n);          for (;;) {           set_current_state(TASK_UNINTERRUPTIBLE);           if (waiter.woken)               break;           schedule();       }       __set_current_state(TASK_RUNNING);   }  klist_remove()不但会调用klist_del()减少引用计数,还会一直阻塞到节点被删除。这个函数才是请求删除节点的线程应该调用的。int klist_node_attached(struct klist_node *n)   {       return (n->n_klist != NULL);   }  klist_node_attached()检查节点是否被包含在某链表中。以上是klist的链表初始化,节点加入,节点删除函数。下面是klist链表遍历函数。struct klist_iter {       struct klist        *i_klist;       struct klist_node   *i_cur;   };         extern void klist_iter_init(struct klist *k, struct klist_iter *i);   extern void klist_iter_init_node(struct klist *k, struct klist_iter *i,                    struct klist_node *n);   extern void klist_iter_exit(struct klist_iter *i);   extern struct klist_node *klist_next(struct klist_iter *i);   klist的遍历有些复杂,因为它考虑到了在遍历过程中节点删除的情况,而且还要忽略那些已被删除的节点。宏实现已经无法满足要求,迫不得已,只能用函数实现,并用struct klist_iter记录中间状态。void klist_iter_init_node(struct klist *k, struct klist_iter *i,                 struct klist_node *n)   {       i->i_klist = k;       i->i_cur = n;       if (n)           kref_get(&n->n_ref);   }      void klist_iter_init(struct klist *k, struct klist_iter *i)   {       klist_iter_init_node(k, i, NULL);   }  klist_iter_init_node()是从klist中的某个节点开始遍历,而klist_iter_init()是从链表头开始遍历的。但你又要注意,klist_iter_init()和klist_iter_init_node()的用法又不同。klist_iter_init_node()可以在其后直接对当前节点进行访问,也可以调用klist_next()访问下一节点。而klist_iter_init()只能调用klist_next()访问下一节点。或许klist_iter_init_node()的本意不是从当前节点开始,而是从当前节点的下一节点开始。static struct klist_node *to_klist_node(struct list_head *n)   {       return container_of(n, struct klist_node, n_node);   }   struct klist_node *klist_next(struct klist_iter *i)   {       void (*put)(struct klist_node *) = i->i_klist->put;       struct klist_node *last = i->i_cur;       struct klist_node *next;          spin_lock(&i->i_klist->k_lock);          if (last) {           next = to_klist_node(last->n_node.next);           if (!klist_dec_and_del(last))               put = NULL;       } else           next = to_klist_node(i->i_klist->k_list.next);          i->i_cur = NULL;       while (next != to_klist_node(&i->i_klist->k_list)) {           if (likely(!knode_dead(next))) {               kref_get(&next->n_ref);               i->i_cur = next;               break;           }           next = to_klist_node(next->n_node.next);       }          spin_unlock(&i->i_klist->k_lock);          if (put && last)           put(last);       return i->i_cur;   }  klist_next()是将循环进行到下一节点。实现中需要注意两点问题:1、加锁,根据经验,单纯对某个节点操作不需要加锁,但对影响整个链表的操作需要加自旋锁。比如之前klist_iter_init_node()中对节点增加引用计数,就不需要加锁,因为只有已经拥有节点引用计数的线程才会特别地从那个节点开始。而之后klist_next()中则需要加锁,因为当前线程很可能没有引用计数,所以需要加锁,让情况固定下来。这既是保护链表,也是保护节点有效。符合kref引用计数的使用原则。2、要注意,虽然在节点切换的过程中是加锁的,但切换完访问当前节点时是解锁的,中间可能有节点被删除(这个通过spin_lock就可以搞定),也可能有节点被请求删除,这就需要注意。首先要忽略链表中已被请求删除的节点,然后在减少前一个节点引用计数时,可能就把前一个节点删除了。这里之所以不调用klist_put(),是因为本身已处于加锁状态,但仍要有它的实现。这里的实现和klist_put()中类似,代码不介意在加锁状态下唤醒另一个线程,但却不希望在加锁状态下调用put()函数,那可能会涉及释放另一个更大的结构。void klist_iter_exit(struct klist_iter *i)   {       if (i->i_cur) {           klist_put(i->i_cur, false);           i->i_cur = NULL;       }   }  klist_iter_exit(),遍历结束函数。在遍历完成时调不调无所谓,但如果想中途结束,就一定要调用klist_iter_exit()。klist主要用于设备驱动模型中,为了适应那些动态变化的设备和驱动,而专门设计的链表。klist并不通用,但它真的很新奇。 我看到它时,震惊于链表竟然可以专门异化成这种样子。如果你是松耦合的结构,如果你手下净是些桀骜不驯的家伙,那么不要只考虑kref,你可能还需要klist。本篇文章来源于 Linux公社网站(www.linuxidc.com)  原文链接:http://www.linuxidc.com/Linux/2011-10/44627p4.htm
前面我们说到过list_head,这是linux中通用的链表形式,双向循环链表,功能强大,实现简单优雅。可如果您认为list_head就是链表的极致,应该在linux链表界一统天下,那可就错了。据我所知,linux内核代码中至少还有两种链表能占有一席之地。一种就是hlist,一种就是本节要介绍的klist。虽然三者不同,但hlist和klist都可以看成是从list_head中发展出来的,用于特殊的链表使用情景。hlist是用于哈希表中。众所周知,哈希表主要就是一个哈希数组,为了解决映射冲突的问题,常常把哈希数组的每一项做成一个链表,这样有多少重复的都可以链进去。但哈希数组的项很多,list_head的话每个链表头都需要两个指针的空间,在稀疏的哈希表中实在是一种浪费,于是就发明了hlist。hlist有两大特点,一是它的链表头只需要一个指针,二是它的每一项都可以找到自己的前一节点,也就是说它不再循环,但仍是双向。令人不解的是,hlist的实现太绕了,比如它明明可以直接指向前一节点,却偏偏指向指针地址,还是前一节点中指向后一节点的指针地址。即使这种设计在实现时占便宜,但它理解上带来的不便已经远远超过实现上带来的小小便利。
同hlist一样,klist也是为了适应某类特殊情形的要求。考虑一个被简化的情形,假设一些设备被链接在设备链表中,一个线程命令卸载某设备,即将其从设备链表中删除,但这时该设备正在使用中,这时就出现了冲突。当前可以设置临界区并加锁,但因为使用一个设备而锁住整个设备链表显然是不对的;又或者可以从设备本身做文章,让线程阻塞,这当然也可以。但我们上节了解了kref,就该知道linux对待这种情况的风格,给它一个引用计数kref,等计数为零就删除。klist就是这么干的,它把kref直接保存在了链表节点上。之前说到有线程要求删除设备,之前的使用仍存在,所以不能实际删除,但不应该有新的应用访问到该设备。klist就提供了一种让节点在链表上隐身的方法。下面还是来看实际代码吧。
klist的头文件是include/linux/klist.h,实现在lib/klist.c。
struct klist_node;  
struct klist {  
    spinlock_t      k_lock;  
    struct list_head    k_list;  
    void            (*get)(struct klist_node *);  
    void            (*put)(struct klist_node *);  
} __attribute__ ((aligned (4)));  
  
#define KLIST_INIT(_name, _get, _put)                   \   
    { .k_lock   = __SPIN_LOCK_UNLOCKED(_name.k_lock),       \  
      .k_list   = LIST_HEAD_INIT(_name.k_list),         \  
      .get      = _get,                     \  
      .put      = _put, }  
  
#define DEFINE_KLIST(_name, _get, _put)                 \   
    struct klist _name = KLIST_INIT(_name, _get, _put)  
  
extern void klist_init(struct klist *k, void (*get)(struct klist_node *),  
               void (*put)(struct klist_node *));  
  
struct klist_node {  
    void            *n_klist;   /* never access directly */  
    struct list_head    n_node;  
    struct kref     n_ref;  
}; 
可以看到,klist的链表头是struct klist结构,链表节点是struct klist_node结构。先看struct klist,除了包含链表需要的k_list,还有用于加锁的k_lock。剩余的get()和put()函数是用于struct klist_node嵌入在更大的结构中,这样在节点初始时调用get(),在节点删除时调用put(),以表示链表中存在对结构的引用。再看struct klist_node,除了链表需要的n_node,还有一个引用计数n_ref。还有一个比较特殊的指针n_klist,n_klist是指向链表头struct klist的,但它的第0位用来表示是否该节点已被请求删除,如果已被请求删除则在链表循环时是看不到这一节点的,循环函数将其略过。现在你明白为什么非要在struct klist的定义后加上__attribute__((aligned(4)))。不过说实话这样在x86下仍然不太保险,但linux选择了相信gcc,毕竟是多年的战友和兄弟了,相互知根知底。
看过这两个结构,想必大家已经较为清楚了,下面就来看看它们的实现。
/* 
 * Use the lowest bit of n_klist to mark deleted nodes and exclude 
 * dead ones from iteration. 
 */  
#define KNODE_DEAD      1LU   
#define KNODE_KLIST_MASK    ~KNODE_DEAD   
  
static struct klist *knode_klist(struct klist_node *knode)  
{  
    return (struct klist *)  
        ((unsigned long)knode->n_klist & KNODE_KLIST_MASK);  
}  
  
static bool knode_dead(struct klist_node *knode)  
{  
    return (unsigned long)knode->n_klist & KNODE_DEAD;  
}  
  
static void knode_set_klist(struct klist_node *knode, struct klist *klist)  
{  
    knode->n_klist = klist;  
    /* no knode deserves to start its life dead */  
    WARN_ON(knode_dead(knode));  
}  
  
static void knode_kill(struct klist_node *knode)  
{  
    /* and no knode should die twice ever either, see we're very humane */  
    WARN_ON(knode_dead(knode));  
    *(unsigned long *)&knode->n_klist |= KNODE_DEAD;  

前面的四个函数都是内部静态函数,帮助API实现的。knode_klist()是从节点找到链表头。knode_dead()是检查该节点是否已被请求删除。
knode_set_klist设置节点的链表头。knode_kill将该节点请求删除。细心的话大家会发现这四个函数是对称的,而且都是操作节点的内部函数。
void klist_init(struct klist *k, void (*get)(struct klist_node *),  
        void (*put)(struct klist_node *))  
{  
    INIT_LIST_HEAD(&k->k_list);  
    spin_lock_init(&k->k_lock);  
    k->get = get;  
    k->put = put;  

klist_init,初始化klist。
static void add_head(struct klist *k, struct klist_node *n)  
{  
    spin_lock(&k->k_lock);  
    list_add(&n->n_node, &k->k_list);  
    spin_unlock(&k->k_lock);  
}  
  
static void add_tail(struct klist *k, struct klist_node *n)  
{  
    spin_lock(&k->k_lock);  
    list_add_tail(&n->n_node, &k->k_list);  
    spin_unlock(&k->k_lock);  
}  
  
static void klist_node_init(struct klist *k, struct klist_node *n)  
{  
    INIT_LIST_HEAD(&n->n_node);  
    kref_init(&n->n_ref);  
    knode_set_klist(n, k);  
    if (k->get)  
        k->get(n);  

又是三个内部函数,add_head()将节点加入链表头,add_tail()将节点加入链表尾,klist_node_init()是初始化节点。注意在节点的引用计数初始化时,因为引用计数变为1,所以也要调用相应的get()函数。
void klist_add_head(struct klist_node *n, struct klist *k)  
{  
    klist_node_init(k, n);  
    add_head(k, n);  
}  
  
void klist_add_tail(struct klist_node *n, struct klist *k)  
{  
    klist_node_init(k, n);  
    add_tail(k, n);  

klist_add_head()将节点初始化,并加入链表头。
klist_add_tail()将节点初始化,并加入链表尾。
它们正是用上面的三个内部函数实现的,可见linux内核中对函数复用有很强的执念,其实这里add_tail和add_head是不用的,纵观整个文件,也只有klist_add_head()和klist_add_tail()对它们进行了调用。
void klist_add_after(struct klist_node *n, struct klist_node *pos)  
{  
    struct klist *k = knode_klist(pos);  
  
    klist_node_init(k, n);  
    spin_lock(&k->k_lock);  
    list_add(&n->n_node, &pos->n_node);  
    spin_unlock(&k->k_lock);  
}  
  
void klist_add_before(struct klist_node *n, struct klist_node *pos)  
{  
    struct klist *k = knode_klist(pos);  
  
    klist_node_init(k, n);  
    spin_lock(&k->k_lock);  
    list_add_tail(&n->n_node, &pos->n_node);  
    spin_unlock(&k->k_lock);  

klist_add_after()将节点加到指定节点后面。
klist_add_before()将节点加到指定节点前面。
这两个函数都是对外提供的API。在list_head中都没有看到有这种API,所以说需求决定了接口。虽说只有一步之遥,klist也不愿让外界介入它的内部实现。
之前出现的API都太常见了,既没有使用引用计数,又没有跳过请求删除的节点。所以klist的亮点在下面,klist链表的遍历。
struct klist_iter {  
    struct klist    *i_klist;  
    struct klist_node   *i_cur;  
};  
  
  
extern void klist_iter_init(struct klist *k, struct klist_iter *i);  
extern void klist_iter_init_node(struct klist *k, struct klist_iter *i,  
                 struct klist_node *n);  
extern void klist_iter_exit(struct klist_iter *i);  
extern struct klist_node *klist_next(struct klist_iter *i); 
以上就是链表遍历需要的辅助结构struct klist_iter,和遍历用到的四个函数。
struct klist_waiter {  
    struct list_head list;  
    struct klist_node *node;  
    struct task_struct *process;  
    int woken;  
};  
  
static DEFINE_SPINLOCK(klist_remove_lock);  
static LIST_HEAD(klist_remove_waiters);  
  
static void klist_release(struct kref *kref)  
{  
    struct klist_waiter *waiter, *tmp;  
    struct klist_node *n = container_of(kref, struct klist_node, n_ref);  
  
    WARN_ON(!knode_dead(n));  
    list_del(&n->n_node);  
    spin_lock(&klist_remove_lock);  
    list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) {  
        if (waiter->node != n)  
            continue;  
  
        waiter->woken = 1;  
        mb();  
        wake_up_process(waiter->process);  
        list_del(&waiter->list);  
    }  
    spin_unlock(&klist_remove_lock);  
    knode_set_klist(n, NULL);  
}  
  
static int klist_dec_and_del(struct klist_node *n)  
{  
    return kref_put(&n->n_ref, klist_release);  
}  
  
static void klist_put(struct klist_node *n, bool kill)  
{  
    struct klist *k = knode_klist(n);  
    void (*put)(struct klist_node *) = k->put;  
  
    spin_lock(&k->k_lock);  
    if (kill)  
        knode_kill(n);  
    if (!klist_dec_and_del(n))  
        put = NULL;  
    spin_unlock(&k->k_lock);  
    if (put)  
        put(n);  
}  
  
/** 
 * klist_del - Decrement the reference count of node and try to remove. 
 * @n: node we're deleting. 
 */  
void klist_del(struct klist_node *n)  
{  
    klist_put(n, true);  

以上的内容乍一看很难理解,其实都是klist实现必须的。因为使用kref动态删除,自然需要一个计数降为零时调用的函数klist_release。
klist_dec_and_del()就是对kref_put()的包装,起到减少节点引用计数的功能。
至于为什么会出现一个新的结构struct klist_waiter,也很简单。之前说有线程申请删除某节点,但节点的引用计数仍在,所以只能把请求删除的线程阻塞,就是用struct klist_waiter阻塞在klist_remove_waiters上。所以在klist_release()调用时还要将阻塞的线程唤醒。knode_kill()将节点设为已请求删除。而且还会调用put()函数。
释放引用计数是调用klist_del(),它通过内部函数klist_put()完成所需操作:用knode_kill()设置节点为已请求删除,用klist_dec_and_del()释放引用,调用可能的put()函数。
/** 
 * klist_remove - Decrement the refcount of node and wait for it to go away. 
 * @n: node we're removing. 
 */  
void klist_remove(struct klist_node *n)  
{  
    struct klist_waiter waiter;  
  
    waiter.node = n;  
    waiter.process = current;  
    waiter.woken = 0;  
    spin_lock(&klist_remove_lock);  
    list_add(&waiter.list, &klist_remove_waiters);  
    spin_unlock(&klist_remove_lock);  
  
    klist_del(n);  
  
    for (;;) {  
        set_current_state(TASK_UNINTERRUPTIBLE);  
        if (waiter.woken)  
            break;  
        schedule();  
    }  
    __set_current_state(TASK_RUNNING);  

klist_remove()不但会调用klist_del()减少引用计数,还会一直阻塞到节点被删除。这个函数才是请求删除节点的线程应该调用的。
int klist_node_attached(struct klist_node *n)  
{  
    return (n->n_klist != NULL);  

klist_node_attached()检查节点是否被包含在某链表中。
以上是klist的链表初始化,节点加入,节点删除函数。下面是klist链表遍历函数。
struct klist_iter {  
    struct klist        *i_klist;  
    struct klist_node   *i_cur;  
};  
  
  
extern void klist_iter_init(struct klist *k, struct klist_iter *i);  
extern void klist_iter_init_node(struct klist *k, struct klist_iter *i,  
                 struct klist_node *n);  
extern void klist_iter_exit(struct klist_iter *i);  
extern struct klist_node *klist_next(struct klist_iter *i);  
klist的遍历有些复杂,因为它考虑到了在遍历过程中节点删除的情况,而且还要忽略那些已被删除的节点。宏实现已经无法满足要求,迫不得已,只能用函数实现,并用struct klist_iter记录中间状态。
void klist_iter_init_node(struct klist *k, struct klist_iter *i,  
              struct klist_node *n)  
{  
    i->i_klist = k;  
    i->i_cur = n;  
    if (n)  
        kref_get(&n->n_ref);  
}  
  
void klist_iter_init(struct klist *k, struct klist_iter *i)  
{  
    klist_iter_init_node(k, i, NULL);  

klist_iter_init_node()是从klist中的某个节点开始遍历,而klist_iter_init()是从链表头开始遍历的。
但你又要注意,klist_iter_init()和klist_iter_init_node()的用法又不同。klist_iter_init_node()可以在其后直接对当前节点进行访问,也可以调用klist_next()访问下一节点。而klist_iter_init()只能调用klist_next()访问下一节点。或许klist_iter_init_node()的本意不是从当前节点开始,而是从当前节点的下一节点开始。
static struct klist_node *to_klist_node(struct list_head *n)  
{  
    return container_of(n, struct klist_node, n_node);  

 
struct klist_node *klist_next(struct klist_iter *i)  
{  
    void (*put)(struct klist_node *) = i->i_klist->put;  
    struct klist_node *last = i->i_cur;  
    struct klist_node *next;  
  
    spin_lock(&i->i_klist->k_lock);  
  
    if (last) {  
        next = to_klist_node(last->n_node.next);  
        if (!klist_dec_and_del(last))  
            put = NULL;  
    } else  
        next = to_klist_node(i->i_klist->k_list.next);  
  
    i->i_cur = NULL;  
    while (next != to_klist_node(&i->i_klist->k_list)) {  
        if (likely(!knode_dead(next))) {  
            kref_get(&next->n_ref);  
            i->i_cur = next;  
            break;  
        }  
        next = to_klist_node(next->n_node.next);  
    }  
  
    spin_unlock(&i->i_klist->k_lock);  
  
    if (put && last)  
        put(last);  
    return i->i_cur;  

klist_next()是将循环进行到下一节点。实现中需要注意两点问题:1、加锁,根据经验,单纯对某个节点操作不需要加锁,但对影响整个链表的操作需要加自旋锁。比如之前klist_iter_init_node()中对节点增加引用计数,就不需要加锁,因为只有已经拥有节点引用计数的线程才会特别地从那个节点开始。而之后klist_next()中则需要加锁,因为当前线程很可能没有引用计数,所以需要加锁,让情况固定下来。这既是保护链表,也是保护节点有效。符合kref引用计数的使用原则。2、要注意,虽然在节点切换的过程中是加锁的,但切换完访问当前节点时是解锁的,中间可能有节点被删除(这个通过spin_lock就可以搞定),也可能有节点被请求删除,这就需要注意。首先要忽略链表中已被请求删除的节点,然后在减少前一个节点引用计数时,可能就把前一个节点删除了。这里之所以不调用klist_put(),是因为本身已处于加锁状态,但仍要有它的实现。这里的实现和klist_put()中类似,代码不介意在加锁状态下唤醒另一个线程,但却不希望在加锁状态下调用put()函数,那可能会涉及释放另一个更大的结构。
void klist_iter_exit(struct klist_iter *i)  
{  
    if (i->i_cur) {  
        klist_put(i->i_cur, false);  
        i->i_cur = NULL;  
    }  

klist_iter_exit(),遍历结束函数。在遍历完成时调不调无所谓,但如果想中途结束,就一定要调用klist_iter_exit()。
klist主要用于设备驱动模型中,为了适应那些动态变化的设备和驱动,而专门设计的链表。klist并不通用,但它真的很新奇。 我看到它时,震惊于链表竟然可以专门异化成这种样子。如果你是松耦合的结构,如果你手下净是些桀骜不驯的家伙,那么不要只考虑kref,你可能还需要klist。

本篇文章来源于 Linux公社网站(www.linuxidc.com)  原文链接:http://www.linuxidc.com/Linux/2011-10/44627p4.htm

前面我们说到过list_head,这是linux中通用的链表形式,双向循环链表,功能强大,实现简单优雅。可如果您认为list_head就是链表的极致,应该在linux链表界一统天下,那可就错了。据我所知,linux内核代码中至少还有两种链表能占有一席之地。一种就是hlist,一种就是本节要介绍的klist。虽然三者不同,但hlist和klist都可以看成是从list_head中发展出来的,用于特殊的链表使用情景。hlist是用于哈希表中。众所周知,哈希表主要就是一个哈希数组,为了解决映射冲突的问题,常常把哈希数组的每一项做成一个链表,这样有多少重复的都可以链进去。但哈希数组的项很多,list_head的话每个链表头都需要两个指针的空间,在稀疏的哈希表中实在是一种浪费,于是就发明了hlist。hlist有两大特点,一是它的链表头只需要一个指针,二是它的每一项都可以找到自己的前一节点,也就是说它不再循环,但仍是双向。令人不解的是,hlist的实现太绕了,比如它明明可以直接指向前一节点,却偏偏指向指针地址,还是前一节点中指向后一节点的指针地址。即使这种设计在实现时占便宜,但它理解上带来的不便已经远远超过实现上带来的小小便利。
同hlist一样,klist也是为了适应某类特殊情形的要求。考虑一个被简化的情形,假设一些设备被链接在设备链表中,一个线程命令卸载某设备,即将其从设备链表中删除,但这时该设备正在使用中,这时就出现了冲突。当前可以设置临界区并加锁,但因为使用一个设备而锁住整个设备链表显然是不对的;又或者可以从设备本身做文章,让线程阻塞,这当然也可以。但我们上节了解了kref,就该知道linux对待这种情况的风格,给它一个引用计数kref,等计数为零就删除。klist就是这么干的,它把kref直接保存在了链表节点上。之前说到有线程要求删除设备,之前的使用仍存在,所以不能实际删除,但不应该有新的应用访问到该设备。klist就提供了一种让节点在链表上隐身的方法。下面还是来看实际代码吧。

klist的头文件是include/linux/klist.h,实现在lib/klist.c。

struct klist_node;  
struct klist {  
    spinlock_t      k_lock;  
    struct list_head    k_list;  
    void            (*get)(struct klist_node *);  
    void            (*put)(struct klist_node *);  
} __attribute__ ((aligned (4)));  
  
#define KLIST_INIT(_name, _get, _put)                   \   
    { .k_lock   = __SPIN_LOCK_UNLOCKED(_name.k_lock),       \  
      .k_list   = LIST_HEAD_INIT(_name.k_list),         \  
      .get      = _get,                     \  
      .put      = _put, }  
  
#define DEFINE_KLIST(_name, _get, _put)                 \   
    struct klist _name = KLIST_INIT(_name, _get, _put)  
  
extern void klist_init(struct klist *k, void (*get)(struct klist_node *),  
               void (*put)(struct klist_node *));  
  
struct klist_node {  
    void            *n_klist;   /* never access directly */  
    struct list_head    n_node;  
    struct kref     n_ref;  
}; 
可以看到,klist的链表头是struct klist结构,链表节点是struct klist_node结构。先看struct klist,除了包含链表需要的k_list,还有用于加锁的k_lock。剩余的get()和put()函数是用于struct klist_node嵌入在更大的结构中,这样在节点初始时调用get(),在节点删除时调用put(),以表示链表中存在对结构的引用。再看struct klist_node,除了链表需要的n_node,还有一个引用计数n_ref。还有一个比较特殊的指针n_klist,n_klist是指向链表头struct klist的,但它的第0位用来表示是否该节点已被请求删除,如果已被请求删除则在链表循环时是看不到这一节点的,循环函数将其略过。现在你明白为什么非要在struct klist的定义后加上__attribute__((aligned(4)))。不过说实话这样在x86下仍然不太保险,但linux选择了相信gcc,毕竟是多年的战友和兄弟了,相互知根知底。

看过这两个结构,想必大家已经较为清楚了,下面就来看看它们的实现。

/* 
 * Use the lowest bit of n_klist to mark deleted nodes and exclude 
 * dead ones from iteration. 
 */  
#define KNODE_DEAD      1LU   
#define KNODE_KLIST_MASK    ~KNODE_DEAD   
  
static struct klist *knode_klist(struct klist_node *knode)  
{  
    return (struct klist *)  
        ((unsigned long)knode->n_klist & KNODE_KLIST_MASK);  
}  
  
static bool knode_dead(struct klist_node *knode)  
{  
    return (unsigned long)knode->n_klist & KNODE_DEAD;  
}  
  
static void knode_set_klist(struct klist_node *knode, struct klist *klist)  
{  
    knode->n_klist = klist;  
    /* no knode deserves to start its life dead */  
    WARN_ON(knode_dead(knode));  
}  
  
static void knode_kill(struct klist_node *knode)  
{  
    /* and no knode should die twice ever either, see we're very humane */  
    WARN_ON(knode_dead(knode));  
    *(unsigned long *)&knode->n_klist |= KNODE_DEAD;  

前面的四个函数都是内部静态函数,帮助API实现的。knode_klist()是从节点找到链表头。knode_dead()是检查该节点是否已被请求删除。

knode_set_klist设置节点的链表头。knode_kill将该节点请求删除。细心的话大家会发现这四个函数是对称的,而且都是操作节点的内部函数。

void klist_init(struct klist *k, void (*get)(struct klist_node *),  
        void (*put)(struct klist_node *))  
{  
    INIT_LIST_HEAD(&k->k_list);  
    spin_lock_init(&k->k_lock);  
    k->get = get;  
    k->put = put;  

klist_init,初始化klist。

static void add_head(struct klist *k, struct klist_node *n)  
{  
    spin_lock(&k->k_lock);  
    list_add(&n->n_node, &k->k_list);  
    spin_unlock(&k->k_lock);  
}  
  
static void add_tail(struct klist *k, struct klist_node *n)  
{  
    spin_lock(&k->k_lock);  
    list_add_tail(&n->n_node, &k->k_list);  
    spin_unlock(&k->k_lock);  
}  
  
static void klist_node_init(struct klist *k, struct klist_node *n)  
{  
    INIT_LIST_HEAD(&n->n_node);  
    kref_init(&n->n_ref);  
    knode_set_klist(n, k);  
    if (k->get)  
        k->get(n);  

又是三个内部函数,add_head()将节点加入链表头,add_tail()将节点加入链表尾,klist_node_init()是初始化节点。注意在节点的引用计数初始化时,因为引用计数变为1,所以也要调用相应的get()函数。

void klist_add_head(struct klist_node *n, struct klist *k)  
{  
    klist_node_init(k, n);  
    add_head(k, n);  
}  
  
void klist_add_tail(struct klist_node *n, struct klist *k)  
{  
    klist_node_init(k, n);  
    add_tail(k, n);  

klist_add_head()将节点初始化,并加入链表头。

klist_add_tail()将节点初始化,并加入链表尾。

它们正是用上面的三个内部函数实现的,可见linux内核中对函数复用有很强的执念,其实这里add_tail和add_head是不用的,纵观整个文件,也只有klist_add_head()和klist_add_tail()对它们进行了调用。

void klist_add_after(struct klist_node *n, struct klist_node *pos)  
{  
    struct klist *k = knode_klist(pos);  
  
    klist_node_init(k, n);  
    spin_lock(&k->k_lock);  
    list_add(&n->n_node, &pos->n_node);  
    spin_unlock(&k->k_lock);  
}  
  
void klist_add_before(struct klist_node *n, struct klist_node *pos)  
{  
    struct klist *k = knode_klist(pos);  
  
    klist_node_init(k, n);  
    spin_lock(&k->k_lock);  
    list_add_tail(&n->n_node, &pos->n_node);  
    spin_unlock(&k->k_lock);  

klist_add_after()将节点加到指定节点后面。

klist_add_before()将节点加到指定节点前面。

这两个函数都是对外提供的API。在list_head中都没有看到有这种API,所以说需求决定了接口。虽说只有一步之遥,klist也不愿让外界介入它的内部实现。

之前出现的API都太常见了,既没有使用引用计数,又没有跳过请求删除的节点。所以klist的亮点在下面,klist链表的遍历。

struct klist_iter {  
    struct klist    *i_klist;  
    struct klist_node   *i_cur;  
};  
  
  
extern void klist_iter_init(struct klist *k, struct klist_iter *i);  
extern void klist_iter_init_node(struct klist *k, struct klist_iter *i,  
                 struct klist_node *n);  
extern void klist_iter_exit(struct klist_iter *i);  
extern struct klist_node *klist_next(struct klist_iter *i); 
以上就是链表遍历需要的辅助结构struct klist_iter,和遍历用到的四个函数。

struct klist_waiter {  
    struct list_head list;  
    struct klist_node *node;  
    struct task_struct *process;  
    int woken;  
};  
  
static DEFINE_SPINLOCK(klist_remove_lock);  
static LIST_HEAD(klist_remove_waiters);  
  
static void klist_release(struct kref *kref)  
{  
    struct klist_waiter *waiter, *tmp;  
    struct klist_node *n = container_of(kref, struct klist_node, n_ref);  
  
    WARN_ON(!knode_dead(n));  
    list_del(&n->n_node);  
    spin_lock(&klist_remove_lock);  
    list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) {  
        if (waiter->node != n)  
            continue;  
  
        waiter->woken = 1;  
        mb();  
        wake_up_process(waiter->process);  
        list_del(&waiter->list);  
    }  
    spin_unlock(&klist_remove_lock);  
    knode_set_klist(n, NULL);  
}  
  
static int klist_dec_and_del(struct klist_node *n)  
{  
    return kref_put(&n->n_ref, klist_release);  
}  
  
static void klist_put(struct klist_node *n, bool kill)  
{  
    struct klist *k = knode_klist(n);  
    void (*put)(struct klist_node *) = k->put;  
  
    spin_lock(&k->k_lock);  
    if (kill)  
        knode_kill(n);  
    if (!klist_dec_and_del(n))  
        put = NULL;  
    spin_unlock(&k->k_lock);  
    if (put)  
        put(n);  
}  
  
/** 
 * klist_del - Decrement the reference count of node and try to remove. 
 * @n: node we're deleting. 
 */  
void klist_del(struct klist_node *n)  
{  
    klist_put(n, true);  

以上的内容乍一看很难理解,其实都是klist实现必须的。因为使用kref动态删除,自然需要一个计数降为零时调用的函数klist_release。

klist_dec_and_del()就是对kref_put()的包装,起到减少节点引用计数的功能。

至于为什么会出现一个新的结构struct klist_waiter,也很简单。之前说有线程申请删除某节点,但节点的引用计数仍在,所以只能把请求删除的线程阻塞,就是用struct klist_waiter阻塞在klist_remove_waiters上。所以在klist_release()调用时还要将阻塞的线程唤醒。knode_kill()将节点设为已请求删除。而且还会调用put()函数。

释放引用计数是调用klist_del(),它通过内部函数klist_put()完成所需操作:用knode_kill()设置节点为已请求删除,用klist_dec_and_del()释放引用,调用可能的put()函数。

/** 
 * klist_remove - Decrement the refcount of node and wait for it to go away. 
 * @n: node we're removing. 
 */  
void klist_remove(struct klist_node *n)  
{  
    struct klist_waiter waiter;  
  
    waiter.node = n;  
    waiter.process = current;  
    waiter.woken = 0;  
    spin_lock(&klist_remove_lock);  
    list_add(&waiter.list, &klist_remove_waiters);  
    spin_unlock(&klist_remove_lock);  
  
    klist_del(n);  
  
    for (;;) {  
        set_current_state(TASK_UNINTERRUPTIBLE);  
        if (waiter.woken)  
            break;  
        schedule();  
    }  
    __set_current_state(TASK_RUNNING);  

klist_remove()不但会调用klist_del()减少引用计数,还会一直阻塞到节点被删除。这个函数才是请求删除节点的线程应该调用的。

int klist_node_attached(struct klist_node *n)  
{  
    return (n->n_klist != NULL);  

klist_node_attached()检查节点是否被包含在某链表中。

以上是klist的链表初始化,节点加入,节点删除函数。下面是klist链表遍历函数。

struct klist_iter {  
    struct klist        *i_klist;  
    struct klist_node   *i_cur;  
};  
  
  
extern void klist_iter_init(struct klist *k, struct klist_iter *i);  
extern void klist_iter_init_node(struct klist *k, struct klist_iter *i,  
                 struct klist_node *n);  
extern void klist_iter_exit(struct klist_iter *i);  
extern struct klist_node *klist_next(struct klist_iter *i);  
klist的遍历有些复杂,因为它考虑到了在遍历过程中节点删除的情况,而且还要忽略那些已被删除的节点。宏实现已经无法满足要求,迫不得已,只能用函数实现,并用struct klist_iter记录中间状态。

void klist_iter_init_node(struct klist *k, struct klist_iter *i,  
              struct klist_node *n)  
{  
    i->i_klist = k;  
    i->i_cur = n;  
    if (n)  
        kref_get(&n->n_ref);  
}  
  
void klist_iter_init(struct klist *k, struct klist_iter *i)  
{  
    klist_iter_init_node(k, i, NULL);  

klist_iter_init_node()是从klist中的某个节点开始遍历,而klist_iter_init()是从链表头开始遍历的。

但你又要注意,klist_iter_init()和klist_iter_init_node()的用法又不同。klist_iter_init_node()可以在其后直接对当前节点进行访问,也可以调用klist_next()访问下一节点。而klist_iter_init()只能调用klist_next()访问下一节点。或许klist_iter_init_node()的本意不是从当前节点开始,而是从当前节点的下一节点开始。

static struct klist_node *to_klist_node(struct list_head *n)  
{  
    return container_of(n, struct klist_node, n_node);  

 
struct klist_node *klist_next(struct klist_iter *i)  
{  
    void (*put)(struct klist_node *) = i->i_klist->put;  
    struct klist_node *last = i->i_cur;  
    struct klist_node *next;  
  
    spin_lock(&i->i_klist->k_lock);  
  
    if (last) {  
        next = to_klist_node(last->n_node.next);  
        if (!klist_dec_and_del(last))  
            put = NULL;  
    } else  
        next = to_klist_node(i->i_klist->k_list.next);  
  
    i->i_cur = NULL;  
    while (next != to_klist_node(&i->i_klist->k_list)) {  
        if (likely(!knode_dead(next))) {  
            kref_get(&next->n_ref);  
            i->i_cur = next;  
            break;  
        }  
        next = to_klist_node(next->n_node.next);  
    }  
  
    spin_unlock(&i->i_klist->k_lock);  
  
    if (put && last)  
        put(last);  
    return i->i_cur;  

klist_next()是将循环进行到下一节点。实现中需要注意两点问题:1、加锁,根据经验,单纯对某个节点操作不需要加锁,但对影响整个链表的操作需要加自旋锁。比如之前klist_iter_init_node()中对节点增加引用计数,就不需要加锁,因为只有已经拥有节点引用计数的线程才会特别地从那个节点开始。而之后klist_next()中则需要加锁,因为当前线程很可能没有引用计数,所以需要加锁,让情况固定下来。这既是保护链表,也是保护节点有效。符合kref引用计数的使用原则。2、要注意,虽然在节点切换的过程中是加锁的,但切换完访问当前节点时是解锁的,中间可能有节点被删除(这个通过spin_lock就可以搞定),也可能有节点被请求删除,这就需要注意。首先要忽略链表中已被请求删除的节点,然后在减少前一个节点引用计数时,可能就把前一个节点删除了。这里之所以不调用klist_put(),是因为本身已处于加锁状态,但仍要有它的实现。这里的实现和klist_put()中类似,代码不介意在加锁状态下唤醒另一个线程,但却不希望在加锁状态下调用put()函数,那可能会涉及释放另一个更大的结构。

void klist_iter_exit(struct klist_iter *i)  
{  
    if (i->i_cur) {  
        klist_put(i->i_cur, false);  
        i->i_cur = NULL;  
    }  

klist_iter_exit(),遍历结束函数。在遍历完成时调不调无所谓,但如果想中途结束,就一定要调用klist_iter_exit()。

klist主要用于设备驱动模型中,为了适应那些动态变化的设备和驱动,而专门设计的链表。klist并不通用,但它真的很新奇。 我看到它时,震惊于链表竟然可以专门异化成这种样子。如果你是松耦合的结构,如果你手下净是些桀骜不驯的家伙,那么不要只考虑kref,你可能还需要klist。


本篇文章来源于 Linux公社网站(www.linuxidc.com)  原文链接:http://www.linuxidc.com/Linux/2011-10/44627p4.htm