Linux那些事儿之我是Hub(13)树,是什么样的树?
同学们,今天我们来讲一棵树.
记得小时候我们看<<白眉大侠>>,记得那段精彩的对白:刀,是什么样的刀?金丝大环刀!剑,是什么样的剑?闭月羞光剑!招,是什么样的招?天地阴阳招!人,是什么样的人?飞檐走壁的人!情,是什么样的情?美女爱英雄!
而今天我们要问的是:树,是什么样的树?答:USB设备树.这是怎样一棵树?让我慢慢的道来.
苏格拉底曾经说过:为人不识谭浩强,精通内核也枉然.
还记得谭浩强大哥书中那个汉诺塔的例子么?那时候我天真的以为这个例子没什么意义,可是今天我终于明白了.谭大哥早就知道我们学了他的书一定会在今后的生活工作中用得到,这不,hub_events()里面第2645行,locktree(),用的就是汉诺塔里的那个经典思想,递归.谭大哥您要是穿裙子的话,我一定第一个拜倒在您的石榴裙下!locktree()定义于drivers/usb/core/hub.c:
985 /* grab device/port lock, returning index of that port (zero based).
986 * protects the upstream link used by this device from concurrent
987 * tree operations like suspend, resume, reset, and disconnect, which
988 * apply to everything downstream of a given port.
989 */
990 static int locktree(struct usb_device *udev)
991 {
992 int t;
993 struct usb_device *hdev;
994
995 if (!udev)
996 return -ENODEV;
997
998 /* root hub is always the first lock in the series */
999 hdev = udev->parent;
1000 if (!hdev) {
1001 usb_lock_device(udev);
1002 return 0;
1003 }
1004
1005 /* on the path from root to us, lock everything from
1006 * top down, dropping parent locks when not needed
1007 */
1008 t = locktree(hdev);
1009 if (t < 0)
1010 return t;
1011
1012 /* everything is fail-fast once disconnect
1013 * processing starts
1014 */
1015 if (udev->state == USB_STATE_NOTATTACHED) {
1016 usb_unlock_device(hdev);
1017 return -ENODEV;
1018 }
1019
1020 /* when everyone grabs locks top->bottom,
1021 * non-overlapping work may be concurrent
1022 */
1023 usb_lock_device(udev);
1024 usb_unlock_device(hdev);
1025 return udev->portnum;
1026 }
咱们传递进来的是咱们这个hub对应的struct usb_device指针,995行自然不必说,就是防止那些唯恐天下不乱的人调用这个函数.
999行,parent,struct usb_device结构体的parent自然也是一个struct usb_device指针.1000行,判断udev的parent指针,你一定觉得奇怪,好像之前从来没有看到过parent指针啊,为何它突然之间出现了?它指向什么呀?对此我只能说抱歉,其实生活中发生的一切,都是那么的突然.其实我也没有办法,Hub驱动作为一个驱动程序,它并非是孤立存在的,没有主机控制器的驱动,没有usb core,Hub驱动的存在将没有任何意义.其实我以前就说过,Hub,准确的说应该是说,Root Hub,它和Host Controller是绑定在一起的,专业一点说叫做集成在一起的,所以它根本不会像我一样除了拥有孤独其它的一无所有.有时候我真的想知道,当全世界孤独的人团结在一起,孤独是会加深,还是消失…
因为Hub驱动不孤立,所以具体来说,作为Root Hub,它的parent指针在Host Controller的驱动程序中就已经赋了值,这个值就是NULL,换句话说,对于Root Hub,它不需要再有父指针了,这个父指针本来就是给从Root Hub连出来的节点用的,让我们打开天窗说亮话,这里这个函数名字叫做locktree,这个意思就很直接了,locktree嘛,顾名思义,锁住一棵树,这棵树就是USB设备树.很显然,USB设备是从Root Hub开始,一个一个往外面连的,比如Root Hub有4个口,每个口连一个USB设备,比如其中有一个是Hub,那么这个Hub有可以继续有多个口,于是一级一级的往下连,最终肯定会连成一棵树,八卦两句,自从某年某月某一天,我家Intel提出了EHCI的规范以来,当今USB世界的发展趋势是,硬件厂商们总是让EHCI主机控制器里面拥有尽可能多的端口,换言之,理想情况就是希望大家别再用外接的Hub了,有一个Root Hub就够用了,也就是说,真的到了那种情况,USB设备树的就不太像树了,顶多就是两级,一级是Root Hub,下一级就是普通设备,严格来说,对于咱们普通人来说,这样子也就够用了,假设你的Root Hub有8个口,你说你够用不够用?鼠标,键盘,音响,U盘,存储卡,还有啥啊?8个口对正常人来说肯定够了,当然你要是心理变态那么另当别论,说不准就有人愿意往一台电脑里插入个三五十个USB设备的,林子大了,什么鸟没有?
所以说写代码也不是一件容易的事情,除了保证你的代码能让正常人正常使用,还得保证变态也能使用.locktree()的想法就是这样,在hub_events()里面加入locktree()的理由很简单,如果你的电脑里有两个hub,一个叫parent一个叫child,child接在parent的某个口上,那么parent在执行下面这段代码的时候,child hub就不要去执行这段代码,否则会引起混乱,为何会引起混乱?要知道,对于一个hub来说,其所有正常的工作都是在hub_events()这个函数里进行的,那么这些工作就比如,一种情况是,删除一个子设备, 这将有可能直接导致USB设备树的拓扑结构发生变化,或者另一种情况,遍历整个子树去执行一个resume或者reset之类的操作,那么很显然,在这种情况下,一个parent hub在进行这些操作的时候,不希望受到child hub的影响,所以在这样一个政治背景下,2004年的夏天,作为Linux内核开发中USB子系统的三剑客之一的David Brownell大侠,决定加入locktree这么一个函数,这个函数的哲学思想很简单,实际上就是借用了我国古代军事思想中的擒贼仙擒王,用David Brownell本人的话说,就是”lock parent first”.每一个Hub在执行hub_events()中下面的那些代码时,(特指locktree那个括号以下的那些代码.)都得获得一把锁,锁住自己,而在锁住自己之前,又先得获得父亲的锁,确切的说,是尝试获得父亲的锁,如果能够获得父亲的锁,那么说明父亲当前没有执行hub_events()(因为否则就没有办法获得父亲的锁),那么这种情况下子hub才可以执行自己的hub_events(),但是需要注意,在执行自己的代码之前,先把父hub的锁给释放掉,因为我们说了,我们的目的是尝试获得父亲的锁,这个尝试的目的是为了保证在我们执行hub_events()之前的那一时刻,父hub并不是正在执行hub_events(),而至于我们已经开始执行了hub_events(),我们就不在乎父hub是否也想开始执行hub_events()了.
那么有人问,父hub复父hub,父hub何其多?刚才不是说了吗?Root Hub就是整棵树的根,Root Hub就没有父hub,所以,整个递归到了父Hub就可以终止了.这也正是为什么1000行那句if语句,在判断出该设备是一个Root Hub之后,马上就执行锁住该设备.而如果不是Root Hub,那么继续往下走,递归调用locktree(),对于locktree(),正常情况下它的返回值大于等于0,所以小于0就算出错了.
然后1015行判断一下,如果我们把父hub锁住了,可是自己却被断开了,即disconnect函数被执行了,那么就立刻停止,把父hub的锁释放,然后返回吧错误代码-ENODEV.
最后1023行,锁住自己,1024行,释放父设备.
1025行,返回当前设备的portnum.portnum就是端口号,您一定奇怪,没见过什么时候为portnum赋值了啊?这就不好意思了,别忘了咱们走到今天这里是在讨论Root Hub,对于Root Hub来说,它本身没有portnum这么一个概念,因为它不插在别的Hub的任何一个口上.所以对于Root Hub来说,它的portnum在Host Controller的驱动程序里给设置成了0.而对于普通的Hub,它的portnum在哪里赋的值呢?我们后面就会看到的.别急.不过友情提醒一下,对于Root Hub来说,这里根本就不会执行到1025行来,刚才说了,对于Root Hub,实际上1002行那里就返回了,而且返回值就是0.
就这样,我们看完了locktree()这个函数,接下来我们又该返回到hub_events()里面去了.再次爆料一下,2004年,当时的内核还只是2.6.8,David Brownell大侠在那个夏天提出来locktree()的时候,遭到了另一位同是三剑客之一的Alan Stern大侠强烈反对,当时Alan Stern认为David Brownell的算法并不好,没能真正解决并发事件的相互干扰问题,Alan Stern自己有他的算法,于是两个人各自坚持自己的意见,在开源社区轰轰烈烈争论了N久,差点没打起来.那个夏天这两个人从7月30日一直吵到了8月18日,要不是8月15日我在珠江电影制片厂外景地看张柏芝拍大运摩托的那个广告,我说不准就会上去劝架,不过像我这样的人去劝架估计人家也不会理睬,人家会问:你丫谁呀?
最终locktree()被加入到了内核里面,而且不止加了一处,除了加入到了hub_events()之外,在另外两个函数usb_suspend_device()和usb_resume_device()里面也有调用locktree().有趣的是,从2.6.9一直到2.6.22.1的内核,我们都能看到locktree()这么一个函数,但是后来,usb_suspend_device/usb_resume_device中没有了这个函数,就比如我们现在看到的2.6.22.1,搜索整个内核,只有hub_events()这一个地方调用了locktree(),对此,Alan Stern给出的说法是,内核中关于USB挂起的支持有了新的改进,不需要再调用locktree了.但是从2.6.23的内核开始,估计整个内核中就不会再有locktree了,Alan Stern大侠在这个夏天,提交了一个patch,最终把locktree相关的代码全部从内核中移除了.如果您对锁机制特别感兴趣,那么研究一下Linux 2.6以来Hub驱动的历史发展,这个过程锁机制的变更,或者专业一点说,叫同步机制算法的不断改进,足以让你写一篇能在国内核心刊物发表的文章来,要知道首都有一所叫做清华的名牌大学,其硕士研究生也就是一篇国内核心就可以毕业了.
更多推荐
所有评论(0)