任小强们说房价高涨从现在开始,股评家们说牛市从5000点开始。他们的开始需要我们的钱袋,我的开始只需要一台电脑,最好再有一杯茶,伴着几支小曲儿,不盯着钱总是会比较惬意的。生容易,活容易,生活不容易,因为要盯着钱。
USB core从USB子系统的初始化开始,我们也需要从那里开始,它们在文件drivers/usb/core/usb.c
我们看到一个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。
876 goto bus_register_failed;
879 goto host_init_failed;
882 goto major_init_failed;
885 goto driver_register_failed;
888 goto usb_devio_init_failed;
894 goto hub_init_failed;
904 usb_devio_init_failed:
906 driver_register_failed:
看到上面定义里的__init标记没,写过驱动的应该不会陌生,它对内核来说就是一种暗示,表明这个函数仅在初始化期间使用,在模块被装载之后,它占用的资源就会释放掉用作它处。它的暗示你懂,可你的暗示,她却不懂或者懂装不懂,多么让人感伤。它在自己短暂的一生中一直从事繁重的工作,吃的是草吐出的是牛奶,留下的是整个USB子系统的繁荣。
受这种精神所感染,我觉得还是有必要为它说的更多些,21世纪多的是任小强,缺的是知恩图报的人。对__init的定义在include/linux/init.h里
43 #define __init __attribute__ ((__section__ (".init.text")))
好像这里的疑问要更多,不过与__init相比,这点辛苦算什么,我会在它强大的精神支持下尽量说清楚的。那么__attribute__是什么?Linux内核代码使用了大量的GNU C扩展,以至于GNU C成为能够编译内核的唯一编译器,GNU C的这些扩展对代码优化、目标代码布局、安全检查等方面也提供了很强的支持。而__attribute__就是这些扩展中的一个,它主要被用来声明一些特殊的属性,这些属性主要被用来指示编译器进行特定方面的优化和更仔细的代码检查。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修饰的初始化函数那?好奇心是科学的原动力,茶叶蛋就是这么煮出来的,原子弹也是这么造出来的,__init背后的哲学总不会比它们还难,芙蓉姐姐说了,我挑战,我喜欢。好像越聊越远了,不过想想多少年前当自己还是青涩少年的时候就对它极度的好奇过,这里还是尽量将它说一下,也顺便积攒下rp,稍后的冠军杯里俺的米兰也好有个开门红。
要回答这个问题,还需要回顾一下上面938行的代码,那里已经提到subsys_initcall也是一个宏,它也在include/linux/init.h里定义
125 #define subsys_initcall(fn) __define_initcall("4",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/i386/kernel/vmlinux.lds文件,你就会见到前所未见的景象。我可以负责任的说,要看懂这个文件是需要一番功夫的,不过大家都是聪明人,聪明人做聪明事,所以你需要做的只是搜索initcall.init,然后便会看到似曾相识的内容
__inicall_start = .;
.initcall.init : AT(ADDR(.initcall.init) – 0xC0000000) {
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.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那些事儿系列之我是U盘》是一篇深入讲解Linux内核中USB驱动技术的文章,由复旦大学的fudan_abc撰写。该系列文章通过风趣的语言和通俗易懂的方式,阐述了Linux内核从2.6.10到2.6.22.1版本中关于USB驱动模块、...
作者用通俗幽默的语言简述了linux的相关知识点,很容易掌握的,建议大家好好学哦 我是U盘 说的是2.6.10的内核 我是Sysfs 说的是2.6.10的内核 戏说USB 说的是2.6.22的内核 我是Hub/UHCI/EHCI 说的是2.6.22.1的内核 ...
Linux是世界上最受欢迎的开源操作系统之一,它被广泛应用于服务器、超级计算机、移动设备以及嵌入式系统中。"兄弟连Linux基础知识与系统管理课件"是一份深入学习Linux操作系统的资源,适合初学者和希望提升技能的...
第一章面试受挫——代码无错就是好? 第二章代码规范、重构 第三章复制 VS复用 第四章业务的封装 第五章体会简单工厂模式的美妙 第六章工厂不好用了?...第十三章设计模式不能戏说!设计模式怎就不能戏说?
- 继承允许一个类(子类或派生类)从另一个类(父类或基类)继承属性和方法,减少了代码重复,提高了代码的重用性。Java使用`extends`关键字,C#使用`: base`关键字实现继承。 4. **多态**: - 多态是指同一种...
首先,从变量名开始,遵循运算符的优先级规则进行分析。 1. `int *p`:这里`*`与`int`结合,表示`p`是一个指向`int`类型的指针。 2. `int *p[3]`:`[]`的优先级高于`*`,所以`p`是一个包含3个元素的数组,每个元素...
特别是在幼儿教育阶段,如何将传统文化与现代科技相结合,激发儿童的学习兴趣和创造力,成为教育工作者面临的挑战之一。"大班音乐:戏说脸谱.ppt"这一课题,就为我们提供了一个将传统艺术与数字媒体巧妙结合的创新...
继承允许一个类(子类)从另一个类(父类)继承其属性和方法,从而实现代码复用。在C#中,一个类可以使用":"符号来继承一个或多个基类。子类不仅可以拥有父类的所有特性,还可以添加新的特性或重写父类的方法。这...
指针的本质、指针的三要素、类型与变量名
**四大发明之活字印刷——面向对象思想的胜利** - **可维护性**:面向对象的设计允许我们轻松地修改现有代码,类似于活字印刷中只需要更换需要更改的部分。这种能力对于软件开发至关重要,尤其是在需求变更频繁的...
幼儿园大班歌唱教案:戏说脸谱.doc
#### 12. 代理模式(Proxy) 代理模式为其他对象提供一个代理以控制对这个对象的访问。它通常用于需要增加额外职责的情况,比如远程代理、虚拟代理等。例如,当我们需要在获取网络数据之前做一些预处理或缓存处理时...
戏说国学.doc
幼儿园教案2021-幼儿园大班歌唱教案:戏说脸谱.doc 本教案旨在通过京剧《戏说脸谱》的唱歌活动,培养幼儿对中国国粹的认识和喜爱,提高幼儿的音乐能力和艺术鉴赏力。通过活动,幼儿可以初步学习京剧《戏说脸谱》的...
戏说绩效工资.doc
从狭义上讲,持续集成可以认为是一种基于某种或者某些变化对软件系统进行的经常性的构建活动(注:这里的构建活动不仅指编译打包 管Thoughtworks的首席科学家Martionfolwer为“持续集成”下了定义,但由于自身背景...
2. **下载安装包**:从官方渠道获取最新的Kangle安装包,这里是"Kangle-3.5.8"。将其上传到服务器的指定目录,例如 `/home/downloads/`。 3. **解压安装包**:使用命令行工具,如`tar`命令,解压缩文件: ``` cd ...
戏说TCP网络编程.doc
”而“矮”字则可能自嘲:“我虽矮小,但也能挡住那些无的放矢之箭。”这种对比,不仅让读者了解汉字形音义的丰富变化,也让人们思考汉字中蕴含的社会意义。 在“庄”对“压”的批评中,反映了社会地位和权力的象征...