设备驱动的probe、remove以及shutdown的顺序

本文探讨了Linux系统中设备驱动的初始化和关闭过程,包括驱动probe与remove的执行时机、不同场景下的驱动初始化顺序及系统关机或重启时的shutdown顺序。

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

转自http://blog.chinaunix.net/uid-29955651-id-5175102.html

系统中存在很多设备,每个设备在kernel中都有对应的驱动,那么这些驱动初始化设备以及关闭设备的顺序是怎样的呢?本文试图解答这个问题。

驱动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

驱动初始化顺序

    从上文可以看出,设备和驱动,任意一个注册时都会调用probe,但调用的前提是另一个已经存在。所以有很多组合场景,不同场景顺序可以各不相同。这里先列出我们希望解答的场景:所有驱动都静态编译在vmlinx而非动态加载的模块,设备都在dts中定义,只讨论相同级别的同一个bus下的设备驱动初始化顺序

    在这种场景下,设备的创建在bus驱动的初始化过程中,解析dts节点,创建所有的设备,而此时这些设备的驱动都还没有加载。

    内核驱动的常规做法是在module_init时注册驱动,还有很多封装module_init的宏定义,比如module_platform_drivermodule_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文件的先后进行调整。

驱动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中的前后顺序


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值