ImageVerifierCode 换一换
格式:DOCX , 页数:16 ,大小:22.32KB ,
资源ID:10582453      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/10582453.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(linux设备驱动之更强的链表klist.docx)为本站会员(b****8)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

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

1、linux设备驱动之更强的链表klist前面我们说到过list_head,这是linux中通用的链表形式,双向循环链表,功能强大,实现简单优雅。可如果您认为list_head就是链表的极致,应该在linux链表界一统天下,那可就错了。据我所知,linux内核代码中至少还有两种链表能占有一席之地。一种就是hlist,一种就是本节要介绍的klist。虽然三者不同,但hlist和klist都可以看成是从list_head中发展出来的,用于特殊的链表使用情景。hlist是用于哈希表中。众所周知,哈希表主要就是一个哈希数组,为了解决映射冲突的问题,常常把哈希数组的每一项做成一个链表,这样有多少重复的都可

2、以链进去。但哈希数组的项很多,list_head的话每个链表头都需要两个指针的空间,在稀疏的哈希表中实在是一种浪费,于是就发明了hlist。hlist有两大特点,一是它的链表头只需要一个指针,二是它的每一项都可以找到自己的前一节点,也就是说它不再循环,但仍是双向。令人不解的是,hlist的实现太绕了,比如它明明可以直接指向前一节点,却偏偏指向指针地址,还是前一节点中指向后一节点的指针地址。即使这种设计在实现时占便宜,但它理解上带来的不便已经远远超过实现上带来的小小便利。 同hlist一样,klist也是为了适应某类特殊情形的要求。考虑一个被简化的情形,假设一些设备被链接在设备链表中,一个线程命

3、令卸载某设备,即将其从设备链表中删除,但这时该设备正在使用中,这时就出现了冲突。当前可以设置临界区并加锁,但因为使用一个设备而锁住整个设备链表显然是不对的;又或者可以从设备本身做文章,让线程阻塞,这当然也可以。但我们上节了解了kref,就该知道linux对待这种情况的风格,给它一个引用计数kref,等计数为零就删除。klist就是这么干的,它把kref直接保存在了链表节点上。之前说到有线程要求删除设备,之前的使用仍存在,所以不能实际删除,但不应该有新的应用访问到该设备。klist就提供了一种让节点在链表上隐身的方法。下面还是来看实际代码吧。 klist的头文件是include/linux/kl

4、ist.h,实现在lib/klist.c。cppview plaincopyprint?1. structklist_node;2. structklist3. spinlock_tk_lock;4. structlist_headk_list;5. void(*get)(structklist_node*);6. void(*put)(structklist_node*);7. _attribute_(aligned(4);8. 9. #defineKLIST_INIT(_name,_get,_put)10. .k_lock=_SPIN_LOCK_UNLOCKED(_name.k_lock)

5、,11. .k_list=LIST_HEAD_INIT(_name.k_list),12. .get=_get,13. .put=_put,14. 15. #defineDEFINE_KLIST(_name,_get,_put)16. structklist_name=KLIST_INIT(_name,_get,_put)17. 18. externvoidklist_init(structklist*k,void(*get)(structklist_node*),19. void(*put)(structklist_node*);20. 21. structklist_node22. voi

6、d*n_klist;/*neveraccessdirectly*/23. structlist_headn_node;24. structkrefn_ref;25. ;可以看到,klist的链表头是struct klist结构,链表节点是struct klist_node结构。先看struct klist,除了包含链表需要的k_list,还有用于加锁的k_lock。剩余的get()和put()函数是用于struct klist_node嵌入在更大的结构中,这样在节点初始时调用get(),在节点删除时调用put(),以表示链表中存在对结构的引用。再看struct klist_node,除了链表需

7、要的n_node,还有一个引用计数n_ref。还有一个比较特殊的指针n_klist,n_klist是指向链表头struct klist的,但它的第0位用来表示是否该节点已被请求删除,如果已被请求删除则在链表循环时是看不到这一节点的,循环函数将其略过。现在你明白为什么非要在struct klist的定义后加上_attribute_(aligned(4)。不过说实话这样在x86下仍然不太保险,但linux选择了相信gcc,毕竟是多年的战友和兄弟了,相互知根知底。看过这两个结构,想必大家已经较为清楚了,下面就来看看它们的实现。cppview plaincopyprint?1. /*2. *Useth

8、elowestbitofn_klisttomarkdeletednodesandexclude3. *deadonesfromiteration.4. */5. #defineKNODE_DEAD1LU6. #defineKNODE_KLIST_MASKKNODE_DEAD7. 8. staticstructklist*knode_klist(structklist_node*knode)9. 10. return(structklist*)11. (unsignedlong)knode-n_klist&KNODE_KLIST_MASK);12. 13. 14. staticboolknode

9、_dead(structklist_node*knode)15. 16. return(unsignedlong)knode-n_klist&KNODE_DEAD;17. 18. 19. staticvoidknode_set_klist(structklist_node*knode,structklist*klist)20. 21. knode-n_klist=klist;22. /*noknodedeservestostartitslifedead*/23. WARN_ON(knode_dead(knode);24. 25. 26. staticvoidknode_kill(structk

10、list_node*knode)27. 28. /*andnoknodeshoulddietwiceevereither,seewereveryhumane*/29. WARN_ON(knode_dead(knode);30. *(unsignedlong*)&knode-n_klist|=KNODE_DEAD;31. 前面的四个函数都是内部静态函数,帮助API实现的。knode_klist()是从节点找到链表头。knode_dead()是检查该节点是否已被请求删除。knode_set_klist设置节点的链表头。knode_kill将该节点请求删除。细心的话大家会发现这四个函数是对称的,而且

11、都是操作节点的内部函数。cppview plaincopyprint?1. voidklist_init(structklist*k,void(*get)(structklist_node*),2. void(*put)(structklist_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。cppview plaincopyprint?1. staticvoidadd_head(structklist*k,st

12、ructklist_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. staticvoidadd_tail(structklist*k,structklist_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. staticvoidklist_node_init(stru

13、ctklist*k,structklist_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()函数。cppview plaincopyprint?1. voidklist_add_head(

14、structklist_node*n,structklist*k)2. 3. klist_node_init(k,n);4. add_head(k,n);5. 6. 7. voidklist_add_tail(structklist_node*n,structklist*k)8. 9. klist_node_init(k,n);10. add_tail(k,n);11. klist_add_head()将节点初始化,并加入链表头。klist_add_tail()将节点初始化,并加入链表尾。它们正是用上面的三个内部函数实现的,可见linux内核中对函数复用有很强的执念,其实这里add_tail和

15、add_head是不用的,纵观整个文件,也只有klist_add_head()和klist_add_tail()对它们进行了调用。cppview plaincopyprint?1. voidklist_add_after(structklist_node*n,structklist_node*pos)2. 3. structklist*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);

16、9. 10. 11. voidklist_add_before(structklist_node*n,structklist_node*pos)12. 13. structklist*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()将节点加到指定节点前面。这两个函

17、数都是对外提供的API。在list_head中都没有看到有这种API,所以说需求决定了接口。虽说只有一步之遥,klist也不愿让外界介入它的内部实现。之前出现的API都太常见了,既没有使用引用计数,又没有跳过请求删除的节点。所以klist的亮点在下面,klist链表的遍历。cppview plaincopyprint?1. structklist_iter2. structklist*i_klist;3. structklist_node*i_cur;4. ;5. 6. 7. externvoidklist_iter_init(structklist*k,structklist_iter*i)

18、;8. externvoidklist_iter_init_node(structklist*k,structklist_iter*i,9. structklist_node*n);10. externvoidklist_iter_exit(structklist_iter*i);11. externstructklist_node*klist_next(structklist_iter*i);以上就是链表遍历需要的辅助结构struct klist_iter,和遍历用到的四个函数。cppview plaincopyprint?1. structklist_waiter2. structlist

19、_headlist;3. structklist_node*node;4. structtask_struct*process;5. intwoken;6. ;7. 8. staticDEFINE_SPINLOCK(klist_remove_lock);9. staticLIST_HEAD(klist_remove_waiters);10. 11. staticvoidklist_release(structkref*kref)12. 13. structklist_waiter*waiter,*tmp;14. structklist_node*n=container_of(kref,stru

20、ctklist_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-

21、list);27. 28. spin_unlock(&klist_remove_lock);29. knode_set_klist(n,NULL);30. 31. 32. staticintklist_dec_and_del(structklist_node*n)33. 34. returnkref_put(&n-n_ref,klist_release);35. 36. 37. staticvoidklist_put(structklist_node*n,boolkill)38. 39. structklist*k=knode_klist(n);40. void(*put)(structkli

22、st_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-Decrementthereferencecountofnodeandtrytoremove.54. *n:nodeweredeleting.55. */56. voidklist_del(structklist

23、_node*n)57. 58. klist_put(n,true);59. 以上的内容乍一看很难理解,其实都是klist实现必须的。因为使用kref动态删除,自然需要一个计数降为零时调用的函数klist_release。klist_dec_and_del()就是对kref_put()的包装,起到减少节点引用计数的功能。至于为什么会出现一个新的结构struct klist_waiter,也很简单。之前说有线程申请删除某节点,但节点的引用计数仍在,所以只能把请求删除的线程阻塞,就是用struct klist_waiter阻塞在klist_remove_waiters上。所以在klist_relea

24、se()调用时还要将阻塞的线程唤醒。knode_kill()将节点设为已请求删除。而且还会调用put()函数。释放引用计数是调用klist_del(),它通过内部函数klist_put()完成所需操作:用knode_kill()设置节点为已请求删除,用klist_dec_and_del()释放引用,调用可能的put()函数。cppview plaincopyprint?1. /*2. *klist_remove-Decrementtherefcountofnodeandwaitforittogoaway.3. *n:nodewereremoving.4. */5. voidklist_remo

25、ve(structklist_node*n)6. 7. structklist_waiterwaiter;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_

26、UNINTERRUPTIBLE);20. if(waiter.woken)21. break;22. schedule();23. 24. _set_current_state(TASK_RUNNING);25. klist_remove()不但会调用klist_del()减少引用计数,还会一直阻塞到节点被删除。这个函数才是请求删除节点的线程应该调用的。cppview plaincopyprint?1. intklist_node_attached(structklist_node*n)2. 3. return(n-n_klist!=NULL);4. klist_node_attached()检查节点是否被包含在某链表中。以上是klist的链表初始化,节点加入,节点删除函数。下面是klist链表遍历函数。cpp

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

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