USB core从USB子系统的初始化开始,我们也需要从那里开始,它们在文件drivers/usb/core/usb.c

subsys_initcall(usb_init);
module_exit(usb_exit);

我们看到一个subsys_initcall,复旦人甲说它也是一个宏,我们可以把它理解为module_init,只不过因为这部分代码比较核心,开发者们把它看作一个子系统,而不仅仅是一个模块,这也很好理解,usbcore这个模块它代表的不是某一个设备,而是所有usb设备赖以生存的模块,Linux中,像这样一个类别的设备驱动被归结为一个子系统。比如pci子系统,比如scsi子系统,基本上,内核源码中drivers/目录下面第一层的每个目录都算一个子系统,因为它们代表了一类设备。subsys_initcall(usb_init)的意思就是告诉我们usb_init是我们真正的初始化函数,而usb_exit()将是整个usb子系统的结束时的清理函数,于是我们就从usb_init开始看起。

既然复旦人甲都这么说了,那咱们就从usb_init开始看起吧。至于子系统在内核里具体的描述,牵涉到linux设备模型了,可以去看ldd3,或者更详细的。目前来说,我们只需要知道子系统通常显示在sysfs分层结构中的顶层,比如块设备子系统对应/sys/block,当然也不一定,usb子系统对应的就是/sys/bus/usb。

drivers/usb/core/usb.c

static int __init usb_init(void)
{int retval;if (nousb) {pr_info("%s: USB support disabled\n", usbcore_name);return 0;}retval = usb_debugfs_init();if (retval)goto out;usb_acpi_register();retval = bus_register(&usb_bus_type);if (retval)goto bus_register_failed;retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);if (retval)goto bus_notifier_failed;retval = usb_major_init();if (retval)goto major_init_failed;retval = usb_register(&usbfs_driver);if (retval)goto driver_register_failed;retval = usb_devio_init();if (retval)goto usb_devio_init_failed;retval = usb_hub_init();if (retval)goto hub_init_failed;retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);if (!retval)goto out;usb_hub_cleanup();
hub_init_failed:usb_devio_cleanup();
usb_devio_init_failed:usb_deregister(&usbfs_driver);
driver_register_failed:usb_major_cleanup();
major_init_failed:bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
bus_notifier_failed:bus_unregister(&usb_bus_type);
bus_register_failed:usb_acpi_unregister();usb_debugfs_cleanup();
out:return retval;
}

看到上面定义里的__init标记没,写过驱动的应该不会陌生,它对内核来说就是一种暗示,表明这个函数仅在初始化期间使用,在模块被装载之后,它占用的资源就会释放掉用作它处。
对__init的定义在include/linux/init.h里

#define __init       __section(.init.text) __cold notrace

好像这里的疑问要更多,不过与 __init 相比,这点辛苦算什么,我会在它强大的精神支持下尽量说清楚的。Linux内核代码使用了大量的GNU C扩展,以至于GNU C成为能够编译内核的唯一编译器,GNU C的这些扩展对代码优化、目标代码布局、安全检查等方面也提供了很强的支持。GNU C支持十几个属性,section是其中的一个,我们查看gcc的手册可以看到下面的描述
‘section ("section-name")'   Normally, the compiler places the code it generates in the `text'
section. Sometimes, however, you need additional sections, or you
need certain particular functions to appear in special sections.
   The `section' attribute specifies that a function lives in a
   particular section. For example, the declaration:

     extern void foobar (void) __attribute__ ((section ("bar")));

   puts the function ‘foobar' in the ‘bar' section. 
 
Some file formats do not support arbitrary sections so the
   ‘section' attribute is not available on all platforms. If you
   need to map the entire contents of a module to a particular
   section, consider using the facilities of the linker instead.

通常编译器将函数放在.text节,变量放在.data 或 .bss 节,使用section属性,可以让编译器将函数或变量放在指定的节中。那么前面对__init的定义便表示将它修饰的代码放在 .init.text 节。连接器可以把相同节的代码或数据安排在一起,比如 __init 修饰的所有代码都会被放在 .init.text 节里,初始化结束后就可以释放这部分内存。
那内核又是如何调用到这些 __init 修饰的初始化函数那?
要回答这个问题,还需要回顾一下上面938行的代码,那里已经提到subsys_initcall也是一个宏,它也在include/linux/init.h里定义

#define subsys_initcall(fn)      __define_initcall(fn, 4)

这里又出现了一个宏__define_initcall,它是用来将指定的函数指针fn放到initcall.init节里,也在include/linux/init.h文件里定义,这里就不多说了,有那点意思就可以了。而对于具体的subsys_initcall宏,则是把fn放到.initcall.init的子节.initcall4.init里。要弄清楚.initcall.init、.init.text和.initcall4.init这样的东东,我们还需要了解一点内核可执行文件相关的概念。

内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init 数据、bass 等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。 vmlinux.lds是存在于 arch/<target>/ 目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。

涉及到的东西越来越多了是吧,先深呼吸,平静一下,坚定而又勇敢的打开arch/arm/kernel/vmlinux.lds文件,你就会见到前所未见的景象。我可以负责任的说,要看懂这个文件是需要一番功夫的,不过大家都是聪明人,聪明人做聪明事,所以你需要做的只是搜索initcall.init,然后便会看到似曾相识的内容

__initcall_start = .;*(.initcallearly.init) __early_initcall_end = .;*(.initcall0.init) *(.initcall0s.init)*(.initcall1.init) *(.initcall1s.init)*(.initcall2.init) *(.initcall2s.init)*(.initcall3.init) *(.initcall3s.init)*(.initcall4.init) *(.initcall4s.init)*(.initcall5.init) *(.initcall5s.init)*(.initcallrootfs.init)*(.initcall6.init) *(.initcall6s.init)*(.initcall7.init) *(.initcall7s.init)__initcall_end = .;

这里的 __initcall_start 指向.initcall.init节的开始,__initcall_end指向它的结尾。而.initcall.init节又被分为了7个子节,分别是
.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init
我们的subsys_initcall宏便是将指定的函数指针放在了 .initcall4.init 子节。其它的比如 core_initcall 将函数指针放在 .initcall1.init 子节,device_initcall 将函数指针放在了 .initcall6.init 子节等等,都可以从include/linux/init.h文件找到它们的定义。各个字节的顺序是确定的,即先调用.initcall1.init中的函数指针再调用.initcall2.init中的函数指针,等等。__init 修饰的初始化函数在内核初始化过程中调用的顺序和.initcall.init节里函数指针的顺序有关,不同的初始化函数被放在不同的子节中,因此也就决定了它们的调用顺序。

至于实际执行函数调用的地方,就在/init/main.c文件里,内核的初始化么,不在那里还能在哪里,里面的do_initcalls函数会直接用到这里的__initcall_start、__initcall_end来进行判断,不多说了。我们的思念已经入滔滔江水泛滥成灾了,还是回到久违的usb_init函数吧。

Linux那些事儿 之 戏说USB(8)从这里开始相关推荐

  1. Linux那些事儿 之 戏说USB(19)设备

    转载地址:http://blog.csdn.net/fudan_abc/article/details/1807800 第一眼看到struct usb_device这个结构,我仿佛置身于衡山路的酒吧里 ...

  2. 【转】Linux那些事儿 之 戏说USB(19)设备

    第一眼看到struct usb_device这个结构,我仿佛置身于衡山路的酒吧里,盯着舞池里扭动的符号,眼神迷离. 交大里苟了几年,毕业了又是住在学校附近的徐虹北路上,沿着虹桥路走过去,到徐家汇不过1 ...

  3. Linux那些事儿 之 戏说USB(33)字符串描述符

    关于字符串描述符,前面的前面已经简单描述过了,地位仅次于设备/配置/接口/端点四大描述符,那四大设备必须得支持,而字符串描述符对设备来说则是可选的. 这并不是就说字符串描述符不重要,对咱们来说,字符串 ...

  4. Linux那些事儿 之 戏说USB(28)设备的生命线(十一)

    现在已经使用GET_DESCRIPTOR请求取到了包含一个配置里所有相关描述符内容的一堆数据,这些数据是raw的,即原始的,所有数据不管是配置描述符.接口描述符还是端点描述符都彼此的挤在一起,所以得想 ...

  5. Linux那些事儿 之 戏说USB(27)设备的生命线(十)

    跟着设备的生命线走到现在,我算是明白了,什么东西的发展都是越往后越高级越复杂.再给张小表,看看现在和上次那张表出现的时候有什么变化. state        USB_STATE_ADDRESS sp ...

  6. Linux那些事儿 之 戏说USB(25)设备的生命线(八)

    回到struct usb_hcd,继续努力的往下看. 7行,又见kref,usb主机控制器的引用计数.struct usb_hcd也有自己专用的引用计数函数,看drivers/usb/core/hcd ...

  7. Linux那些事儿 之 戏说USB(22)设备的生命线(五)

    下面接着看那三个基本点. 第一个基本点,usb_alloc_urb函数,创建urb的专用函数,为一个urb申请内存并做初始化,在drviers/usb/core/urb.c里定义. struct ur ...

  8. Linux那些事儿 之 戏说USB(15)设备

    struct usb_device结构冗长而又杂乱 include/linux/usb.h struct usb_device {int devnum;char devpath[16];u32 rou ...

  9. Linux那些事儿 之 戏说USB(25)设备的生命线(四)

    转载地址:http://blog.csdn.net/fudan_abc/article/details/1819919 洗澡是屁股享福,脑袋吃苦:看电影是脑袋享福,屁股吃苦:看内核代码是脑袋.屁股都吃 ...

  10. Linux那些事儿 之 戏说USB(大结局)还是那个match

    从上次在几米的向左走向右走遇到usb总线的那个match函数usb_device_match()开始到现在,遇到了设备,遇到了设备驱动,遇到了接口,也遇到了接口驱动,期间还多次遇到usb_device ...

最新文章

  1. java automapper 使用_19.AutoMapper 之开放式泛型(Open Generics)
  2. tkinter项目实战_Python GUI项目实战(二)主窗体的界面设计与实现
  3. DeepMind丢掉了归一化,让图像识别训练速度提升了8.7倍 | 已开源
  4. Java Web开发入门 - 第5章 Git
  5. 数组与字符串三(Cocos2d-x 3.x _Array容器)
  6. java 网络 io流_【015期】JavaSE面试题(十五):网络IO流
  7. java swing jar 字体不一样_终于读完了腾讯T4架构师整理的Java深度学习笔记
  8. 容器源码分析之ArrayList(二)
  9. SQL Server 2005系列教学(9) SQL 存储过程
  10. 返回一个数组的连续子数组和的最大值
  11. python之argmax
  12. oracle 登录不了的解决
  13. 小黑与Vista试用手记
  14. Apache Shiro框架简介
  15. windows minidump 的那些事
  16. mysql recordcount 1_移植到 MySQL-对 MYSQL 数据库使用 ASP Recordcount 的问题
  17. 微信小游戏开发实战教程15-关卡编辑器的制作以及关卡分享功能的实现
  18. linux文件名小写变大写命令,通过Ubuntu命令行将文件名转换为小写
  19. 生活没那么复杂,“不要脸”就对了
  20. python二级考试基础编程历练,临时抱抱佛脚;逢考必过,奥利给!

热门文章

  1. android 根据时间获取时间对应的星期
  2. Cnnot find System Java Compiler Ensure that you have installed a JDK
  3. 2022-2028年中国B2C电子商务市场投资分析及前景预测报告
  4. php 二维数组排序,多维数组排序
  5. 【转】Flask安装
  6. 读《大道至简》第六章感想
  7. WordPress数据库优化技巧
  8. 关于css中float的一切
  9. java的标记接口_Java中的标记接口?
  10. 《OpenCV3编程入门》学习笔记1 邂逅OpenCV