linux设备驱动之更强的链表klist.docx

上传人:b****8 文档编号:10582453 上传时间:2023-02-21 格式:DOCX 页数:16 大小:22.32KB
下载 相关 举报
linux设备驱动之更强的链表klist.docx_第1页
第1页 / 共16页
linux设备驱动之更强的链表klist.docx_第2页
第2页 / 共16页
linux设备驱动之更强的链表klist.docx_第3页
第3页 / 共16页
linux设备驱动之更强的链表klist.docx_第4页
第4页 / 共16页
linux设备驱动之更强的链表klist.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

linux设备驱动之更强的链表klist.docx

《linux设备驱动之更强的链表klist.docx》由会员分享,可在线阅读,更多相关《linux设备驱动之更强的链表klist.docx(16页珍藏版)》请在冰豆网上搜索。

linux设备驱动之更强的链表klist.docx

linux设备驱动之更强的链表klist

前面我们说到过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。

[cpp] viewplaincopyprint?

1.struct klist_node;  

2.struct klist {  

3.    spinlock_t      k_lock;  

4.    struct list_head    k_list;  

5.    void            (*get)(struct klist_node *);  

6.    void            (*put)(struct klist_node *);  

7.} __attribute__ ((aligned (4)));  

8.  

9.#define KLIST_INIT(_name, _get, _put)                   \  

10.    { .k_lock   = __SPIN_LOCK_UNLOCKED(_name.k_lock),       \  

11.      .k_list   = LIST_HEAD_INIT(_name.k_list),         \  

12.      .get      = _get,                     \  

13.      .put      = _put, }  

14.  

15.#define DEFINE_KLIST(_name, _get, _put)                 \  

16.    struct klist _name = KLIST_INIT(_name, _get, _put)  

17.  

18.extern void klist_init(struct klist *k, void (*get)(struct klist_node *),  

19.               void (*put)(struct klist_node *));  

20.  

21.struct klist_node {  

22.    void            *n_klist;   /* never access directly */  

23.    struct list_head    n_node;  

24.    struct kref     n_ref;  

25.};  

可以看到,klist的链表头是structklist结构,链表节点是structklist_node结构。

先看structklist,除了包含链表需要的k_list,还有用于加锁的k_lock。

剩余的get()和put()函数是用于structklist_node嵌入在更大的结构中,这样在节点初始时调用get(),在节点删除时调用put(),以表示链表中存在对结构的引用。

再看structklist_node,除了链表需要的n_node,还有一个引用计数n_ref。

还有一个比较特殊的指针n_klist,n_klist是指向链表头structklist的,但它的第0位用来表示是否该节点已被请求删除,如果已被请求删除则在链表循环时是看不到这一节点的,循环函数将其略过。

现在你明白为什么非要在structklist的定义后加上__attribute__((aligned(4)))。

不过说实话这样在x86下仍然不太保险,但linux选择了相信gcc,毕竟是多年的战友和兄弟了,相互知根知底。

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

[cpp] viewplaincopyprint?

1./* 

2. * Use the lowest bit of n_klist to mark deleted nodes and exclude 

3. * dead ones from iteration. 

4. */  

5.#define KNODE_DEAD      1LU  

6.#define KNODE_KLIST_MASK    ~KNODE_DEAD  

7.  

8.static struct klist *knode_klist(struct klist_node *knode)  

9.{  

10.    return (struct klist *)  

11.        ((unsigned long)knode->n_klist & KNODE_KLIST_MASK);  

12.}  

13.  

14.static bool knode_dead(struct klist_node *knode)  

15.{  

16.    return (unsigned long)knode->n_klist & KNODE_DEAD;  

17.}  

18.  

19.static void knode_set_klist(struct klist_node *knode, struct klist *klist)  

20.{  

21.    knode->n_klist = klist;  

22.    /* no knode deserves to start its life dead */  

23.    WARN_ON(knode_dead(knode));  

24.}  

25.  

26.static void knode_kill(struct klist_node *knode)  

27.{  

28.    /* and no knode should die twice ever either, see we're very humane */  

29.    WARN_ON(knode_dead(knode));  

30.    *(unsigned long *)&knode->n_klist |= KNODE_DEAD;  

31.}  

前面的四个函数都是内部静态函数,帮助API实现的。

knode_klist()是从节点找到链表头。

knode_dead()是检查该节点是否已被请求删除。

knode_set_klist设置节点的链表头。

knode_kill将该节点请求删除。

细心的话大家会发现这四个函数是对称的,而且都是操作节点的内部函数。

[cpp] viewplaincopyprint?

1.void klist_init(struct klist *k, void (*get)(struct klist_node *),  

2.        void (*put)(struct klist_node *))  

3.{  

4.    INIT_LIST_HEAD(&k->k_list);  

5.    spin_lock_init(&k->k_lock);  

6.    k->get = get;  

7.    k->put = put;  

8.}  

klist_init,初始化klist。

[cpp] viewplaincopyprint?

1.static void add_head(struct klist *k, struct klist_node *n)  

2.{  

3.    spin_lock(&k->k_lock);  

4.    list_add(&n->n_node, &k->k_list);  

5.    spin_unlock(&k->k_lock);  

6.}  

7.  

8.static void add_tail(struct klist *k, struct klist_node *n)  

9.{  

10.    spin_lock(&k->k_lock);  

11.    list_add_tail(&n->n_node, &k->k_list);  

12.    spin_unlock(&k->k_lock);  

13.}  

14.  

15.static void klist_node_init(struct klist *k, struct klist_node *n)  

16.{  

17.    INIT_LIST_HEAD(&n->n_node);  

18.    kref_init(&n->n_ref);  

19.    knode_set_klist(n, k);  

20.    if (k->get)  

21.        k->get(n);  

22.}  

又是三个内部函数,add_head()将节点加入链表头,add_tail()将节点加入链表尾,klist_node_init()是初始化节点。

注意在节点的引用计数初始化时,因为引用计数变为1,所以也要调用相应的get()函数。

[cpp] viewplaincopyprint?

1.void klist_add_head(struct klist_node *n, struct klist *k)  

2.{  

3.    klist_node_init(k, n);  

4.    add_head(k, n);  

5.}  

6.  

7.void klist_add_tail(struct klist_node *n, struct klist *k)  

8.{  

9.    klist_node_init(k, n);  

10.    add_tail(k, n);  

11.}  

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

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

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

 

[cpp] viewplaincopyprint?

1.void klist_add_after(struct klist_node *n, struct klist_node *pos)  

2.{  

3.    struct klist *k = knode_klist(pos);  

4.  

5.    klist_node_init(k, n);  

6.    spin_lock(&k->k_lock);  

7.    list_add(&n->n_node, &pos->n_node);  

8.    spin_unlock(&k->k_lock);  

9.}  

10.  

11.void klist_add_before(struct klist_node *n, struct klist_node *pos)  

12.{  

13.    struct klist *k = knode_klist(pos);  

14.  

15.    klist_node_init(k, n);  

16.    spin_lock(&k->k_lock);  

17.    list_add_tail(&n->n_node, &pos->n_node);  

18.    spin_unlock(&k->k_lock);  

19.}  

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

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

这两个函数都是对外提供的API。

在list_head中都没有看到有这种API,所以说需求决定了接口。

虽说只有一步之遥,klist也不愿让外界介入它的内部实现。

 

 

 

之前出现的API都太常见了,既没有使用引用计数,又没有跳过请求删除的节点。

所以klist的亮点在下面,klist链表的遍历。

[cpp] viewplaincopyprint?

1.struct klist_iter {  

2.    struct klist    *i_klist;  

3.    struct klist_node   *i_cur;  

4.};  

5.  

6.  

7.extern void klist_iter_init(struct klist *k, struct klist_iter *i);  

8.extern void klist_iter_init_node(struct klist *k, struct klist_iter *i,  

9.                 struct klist_node *n);  

10.extern void klist_iter_exit(struct klist_iter *i);  

11.extern struct klist_node *klist_next(struct klist_iter *i);  

以上就是链表遍历需要的辅助结构structklist_iter,和遍历用到的四个函数。

 

[cpp] viewplaincopyprint?

1.struct klist_waiter {  

2.    struct list_head list;  

3.    struct klist_node *node;  

4.    struct task_struct *process;  

5.    int woken;  

6.};  

7.  

8.static DEFINE_SPINLOCK(klist_remove_lock);  

9.static LIST_HEAD(klist_remove_waiters);  

10.  

11.static void klist_release(struct kref *kref)  

12.{  

13.    struct klist_waiter *waiter, *tmp;  

14.    struct klist_node *n = container_of(kref, struct klist_node, n_ref);  

15.  

16.    WARN_ON(!

knode_dead(n));  

17.    list_del(&n->n_node);  

18.    spin_lock(&klist_remove_lock);  

19.    list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) {  

20.        if (waiter->node !

= n)  

21.            continue;  

22.  

23.        waiter->woken = 1;  

24.        mb();  

25.        wake_up_process(waiter->process);  

26.        list_del(&waiter->list);  

27.    }  

28.    spin_unlock(&klist_remove_lock);  

29.    knode_set_klist(n, NULL);  

30.}  

31.  

32.static int klist_dec_and_del(struct klist_node *n)  

33.{  

34.    return kref_put(&n->n_ref, klist_release);  

35.}  

36.  

37.static void klist_put(struct klist_node *n, bool kill)  

38.{  

39.    struct klist *k = knode_klist(n);  

40.    void (*put)(struct klist_node *) = k->put;  

41.  

42.    spin_lock(&k->k_lock);  

43.    if (kill)  

44.        knode_kill(n);  

45.    if (!

klist_dec_and_del(n))  

46.        put = NULL;  

47.    spin_unlock(&k->k_lock);  

48.    if (put)  

49.        put(n);  

50.}  

51.  

52./** 

53. * klist_del - Decrement the reference count of node and try to remove. 

54. * @n:

 node we're deleting. 

55. */  

56.void klist_del(struct klist_node *n)  

57.{  

58.    klist_put(n, true);  

59.}  

以上的内容乍一看很难理解,其实都是klist实现必须的。

因为使用kref动态删除,自然需要一个计数降为零时调用的函数klist_release。

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

至于为什么会出现一个新的结构structklist_waiter,也很简单。

之前说有线程申请删除某节点,但节点的引用计数仍在,所以只能把请求删除的线程阻塞,就是用structklist_waiter阻塞在klist_remove_waiters上。

所以在klist_release()调用时还要将阻塞的线程唤醒。

knode_kill()将节点设为已请求删除。

而且还会调用put()函数。

释放引用计数是调用klist_del(),它通过内部函数klist_put()完成所需操作:

用knode_kill()设置节点为已请求删除,用klist_dec_and_del()释放引用,调用可能的put()函数。

[cpp] viewplaincopyprint?

1./** 

2. * klist_remove - Decrement the refcount of node and wait for it to go away. 

3. * @n:

 node we're removing. 

4. */  

5.void klist_remove(struct klist_node *n)  

6.{  

7.    struct klist_waiter waiter;  

8.  

9.    waiter.node = n;  

10.    waiter.process = current;  

11.    waiter.woken = 0;  

12.    spin_lock(&klist_remove_lock);  

13.    list_add(&waiter.list, &klist_remove_waiters);  

14.    spin_unlock(&klist_remove_lock);  

15.  

16.    klist_del(n);  

17.  

18.    for (;;) {  

19.        set_current_state(TASK_UNINTERRUPTIBLE);  

20.        if (waiter.woken)  

21.            break;  

22.        schedule();  

23.    }  

24.    __set_current_state(TASK_RUNNING);  

25.}  

klist_remove()不但会调用klist_del()减少引用计数,还会一直阻塞到节点被删除。

这个函数才是请求删除节点的线程应该调用的。

[cpp] viewplaincopyprint?

1.int klist_node_attached(struct klist_node *n)  

2.{  

3.    return (n->n_klist !

= NULL);  

4.}  

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

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

下面是klist链表遍历函数。

 

[cpp

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 商业计划

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1