Linux那些事儿之我是Hub(5)

来源:互联网 发布:java程序 笔记 编辑:程序博客网 时间:2024/04/29 11:43

hub_thread()中还有一个函数没有讲.它就是try_to_freeze().这是电源管理相关的.对大多数人来说,关于这个函数,了解就可以了.以下的内容就当科普性质吧,也算哥们儿为奥运做点贡献,提高国民科学文化知识水平.随着Linux开始支持suspended之后,西方的资本家们提倡,每一个内核进程都应该在适当的时候,调用try_to_freeze().什么意思呢?有这样一个flag,PF_NOFREEZE,如果你这个进程或者内核线程不想进入suspended状态,那么你就可以设置这个flag,正如我们在usb-storage中,usb_stor_control_thread()中做的那样.那就属于众人皆醉你独醒的情况,就是说大家都挂起了,唯独你想表现一下自己作为80后的与众不同的个性.而对于大多数内核线程来说,目前主流的看法是希望你能在某个地方调用try_to_freeze(),这个函数的作用是检测一个flag有没有设置,哪个flag呢,TIF_FREEZE,每个体系结构定义了自己的与这有关的flags.比如i386的,include/asm-i386/thread_info.h中:


    120 /*


    121  * thread information flags


    122  * - these are process state flags that various assembly files may need to access


    123  * - pending work-to-be-done flags are in LSW


    124  * - other flags in MSW


    125  */


    126 #define TIF_SYSCALL_TRACE       0       /* syscall trace active */


    127 #define TIF_NOTIFY_RESUME       1       /* resumption notification requested */


    128 #define TIF_SIGPENDING          2       /* signal pending */


    129 #define TIF_NEED_RESCHED        3       /* rescheduling necessary */


    130 #define TIF_SINGLESTEP          4       /* restore singlestep on return to user mode */


    131 #define TIF_IRET                5       /* return with iret */


    132 #define TIF_SYSCALL_EMU         6       /* syscall emulation active */


    133 #define TIF_SYSCALL_AUDIT       7       /* syscall auditing active */


    134 #define TIF_SECCOMP             8       /* secure computing */


    135 #define TIF_RESTORE_SIGMASK     9       /* restore signal mask in do_signal() */


    136 #define TIF_MEMDIE              16


    137 #define TIF_DEBUG               17      /* uses debug registers */


    138 #define TIF_IO_BITMAP           18      /* uses I/O bitmap */


139 #define TIF_FREEZE              19      /* is freezing for suspend */


TIF就是thread info的意思.众所周知,struct task_struct是一个表示进程的结构体,而除此之外,又有一个叫做struct thread_info的结构体来代表内核线程,而struct thread_info中有一个成员struct task_struct *task,所以我们很好理解,实际上就是说对普通进程来说,我们准备一个struct task_struct就可以描述了,而对于内核线程来说,它可能要包含更多的信息,光一个struct task_struct还不足以表达它的全部.所以就跳出来一个叫做struct thread_info的结构体,而struct thread_info中也有一个成员叫做unsigned long flags.以上定义的这些宏就是和这个flags对应的.我们知道struct task_struct里边实际上已经有一个unsigned long flags,而这两者的区别在于,struct thread_info中的flags标志的是更为底层的信息,江湖中称之为low level flags,理由是这些标志位通常并不是我们直接设置的,而是底层专门管电源管理的代码来设置.而我们能设置的就是像PF_NOFREEZE这样的flag,它就是属于struct task_struct的,就像我们在usb-storage中做的那样,设置current->flags.


所以我们就知道,try_to_freeze()就是判断这个low level flags里面是否设置了这么一位,TIF_FREEZE,那么谁会设置这一位呢?不是别人,正是你.你不承认是吧,你说计算机为什么会进入suspended状态?是不是你让它进入的?比如笔记本,你要是不合上笔记本,或者你不按你笔记本键盘上那个suspend键,或者说你不做任何触发suspended的操作,那么计算机肯定不会自己进入suspended状态,或者说进入挂起状态.说挂起可能还不是很准确,因为Linux中,suspend是包括挂起和暂停的,也就是说包括了Windows下的那种休眠.在Linux下都称作suspend,但是你可以选择是挂起还是暂停.所以我们还是直接说suspend吧,比如当你触发了suspend的操作,那么从内核代码来讲,它就会对每一个进程进行判断,如果设置了PF_NOFREEZE这个flag,那么就没有必然让这个进程去进入suspended状态了,西方的人比较幽默,他们把这一举动称为使进程进入电冰箱,就是说把进程冷冻起来.如果没有设置,那么就说明你并不反对内核让你进入suspended,这就相当于一个人被别人侵犯,她的态度是半推半就,那么对方就会肆无忌惮的去做他想做的事情.具体来说,kernel会做什么呢?最重要的就是设置了TIF_FREEZE这个flag,然后给当前进程发送信号.理想情况下,hub_events()函数会执行完,然后调用wait_event_interruptible()进入睡眠,然后在接到信号之后,wait_event_interruptible()会退出,然后try_to_freeze()会被执行,try_to_freeze()实际上会判断TIF_FREEZE是否被设置,如果是,就调用refrigerator()来设置一个叫做PF_FROZEN的flag,从而进入suspended或者说进入frozen,也就是说进入电冰箱,或者说进入冷冻状态.应该说,电源管理是Linux内核中目前比较热门的话题,关于电源管理的代码也是近年来才不断发展起来的,尤其在2.6.20开始,代码中绝大多数内核线程里都引入了try_to_freeze()这么一个函数来配合suspended特性.try_to_freeze()被称为suspend hook.而当系统从suspended状态恢复过来时候,一个叫做thaw_processes()的函数会被调用,从而清除掉PF_FROZEN这个flag,然后这里进入frozen的进程又会被恢复,会被解冻.即refrigerator函数会结束,然后do/while开始新的一轮循环.如果循环条件不满足,那么循环结束,pr_debug就是一句打印语句,而usbcore_name就是一个字符串,”usbcore”.然后hub_thread()返回0.故事就结束了.


小结一下:如果你对以上这些讨论电源管理的东西不感兴趣,不要紧,丝毫不要紧,Linux中很多关于电源管理的东西至今为止还都是不成熟的,写代码的兄弟们天天都在修改.问题多多,革命尚未成功,同志仍需努力.值此北京奥运会倒计时一周年之际,就让我们在精神上支持一下以Rafael J. Wysocki为代表的战斗在Linux内核电源管理领域前线的同志们.等将来我们国家房价降下去了,大家生活压力不是那么大了,让我们也去加入这支队伍吧.不过我们80后是没希望了,房价还没降,猪肉价格倒是已经涨上来了.一句话,如果你不想支持电源管理,那么你编译内核的时候把CONFIG_PM给关了,一了百了.不过,有一个问题,usb设备实际上是有节电这么一个特性的,也就是说你翻一翻usb的各种规范,其中就有一个suspend一个resume,就是挂起和恢复,换言之,硬件本身有这样的特性,你要是软件不支持的话,那你不心虚吗?写出来的代码你敢给客户用吗?所以没有办法,接下来我们要是看到电源管理的代码还是会讲,我们只能认了.命苦不能怪政府,点背不能怪社会.一个利好消息是,除了这里这个try_to_freeze()比较难一点外,剩下的在usb中出现的电源管理的代码实际上相对来说不是很难理解,毕竟那些东西和硬件规范是对应的,都有章可循,硬件怎么规定就怎么做,所以,不用太担心.


摆平了外面的这行代码,于是现在我们安心来看hub_events()了.这又是一个变态的函数,好几百行,我简直怀疑写代码的人是不是吃了黄金搭档,补足了钙铁锌硒维生素,要不然怎么能精力这么充沛写了一个长函数又一个,而且个个都是有技术含量的,苦了我们这些看代码的了.算了,不跟这些人一般见识,记不记得小时候父母最伤我们自尊的事情就是经常拿比我们”行”的人刺激我们,这些事情总让我们很受伤,现在我们长大了,又何必再去自己寻找这些烦恼呢,我们这一代人的烦恼还少吗?我们小的时候,大学生是天子骄子,当我们大学毕业的时候,大学生是锅里的饺子.我们没能工作的时候,工作也是分配的,我们可以工作的时候,撞得头破血流才勉强找份饿不死人的工作做.不想这些了,乐观一点看,这些人比我们懂更多C语言,可是他们未必像我们一样懂那么多网络语言,我们未必懂他们写的东西,可是我们说火星,说orz,说我顶你个肺,他们也不懂.hub_events(),还是来自drivers/usb/core/hub.c,我们一段一段来看:


   2595 static void hub_events(void)


   2596 {


   2597         struct list_head *tmp;


   2598         struct usb_device *hdev;


   2599         struct usb_interface *intf;


   2600         struct usb_hub *hub;


   2601         struct device *hub_dev;


   2602         u16 hubstatus;


   2603         u16 hubchange;


   2604         u16 portstatus;


   2605         u16 portchange;


   2606         int i, ret;


   2607         int connect_change;


   2608


   2609         /*


   2610          *  We restart the list every time to avoid a deadlock with


   2611          * deleting hubs downstream from this one. This should be


   2612          * safe since we delete the hub from the event list.


   2613          * Not the most efficient, but avoids deadlocks.


   2614          */


   2615         while (1) {


   2616


   2617                 /* Grab the first entry at the beginning of the list */


   2618                 spin_lock_irq(&hub_event_lock);


   2619                 if (list_empty(&hub_event_list)) {


   2620                         spin_unlock_irq(&hub_event_lock);


   2621                         break;


   2622                 }


   2623


   2624                 tmp = hub_event_list.next;


   2625                 list_del_init(tmp);


   2626


   2627                 hub = list_entry(tmp, struct usb_hub, event_list);


   2628                 hdev = hub->hdev;


   2629                 intf = to_usb_interface(hub->intfdev);


   2630                 hub_dev = &intf->dev;


   2631


   2632                 dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",


   2633                                 hdev->state, hub->descriptor


   2634                                         ? hub->descriptor->bNbrPorts


   2635                                         : 0,


   2636                                 /* NOTE: expects max 15 ports... */


   2637                                 (u16) hub->change_bits[0],


   2638                                 (u16) hub->event_bits[0]);


   2639


   2640                 usb_get_intf(intf);


   2641                 spin_unlock_irq(&hub_event_lock);


仔细一看这段代码你会发现我们中国古代军事思想中的声东击西又被人偷师了.2615行,一个while(1)循环,2619行,判断hub_event_list是否为空,是不是觉得很有趣,第一次调用这个函数的时候,hub_event_list就是初值,我们说过初值为空.所以这里就是空,即list_empty()返回1,然后break语句跳出while循环,你知道while循环的结尾在哪里吗?就是这个hub_events()函数的结尾,也就是说这里几百行的代码我们就结束了,我们直接退出这个函数,返回到hub_thread()中,调用wait_event_interruptible()进入睡眠,然后等待有事件的发生,对于hub来说,事件的发生,比如你插入一个设备到hub口里,就会触发一件事件.而第一件事件的发生其实是hub驱动程序本身的初始化,即我们说过,由于root hub的存在,所以hub_probe必然会被调用,确切地说,就是在host controller的驱动程序中,一定会调用hub_probe的.如果你问我到底什么时候会调用,那么我无可奉告,因为这是在host controller的驱动程序中,不管你的host controller是属于OHCI的,还是UHCI的,还是EHCI的,最终在它们的初始化代码中都会调用一个叫做hcd_register_root()的函数,进而转战usb_register_root_hub(),几经周转,最终hub_probe就会被调用.所以你根本不用担心这个函数什么时刻会被调用,反正总会有这么一个时刻.正如半生缘里说的一样,我要叫你知道,这个世界上有一个人会一直等你,无论在什么时候,无论你在什么地方,反正总会有这样一个人.


所以,我们就转战去到hub_probe吧,这里hub_events()只是虚晃一枪,不过你别忘了,等到hub_event_list里面有东西了之后,我们还会回来的.要知道hub_events()这个函数才是真正的hub驱动的核心函数,所有的故事都是在这里发生的.所以,就像你给了某人一个承诺,承诺你还会回来.有了承诺,时间似乎有了刻度,有了承诺,等待也被赋予了意义.只是,也许,有一种爱经不起伤害,有一种爱经不起等待.


最后需要记住的是wait_event_interruptible()的第一个参数是&khubd_wait,关于这个函数我们在usb-storage里面已经看过多次了,其中khubd_wait定义于drivers/usb/core/hub.c:


     85 /* Wakes up khubd */


     86 static DECLARE_WAIT_QUEUE_HEAD(khubd_wait);


这无非就是一个等待队列头,所以我们很清楚,将来要唤醒这个睡眠进程的一定是类似这样的一行代码:wake_up(&khubd_wait),没错,you got it!整个内核代码中只有一个地方会调用这个代码,那就是kick_khubd(),不过调用kick_khubd()的地方可不少,我们走着瞧.