usb_register()和usb_deregister()这两个函数我们当初分析usb storage的时候就已经见到过了.当时我们就说过了,这个函数是用来向usb核心层,即usb core,注册一个usb设备驱动的.那年我们注册了一个struct usb_driver usb_storage_driver.而这里我们注册的是hub的驱动程序所对应的struct usb_driver结构体变量.定义于drivers/usb/core/hub.c中:
2841 static struct usb_driver hub_driver = {
2842 .name = "hub",
2843 .probe = hub_probe,
2844 .disconnect = hub_disconnect,
2845 .suspend = hub_suspend,
2846 .resume = hub_resume,
2847 .pre_reset = hub_pre_reset,
2848 .post_reset = hub_post_reset,
2849 .ioctl = hub_ioctl,
2850 .id_table = hub_id_table,
2851 .supports_autosuspend = 1,
2852 };
和咱们storage那个情况一样,最重要的一个函数就是hub_probe,对应咱们那里的storage_probe().很多事情都在这期间发生了.不过有一点你必须明白,当初storage_probe()被调用是发生在usb-storage模块被加载了并且检测到了有设备插入之后的情况下,也就是说有两个前提,第一个usb-storage被加载了,第二个设备插入了被检测到了,于是storage_probe()被调用.而hub,说她特别,我可绝不是忽悠你.hub本身就是两种,一种是普通的hub,一种是root hub.对于普通hub,它完全可能也是和U盘一样,在某个时刻被你插入,然后这种情况下hub_probe被调用,但是对于root hub就不需要这么多废话了,root hub肯定是有的,只要你有host controller,就一定会有root hub,所以hub_probe()基本上是很自然的就被调用了,不用说非得等待某个插入事件的发生,没这个必要.当然如果你要抬杠,你说那如果没有usb host controller那就不用初始化root hub了,这简直是废话,没有usb host controller就没有usb设备能工作.那么usb core这整个模块你就没有必要分析了.所以,只要你有usb host controller,那么在usb host controller的驱动程序初始化的过程中,它就会调用hub_probe()来探测root hub,不管你的host controller是ohci,uhci,还是ehci的接口.别急,慢慢来.
如果register一切顺利的话,那么返回0.要不返回负数,返回负数就说明出错了.如果这一步都能出错,那我只能说你运气真的很好,中国队要有你这样的运气,早就拿世界杯冠军了,哪能像现在这样和缅甸进行生死战.Ok,假设这一步没有fail,如果真的fail你也不用急,Linus肯定比你急,Greg更急,恐怕这位Linux中pci/usb的维护者一世英名就被这样你毁了.
Ok,2862行,这行代码其实是很有技术含量的.不过对写驱动的人来说,其作用就和我们当年那个kernel_thread()是的,或者如果你忘记了我们曾经讲过的创建usb-storage精灵进程的kernel_thread(),那么你就把这个当作fork吧.世界在变,Linux内核代码当然也在变,小时候我们看周润发赵雅芝演的上海滩,长大了我们看孙俪黄晓明演的上海滩.都是上海滩,只是版本不同,只是内容不同.内核kthread_run()就扮演着2.6.10内核那个kernel_thread()的作用.不过当时kernel_thread()返回值是一个int型的,而kthread_run()返回的却是struct task_struct结构体指针.这里等号左边的khubd_task是我们自己定义的一个struct task_struct指针.
88 static struct task_struct *khubd_task;
struct task_struct不用多说,记录进程的数据结构.每一个进程都用一个struct task_struct结构体变量来表示.所以这里所做的就是记录下创建好的内核进程,以便日后要卸载模块的时候可以用另一个函数来结束这个内核进程(你也可以叫内核线程,看自己喜欢啰,又没有人逼着我说哪一个.),到时候我们会调用kthread_stop(khubd_task)来结束这个内核线程,这个函数的调用我们将会在usb_hub_cleanup()函数里看到.而usb_hub_cleanup()正是usb hub里面和usb_hub_init()相对应的函数.
2863行,判断一下khubd_task,IS_ERR是一个宏,用来判断指针的.当你创建了一个进程,你当然想知道这个进程创建成功了没有.以前我们注意到每次申请内存的时候都会做一次判断,你说创建进程是不是也要申请内存?不申请内存谁来记录struct task_struct?很显然,要判断.以前我们判断的是指针是否为空,但是Linux内核不是上海滩,越老越经典.以后接触代码多了你会发现,我们以前在usb storage申请内存的时候用的都是kmalloc(),但是其实Linux内核中有很多种内存申请的方式,而这些方式所返回的内存地址也是不一样的,所以并不是每一次我们都只要判断指针是否为空就可以了.事实上,每一次调用kthread_run()之后,我们都会用一个IS_ERR()来判断指针是否有效.IS_ERR()为1就表示指针有错,或者准确一点说叫做指针无效.什么叫指针无效?如果你和我一样,觉得生活很无聊,那么本节最后一段会专门给你个解释,其他人就不用去管IS_ERR()了,让我们继续往下看,只需要记得,如果你不希望发生缺页异常这样的错误的话,那么请记住,每次调用完kthread_run()之后要用IS_ERR()来检测一下返回的指针.如果IS_ERR()返回值是0,那么说明没有问题,于是return 0,也就是说usb_hub_init()就这么结束了.反之,就会执行usb_deregister(),因为内核线程没有成功创建,hub就没法驱动起来了.于是就没有必要瞎耽误工夫了,该干嘛干嘛去.最后函数在2870行,返回-1.回到usb_init()中我们会知道,接下来usb_hub_cleanup()就会被调用.usb_hub_cleanup()同样定义于drivers/usb/core/hub.c中,
2873 void usb_hub_cleanup(void)
2874 {
2875 kthread_stop(khubd_task);
2876
2877 /*
2878 * Hub resources are freed for us by usb_deregister. It calls
2879 * usb_driver_purge on every device which in turn calls that
2880 * devices disconnect function if it is using this driver.
2881 * The hub_disconnect function takes care of releasing the
2882 * individual hub resources. -greg
2883 */
2884 usb_deregister(&hub_driver);
2885 } /* usb_hub_cleanup() */
这个函数我想没有任何必要解释了吧.kthread_stop()和刚才的kthread_run()对应,usb_deregister()和usb_register()对应.
总之,如果创建子进程出了问题,那么一切都免谈.啥也别玩了.歇菜了.
反之,如果成功了,那么kthread_run()的三个参数就是我们要关注的了,第一个hub_thread(),子进程将从这里开始执行.第二个是hub_thread()的参数,传递的是NULL,第三个参数就是精灵进程的名字,你ps –el看一下,比如像偶的这样子:
localhost:/usr/src/linux-2.6.22.1/drivers/usb/core # ps -el | grep khubd
1 S 0 1963 27 0 70 -5 - 0 hub_th ? 00:00:00 khubd
你就会发现有这么一个精灵进程运行着.所以,下一步,让我们进入hub_thread()来看看这个子进程吧,很显然,关于父进程,我们没什么好看的了.
======================华丽的分割线=====================
人的无聊,有时候很难用语言表达.以下关于IS_ERR的文字仅献给无聊的你.如果你对内存管理没有任何兴趣,就不用往下看了,跳到下一节吧.要想明白IS_ERR(),首先你得知道有一种空间叫做内核空间,不清楚也不要紧,我也不是很清楚,曾经,在复旦,上操作系统这门课的时候,我一度以为我已经成为天使了,因为我天天上课都在听天书.后来,确切地说是去年,我去微软全球技术中心(GSTC)面试的时候,那个manager就要我解释这个名词,要我谈一谈对内核空间和用户空间的理解,其实我也挺纳闷的,我只不过是希望能成为微软的一名技术支持工程师,居然还要懂内核,你说这是什么世道?中学时候,老师不是跟我说只要学好数理化,走遍天下都不怕吗?算了,不去想这些伤心往事了.结合IS_ERR()的代码来看,来自include/linux/err.h:
8 /*
9 * Kernel pointers have redundant information, so we can use a
10 * scheme where we can return either an error code or a dentry
11 * pointer with the same return value.
12 *
13 * This should be a per-architecture thing, to allow different
14 * error and pointer decisions.
15 */
16 #define MAX_ERRNO 4095
17
18 #ifndef __ASSEMBLY__
19
20 #define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
21
22 static inline void *ERR_PTR(long error)
23 {
24 return (void *) error;
25 }
26
27 static inline long PTR_ERR(const void *ptr)
28 {
29 return (long) ptr;
30 }
31
32 static inline long IS_ERR(const void *ptr)
33 {
34 return IS_ERR_VALUE((unsigned long)ptr);
35 }
36
37 #endif
关于内核空间,我只想说,所有的驱动程序都是运行在内核空间,内核空间虽然很大,但总是有限的.要知道即便是我们这个幅员辽阔的伟大祖国其空间也是有限的,也只有960万平方公里,所以内核空间当然也是一个有限的空间,而在这有限的空间中,其最后一个page是专门保留的,也就是说一般人不可能用到内核空间最后一个page的指针.换句话说,你在写设备驱动程序的过程中,涉及到的任何一个指针,必然有三种情况,一种是有效指针,一种是NULL,空指针,一种是错误指针,或者说无效指针.而所谓的错误指针就是指其已经到达了最后一个page.比如对于32bit的系统来说,内核空间最高地址0xffffffff,那么最后一个page就是指的0xfffff000~0xffffffff(假设4k一个page).这段地址是被保留的,一般人不得越雷池半步,如果你发现你的一个指针指向这个范围中的某个地址,那么恭喜你,你的代码肯定出错了.
那么你是不是很好奇,好端端的内核空间干嘛要留出最后一个page?这不是缺心眼儿吗?明明自己有1000块钱,非得对自己说只能用900块.实在不好意思,你说错了,这里不仅不是浪费一个page,反而是充分利用资源,把一个东西当两个东西来用.
看见16行那个MAX_ERRNO了吗?一个宏,定义为4095,MAX_ERRNO就是最大错误号,Linux内核中,出错有多种可能,因为有许许多多种错误,就像一个人进监狱,可能是像迟志强那样,在事业如日中天的时候女孩,可能是像张君大哥那样,因为长沙友谊商城后又抢农业银行,亦或者是马加爵大侠那样,受同学的气,最终让铁锤来说话.关于Linux内核中的错误,我们看一下include/asm-generic/errno-base.h文件:
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define <country-region w:st="on"><place w:st="on">EDOM</place></country-region> 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
最常见的几个是-EBUSY,-EINVAL,-ENODEV,-EPIPE,-EAGAIN,-ENOMEM,我相信不用说你写过代码调试过代码,只要你使用过Linux就有可能见过这几个错误,因为它们确实经常出现.这些是每个体系结构里都有的,另外各个体系结构也都定义了自己的一些错误代码.这些东西当然也都是宏,实际上对应的是一些数字,这个数字就叫做错误号.而对于Linux内核来说,不管任何体系结构,最多最多,错误号不会超过4095.而4095又正好是比4k小1,即4096减1.而我们知道一个page可能是4k,也可能是更多,比如8k,但至少它也是4k,所以留出一个page出来就可以让我们把内核空间的指针来记录错误了.什么意思呢?比如我们这里的IS_ERR(),它就是判断kthread_run()返回的指针是否有错,如果指针并不是指向最后一个page,那么没有问题,申请成功了,如果指针指向了最后一个page,那么说明实际上这不是一个有效的指针,这个指针里保存的实际上是一种错误代码.而通常很常用的方法就是先用IS_ERR()来判断是否是错误,然后如果是,那么就调用PTR_ERR()来返回这个错误代码.只不过咱们这里,没有调用PTR_ERR()而已,因为起决定作用的还是IS_ERR(),而PTR_ERR()只是返回错误代码,也就是提供一个信息给调用者,如果你只需要知道是否出错,而不在乎因为什么而出错,那你当然不用调用PTR_ERR()了,毕竟,男人,简单就好.当然,这里如果出错了的话,最终usb_deregister()会被调用,并且usb_hub_init()会返回-1.
相关推荐
### Linux那些事儿之我是HUB:深入理解USB HUB与Linux内核 #### 引言 在《Linux那些事儿之我是HUB》这本书中,作者通过个人经历与技术探索相结合的方式,介绍了USB HUB(集线器)的概念、工作原理以及在Linux内核中...
Linux那些事儿之我是Hub Linux那些事儿之我是USB Core Linux那些事儿之我是UHCI Linux那些事儿之我是EHCI控制器 Linux那些事儿之我是PCI Linux那些事儿之我是SCSI硬盘 Linux那些事儿之我是Block层 Linux那些事儿之我...
读过《linux那些事儿之我是U盘》的人,都知道其风格,我就不多说了。 导读: linux那些事儿之我是U盘 linux那些事儿之我是HUB linux那些事儿之我是USB Core linux那些事儿之我是UHCI Linux那些事儿之我是EHCI主机控制...
### Linux那些事儿之我是USB(第2版)关键知识点概览 #### 一、书籍概述 - **核心主题**:本书主要围绕Linux内核中的USB子系统展开,深入剖析其工作原理和技术细节。 - **目标读者**:面向Linux初学者、驱动开发者...
本压缩包文件"linux那些事儿之我是USB.zip"包含了深入理解Linux USB驱动及内核相关知识的九个文档,包括Block层、EHCI主机控制器、HUB、PCI、SCSI硬盘、Sysfs、UHCI、USB core以及U盘。这些文档旨在提供一个系统性的...
导读.doc Linux那些事儿之我是Block层.pdf Linux那些事儿之我是EHCI主机控制器.pdf Linux那些事儿之我是Hub.pdf Linux那些事儿之我是USB_core.pdf Linux那些事儿之我是U盘.pdf等等 Linux那些事儿系列全在这里了
3. **Linux那些事儿之我是U盘.pdf**: USB闪存驱动器,通常称为U盘,是常见的便携式存储设备。这部分可能讲述Linux如何识别、挂载和管理U盘,包括使用ums(USB Mass Storage)驱动程序,以及如何处理文件系统的读写...
》包括《Linux那些事儿之我是Hub》、《Linux那些事儿之我是Sysfs》《Linux那些事儿之我是UHCI》、《Linux那些事儿之我是USB core》、《Linux那些事儿之我是U盘》,令人叹为观止的一个linux系列书籍。只能说,江山代...
本文件包括第一第二完成版,第二版基于2.6.22内核,对USB子系统的大部分源代码逐行进行分析,系统地阐释了Linux内核中USB子系统是如何运转的,子系统内部的各个模块之间是如何互相协作、配合的。本次改版修改了第1版...
Linux那些事儿之我是Block层 ...Linux那些事儿之我是Hub Linux那些事儿之我是PCI Linux那些事儿之我是SCSI硬盘 Linux那些事儿之我是Sysfs Linux那些事儿之我是UHCI Linux那些事儿之我是USB core Linux那些事儿之我是U盘
“Linux那些事儿之我是HUB.pdf”会介绍USB集线器的工作原理。在Linux系统中,HUB驱动程序管理USB集线器,允许连接多个USB设备,并且会讨论如何处理拓扑变化和设备分配。 “Linux那些事儿之我是U盘.pdf”将关注通用...
### Linux那些事儿之我是EHCI主机控制器 #### 引言 在深入了解EHCI(Enhanced Host Controller Interface,增强型主机控制器接口)之前,让我们先简要回顾一下EHCI的背景及其在Linux系统中的作用。EHCI是USB 2.0...
《Linux那些事儿之我是Hub.pdf》将深入解析Hub在Linux中的实现,包括Hub设备的识别、设备连接和断开的处理、端口状态监控以及电源管理等方面。理解Hub的工作机制对于多设备环境下的系统集成至关重要。 USB core是...
Linux那些事儿之我是EHCI主机控制器.pdf Linux那些事儿之我是Block层.pdf Linux那些事儿之我是SCSI硬盘.pdf Linux那些事儿之我是Sysfs.pdf ...Linux那些事儿之我是Hub.pdf Linux那些事儿之我是UHCI.pdf
Linux那些事儿之我是U盘 Linux那些事儿之我是USB_core Linux那些事儿之我是UHCI Linux那些事儿之我是Sysfs ...Linux那些事儿之我是Hub Linux那些事儿之我是EHCI主机控制器 Linux那些事儿之我是Block层
Linux那些事儿之我是Block层.pdf Linux那些事儿之我是EHCI主机控制器.pdf Linux那些事儿之我是PCI.pdf Linux那些事儿之我是SCSI硬盘.pdf 注: 之前有人已经上传了《Linux那些事儿 系列》,其已经包含了:hub,sysfs...