• ⭐驱动之module_init/module_exit与系统启动关系


    在前面helloworld的编写里面,我们使用了两个宏分别是module_init和module_exit,这里分析下为什么使用这两个宏。

    在写模块的时候有两个特殊的函数,分别是init_module和cleanup_module,这两个函数分别在insmod的时候和rmmod的时候调用,并且insmod和rmmod只识别这两个特殊的函数,可是我们前面的例子里面并没有这两个函数。怎么会这样呢,那就必须得说说module_init/module_exit了。

    一个驱动可以作为一个模块动态的加载到内核里,也可以作为内核的一部分静态的编译进内核,module_init/module_exit也就有了两个含义:

    一、动态编译成模块

    在内核里有如下定义:

            /* Each module must use one module_init(). */
            #define module_init(initfn) 
                    static inline initcall_t __inittest(void) 
                    { return initfn; } 
                    int init_module(void) __attribute__((alias(#initfn)));
    
    /* This is only required if you want to be unloadable. */
            #define module_exit(exitfn) 
                    static inline exitcall_t __exittest(void) 
                    { return exitfn; } 
                     void cleanup_module(void) __attribute__((alias(#exitfn)));

    首先我们可以发现发现module_init有两个含义:

    1、验证加载函数的格式

    static inline initcall_t __inittest(void)

    { return initfn; }

    这个函数的作用是验证我们穿过来的加载函数格式是否正确,linux内核规定加载函数的的原型是:

    typedef int (*initcall_t)(void);

    所以我们写加载函数的时候必须是返回值为int参数为void的函数,这个在内核里要求比较严格,所以我们写加载函数的时候必须按照这个约定。

    2、定义别名

    int init_module(void) __attribute__((alias(#initfn)));

    这段代码的作用是给我们的加载函数定义一个别名,别名就是我们前面提到的init_module,这样insmod就能够执行我们的加载函数了。

    module_exit的作用和module_init一样,同样也是验证函数格式和定义别名。

    二、静态编译

    在静态编译的时候module_init的定义如下:
            #define module_init(x) __initcall(x);
            #define __initcall(fn) device_initcall(fn)
            #define device_initcall(fn) __define_initcall("6",fn,6)
            #define __define_initcall(level,fn,id)
                    static initcall_t __initcall_##fn##id __used
                    __attribute__((__section__(".initcall" level ".init"))) = fn

    通过这些段代码,我们能够看出最终的结果是将我们的使用module_init修饰的函数指针链接到一个叫.initcall的段里,也就是说最终所以的使用module_init修饰的函数指针都被链接在这个段里,最终内核在启动的时候顺序调用所有链接在这个段里的函数,实现设备的初始化。

    module_exit在静态编译的时候没有意义,因为静态编译的驱动无法卸载!

     显然 对动态加载的模块是无效的;

    Init.h中有相关initcall的启动次序,在system.map中可看出具体的__initcall指针的前后次序

    #define pure_initcall(fn) __define_initcall("0",fn,0)

    #define core_initcall(fn) __define_initcall("1",fn,1)

    #define core_initcall_sync(fn) __define_initcall("1s",fn,1s)

    #define postcore_initcall(fn) __define_initcall("2",fn,2)

    #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)

    #define arch_initcall(fn) __define_initcall("3",fn,3)

    #define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)

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

    #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)

    #define fs_initcall(fn) __define_initcall("5",fn,5)

    #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)

    #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)

    #define device_initcall(fn) __define_initcall("6",fn,6)

    #define device_initcall_sync(fn) __define_initcall("6s",fn,6s)

    #define late_initcall(fn) __define_initcall("7",fn,7)

    #define late_initcall_sync(fn) __define_initcall("7s",fn,7s)

    module_init在的启动序号为6,它的展开后就是__define_initcall("6",fn,6)

    #definedevice_initcall(fn) __define_initcall("6",fn,6)

    #define__initcall(fn) device_initcall(fn)

    #definemodule_init(x) __initcall(x);


    Kernel通过调用do_initcalls(void)加载模块,具体流程如下图:

    static void__init do_initcalls(void)

    {

    initcall_t*fn;

    for (fn =__early_initcall_end; fn < __initcall_end; fn++)

    do_one_initcall(*fn);

    /* Makesure there is no pending stuff from the initcall sequence */

    flush_scheduled_work();

    }

    因此驱动模块在Kernel启动过程中的启动次序是非常靠后的

    具体的每个驱动的启动次序可以从system.map看出,特别对于同一个优先级的各类驱动:

    c003288ct __initcall_i2c_init2

    c00328b0 t__initcall_video_early_init3

    c00328b4 t__initcall_video2_early_init3

    c00328b8t __initcall_aml_i2c_init3

    c0032c18t __initcall_i2c_dev_init6

    c0032c28 t__initcall_videodev_init6

    c0032c30t __initcall_v4l2_i2c_drv_init6

    c0032c34t __initcall_v4l2_i2c_drv_init6

    c0032d24 t__initcall_video_init6

    c0032d28 t__initcall_video2_init6

    对于同一级别的 __initcall的次序 主要由MakeFile中.o文件的链接次序决定,具体看Kernel下的主Makefile ---- Build vmlinux

    以及kernel/driver 下的obj-y

    /* end */

  • 相关阅读:
    GET POST区别
    http1.0 1.1 2.0区别
    分布式系统理论之Quorum机制
    MySQL解析过程、执行过程
    redis常见问题和解决方案
    Windows下安装Linux虚拟机的用途和好处
    ping,telnet,ssh命令的理解
    强化学习入门 第五讲 值函数逼近
    强化学习入门第四讲 时间差分方法
    强化学习基础 第三讲 蒙特卡罗方法
  • 原文地址:https://www.cnblogs.com/Ph-one/p/6112522.html
Copyright © 2020-2023  润新知