首页 > 其他分享 >module_init机制

module_init机制

时间:2023-02-10 23:57:20浏览次数:41  
标签:__ -- module start init 机制 initcall

模块代码有两种运行方式,一是静态编译连接进内核,在系统启动过程中进行初始化;一是编译成可动态加载的module,通过insmod动态加载重定位到内核。这两种方式可以在Makefile中通过obj-y或obj-m选项进行选择。

而一旦可动态加载的模块目标代码(.ko)被加载重定位到内核,其作用域和静态链接的代码是完全等价的。所以这种运行方式的优点显而易见:

  1. 可根据系统需要运行动态加载模块,以扩充内核功能,不需要时将其卸载,以释放内存空间;
  2. 当需要修改内核功能时,只需编译相应模块,而不必重新编译整个内核。

因为这样的优点,在进行设备驱动开发时,基本上都是将其编译成可动态加载的模块。但是需要注意,有些模块必须要编译到内核,随内核一起运行,从不卸载,如 vfs、platform_bus等。

那么同样一份C代码如何实现这两种方式的呢?答案就在于module_init宏!

#ifndef MODULE

#define module_init(x)    __initcall(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) __copy(initfn)            \
        __attribute__((alias(#initfn)));        \
    __CFI_ADDRESSABLE(init_module, __initdata);
#endif

 

显然,MODULE 是由Makefile控制的。上面部分用于将模块静态编译连接进内核,下面部分用于编译可动态加载的模块。接下来我们对这两种情况进行分析。

 

方式一:#ifndef MODULE

module_intit(fn) 展开来就是:

  static initcall_t 变量名 __used __attribute__((__section__(.initcall6.init))) = fn;

定义一个函数指针类型的变量,并把该变量放在代码段 .initcall6.init

接下来看链接脚本vmlinux.lds

 .init.data : AT(ADDR(.init.data) - 0) 
{
  KEEP(*(SORT(___kentry+*)))
  ...
  __initcall_start = .;
  KEEP(*(.initcallearly.init))
  __initcall0_start = .;
  KEEP(*(.initcall0.init))
  KEEP(*(.initcall0s.init))
  ...
  __initcall6_start = .;
  KEEP(*(.initcall6.init))
  KEEP(*(.initcall6s.init))
  __initcall7_start = .;
  KEEP(*(.initcall7.init))
  KEEP(*(.initcall7s.init))
  __initcall_end = .;
  ...
  . = ALIGN(4);
  __initramfs_start = .;
  KEEP(*(.init.ramfs))
  . = ALIGN(8);
  KEEP(*(.init.ramfs.info)) }

 

上面这些代码段最终在kernel.img中按先后顺序组织,也就决定了位于其中的一些函数的执行先后顺序(__initcall_hello_init6 位于 .initcall6.init 段中)。.init 或者 .initcalls 段的特点就是,当内核启动完毕后,这个段中的内存会被释放掉。这一点从内核启动信息可以看到:
Freeing unused kernel memory: 124K (80312000 - 80331000)

那么存放于 .initcall6.init 段中的 __initcall_hello_init6 是怎么样被调用的呢?我们看文件 init/main.c,代码梳理如下:

start_kernel
|
--> arch_call_rest_init
  |
  --> rest_init
    |
      --> kernel_thread
        |
        --> kernel_init
            |
            --> kernel_init_freeable
                |
                --> do_basic_setup
                    |
                    --> do_initcalls
                        |
                        --> do_initcall_level(level)
                            |
                            --> do_one_initcall(initcall_t fn)
static initcall_entry_t *initcall_levels[] __initdata = {
    __initcall0_start,
    __initcall1_start,
    __initcall2_start,
    __initcall3_start,
    __initcall4_start,
    __initcall5_start,
    __initcall6_start,
    __initcall7_start,
    __initcall_end,
};

 

 do_initcalls()会遍历数组initcall_levels[],数组的每个成员是一个代码段,do_initcall_level()会遍历代码段的所有函数指针,do_one_initcall()则执行每个函数指针。因为编译器根据链接脚本的要求将各个函数指针链接到了指定的位置,所以可以放心地用 do_one_initcall(*fn) 来执行相关初始化函数。

我们例子中的 module_init(hello_init) 是 level6 的 initcalls 段,比较靠后调用,很多外设驱动都调用 module_init 宏,如果是静态编译连接进内核,则这些函数指针会按照编译先后顺序插入到 initcall6.init 段中,然后等待 do_initcalls 函数调用。

方式二:#else

 

#define module_init(initfn)                    \
    static inline initcall_t __maybe_unused __inittest(void)        \
    { return initfn; }                    \
    int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));        \

__inittest() 仅仅是为了检测定义的函数是否符合 initcall_t 类型,如果不是 __inittest 类型在编译时将会报错。

 

alias 属性是 gcc 的特有属性,将 init_module 定义为 initfn 的别名。所以 module_init(hello_init) 的作用就是定义一个变量 init_module,其地址和 hello_init 是一样的。

// filename: HelloWorld.c
 
#include <linux/module.h>
#include <linux/init.h>
 
static int hello_init(void)
{
    printk(KERN_ALERT "Hello World\n");
    return 0;
}
 
static void hello_exit(void)
{
    printk(KERN_ALERT "Bye Bye World\n");
}
 
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");

上述例子编译可动态加载模块过程中,会自动产生 HelloWorld.mod.c 文件,内容如下:

#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
 
MODULE_INFO(vermagic, VERMAGIC_STRING);
 
struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
    .name = KBUILD_MODNAME,
    .init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
    .exit = cleanup_module,
#endif
    .arch = MODULE_ARCH_INIT,
};
 
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";

 

 可知,其定义了一个类型为 module 的全局变量 __this_module,成员 init 为 init_module(即 hello_init),且该变量链接到 .gnu.linkonce.this_module 段中。

 

编译后所得的 HelloWorld.ko 需要通过 insmod 将其加载进内核,由于 insmod 是 busybox 提供的用户层命令,所以我们需要阅读 busybox 源码。

// modutils/insmod.c
int insmod_main(int argc UNUSED_PARAM, char **argv)
{
    char *filename;
...
    rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0));
    if (rc)
        bb_error_msg("can't insert '%s': %s", filename, moderror(rc));

    return rc;
}

insmod_main
|
--> bb_init_module
    |
    --> init_module

 

 

// modutils/modutils.c
/* Return:
 * 0 on success,
 * -errno on open/read error,
 * errno on init_module() error
 */
int FAST_FUNC bb_init_module(const char *filename, const char *options)
{
    size_t image_size;
    char *image;
    int rc;
...

    errno = 0;
    init_module(image, image_size, options);
    rc = errno;
    if (mmaped)
        munmap(image, image_size);
    else
        free(image);
    return rc;
}

 

 而 init_module 定义如下: //此 init_module 不同于驱动代码编译出来的 init_module,此处是 busybox 内的宏。

// modutils/modutils.c
#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)

//上面的宏逻辑上相当于
# define init_module(mod, len, opts) sys_init_module(mod, len, opts)

 

syscall()是C库提供的一个函数,它调用系统调用。函数原型如下:

long int syscall (long int sysno, ...);

 

sysno:系统调用号

... :为剩余可变长的参数,为系统调用所带的参数,根据系统调用的不同,可带0~5个不等的参数,如果超过特定系统调用能带的参数,多余的参数被忽略。

返回值:该函数返回值为特定系统调用的返回值,在系统调用成功之后你可以将该返回值转化为特定的类型,如果系统调用失败则返回 -1,错误代码存放在 errno 中。

 

sys_init_module()源码

SYSCALL_DEFINE3(init_module, void __user *, umod,
        unsigned long, len, const char __user *, uargs)
{
    int err;
    struct load_info info = { };

    err = may_init_module();
    if (err)
        return err;

    pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
           umod, len, uargs);

    err = copy_module_from_user(umod, len, &info);
    if (err)
        return err;

    return load_module(&info, uargs, 0);
}

调用关系:
SYSCALL_DEFINE3(init_module, ...)
|
-->load_module
    |
    --> do_init_module(mod)
        |
        --> do_one_initcall(mod->init);

 

 

 do_one_initcall(mod->init):这里就是执行驱动加载函数,mod->init是指向module_init宏指定的驱动加载函数的函数指针

 load_module():在函数内部会将描述驱动的struct module结构体注册到全局变量modules中

 

标签:__,--,module,start,init,机制,initcall
From: https://www.cnblogs.com/god-of-death/p/17110687.html

相关文章

  • RPC优雅关闭机制详解
    1关闭为什么有问题?系统为啥非要拆分?更方便、更快速迭代业务,但也得经常更新应用系统,时不时还老要重启服务器。重启服务过程中,RPC怎么做到让调用方系统不出问题?2上线流程当......
  • python的垃圾回收机制
    1.垃圾回收机制的算法分类python垃圾回收算法通常有三类:引用计数,标记清除和分代回收,主要以引用计数为主,标记清除和分代回收为辅 2.对象的存储方式——refchain环......
  • 79、异常机制
    当认证微服务通过数据校验后,要远程调用用户/会员模块进行真正的注册。我们要检查用户提交的用户名和手机号等在数据库是否已经存在了。我们可以通过boolean值判断是否已经......
  • Tomcat启动报错:严重: Unable to process Jar entry [module-info.class]
    1、Springboot版本:2.1.4.RELEASE2、Tomcat版本:apache-tomcat-8.0.303、原因:Tomcat版本底,Springboot2.1.4对应的Tomcat版本应该是:tomcat8.5.16以上4、报错代码:严重:Una......
  • 4.1内存的物理机制很简单
       内存实际上是一种名为内存IC的电子元件。虽然内存IC包括DRAM、SRAM、ROM等多种形式,但从外部来看,基本机制都是一样的。内存IC中有电源、地址信号、数据信号、控制......
  • 好客租房163-css Module的应用
    1使用cssModules修改NavHeader样式2在样式文件中修改当前组件的样式3对于组件库中已经有的全局样式(比如:am-navber-title)importReactfrom'react'import{NavBar}fro......
  • 好客租房161-css modules的说明
    cssmodules通过对css类名重命名保证每个类名的唯一性从而避免样式冲突的问题换句话:所有类名就具有局部作用域只能当前组件内部生效webpack的css-loader原因......
  • RPC异常重试机制详解
    1异常重试的意义发起一次RPC调用,调用远程的一个服务,如用户的登录操作,先对用户的用户名以及密码进行验证,验证成功后,获取用户基本信息。通过远程的用户服务获取用户基本信息......
  • 内存泄露 内存释放 和垃圾回收机制
    答:当使用一个参数,会进行内存的分配,内存的使用,内存的释放。什么会导致内存泄露答:1.意外的全局变量=》必须确保在使用过后将它设置为null2定时器=》清除定......
  • Iterator的fail-fast、fail-safe机制
    ArrayList是fail-fast的典型代表,遍历的同时不能修改,尽快失败原理是arrayList中有一个修改次数变量,每次修改list这个变量都会+1获取迭代器时将arrayList中的修改次数变量......