当前位置:首页 > IT技术

module_init()加载设备驱动

时间:2019-06-06 07:40:00来源:IT技术作者:SEO探针小编阅读:62次「手机版」
 

module_init

我们知道在写设备驱动的时候通常要为某个设备实现xxx_init函数,并将该函数传入module_init(xxx_init), 当kernel启动之后该设备驱动就可以被内核加载,这一章节将以倒叙的方式详细介绍了内核是如何加载module_init()函数,并最终调用到xxx_init函数的。

module_init()定义在include/linux/module.h中,

#ifndef MODULE

#define module_init(x)    __initcall(x);

#define module_exit(x)    __exitcall(x);

#else /* MODULE */

/* Each module must use one module_init(). */

#define module_init(initfn)                    \

static inline initcall_t __maybe_unused __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 __maybe_unused __exittest(void)        \

{ return exitfn; }                    \

void cleanup_module(void) __attribute__((alias(#exitfn)));

#endif

其中有两部分定义,设备驱动的加载有两种方式,一种是编译进内核,一种是以模块的方式加载,加载方式不同定义的形式也略有不同。#ifndef MODULE表明当设备驱动编译进内核时, module_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)

#define __initcall(fn) device_initcall(fn)

从__inicall定义可以,module_init()-->__initcall(fn)--->__define_initcall(fn)---> __define_initcall(fn, 6),可以看到在initcall段中启动的部分最终都是通过__define_initcall设置的。这部分的代码可以在include/linux/int.h中找到。

#define __define_initcall(fn, id) \

static initcall_t __initcall_##fn##id __used \

__attribute__((__section__(".initcall" #id ".init"))) = fn;

下面分析一下该宏的具体细节,比如,__define_initcall传递过来的参数为(xxx_init,  6), 经过##和#的作用之后,就将xxx_init连接到之前的字符串中,为__initcall_xxx_init6,除此之外还有一个section的定义".initcall" #id ".init",由于前后两部分都是字符串,所以#id的作用就是字符串化,组合成.initcall6.init.

说了这么多,貌似我们再跟踪代码就跟不下去了,因为你再也找不到代码的下一步调用在哪里了,我们在代码中搜索一下发现在vmlinux.lds.S中找到了线索,我们本文是以arm64为前提的所以文件的路径为(./arch/arm64/kernel/vmlinux.lds.S). 在这个链接器脚本中我们发现了很多段,其中也包含上文提到的initcall段,现在以一张图片来展示各个段的全貌.....

#define __init        __section(.init.text) __cold  __latent_entropy __noinitretpoline

#define __initdata    __section(.init.data)

#define __initconst    __section(.init.rodata)

#define __exitdata    __section(.exit.data)

#define __exit_call    __used __section(.exitcall.exit)

除此之外发现,以__init标记的函数或者变量都是放置在.init.text段中,__initdata标记的函数或者变量都是放置在.init.data段中....,所以驱动的xxx_init函数都是放置在init.text段中,module_init函数将xxx_init的函数指针放置到了initcall6.init段中,kernel启动过程中先加载到initcall6.init段中的函数指针然后加载到init.text段中的函数实体。

接下来分析kernel启动过程时如何加载到initcall6段中的内容的,

start_kernel---->rest_init---->kernel_thread(kernel_init, NULL, clone_FS)---->kernel_init_freeable---->do_basic_setup---->do_initcalls-->do_initcall_level---->do_one_initcall

由上述的流程,开机过程会调用start_kernel进而会调用到do_initcalls,

static void __init do_initcalls(void)

{

int level;

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

do_initcall_level(level);

}

该函数中会计算initcall_levels数组的大小,并循环调用do_initcall_level。你会发现,initcall_levels的定义就是本.c文件中,并且定义为__initdata,即放置在init.data段中。

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_levels定义为指针数组,即数组中每个元素都是__initcallx_start的起始地址。跟踪一下代码发现extern initcall_t __initcall_start[]都有类似的地定义,如果这个时候你再看下vmlinux.lds.S便会豁然开朗。最后开始让我们好好看看do_one_initcall函数吧,这里我把不必要的内容都去掉了,留下的都是精华,很明显fn就是 __initcall6_start地址了,通过循环initcall6中的所有内容都会被依次加载。

int __init_or_module do_one_initcall(initcall_t fn)

{   .........

if (initcall_debug)

ret = do_one_initcall_debug(fn);

else

ret = fn();

........

}

这里的initcall_debug故名思意就是开启debug相关的功能,具体的功能是每个驱动模块加载的时间,做系统manbetx体育的朋友对这个变量一定时非常熟悉的。

相关阅读

module_init的加载和释放

转自:https://blog.csdn.net/dysh1985/article/details/7597105 像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含Kern

谷歌推出站点测速工具:检测网页移动设备友好度及加载速

站长之家(ChinaZ.com)6月3日消息,上月Netflix推出一个简洁的测速站点Fast.com后,搜索巨头谷歌也推出了一个测速站点Test My Site,不过

Spring contextConfigLocation默认加载文件的位置

  在使用Spring框架的时候,如果我们使用的是XML文件配置Bean的方式的话,我们往往会在web.xml里面配置如下内容: <context-pa

电脑开机黑屏,只有鼠标箭头(windows无法加载桌面)?

有时我们打开电脑会出现这样的情况:开机声音响完,但是显示屏只有一个鼠标箭头可见,但是迟迟不见桌面加载出来,如图1所示一样。这时我

dhtmlxtree动态加载节点数据的小随笔

最近做了一个这个东西,颇有些感触,随笔记录一下自己的过程。首先特别感谢:https://blog.csdn.net/cfl20121314/article/details/4

分享到:

栏目导航

推荐阅读

热门阅读