Linux驱动-module_init
Linux驱动开发中,设备是在加载linux内核的时候,通过 module_init 进行挂载。
这个module_init 是什么呢?这里网上搜索找了一些资料,学习记录一下。
一 Code实例
如下图,337 行的 module_init 会调用到 317行的ap3216c_init这个函数,ap3216c_init是驱动入口函数。
在这里,ap3216c_init( ) 会新增一个设备的驱动结构体——ap3216c_driver。
同样对应的,有加载就有卸载。338行的moudle_exit 会调用到330行的ap3216c_exit这个函数。
ap3216c_exit() 是驱动的出口函数,会去删除一个设备——ap3216c_driver。
二 module_init 架构图
三 module_init 调用流程
当bootloader加载完kernel并解压并放置与内存中准备开始运行,首先被调用的函数是start_kernel。
start_kernel -> reset_init -> kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
|
|->static int __ref kernel_init(void *unused)
|
|-> kernel_init_freeable( )
|
|-> do_basic_setup();
|
|——> do_initcalls();
其中有个重要的流程:
fn = initcall_levels[level],假设level = 0,即fn = initcall_levels[0] =__initcall0_start;
那么 fn 指向了链接脚本中的 __initcall0_start地址。
每当 fn++ 也就是 fn 逐次指向注册到.initcall0.init 和 .initcall0s.init 段中的函数地址。
for 循环的条件是 fn< initcall_levels[ level +1 ] = initcall_levels[ 0 + 1 ] = initcall_levels[1]= __initcall1_start
简单来说,就是对所有的段,遍历每个段中的函数。
四 函数流程
从上到下的顺序执行。
总览
具体分析:
1 start_kernel()
2 rest_init(void)
3 kernel_init(void unused)
4 kernel_init_freeable
5 do_basic_setup
6 static void __init do_initcalls(void)
7 static void __init do_initcall_level (int level)
8 do_one_initcall(initcall_t fn)
9 arch/arm/kernel/vmlinux.lds
10 __define_initcall
11 include\linux\init.h
五 为什么使用module_init
原因1:使用方便
在传统的思想中,各个模块的初始化函数会在一个固定的init函数里调用比如:
一般会定义一个总的init 函数,在这个函数里面实现各个模块的init。
当要新增一个模块的时候,就需要对这个init函数进行修改。
void init()
{
init_a();
init_b();
...
}
使用module_init,即可直接新增模块而不用去修改这个所谓的“init”函数。
原因2:初始化之后可以释放代码空间。
使用moudle_init,初始化函数在完成初始化后,代码占用的空间会被释放。
linux kernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码一般都只执行一次,为了有更有效的利用内存,这些代码所占用的内存可以释放出来。
linux就是这样做的,对只需要初始化运行一次的函数都加上__init属性。在kernel初始化后期,释放所有这些函数代码所占的内存空间。
实现方式:
连接器把带__init属性的函数放在同一个section里,在用完以后,把整个section释放掉。
init/main.c中start_kernel,这个函数的最后一行是rest_init()
六 参考
https://blog.csdn.net/weixin_44698673/article/details/123904354
https://blog.csdn.net/u013216061/article/details/72511653
https://blog.csdn.net/yxtxiaotian/article/details/83985965