Chinaunix首页 | 论坛 | 认证专区 | 博客

linuxDOS的ChinaUnix博客

一个人的差异在于业余时间

  • 博客访问: 610268
  • 博文数量: 55
  • 博客积分: 400
  • 博客等级: 一等列兵
  • 技术积分: 2730
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-30 13:08
  • 认证徽章:
个人简介

一个人的差异在于业余时间

文章分类

全部博文(55)

文章存档

2017年(1)

2016年(2)

2015年(20)

2014年(12)

2013年(7)

2012年(12)

2011年(1)

微信关注

IT168企业级官微



微信号:IT168qiye



系统架构师大会



微信号:SACC2013

内核基础架构---宏卷标 2014-03-25 11:57:42

分类: LINUX

   在看网络设备初始化一节时,有个特殊的宏没有讲,static int __init net_dev_init(void) 即 粗体字__init ,如果经常看驱动或者内核代码人,会经常看到,当然在驱动模块初始化函数,我们也会经常看到.  它属于特殊的宏卷标,在gcc编译的时候会特殊处理. 参考内核2.6.34
  首先我们看: 
  

static int __init net_dev_init(void)

subsys_initcall(net_dev_init);
我们就来分析一下,__init 和 subsys_initcall,  直接看 linux/init.h  

点击(此处)折叠或打开

  1. /* These are for everybody (although not all archs will actually
  2.    discard it in modules) */
  3. #define __init        __section(.init.text) __cold notrace
  4. #define __initdata    __section(.init.data)
  5. #define __initconst    __section(.init.rodata)
  6. #define __exitdata    __section(.exit.data)
  7. #define __exit_call    __used __section(.exitcall.exit)
即__init 由它修饰的函数代码会放到.init.text 内存节中.但是它又是什么时候被调用初始化的呢?
我们发现net_dev_init又被 subsys_initcall修饰了 !

点击(此处)折叠或打开

  1. #ifndef MODULE

  2. #ifndef __ASSEMBLY__

  3. /* initcalls are now grouped by functionality into separate
  4.  * subsections. Ordering inside the subsections is determined
  5.  * by link order.
  6.  * For backwards compatibility, initcall() puts the call in
  7.  * the device init subsection.
  8.  *
  9.  * The `id' arg to __define_initcall() is needed so that multiple initcalls
  10.  * can point at the same handler without causing duplicate-symbol build errors.
  11.  */

  12. #define __define_initcall(level,fn,id) \
  13.     static initcall_t __initcall_##fn##id __used \
  14.     __attribute__((__section__(".initcall" level ".init"))) = fn

  15. /*
  16.  * Early initcalls run before initializing SMP.
  17.  *
  18.  * Only for built-in code, not modules.
  19.  */
  20. #define early_initcall(fn)        __define_initcall("early",fn,early)

  21. /*
  22.  * A "pure" initcall has no dependencies on anything else, and purely
  23.  * initializes variables that couldn't be statically initialized.
  24.  *
  25.  * This only exists for built-in code, not for modules.
  26.  */
  27. #define pure_initcall(fn)        __define_initcall("0",fn,0)

  28. #define core_initcall(fn)        __define_initcall("1",fn,1)
  29. #define core_initcall_sync(fn)        __define_initcall("1s",fn,1s)
  30. #define postcore_initcall(fn)        __define_initcall("2",fn,2)
  31. #define postcore_initcall_sync(fn)    __define_initcall("2s",fn,2s)
  32. #define arch_initcall(fn)        __define_initcall("3",fn,3)
  33. #define arch_initcall_sync(fn)        __define_initcall("3s",fn,3s)
  34. #define subsys_initcall(fn)        __define_initcall("4",fn,4)
  35. #define subsys_initcall_sync(fn)    __define_initcall("4s",fn,4s)
  36. #define fs_initcall(fn)            __define_initcall("5",fn,5)
  37. #define fs_initcall_sync(fn)        __define_initcall("5s",fn,5s)
  38. #define rootfs_initcall(fn)        __define_initcall("rootfs",fn,rootfs)
  39. #define device_initcall(fn)        __define_initcall("6",fn,6)
  40. #define device_initcall_sync(fn)    __define_initcall("6s",fn,6s)
  41. #define late_initcall(fn)        __define_initcall("7",fn,7)
  42. #define late_initcall_sync(fn)        __define_initcall("7s",fn,7s)

  43. #define __initcall(fn) device_initcall(fn)

  44. #define __exitcall(fn) \
  45.     static exitcall_t __exitcall_##fn __exit_call = fn

  46. #define console_initcall(fn) \
  47.     static initcall_t __initcall_##fn \
  48.     __used __section(.con_initcall.init) = fn

  49. #define security_initcall(fn) \
  50.     static initcall_t __initcall_##fn \
  51.     __used __section(.security_initcall.init) = fn

  52. struct obs_kernel_param {
  53.     const char *str;
  54.     int (*setup_func)(char *);
  55.     int early;
  56. };

  57. /*
  58.  * Only for really core code. See moduleparam.h for the normal way.
  59.  *
  60.  * Force the alignment so the compiler doesn't space elements of the
  61.  * obs_kernel_param "array" too far apart in .init.setup.
  62.  */
  63. #define __setup_param(str, unique_id, fn, early)            \
  64.     static const char __setup_str_##unique_id[] __initconst    \
  65.         __aligned(1) = str; \
  66.     static struct obs_kernel_param __setup_##unique_id    \
  67.         __used __section(.init.setup)            \
  68.         __attribute__((aligned((sizeof(long)))))    \
  69.         = { __setup_str_##unique_id, fn, early }

  70. #define __setup(str, fn)                    \
  71.     __setup_param(str, fn, fn, 0)

  72. /* NOTE: fn is as per module_param, not Emits warning if fn
  73.  * returns non-zero. */
  74. #define early_param(str, fn)                    \
  75.     __setup_param(str, fn, fn, 1)

  76. /* Relies on boot_command_line being set */
  77. void __init parse_early_param(void);
  78. void __init parse_early_options(char *cmdline);
  79. #endif /* __ASSEMBLY__ */

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

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

  100. #else /* MODULE */

  101. /* Don't use these in modules, but some people do... */
  102. #define early_initcall(fn)        module_init(fn)
  103. #define core_initcall(fn)        module_init(fn)
  104. #define postcore_initcall(fn)        module_init(fn)
  105. #define arch_initcall(fn)        module_init(fn)
  106. #define subsys_initcall(fn)        module_init(fn)
  107. #define fs_initcall(fn)            module_init(fn)
  108. #define device_initcall(fn)        module_init(fn)
  109. #define late_initcall(fn)        module_init(fn)

  110. #define security_initcall(fn)        module_init(fn)

  111. /* Each module must use one module_init(). */
  112. #define module_init(initfn)                    \
  113.     static inline initcall_t __inittest(void)        \
  114.     { return initfn; }                    \
  115.     int init_module(void) __attribute__((alias(#initfn)));

  116. /* This is only required if you want to be unloadable. */
  117. #define module_exit(exitfn)                    \
  118.     static inline exitcall_t __exittest(void)        \
  119.     { return exitfn; }                    \
  120.     void cleanup_module(void) __attribute__((alias(#exitfn)));

  121. #define __setup_param(str, unique_id, fn)    /* nothing */
  122. #define __setup(str, func)             /* nothing */
  123. #endif

这里我们发现(先无视掉里面else部分 ^^)
  1. #define subsys_initcall(fn)        __define_initcall("4",fn,4)
当然__define_initcall展开:

点击(此处)折叠或打开

  1. /* initcalls are now grouped by functionality into separate
  2.  * subsections. Ordering inside the subsections is determined
  3.  * by link order.
  4.  * For backwards compatibility, initcall() puts the call in
  5.  * the device init subsection.
  6.  *
  7.  * The `id' arg to __define_initcall() is needed so that multiple initcalls
  8.  * can point at the same handler without causing duplicate-symbol build errors.
  9.  */

  10. #define __define_initcall(level,fn,id) \
  11.     static initcall_t __initcall_##fn##id __used \
  12.     __attribute__((__section__(".initcall" level ".init"))) = fn
这里我们举个实际的例子吧

subsys_initcall(usb_init)转换后就变成了 static initcall_t  __initcall_usbinit4   __attribute_used__ \
__attribute__((__section__(".initcall 4.init"))) = usb_init
就是把usb_init的函数入口指针存放在.initcall4.init中.既然提到了.initcall4.init这个玩意,我们就展开说一下.

而支持这些的基础的识别来自强大的gcc 编译器的扩展语法:
 info gcc C Extensions Attribute Syntax
section ("SECTION-NAME")'
     Normally, the compiler places the objects it generates in sections
     like `data' and `bss'.  Sometimes, however, you need additional
     sections, or you need certain particular variables to appear in
     special sections, for example to map to special hardware.  The
     `section' attribute specifies that a variable (or function) lives
     in a particular section.  For example, this small program uses
     several specific section names:
          struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
          struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
          char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };
          int init_data __attribute__ ((section ("INITDATA"))) = 0;


          main()
          {
            /* Initialize stack pointer */
            init_sp (stack + sizeof (stack));


            /* Initialize initialized data */
            memcpy (&init_data, &data, &edata - &data);


            /* Turn on the serial ports */
            init_duart (&a);
            init_duart (&b);
          }


     Use the `section' attribute with an _initialized_ definition of a
     _global_ variable, as shown in the example.  GCC issues a warning
     and otherwise ignores the `section' attribute in uninitialized
     variable declarations.


     You may only use the `section' attribute with a fully initialized
     global definition because of the way linkers work.  The linker
     requires each object be defined once, with the exception that
     uninitialized variables tentatively go in the `common' (or `bss')
     section and can be multiply "defined".  You can force a variable
     to be initialized with the `-fno-common' flag or the `nocommon'
     attribute.


     Some file formats do not support arbitrary sections so the
     `section' attribute is not available on all platforms.  If you
     need to map the entire contents of a module to a particular
     section, consider using the facilities of the linker instead.


很多时候我们还会看到module_init这样的东西. 

/**

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

#define __initcall(fn)  device_initcall(fn)

#define device_initcall(fn)                 __define_initcall("6",fn,6)

所以很容易看出来module_init和subsys_init的区别.
然而,它们又是如何运作和调用的呢? 

我们来看include/asm-generic/vmlinux.lds.h

#define INITCALLS                                                           \

         *(.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)

继续看arch/XXX/kernel/vmlinux.lds

file:/arch/XXX/kernel/vmlinux.lds

点击(此处)折叠或打开

  1. .init.text : AT(ADDR(.init.text) - 0) { _sinittext = .; *(.init.text) *(.cpuinit.text) *(.meminit.text) _einittext = .; }
  2.  .init.data : AT(ADDR(.init.data) - 0) { *(.init.data) *(.cpuinit.data) *(.meminit.data) . = ALIGN(8); __ctors_start = .; *(.ctors) __ctors_end = .; *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata) . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .; 
  3. __initcall_start = .;
  4. *(.initcallearly.init) __early_initcall_end = .;
  5. *(.initcall0.init) 
  6. *(.initcall0s.init)
  7. *(.initcall1.init)
  8. *(.initcall1s.init)
  9. *(.initcall2.init) 
  10. *(.initcall2s.init) 
  11. *(.initcall3.init) 
  12. *(.initcall3s.init) 
  13. *(.initcall4.init)
  14. *(.initcall4s.init)
  15. *(.initcall5.init) 
  16. *(.initcall5s.init) 
  17. *(.initcallrootfs.init)
  18. *(.initcall6.init) 
  19. *(.initcall6s.init) 
  20. *(.initcall7.init) 
  21. *(.initcall7s.init) 
  22. __initcall_end = .; 
  23. __con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .; __security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .; }

我们在vmlinux.ids.h中还看到

点击(此处)折叠或打开

  1. #define INIT_CALLS                            \
  2.         VMLINUX_SYMBOL(__initcall_start) = .;            \
  3.         INITCALLS                        \
  4.         VMLINUX_SYMBOL(__initcall_end) = .;


那么系统是如何执行这些函数呢?

此话就长了阿~ 话说盘古开天芙蓉姐姐补天后我们来到了main.c这个linux中举足轻重的文件
进入start_kernel
start_kernel  -->rest_init() -->kernel_init()  --> do_basic_setup()  -->do_initcalls()

init/main.c :

点击(此处)折叠或打开

  1. static void __init do_initcalls(void)
  2. {
  3.     initcall_t *call;

  4.     for (call = __early_initcall_end; call < __initcall_end; call++)
  5.         do_one_initcall(*call);

  6.     /* Make sure there is no pending stuff from the initcall sequence */
  7.     flush_scheduled_work();
  8. }
我们看kernel_init函数

点击(此处)折叠或打开

  1. static int __init kernel_init(void * unused)
  2. {
  3.     /*
  4.      * Wait until kthreadd is all set-up.
  5.      */
  6.     wait_for_completion(&kthreadd_done);
  7.     lock_kernel();

  8.     /*
  9.      * init can allocate pages on any node
  10.      */
  11.     set_mems_allowed(node_states[N_HIGH_MEMORY]);
  12.     /*
  13.      * init can run on any cpu.
  14.      */
  15.     set_cpus_allowed_ptr(current, cpu_all_mask);
  16.     /*
  17.      * Tell the world that we're going to be the grim
  18.      * reaper of innocent orphaned children.
  19.      *
  20.      * We don't want people to have to make incorrect
  21.      * assumptions about where in the task array this
  22.      * can be found.
  23.      */
  24.     init_pid_ns.child_reaper = current;

  25.     cad_pid = task_pid(current);

  26.     smp_prepare_cpus(setup_max_cpus);

  27.     do_pre_smp_initcalls();
  28.     start_boot_trace();

  29.     smp_init();
  30.     sched_init_smp();

  31.     do_basic_setup();

  32.     /*
  33.      * check if there is an early userspace init. If yes, let it do all
  34.      * the work
  35.      */

  36.     if (!ramdisk_execute_command)
  37.         ramdisk_execute_command = "/init";

  38.     if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
  39.         ramdisk_execute_command = NULL;
  40.         prepare_namespace();
  41.     }

  42.     /*
  43.      * Ok, we have completed the initial bootup, and
  44.      * we're essentially up and running. Get rid of the
  45.      * initmem segments and start the user-mode stuff..
  46.      */

  47.     init_post();
  48.     return 0;
  49. }
这里面两个重要的函数 do_basic_setupdo_pre_smp_initcalls

 在do_basic_setup之前:

do_pre_smp_initcalls();

点击(此处)折叠或打开

  1. static void __init do_pre_smp_initcalls(void)
  2. {
  3.     initcall_t *call;

  4.     for (call = __initcall_start; call < __early_initcall_end; call++)
  5.         do_one_initcall(*call);
  6. }


我们发现它们都共同调用了do_one_initcall 

点击(此处)折叠或打开

  1. int do_one_initcall(initcall_t fn)
  2. {
  3.     int count = preempt_count();
  4.     ktime_t calltime, delta, rettime;

  5.     if (initcall_debug) {
  6.         call.caller = task_pid_nr(current);
  7.         printk("calling %pF @ %i\n", fn, call.caller);
  8.         calltime = ktime_get();
  9.         trace_boot_call(&call, fn);
  10.         enable_boot_trace();
  11.     }

  12.     ret.result = fn();

  13.     if (initcall_debug) {
  14.         disable_boot_trace();
  15.         rettime = ktime_get();
  16.         delta = ktime_sub(rettime, calltime);
  17.         ret.duration = (unsigned long long) ktime_to_ns(delta) >> 10;
  18.         trace_boot_ret(&ret, fn);
  19.         printk("initcall %pF returned %d after %Ld usecs\n", fn,
  20.             ret.result, ret.duration);
  21.     }

  22.     msgbuf[0] = 0;

  23.     if (ret.result && ret.result != -ENODEV && initcall_debug)
  24.         sprintf(msgbuf, "error code %d ", ret.result);

  25.     if (preempt_count() != count) {
  26.         strlcat(msgbuf, "preemption imbalance ", sizeof(msgbuf));
  27.         preempt_count() = count;
  28.     }
  29.     if (irqs_disabled()) {
  30.         strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
  31.         local_irq_enable();
  32.     }
  33.     if (msgbuf[0]) {
  34.         printk("initcall %pF returned with %s\n", fn, msgbuf);
  35.     }

  36.     return ret.result;
  37. }

这里我们看到先初始化__initcall_start里的函数,然后在初始化__early_initcall

当然分析完,我们应该看一个实际的例子,比如armmips架构等,我们在编译内核的时候生成arch/XXX/kernel/vmlinux.lds  发现秘密.

可以发现__init对应的section(.init.text)和__initdata对应的section(.init.data)都在.init段中。同样,这里定义的其他一些section也都会在使用完后被释放,如.init.setup,.initcall1.init等。

释放memory的大小会在系统启动过程中打印出来:

Freeing init memory: 128K

__init和__exit宏的作用

内核的部分函数带有__init和__exit宏,负责“初始化”和“清理收尾”该函数。如果该模块被编译进内核,而不是动态加载。宏 __init的使用会在初始化完成后丢弃该函数并收回所占内存,

宏__initdata同__init 类似,只不过对变量有效。简单来说是指示gcc把标记的数据或者函数放到指定sector。
linux中把一些启动及初始化时候用的数据用__init标识,然后在适当的时候把它们释放,回收内存。

宏__exit将忽略“清理收尾”的函数如果该模块被编译进内核。同宏 __exit一样,对动态加载模块是无效的。这很容易理解。编译进内核的模块 是没有清理收尾工作的, 而动态加载的却需要自己完成这些工作。

这些宏在头文件linux/init.h定义,用来释放内核占用的内存。 当你在启动时看到这样的Freeing unused kernel memory: 236k freed内核输出,上面的 那些正是内核所释放的


下面是一些常用的宏:

·   __init ,标记内核启动时使用的初始化代码,内核启动完成后不再需要。以此标记的代码位于.init.text 内存区域。它的宏定义是这样的:

·     

#define _ _init    _ _attribute_ _ ((_ _section_ _ (".text.init")))

·   __exit ,标记退出代码,对于非模块无效。

·   __initdata ,标记内核启动时使用的初始化数据结构,内核启动完成后不再需要。以此标记的代码位于.init.data 内存区域。

·   __devinit ,标记设备初始化使用的代码。

·   __devinitdata ,标记初始化设备数据结构的函数。

·   __devexit ,标记移除设备时使用的代码。

·   xxx_initcall ,一系列的初始化代码,按降序优先级排列。


这里需要额外说明一下,什么是内核可执行文件?
可执行文件映像中包含了进程执行的代码和数据,同时也包含了操作系统把镜像放入内存并执行的信息。具体信息可以网上查。


这些信息包括文本段、数据段、init数据段、bass段等。这些数据都有一个叫做“链接器脚步”的文件链接并装入,这个文件的功能就是实现各个段装入到指定位置. 前面我们看到的vmlinux.ids就是这个脚本文件。
当然还有其他的东西没有说道,只为学习总结.








阅读(2277) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册