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

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

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

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为例

注意: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) = .;

将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,
};

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

.initcall##level##.init段中的函数最终会在do_one_initcall函数中执行。调用关系如下所示

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)


目录
相关文章
|
12月前
|
Linux vr&ar 开发者
驱动编译进Linux内核
驱动编译进Linux内核
177 0
|
12月前
|
存储 Unix Linux
Linux设备驱动程序(二)——建立和运行模块
本章介绍所有的关于模块和内核编程的关键概念,通过一个 hello world 模块来认识驱动加载的流程及相关细节。
97 0
|
Linux
【Linux系统开发】 x210开发板 虚拟驱动创建流程(驱动编译进内核)
【Linux系统开发】 x210开发板 虚拟驱动创建流程(驱动编译进内核)
107 0
|
NoSQL Linux 编译器
操作系统课程设计:新增Linux驱动程序(重制版)(一)
操作系统课程设计:新增Linux驱动程序(重制版)
175 1
操作系统课程设计:新增Linux驱动程序(重制版)(一)
|
Linux C语言 Windows
操作系统课程设计:新增Linux驱动程序(重制版)(三)
操作系统课程设计:新增Linux驱动程序(重制版)
178 0
操作系统课程设计:新增Linux驱动程序(重制版)(三)
|
Linux Shell
操作系统课程设计:新增Linux驱动程序(重制版)(二)
操作系统课程设计:新增Linux驱动程序(重制版)
120 0
操作系统课程设计:新增Linux驱动程序(重制版)(二)
驱动开发:内核中枚举进线程与模块
内核枚举进程使用`PspCidTable` 这个未公开的函数,它能最大的好处是能得到进程的EPROCESS地址,由于是未公开的函数,所以我们需要变相的调用这个函数,通过`PsLookupProcessByProcessId`函数查到进程的EPROCESS,如果`PsLookupProcessByProcessId`返回失败,则证明此进程不存在,如果返回成功则把EPROCESS、PID、PPID、进程名等通过DbgPrint打印到屏幕上。
442 0
驱动开发:内核中枚举进线程与模块
驱动开发:实现驱动加载卸载工具
驱动程序加载工具有许多,最常用的当属`KmdManager`工具,如果驱动程序需要对外发布那我们必须自己编写实现一个驱动加载工具,当需要使用驱动时可以拉起自己的驱动,如下将实现一个简单的驱动加载工具,该工具可以实现基本的,安装,加载,关闭,卸载等操作日常使用完全没问题。
411 0
|
存储 API C语言
Win知识 - 程序是怎样跑起来的——应用和硬件无关?
Win知识 - 程序是怎样跑起来的——应用和硬件无关?
120 0
Win知识 - 程序是怎样跑起来的——应用和硬件无关?
|
Java
移植JDK,确实需要CPU指令级的工作
移植JDK,确实需要CPU指令级的工作
105 0

热门文章

最新文章