4.一样的精灵,不一样的API(1)

本文深入探讨USB Hub驱动程序的工作原理,特别是hub_probe函数的作用及如何通过usb_register注册Hub驱动。文章还介绍了kthread_run和kthread_stop在创建与终止内核线程中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4.一样的精灵,不一样的API(1)

usb_register()这个函数是用来向USB核心层,即USB Core,注册一个USB设备驱动的,而这里我们注册的是Hub的驱动程序所对应的struct usb_driver结构体变量。定义于drivers/usb/ core/hub.c中:

  1. 2841 static struct usb_driver hub_driver = {  
  2. 2842    .name =         "hub",  
  3. 2843    .probe =        hub_probe,  
  4. 2844    .disconnect =   hub_disconnect,  
  5. 2845        .suspend =      hub_suspend,  
  6. 2846    .resume =       hub_resume,  
  7. 2847    .pre_reset =    hub_pre_reset,  
  8. 2848    .post_reset =   hub_post_reset,  
  9. 2849    .ioctl =        hub_ioctl,  
  10. 2850        .id_table =     hub_id_table,  
  11. 2851    .supports_autosuspend = 1,  
  12. 2852 };  

这里面最重要的一个函数就是hub_probe,很多事情都在这期间发生了。每个USB设备(或者说所有设备)的驱动都会有一个probe函数,比如U盘的probe函数就是storage_probe(),不过storage_probe()被调用需要有两个前提:第一个前提是usb-storage被加载了,第二个前提是U盘等设备插入了被检测到了。

而Hub,说它特别,我可绝不是"忽悠"你。Hub本身就有两种:一种是普通的Hub,一种是Root Hub。对于普通Hub,它完全可能也和U盘一样,在某个时刻被插入,然后在这种情况下hub_probe被调用,但是对于Root Hub就不需要这么多废话了,Root Hub肯定是有的,只要你有USB主机控制器,就一定会有Root Hub,所以hub_probe()基本上很自然地就被调用了,不用说非得等待某个插入事件的发生,没这个必要。当然没有USB主机控制器就没有USB设备能工作。那么USB Core这整个模块你就没有必要分析了。所以,只要你有USB主机控制器,那么在USB主机控制器的驱动程序初始化的过程中,它就会调用hub_probe()来探测Root Hub,不管你的主机控制器是OHCI、UHCI还是EHCI的接口。

如果register一切顺利的话,那么返回值为0。如果返回值为负数,就说明出错了。现在假设这一步没有出错。

usb_hub_init()的2862行,这行代码其实是很有技术含量的,不过对于写驱动的人来说,其作用就和当年的kernel_thread()相当。不过kernel_thread()返回值是一个int型的,而kthread_run()返回的却是struct task_struct结构体指针。这里等号左边的khubd_task是我们自己定义的一个struct task_struct指针:

  1. 88 static struct task_struct *khubd_task; 

struct task_struct不用多说,记录进程的数据结构。每一个进程都用一个struct task_struct结构体变量来表示。所以这里所做的就是记录下创建好的内核进程,以便日后要卸载模块时可以用另一个函数来结束这个内核进程(你也可以叫内核线程),到时我们会调用kthread_stop(khubd_task)函数来结束这个内核线程,这个函数的调用我们将会在usb_hub_cleanup()函数中看到。而usb_hub_cleanup()正是Hub里面和usb_hub_init()相对应的函数。

2863行,判断khubd_task,IS_ERR是一个宏,用来判断指针的。当你创建了一个进程,你当然想知道这个进程创建成功了没有。

以前我们注意到每次申请内存时都会做一次判断,你说创建进程是不是也要申请内存?不申请内存谁来记录struct task_struct?很显然,要进行判断。以前我们判断的是指针是否为空。以后接触代码多了你会发现,其实Linux内核中有很多种内存申请的方式,而这些方式所返回的内存地址也是不一样的,所以并不是每一次我们都只要判断指针是否为空就可以了。事实上,每一次调用kthread_run()之后,我们都会用一个IS_ERR()来判断指针是否有效。IS_ERR()为1就表示指针有错,或者准确一点说叫做指针无效。

什么叫指针无效?后面会专门解释,让我们继续往下看,只需要记得,如果你不希望发生缺页异常这样的错误的话,每次调用完kthread_run()之后要用IS_ERR()来检测返回的指针。如果IS_ERR()返回值是0,那么说明没有问题,于是返回值为 0,也就是说usb_hub_init()就这么结束了。反之,就会执行usb_deregister(),因为内核线程没有成功创建,hub就没法驱动起来了。

最后函数在2870行,返回值为-1。回到usb_init()函数中我们会知道,接下来usb_hub_ cleanup()就会被调用。usb_hub_cleanup()同样定义于drivers/usb/core/hub.c中:

  1. 2873 void usb_hub_cleanup(void)  
  2. 2874 {  
  3. 2875    kthread_stop(khubd_task);  
  4. 2876  
  5. 2877    /*  
  6. 2878     * Hub resources are freed for us by usb_deregister. It calls  
  7. 2879         * usb_driver_purge on every device which in turn calls that  
  8. 2880     * devices disconnect function if it is using this driver.  
  9. 2881     * The hub_disconnect function takes care of releasing the  
  10. 2882     * individual hub resources. -greg  
  11. 2883     */  
  12. 2884    usb_deregister(&hub_driver);  
  13. 2885 } /* usb_hub_cleanup() */  

这个函数我想没有任何必要解释了吧。kthread_stop()和刚才的kthread_run()对应,usb_deregister()和usb_register()对应。

总之,如果创建子进程出了问题,那么一切都免谈。

反之,如果成功了,那么kthread_run()的三个参数就是我们要关注的了:第一个是hub_thread(),子进程将从这里开始执行。第二个是hub_thread(),传递的是NULL,第三个参数就是精灵进程的名字ps -el,如下所示:

  1. localhost:/usr/src/linux-2.6.22/drivers/usb/core # ps -el | grep khubd  
  2. 1 S     0  1963    27  0  70  -5 -     0 hub_th ?        00:00:00 khubd  

你就会发现有这么一个精灵进程运行着。所以,下一步,让我们进入hub_thread()来查看这个子进程吧。

以下是关于IS_ERR的介绍文字。如果你对内存管理没有任何兴趣,就不用往下看了。要想明白IS_ERR(),首先你得知道有一种空间叫做内核空间,不清楚也不要紧。结合IS_ERR()的代码来看,来自include/linux/err.h:

  1. 16 #define MAX_ERRNO       4095  
  2. 17  
  3. 18 #ifndef __ASSEMBLY__  
  4. 19  
  5. 20 #define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)  
  6. 21  
  7. 22 static inline void *ERR_PTR(long error)  
  8. 23 {  
  9. 24          return (void *) error;  
  10. 25 }  
  11. 26  
  12. 27 static inline long PTR_ERR(const void *ptr)  
  13. 28 {  
  14. 29      return (long) ptr;  
  15. 30 }  
  16. 31  
  17. 32 static inline long IS_ERR(const void *ptr)  
  18. 33 {  
  19. 34      return IS_ERR_VALUE((unsigned long)ptr);  
  20. 35 }  
  21. 36  
  22. 37 #endif  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值