Chinaunix首页 | 论坛 | 博客
  • 博客访问: 20168
  • 博文数量: 2
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 17
  • 用 户 组: 普通用户
  • 注册时间: 2016-12-18 23:05
文章分类
文章存档

2018年(1)

2016年(1)

我的朋友

分类: LINUX

2016-12-18 23:08:42

一、初始化宏

1.1、定义

        在内核里经常可以看到__init, __devinit这样的语句,这都是在init.h中定义的宏,gcc在编译时会将被修饰的
内容放到这些宏所代表的section,
编译器通过这些宏可以把代码优化放到合适的内存位置,以减少内存占用和提
高内核效率。

        其典型的定义如下:

点击(此处)折叠或打开

  1. #define __init __section(.init.text) __cold notrace
  2. #define __initdata __section(.init.data)
  3. #define __initconst __section(.init.rodata)
  4. #define __exitdata __section(.exit.data)
  5. #define __exit_call __used __section(.exitcall.exit)
        其典型用法如下:

点击(此处)折叠或打开

  1. static int __init xxx_drv_init(void)
  2. {
  3.      return pci_register_driver(&xxx_driver);
  4. }

        根据上面的定义与用法,xxx_drv_init()函数将会被link到.init.text段。之所以加入这样的宏,原因有2:

        1、一部分内核初始化机制依赖与它。
        如kernel将初始化要执行的init函数,分为7个级别,core_initcall, postcore_initcall, arch_initcall,
subsys_initcall, fs_iitcall, device_initcall, late_initcall。这7个级别优先级递减,即先执行core_initcall,
最后执行late_initcall(二中将具体讲述这些宏)。通过使用文中提到的宏,gcc会将初始化代码按下面的结构安
排:



        在内核初始化时,从__initcall_start到__initcall_end之间的initcall被一次执行。

        2、提高系统效率

        初始化代码的特点是:在系统启动时运行,且一旦运行后马上退出内存,不再占用内存。

1.2、宏用法

        driver中的使用:

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

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

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

        module_init, module_exit宏所调用的函数,需要分别用__init和__exit来标记。

        pci_driver数据结构不需要标记

        probe和remove函数用__devinit和__devexit来标记

        如果remove使用__devexit标记,则在pci_drvier结构中要用__devexit_p(remove)来引用remove函数

        如果不确定需不需要添加宏,则不要添加
        __initdata:标记内核启动时使用的初始化数据结构,内核启动完成后不再需要。以此标记的代码位于.init.data 内存区域。

        __devinit:标记设备初始化所用的代码。

        __devinitdata:标记设备初始化所用的数据结构的函数。

        __devexit:标记设备移除时所用的代码。

        xxx_initcall:7个级别的初始化函数。

二、初始化函数

    内核选项解析完成之后,各个子系统的初始化即进入第二部分—入口函数的调用。通常USB、PCI这样的子系统都会有一个名为
subsys_initcall的入口。

    以下代码来自 linux内核源码中 include/linux/init.h 文件

点击(此处)折叠或打开

  1. #define pure_initcall(fn) __define_initcall("0",fn,1)
  2. #define core_initcall(fn) __define_initcall("1",fn,1)
  3. #define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
  4. #define postcore_initcall(fn) __define_initcall("2",fn,2)
  5. #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
  6. #define arch_initcall(fn) __define_initcall("3",fn,3)
  7. #define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
  8. #define subsys_initcall(fn) __define_initcall("4",fn,4)
  9. #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
  10. #define fs_initcall(fn) __define_initcall("5",fn,5)
  11. #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
  12. #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
  13. #define device_initcall(fn) __define_initcall("6",fn,6)
  14. #define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
  15. #define late_initcall(fn) __define_initcall("7",fn,7)
  16. #define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
  17. #define __initcall(fn) device_initcall(fn)
        这些入口有个共同的特征,它们都是使用__define_initcall宏定义的。它们的调用也不是随便的,而是按照一定顺序的,这个顺
序就取决于 __define_initcall宏。__define_initcall宏用来将指定的函数指针放到.initcall.init节里。

    .initcall.init节

    内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init数据、bass等等。这些对象文件都是

由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输

入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。vmlinux.lds是存在于arch//目录中的

内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。在vmlinux.lds文件里查找initcall.init就可以看到下

面的内容。

点击(此处)折叠或打开

  1. __inicall_start = .;
  2.     .initcall.init : AT(ADDR(.initcall.init) – 0xC0000000) {
  3.     *(.initcall1.init)
  4.     *(.initcall2.init)
  5.     *(.initcall3.init)
  6.     *(.initcall4.init)
  7.     *(.initcall5.init)
  8.     *(.initcall6.init)
  9.     *(.initcall7.init)
  10.     }
  11.     __initcall_end = .;
    这就告诉我们.initcall.init节又分成了7个子节,而xxx_initcall入口函数指针具体放在哪一个子节里边是由xxx_initcall定
义的,__define_initcall宏的参数决定的,比如core_initcall将函数指针放在.initcall1.init子节,device_initcall将函数
指针放在了.initcall6.init子节等等。各个子节的顺序是确定的,即先调用.initcall1.init中的函数指针再调用.initcall2.init
中的函数指针,等等。不同的入口函数被放在不同的子节中,因此也就决定了它们的调用顺序。

    注意:设备驱动程序中常见的module_init(x)函数,查看init.h文件发现,

    #define module_init(x)__initcall(x);

    #define __initcall(fn) device_initcall(fn)

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

    这样推断 module_init 调用优先级为6低于subsys_initcall调用优先级4.

    do_initcalls()函数

    那些入口函数的调用由do_initcalls函数来完成。

    do_initcall函数通过for循环,由__initcall_start开始,直到__initcall_end结束,依次调用识别到的初始化函数。而位于
__initcall_start和__initcall_end之间的区域组成了.initcall.init节,其中保存了由 xxx_initcall形式的宏标记的函数地址,
do_initcall函数可以很轻松的取得函数地址并执行其指向的函数。

    .initcall.init节所保存的函数地址有一定的优先级,越前面的函数优先级越高,也会比位于后面的函数先被调用。

    由do_initcalls函数调用的函数不应该改变其优先级状态和禁止中断。因此,每个函数执行后,do_initcalls会检查该函数是否做
了任何变化,如果有必要,它会校正优先级和中断状态。另外,这些被执行的函数有可以完成一些需要异步执行的任务,flush_scheduled_work
函数则用于确保do_initcalls函数在返回前等待这些异步任务结束。

阅读(2058) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:Linux3.2.8系统启动过程及启动延时初步分析

给主人留下些什么吧!~~