linux驱动模块 之 module_init()

本文详细介绍了Linux驱动模块加载过程中module_init()宏的作用和工作原理。通过示例代码展示了module_init()如何将函数放入特定段,并通过内核初始化调用顺序完成驱动初始化。内容包括宏展开、函数指针、编译器扩展以及内核初始化调用流程。

 

注:以下讨论建立在内核 3.2.9基础之上。

linux驱动模块的加载由module_init() 宏功能出发,其用法例:

int __init fimc_md_init(void)

{

      int ret;

      request_module("s5p-csis");

      ret = fimc_register_driver();

      if (ret)

             return ret;

      return platform_driver_register(&fimc_md_driver);

}

 

module_init(fimc_md_init); 

从上面的例子可以看出,该宏函数的参数是__init 修饰的函数的函数名,故而可知其传入的是函数指针。那__init这个又是什么呢,我们把它展开来看一下,

#define __init __attribute__ ((__section__ (".text.init")))

好的,这里遇到了__attribute__ 为了更进一步的理解,这里不得不提及到 GNU C编译器扩展的一些知识作为辅助,其具体内容可见转载的《GNU C 扩展之__attribute__ 机制简介》这篇文章,这里只对__attribute__ 做简单的描述,__attribute__GNU C编译器的扩展功能,其作用是修改函数、变量和类型的编译属性,括号内的__section__表示段属性,整句的意思是将该宏修饰的函数或变量放入指定的段中,这里是.text.init 段,该例子中 __init 的作用就是 将函数fimc_md_init(void)编译的时候放入段.text.init 中,由于一些初始化操作只会运行一次,为了节约内存,在这些段内的代码运行完之后,linux会将其代码占用的内存释放掉。现在转过来继续聊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

好的,基本展开完成,这里出现了initcall_t,查看一下定义,

typedef int (*initcall_t)(void);很清晰,就是typedef了个函数指针类型,连贯起来理解以上宏内容就是,定义了一个函数指针变量__initcall_##fn##id,然后将其初始化为fn,并通过__attribute__的编译选项,将其放入".initcall" level ".init" 中,这里的##是预编译时的字串连接符,该例子中把字串替换进去后的结果是

static initcall_t __initcall_fimc_md_init6 __used

      __attribute__((__section__(".initcall6.init"))) = fn

到现在我们明白了,module_init(x)的作用就是在.initcall6.init段中定义一个函数指针变量,然后将其初始化成 x。那这样做的具体作用是啥呢,这个时候我们有必要先看看.initcall6.init了。

上面说到的#define device_initcall(fn)              __define_initcall("6",fn,6) 出现在 init.h文件中,跟它并列的几个宏如下:

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

 

按照上面分析,以上宏最终会将定义的相应的函数指针变量放到以下段中:

 

.initcall0.init

.initcall0s.init

.initcall1.init

.initcall1s.init

.initcall2.init

.initcall2s.init

…..

.initcall7s.init

 

我们看看以上段标记引用的地方,在vmlinux.lds.h代码片段:

#defineINITCALLS                                               \

      *(.initcallearly.init)                                     \

      VMLINUX_SYMBOL(__early_initcall_end) = .;                 \

     *(.initcall0.init)                                         \

     *(.initcall0s.init)                                       \

     *(.initcall1.init)                                         \

     *(.initcall1s.init)                                       \

     *(.initcall2.init)                                         \

     *(.initcall2s.init)                                       \

     *(.initcall3.init)                                         \

     *(.initcall3s.init)                                       \

     *(.initcall4.init)                                         \

     *(.initcall4s.init)                                       \

     *(.initcall5.init)                                         \

     *(.initcall5s.init)                                       \

      *(.initcallrootfs.init)                                         \

     *(.initcall6.init)                                         \

     *(.initcall6s.init)                                       \

     *(.initcall7.init)                                         \

     *(.initcall7s.init)

 

以上各段按顺序排列,初始化运行的时候会按找上面的顺序挨个运行,具体调用位置往下看。

 

#defineINIT_CALLS                                             \

             VMLINUX_SYMBOL(__initcall_start) = .;                \  // 导出标记__initcall_start

             INITCALLS                                      \

             VMLINUX_SYMBOL(__initcall_end) = .;

 

 

#define INIT_DATA_SECTION(initsetup_align)                      \

      .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) {         \

             INIT_DATA                                       \

             INIT_SETUP(initsetup_align)                            \

             INIT_CALLS                                     \

             CON_INITCALL                                      \

             SECURITY_INITCALL                             \

             INIT_RAM_FS                                         \

      }

 

 

vmlinux.lds.S中,

SECTIONS

{

……

      /* Will be freed after init */

      __init_begin = ALIGN(PAGE_SIZE);

      INIT_TEXT_SECTION(PAGE_SIZE)

      INIT_DATA_SECTION(16)              /*编译器指定的初始化对齐方式*/

      PERCPU_SECTION(L1_CACHE_BYTES)

      /* Align to THREAD_SIZE rather than PAGE_SIZE here so any padding page

         needed for the THREAD_SIZE aligned init_task gets freed after init */

      . = ALIGN(THREAD_SIZE);

      __init_end = .;

      /* Freed after init ends here */

……….

}

 

 

 

 

Main.c

 

extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[]; // 眼熟吧,就是上面提到的导出的符号

 

static void __initdo_pre_smp_initcalls(void)

{

      initcall_t *fn;

 

      for (fn = __initcall_start; fn < __early_initcall_end; fn++)// 按照上面说的段顺序,依次调用标记之间的所有函数指针,这种不同段的调用顺序,也就是分布在各段函数指针所指向的初始化代码的先后顺序,但同一段内的不同模块的函数指针调用顺序不确定。

 

             do_one_initcall(*fn);

}

 

往上寻找一下该函数的调用关系,

 

static int __init kernel_init(void * unused)

{

      /*

       * Wait until kthreadd is all set-up.

       */

      wait_for_completion(&kthreadd_done);

      /*

       * init can allocate pages on any node

       */

      set_mems_allowed(node_states[N_HIGH_MEMORY]);

      /*

       * init can run on any cpu.

       */

      set_cpus_allowed_ptr(current, cpu_all_mask);

 

      cad_pid = task_pid(current);

 

      smp_prepare_cpus(setup_max_cpus);

 

      do_pre_smp_initcalls();

      lockup_detector_init();

 

      smp_init();

      sched_init_smp();

 

      do_basic_setup();

 

      /* Open the /dev/console on the rootfs, this should never fail */

      if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)

             printk(KERN_WARNING "Warning: unable to open an initial console.\n");

 

      ………..

 

      init_post();

      return 0;

}

 

 

static noinline void __init_refokrest_init(void)

{

      int pid;

 

      rcu_scheduler_starting();

      /*

       * We need to spawn init first so that it obtains pid 1, however

       * the init task will end up wanting to create kthreads, which, if

       * we schedule it before we create kthreadd, will OOPS.

       */

      kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //启动 kernel_init

      numa_default_policy();

      pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

      rcu_read_lock();

      kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);

      rcu_read_unlock();

      complete(&kthreadd_done);

 

      /*

       * The boot idle thread must execute schedule()

       * at least once to get things moving:

       */

      init_idle_bootup_task(current);

      preempt_enable_no_resched();

      schedule();

 

      /* Call into cpu_idle with preempt disabled */

      preempt_disable();

      cpu_idle();

}

 

asmlinkage void __initstart_kernel(void)  // 很明显了,这是启动 kernel的函数

{

      char * command_line;

      extern const struct kernel_param __start___param[], __stop___param[];

 

      smp_setup_processor_id();

 

      /*

       * Need to run as early as possible, to initialize the

       * lockdep hash:

       */

      lockdep_init();

      debug_objects_early_init();

 

      /*

       * Set up the the initial canary ASAP:

       */

      boot_init_stack_canary();

 

      cgroup_init_early();

 

      …..

 

      acpi_early_init(); /* before LAPIC and SMP init */

      sfi_init_late();

 

      ftrace_init();

 

      /* Do the rest non-__init'ed, we're now alive */

      rest_init();

}

 

以上调用过程已经很明晰了,module_init()的功能介绍也就到此结束了。

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值