在写驱动的过程中,对于入口函数与出口函数我们会用一句话来修饰他们:module_init与module_exit,那会什么经过修饰后,内核就能狗调用我们编写的入口函数与出口函数呢?下面就来分析内核调用module_init的过程(这里暂时分析编译进内核的模块,不涉及动态加载的模块),以这个过程为例子来了解内核对于不同段的函数的调用过程。

下面从内核的start_kernel函数开始分析,下面是调用过程:

start_kernel
rest_init
kernel_init
do_basic_setup
do_initcalls()

直接看到do_initcalls函数,看到第6行的for循环,它从__initcall_start开始到__initcall_end结束,调用在这区间内的函数,那么这些函数在哪里定义的呢?

 static void __init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count(); for (call = __initcall_start; call < __initcall_end; call++) {/* 调用__initcall_start到__initcall_end内的函数*/
ktime_t t0, t1, delta;
char *msg = NULL;
char msgbuf[];
int result; if (initcall_debug) {
printk("Calling initcall 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk("\n");
t0 = ktime_get();
} result = (*call)(); if (initcall_debug) {
t1 = ktime_get();
delta = ktime_sub(t1, t0); printk("initcall 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk(" returned %d.\n", result); printk("initcall 0x%p ran for %Ld msecs: ",
*call, (unsigned long long)delta.tv64 >> );
print_fn_descriptor_symbol("%s()\n",
(unsigned long) *call);
} if (result && result != -ENODEV && initcall_debug) {
sprintf(msgbuf, "error code %d", result);
msg = msgbuf;
}
if (preempt_count() != count) {
msg = "preemption imbalance";
preempt_count() = count;
}
if (irqs_disabled()) {
msg = "disabled interrupts";
local_irq_enable();
}
if (msg) {
printk(KERN_WARNING "initcall at 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk(": returned with %s\n", msg);
}
} /* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}

继续往下看,我们搜索整个内核源码,发现找不到__initcall_start与__initcall_end的定义,其实这两个变量被定义在arch/arm/kernel/vmlinux.lds中,它是在内核编译的时候生成的,是整个内核源码的链接脚本,我们把关键部分代码抽出来。可以看到在__initcall_start与__initcall_end之间又被分成了许多段:从*(.initcall0.init) ~*(.initcall7s.init)。

  .init : { /* Init code and data        */
*(.init.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
. = ALIGN();
__setup_start = .;
*(.init.setup)
__setup_end = .;
__early_begin = .;
*(.early_param.init)
__early_end = .;
__initcall_start = .;
*(.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)
__initcall_end = .;
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
__security_initcall_start = .;
*(.security_initcall.init)
__security_initcall_end = .; . = ALIGN();
__initramfs_start = .;
usr/built-in.o(.init.ramfs)
__initramfs_end = .; . = ALIGN();
__per_cpu_start = .;
*(.data.percpu)
__per_cpu_end = .; __init_begin = _stext;
*(.init.data)
. = ALIGN();
__init_end = .; }

分析到这里,我们回过头再继续看module_init的定义,它被定义在include\linux\init.h中:

 #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 __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
typedef int (*initcall_t)(void);

根据以上定义,最终把module_init展开可以得到:这句话的意思就是只要调用module_init(x),就把x定义为initcall_t类型的函数,并且这个函数函数名为__initcall_x6,它被存放在.initcall6.init中,而这个段正好位于__initcall_start与__initcall_end之间。所以它被内核调用的时机就是在do_initcalls函数的for循环中。

#define module_init(x)     static initcall_t __initcall_x6 __attribute_used__  __attribute__((__section__(".initcall" 6 ".init"))) = x

对于其他的段,也是类似的,内核会在某个地方利用for循环而调用到在其他地方所定义的段。

Linux内核源码阅读记录一之分析存储在不同段中的函数调用过程的更多相关文章

  1. ubuntu下linux内核源码阅读工具和调试方法总结

    http://blog.chinaunix.net/uid-20940095-id-66148.html 一 linux内核源码阅读工具 windows下当然首选source insight, 但是l ...

  2. linux内核源码阅读之facebook硬盘加速利器flashcache

    从来没有写过源码阅读,这种感觉越来越强烈,虽然劣于文笔,但还是下定决心认真写一回. 源代码下载请参见上一篇flashcache之我见 http://blog.csdn.net/liumangxiong ...

  3. linux内核源码阅读之facebook硬盘加速flashcache之八

    前面我们的分析中重点关注正常的数据流程,这一小节关注如果有异常,那么流程是怎么走完的呢? 1)创建新任务时kcached_job申请不到 2)读写命中时cache块为忙 3)系统关机时处理,系统开机时 ...

  4. linux内核源码阅读之facebook硬盘加速flashcache之三

    上一节讲到在刷缓存的时候会调用new_kcahed_job创建kcached_job,由此我们也可以看到cache数据块与磁盘数据的对应关系.上一篇:http://blog.csdn.net/lium ...

  5. linux内核源码阅读之facebook硬盘加速flashcache之六

    其实到目前为止,如果对读流程已经能轻松地看懂了,那么写流程不需要太多脑细胞.我觉得再写下去没有太大的必要了,后面想想为了保持flashcache完整性,还是写出来吧.接着到写流程: 1530stati ...

  6. linux内核源码阅读之facebook硬盘加速flashcache之四

    这一小节介绍一下flashcache读写入口和读写的基础实现. 首先,不管是模块还是程序,必须先找到入口,用户态代码会经常去先看main函数,内核看module_init,同样看IO流时候也要找到入口 ...

  7. linux内核源码阅读之facebook硬盘加速flashcache之二

    flashcache数据结构都在flashcache.h文件中,但在看数据结构之前,需要先过一遍flashcache是什么,要完成哪些功能?如果是自己设计这样一个系统的话,大概要怎么设计. 前面讲过, ...

  8. linux内核源码阅读之facebook硬盘加速flashcache之五

    正常流程到flashcache_map的1623行或1625行,按顺序先看读流程: 1221static void 1222flashcache_read(struct cache_c *dmc, s ...

  9. Linux内核源码分析

    Linux源码下载: https://www.kernel.org/ https://git.kernel.org/ Linux内核源码阅读以及工具(转): https://blog.csdn.net ...

随机推荐

  1. javaScript 数据类型,变量的类型转换,typeof()可以判断变量类型

    js的数据类型和常见隐式转化逻辑. 一.六种数据类型 原始类型(基本类型):按值访问,可以操作保存在变量中实际的值.原始类型汇总中null和undefined比较特殊. 引用类型:引用类型的值是保存在 ...

  2. Count the Colors ZOJ - 1610 区间颜色覆盖

    #include<stdio.h> #include<iostream> #include<string.h> #include<algorithm> ...

  3. wxpython 简单例子:显示文本框的窗口显示鼠标位置

    简单例子来自教程: #!/bin/env python import wx class MyFrame(wx.Frame): def __init__(self): wx.Frame.__init__ ...

  4. java静态初始化块的执行顺序

    先来观察下面的代码 package trr; class Root { static{ System.out.println("Root的静态初始化块"); } { System. ...

  5. 【巨杉数据库SequoiaDB】巨杉数据库 v5.0 Beta版 正式发布

    2020年疫情的出现对众多企业运营造成了严重的影响.面对突发状况,巨杉利用长期积累的远程研发协作体系,仍然坚持进行技术创新,按照已有规划­­推进研发工作,正式推出了巨杉数据库(SequoiaDB) v ...

  6. bootstrap-table中时间戳转换为日期格式。

    { field: 'createdTime', title: '创建时间', formatter: function (value, row, index) { return changeDateFo ...

  7. Qt Gui 第六章布局管理

    1.QRadioButton之间如何互斥 其中一种方法是将各个QRadioButton控件放在同一个toolbarsLayout或者toolbarsGroupBox即可:如下所示 toolbarsGr ...

  8. Java连载76-基础数据类型包装类型及其方法简介

    一.java中八种基本数据类型对应的包装类型 基本数据类型      包装类型 byte                    java.lang.Byte short                ...

  9. 深入理解IP之CIDR

    现代IP基于分类的IP越来越少,而基于CIDR的方式的越来越多.那么可以看下面这篇文章: https://www.cnblogs.com/hark0623/p/6547432.html 这篇文章对CI ...

  10. 关闭Apache的目录浏览功能

    一.默认情况 默认情况下,Apache的配置文件C:\web\apache2.4\conf/httpd.conf中有如下参数: 引用 <Directory "/var/www/html ...