编译进内核的驱动是如何工作的

编译进内核的驱动是如何工作的


文章目录

  • 编译进内核的驱动是如何工作的
  • 编译进内核的驱动系统是如何运行的?
    • module_init
    • __initcall##level##_start关联到".initcall##level##.init"段和".initcall##level##s.init"段
    • 执行.initcall##level##.init段中的函数
    • 让驱动快一点被加载


编译进内核的驱动系统是如何运行的?

module_init

几乎所有的驱动都要用到module_init,为什么都要用到module_init呢?module_init做
了哪些事情呢?我们来分下module_init的代码。
module_init函数原型在当中,可以找到如下代码:
注意:module_exit在编译进内核的时侯投有意因为静态编译的驱动无法卸载!所所以只分析module_init。
include/linux/init.h

#ifndef MODULE


/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 * 
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x)    __initcall(x);

/**
 * module_exit() - driver exit entry point
 * @x: function to be run when driver is removed
 * 
 * module_exit() will wrap the driver clean-up code
 * with cleanup_module() when used with rmmod when
 * the driver is a module.  If the driver is statically
 * compiled into the kernel, module_exit() has no effect.
 * There can only be one per module.
 */
#define module_exit(x)    __exitcall(x);

#else /* MODULE */


/* 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)));

#define __setup_param(str, unique_id, fn)    /* nothing */
#define __setup(str, func)             /* nothing */
#endif

对于module_init的定义是一个条件编译,如果没有定义MODULE,则module_init为
initcall(x);如果定义了MODULE
,module_init为int init_module(void) attribute((alias(#initfn)));
MODULE是由顶层Makefile定义的,打开内核源码下的顶层Makefile,

驱动编译进内核KBUILD_AFLAGS_KERNEL决定
驱动编译成模块KBUILD_AFLAGS_MODULE决定
所以这里时驱动编译成模块

#define module_init(x)    __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)        __define_initcall(fn, 6)
#define __define_initcall(fn, id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" #id ".init"))) = fn; \
    LTO_REFERENCE_INITCALL(__initcall_##fn##id)

以hello_world为例
编译进内核的驱动是如何工作的_第1张图片

注意:initcall_t是函数指针,原型如下

typedef int (*initcall_t)(void);

所以,当时使用module_init宏定义接囗以后,会声明了一个initcall_helloworld6函数指针变量。將这个函数指针初始化为hello_world,编译的时候将这个函数指针放在.initcall6.init段中。
内核中很多驱动都用了module_init,这些函数指针会按照编译先后顺序放在.initcall6.init段中。
除了module_init,还有其他的宏定义接囗,他们的原型都是defin_initcall,区别就是优先级不一样,也就是系统启动的时侯的启动顺序不一样,module_init的优先级是6。
include/linux/init.h

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

#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##level##_start关联到".initcall##level##.init"段和".initcall##level##s.init"段

注:这里的##level##代表就是组1,2,3,…
打开include/asm-generic/vmlinux.lds.h文件。找到以下代码

#define INIT_CALLS_LEVEL(level)                        \
        VMLINUX_SYMBOL(__initcall##level##_start) = .;        \
        *(.initcall##level##.init)                \
        *(.initcall##level##s.init)                \

#define INIT_CALLS                            \
        VMLINUX_SYMBOL(__initcall_start) = .;            \
        *(.initcallearly.init)                    \
        INIT_CALLS_LEVEL(0)                    \
        INIT_CALLS_LEVEL(1)                    \
        INIT_CALLS_LEVEL(2)                    \
        INIT_CALLS_LEVEL(3)                    \
        INIT_CALLS_LEVEL(4)                    \
        INIT_CALLS_LEVEL(5)                    \
        INIT_CALLS_LEVEL(rootfs)                \
        INIT_CALLS_LEVEL(6)                    \
        INIT_CALLS_LEVEL(7)                    \
        VMLINUX_SYMBOL(__initcall_end) = .;

编译进内核的驱动是如何工作的_第2张图片

将INIT-CALLS宏展开以后:

#define INIT_CALLS                            \
        __initcall_start = .;            \
        *(.initcallearly.init)                    \
  
                  __initcall0_start = .;            \
        *(.initcall0.init)                    \
                *(.initcall0s.init)                                    \
                
        INIT_CALLS_LEVEL(1)                    \
        INIT_CALLS_LEVEL(2)                    \
        INIT_CALLS_LEVEL(3)                    \
        INIT_CALLS_LEVEL(4)                    \  
        INIT_CALLS_LEVEL(5)                    \
  
                  __initrootfs_start = .;            \
        *(.initrootfs.init)                    \
                *(.initrootfss.init)                                    \
                
                   __initcall6_start = .;            \
        *(.initcall6.init)                    \
                *(.initcall6s.init)                                    \
  
        INIT_CALLS_LEVEL(7)                    \
        VMLINUX_SYMBOL(__initcall_end) = .;

所以宏INIT_CALLS的作用就是相同等级的段会放在同一块内存区域,不同等级的块的内存区域会按照等级的大小依次链接在一起。
这里的__initcall0_startt是一个变量。__initcall0_start中记录着__initcall0_start段的首地址。这个__initcall0_start变量在哪用的呢?在init/main.c中,通过extern的方法使用了
__initcall0_start等变量。然后将这些首地址放在了数组initcall_levels中。
init/main.c

extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];

static initcall_t *initcall_levels[] __initdata = {
    __initcall0_start,
    __initcall1_start,
    __initcall2_start,
    __initcall3_start,
    __initcall4_start,
    __initcall5_start,
    __initcall6_start,
    __initcall7_start,
    __initcall_end,
};

编译进内核的驱动是如何工作的_第3张图片

执行.initcall##level##.init段中的函数

.initcall##level##.init段中的函数最终会在do_one_initcall函数中执行。调用关系如下所示
编译进内核的驱动是如何工作的_第4张图片

start_kernel定义在init/main.c当中,大家可以自己上按照上图中的调用关系追踪。
do_initcalls定义在init/main.c文件当中。我们找到以下代码,以下代码为核心代码:

static void __init do_initcalls(void)
{
    int level;

    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
        do_initcall_level(level);
}

这个for循环一共会循环执行7次do_initcall_level从for循环中我们看出来0的优先级要大于1,数字越小优先级越高。相同等级带s的优先级要小于不带s的优先级。
在do_initcall_level里面执行每一个段中的保存的函数指针

static void __init do_initcall_level(int level)
{
    initcall_t *fn;

    strcpy(initcall_command_line, saved_command_line);
    parse_args(initcall_level_names[level],
           initcall_command_line, __start___param,
           __stop___param - __start___param,
           level, level,
           &repair_env_string);

    for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
        do_one_initcall(*fn);
}

让驱动快一点被加载

使用比modue_init优先级更高的函数:

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

#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)

你可能感兴趣的:(Linux驱动V2.0,#,内核模块,arm开发,linux,驱动开发,C语言,嵌入式)