分类: LINUX
2015-08-27 11:28:10
系统中存在很多设备,每个设备在kernel中都有对应的驱动,那么这些驱动初始化设备以及关闭设备的顺序是怎样的呢?本文试图解答这个问题。
1 驱动probe/remove执行时机
probe的执行有两个时机,一是设备创建时,二是驱动注册时;remove相对也有两个执行时机,一是设备注销时,二是驱动注销时。
设备创建时:
无论是通过platform_ device _register,还是通过dts创建设备,最终会有以下流程
device_add
-> bus_probe_device
-> device_attach
-> bus_for_each_drv(dev->bus, NULL, dev, __device_attach) //遍历bus所有驱动
-> __device_attach //如果match,则启动probe
-> driver_probe_device
-> really_probe
-> drv->probe
驱动注册时:
driver_register
-> bus_add_driver
-> driver_attach
-> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) //遍历bus所有驱动
-> __driver_attach //如果match,则启动probe
-> driver_probe_device
-> really_probe
-> drv->probe
设备注销时:
device_del
-> bus_remove_device
-> device_release_driver
-> __device_release_driver
-> drv->remove
驱动注销时:
driver_unregister
-> bus_remove_driver
-> driver_detach //遍历所有该驱动控制的设备
-> __device_release_driver
-> drv->remove
2 驱动初始化顺序
从上文可以看出,设备和驱动,任意一个注册时都会调用probe,但调用的前提是另一个已经存在。所以有很多组合场景,不同场景顺序可以各不相同。这里先列出我们希望解答的场景:所有驱动都静态编译在vmlinx而非动态加载的模块,设备都在dts中定义,只讨论相同级别的同一个bus下的设备驱动初始化顺序。
在这种场景下,设备的创建在bus驱动的初始化过程中,解析dts节点,创建所有的设备,而此时这些设备的驱动都还没有加载。
内核驱动的常规做法是在module_init时注册驱动,还有很多封装module_init的宏定义,比如module_platform_driver、module_i2c_driver等等,它们的核心思想就是把设备注册和module_init封装到一起,简化调用。
此时驱动的初始化顺序,就是驱动模块的加载顺序。那么驱动的加载顺序是由什么决定的呢?首先看module_init的定义,如上假设的情况下所有模块静态编译,而非模块化编译,module_init定义如下:
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
这些initcall被赋予了不同级别,其中device_initcall的级别为6,相对较低,在系统启动较晚调用,调用流程如下:
start_kernel
-> rest_init
-> kernel_init
-> kernel_init_freeable
-> do_basic_setup
-> do_initcalls //遍历各个优先级
-> do_initcall_level //遍历相同优先级各个函数
-> do_one_initcall
遍历相同优先级的initcall时,是按照地址从低到高进行的,因此调用顺序就是编译时连接到initcall section的顺序,通过System.map可以查到顺序,例如:
62054:ffffffc000e61c38 T __initcall_start
62055:ffffffc000e61c38 t __initcall_trace_init_flags_sys_exitearly
62057:ffffffc000e61c40 t __initcall_trace_init_flags_sys_enterearly
62058:ffffffc000e61c48 t __initcall_init_hw_perf_eventsearly
62059:ffffffc000e61c50 t __initcall_cpu_suspend_initearly
链接顺序可以通过调整Makefile中的.o文件的先后进行调整。
3 驱动shutdown顺序
系统关机或重启的过程中,会调用设备驱动的shutdown函数来完成设备的关闭操作,有需要的设备可以在驱动中定义该函数。
其调用流程如下:
kernel_restart
-> kernel_restart_prepare
-> device_shutdown //逆向遍历devices_kset->list所有device
-> dev->driver->shutdown
由此可见,各个驱动shutdown的顺序由设备在链表中的位置决定,后添加的先调用。
设备添加到链表中的流程如下:
device_initialize
device_add
-> kobject_add
-> kobj_kset_join
-> list_add_tail(&kobj->entry, &kobj->kset->list)
由此可见,设备注册时,会把节点添加到devices_kset->list末尾
因此驱动的shutdown顺序是设备注册的逆序,而在通过dts创建设备的系统中,设备的注册顺序是设备节点在dts中的前后顺序。