linux驱动的入口函数module_init的加载和释放

就像你写c程序需要包含c库的头文件那样,linux内核编程也需要包含kernel头文件,大多的linux驱动程序需要包含下面三个头文件:
#include
#include
#include
其中,init.h 定义了驱动的初始化和退出相关的函数,kernel.h 定义了经常用到的函数原型及宏定义,module.h 定义了内核模块相关的函数、变量及宏。
几乎每个linux驱动都有个module_init(与module_exit的定义在init.h (/include/linux)中)。没错,驱动的加载就靠它。为什么需要这样一个宏?原因是按照一般的编程想法,各部分的初始化函数会在一个固定的函数里调用比如:
void init(void)
{
init_a();
init_b();
}
如果再加入一个初始化函数呢,那么在init_b()后面再加一行:init_c();这样确实能完成我们的功能,但这样有一定的问题,就是不能独立的添加初始化函数,每次添加一个新的函数都要修改init函数。可以采用另一种方式来处理这个问题,只要用一个宏来修饰一下:
void init_a(void)
{
}
__initlist(init_a, 1);
它是怎么样通过这个宏来实现初始化函数列表的呢?先来看__initlist的定义:
[cpp]view plaincopy
#define__init__attribute__((unused,__section__(.initlist)))
#define__initlist(fn,lvl)/
staticinitlist_t__init_##fn__init={/
magic:init_magic,/
callback:fn,/
level:lvl}
请注意:__section__(.initlist),这个属性起什么作用呢?它告诉连接器这个变量存放在.initlist区段,如果所有的初始化函数都是用这个宏,那么每个函数会有对应的一个initlist_t结构体变量存放在.initlist区段,也就是说我们可以在.initlist区段找到所有初始化函数的指针。怎么找到.initlist区段的地址呢?
extern u32 __initlist_start;
extern u32 __initlist_end;
这两个变量起作用了,__initlist_start是.initlist区段的开始,__initlist_end是结束,通过这两个变量我们就可以访问到所有的初始化函数了。这两个变量在那定义的呢?在一个连接器脚本文件里
[cpp]view plaincopy
.=align(4);
.initlist:{
__initlist_start=.;
*(.initlist)
__initlist_end=.;
}
这两个变量的值正好定义在.initlist区段的开始和结束地址,所以我们能通过这两个变量访问到所有的初始化函数。
与此类似,内核中也是用到这种方法,所以我们写驱动的时候比较独立,不用我们自己添加代码在一个固定的地方来调用我们自己的初始化函数和退出函数,连接器已经为我们做好了。先来分析一下module_init。定义如下:
[cpp]view plaincopy
#definemodule_init(x)__initcall(x);//include/linux/init.h
#define__initcall(fn)device_initcall(fn)
#definedevice_initcall(fn)__define_initcall(6,fn,6)
#define__define_initcall(level,fn,id)/
staticinitcall_t__initcall_##fn##id__used/
__attribute__((__section__(.initcalllevel.init)))=fn
如果某驱动想以func作为该驱动的入口,则可以如下声明:module_init(func);被上面的宏处理过后,变成__initcall_func6 __used加入到内核映像的.initcall区。内核的加载的时候,会搜索.initcall中的所有条目,并按优先级加载它们,普通驱动程序的优先级是6。其它模块优先级列出如下:值越小,越先加载。
[cpp]view plaincopy
#definepure_initcall(fn)__define_initcall(0,fn,0)
#definecore_initcall(fn)__define_initcall(1,fn,1)
#definecore_initcall_sync(fn)__define_initcall(1s,fn,1s)
#definepostcore_initcall(fn)__define_initcall(2,fn,2)
#definepostcore_initcall_sync(fn)__define_initcall(2s,fn,2s)
#definearch_initcall(fn)__define_initcall(3,fn,3)
#definearch_initcall_sync(fn)__define_initcall(3s,fn,3s)
#definesubsys_initcall(fn)__define_initcall(4,fn,4)
#definesubsys_initcall_sync(fn)__define_initcall(4s,fn,4s)
#definefs_initcall(fn)__define_initcall(5,fn,5)
#definefs_initcall_sync(fn)__define_initcall(5s,fn,5s)
#definerootfs_initcall(fn)__define_initcall(rootfs,fn,rootfs)
#definedevice_initcall(fn)__define_initcall(6,fn,6)
#definedevice_initcall_sync(fn)__define_initcall(6s,fn,6s)
#definelate_initcall(fn)__define_initcall(7,fn,7)
#definelate_initcall_sync(fn)__define_initcall(7s,fn,7s)
可以看到,被声明为pure_initcall的最先加载。
module_init除了初始化加载之外,还有后期释放内存的作用。linux kernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码一般都只执行一次,为了有更有效的利用内存,这些代码所占用的内存可以释放出来。
linux就是这样做的,对只需要初始化运行一次的函数都加上__init属性,__init 宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,module_exit的参数卸载时同__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作,显然__init和__exit对动态加载的模块是无效的,只支持完全编译进内核。
在kernel初始化后期,释放所有这些函数代码所占的内存空间。连接器把带__init属性的函数放在同一个section里,在用完以后,把整个section释放掉。当函数初始化完成后这个区域可以被清除掉以节约系统内存。kenrel启动时看到的消息“freeing unused kernel memory: xxxk freed”同它有关。
我们看源码,init/main.c中start_kernel是进入kernel()的第一个c函数,在这个函数的最后一行是rest_init();
static void rest_init(void)
{
.....
kernel_thread(kernel_init, null, clone_fs | clone_sighand);
unlock_kernel();
cpu_idle();
.....
}
创建了一个内核线程,主函数kernel_init末尾有个函数:
[cpp]view plaincopy
/*
*ok,wehavecompletedtheinitialbootup,and
*we'reessentiallyupandrunning.getridofthe
*initmemsegmentsandstarttheuser-modestuff..
*/
init_post();
这个init_post中的第一句就是free_initmem();就是用来释放初始化代码和数据的。
[cpp]view plaincopy
voidfree_initmem(void)
{
if(!machine_is_integrator()&&!machine_is_cintegrator()){
free_area((unsignedlong)(&__init_begin),
(unsignedlong)(&__init_end),
init);
}
}
接下来就是kernel内存管理的事了。

明年6月执行!续航200km以下的纯电车型不再免除购置税!
一种集成Helmholtz谐振腔的声压增强型PMUT设计
锂电池将重新定义电动汽车
Verizon携手合作伙伴加速释放5G技术的最大潜能
WT2605C-L011语音芯片IC:蓝牙音频与数据传输功能的集成优势及应用
linux驱动的入口函数module_init的加载和释放
老旗舰和新中端到底应该怎么选择 小米和vivo拿错了剧本
混合介质PCB多层板层压制造用材简介
innovus中的DanglingWire的理解和处理
医疗器械产业科学发展高层峰会在京举行
详解无线传感器网络实时数据融合策略
工厂电表数据采集系统助力能耗分析与节能优化
机智云入围大鲸榜·2023工业AI高成长科技公司TOP30
华为:AI将成为颠覆移动互联网时代的下一个拐点
方波,三角波发生器电路图
中国联通推出“5G快闪直播间”为数字娱乐跨界注入了更多活力
在人工智能的市场发展态势下 芯片行业又将赢来新的洗牌
可自然降解且生物相容的可植入微型超级电容器
区块链的跨链项目Polkadot介绍
国际标准单位