一、初始化宏

1.1、定义

    在内核里经常可以看到__init, __devinit这样的语句,这都是在init.h中定义的宏,gcc在编译时会将被修饰的
  • 1
  • 2

内容放到这些宏所代表的section,编译器通过这些宏可以把代码优化放到合适的内存位置,以减少内存占用和提 
高内核效率。

    其典型的定义如下:
  • 1
  • 2
    #define __init __section(.init.text) __cold notrace#define __initdata __section(.init.data)#define __initconst __section(.init.rodata)#define __exitdata __section(.exit.data)#define __exit_call __used __section(.exitcall.exit)
  • 1
  • 2
  • 3
  • 4
  • 5
    其典型用法如下:
  • 1
  • 2
    static int __init xxx_drv_init(void){return pci_register_driver(&xxx_driver);}
  • 1
  • 2
  • 3
  • 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会将初始化代码按下面的结构安排:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

    在内核初始化时,从__initcall_start到__initcall_end之间的initcall被一次执行。2、提高系统效率初始化代码的特点是:在系统启动时运行,且一旦运行后马上退出内存,不再占用内存。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

1.2、宏用法

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

是这样的:

    #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个级别的初始化函数。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

二、初始化函数

内核选项解析完成之后,各个子系统的初始化即进入第二部分—入口函数的调用。通常USB、PCI这样的子系统都会有一个名为subsys_initcall的入口。以下代码来自 linux内核源码中 include/linux/init.h 文件
  • 1
  • 2
  • 3
  • 4
    #define pure_initcall(fn) __define_initcall("0",fn,1)#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)#define module_init(x) __initcall(x)#define __initcall(fn) device_initcall(fn)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
    这些入口有个共同的特征,它们都是使用__define_initcall宏定义的。它们的调用也不是随便的,而是按照一定顺序的,这个顺
  • 1
  • 2

序就取决于 __define_initcall宏。__define_initcall宏用来将指定的函数指针放到.initcall.init节里。

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

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

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

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

面的内容。

   __inicall_start = .;.initcall.init : AT(ADDR(.initcall.init) – 0xC0000000) {*(.initcall1.init)*(.initcall2.init)*(.initcall3.init)*(.initcall4.init)*(.initcall5.init)*(.initcall6.init)*(.initcall7.init)}__initcall_end = .;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
这就告诉我们.initcall.init节又分成了7个子节,而xxx_initcall入口函数指针具体放在哪一个子节里边是由xxx_initcall定
  • 1
  • 2

义的,__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结束,依次调用识别到的初始化函数。而位于
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

__initcall_start和__initcall_end之间的区域组成了.initcall.init节,其中保存了由 xxx_initcall形式的宏标记的函数地址, 
do_initcall函数可以很轻松的取得函数地址并执行其指向的函数。

.initcall.init节所保存的函数地址有一定的优先级,越前面的函数优先级越高,也会比位于后面的函数先被调用。由do_initcalls函数调用的函数不应该改变其优先级状态和禁止中断。因此,每个函数执行后,do_initcalls会检查该函数是否做
  • 1
  • 2
  • 3
  • 4

了任何变化,如果有必要,它会校正优先级和中断状态。另外,这些被执行的函数有可以完成一些需要异步执行的任务,flush_scheduled_work 
函数则用于确保do_initcalls函数在返回前等待这些异步任务结束。

Linux中__init、__devinit等初始化宏解析和入口函数相关推荐

  1. Linux中的两个经典宏定义:获取结构体成员地址,根据成员地址获得结构体地址;Linux中双向链表的经典实现。...

    倘若你查看过Linux Kernel的源码,那么你对 offsetof 和 container_of 这两个宏应该不陌生.这两个宏最初是极客写出的,后来在Linux内核中被推广使用. 1. offse ...

  2. c++ getpid函数_C Linux中的getpid()和getppid()函数

    c++ getpid函数 If we are working on the processes, signals related programming using C language in Lin ...

  3. Linux中的两个经典宏定义

    倘若你查看过Linux Kernel的源码,那么你对 offsetof 和 container_of 这两个宏应该不陌生.这两个宏最初是极客写出的,后来在Linux内核中被推广使用. 1. offse ...

  4. linux中IIO子系统------IIO数据结构部分解析1

    一.kernel/include/linux/iio/iio.h 1.iio_dev结构体 struct iio_dev {int id;struct module *driver_module;in ...

  5. linux中ping命令详解,全面解析Ping命令

    Ping是每个人学习都必须接触到的命令,可是Ping在多少人手中只是检测对方是否在线的工具,却并不知道ping的许多其他用途.Ping是电脑系统自带的一个可执行命令,可以用来检测网络是否通畅和网速的快 ...

  6. Linux中_IO,_IOR,_IOW,_IOWR宏的使用

    在驱动程序里, ioctl() 函数上传送的变量 cmd 是应用程序用于区别设备驱动程序请求处理内容的值.cmd除了可区别数字外,还包含有助于处理的几种相应信息. cmd的大小为 32位,共分 4 个 ...

  7. linux中sed命令用例,sed解析和用例(马哥视频笔记)

    sed(流编辑器) sed基本用法: sed:Stream EDitor 行编辑器(另一种:全屏编辑器:vi.vim) sed:模式空间 sed默认部编辑源文件,仅对模式空间中的数据做处理:而后将模式 ...

  8. linux数据包注释,关于 linux中TCP数据包(SKB)序列号的小笔记

    关于  SKB序列号的小笔记 为了修改TCP协议,现在遇到了要改动tcp分组的序列号,但是只是在tcp_sendmsg函数中找到了SKB的end_seq  一直没有找到seq 不清楚在那里初始化了,就 ...

  9. linux中的权限管理,Linux中的用户和权限管理

    Linux是多用户,多任务操作系统:多用户是指多个用户可以同时使用系统资源,而多任务指同时运行多个进程. 用户是能够获取系统资源的权限的集合,Linux通过用户实现资源分隔. 用户组是具有相同特征用户 ...

最新文章

  1. 并发编程2:认识并发编程的利与弊
  2. Python编程基础:第四十六节 super函数Super Function
  3. hdu2019——数列有序解题报告
  4. 前端学习(2990):vue+element今日头条管理--模块介绍
  5. 从零开始编写自己的C#框架(27)——什么是开发框架
  6. dynamic programming动态规划初步理解【-1】
  7. JavaScript点击背景图片切换
  8. Spring——DI
  9. 设计模式 | 模板模式
  10. centos7配置mysql主从:一主多从
  11. 基于Kinect Azure的多相机数据采集(二)
  12. camera中文版软件 ip_Synergy下载|Synergy 1.8.4 中文版
  13. 计算机网络实验-Packet Tracer实验
  14. 太酷炫了,我用python画出了北上广深的地铁路线动态图
  15. JZOJ3291. 【JSOI2013】快乐的JYY
  16. 软考备考-流水线吞吐率的计算
  17. AutoCAD 2023安装教程
  18. 【车载开发系列】UDS诊断---输入输出控制($0x2F)
  19. 禁止win7/win8自动更新到win10
  20. openpyxl单元格公式批注字体对齐边框填充

热门文章

  1. 特征因子是什么意思_【空间分析】地理探测器应用-分异及因子探测、交互作用探测(共享单车骑行目的地时空特征与影响因素)...
  2. 音频放大电路_低音升压功率放大器电子电路的完整设计
  3. KNN K近邻算法学习
  4. 【Pytorch】model.train()和model.eval()用法和区别,以及model.eval()和torch.no_grad()的区别
  5. vue获取剪切板内容_vue通过clipboard插件实现复制到剪切板功能
  6. Property or method “id“ is not defined on the instance but referenced during render.Make sure ......
  7. win10 查看文件扩展名 - 教程篇
  8. Vue2.0项目安装Mint-UI - cmd篇
  9. php 文章浏览量 缓存,WordPress缓存文章浏览量访问不自动增加怎么办?WordPress缓存导致文章阅读数点赞数不更新...
  10. 征途手游2新开区服务器维护多久,《征途2手游》开启新服“星火燎原”