就像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含Kernel头文件,大多的Linux驱动程序需要包含下面三个头文件:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
其中,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的定义:
-
#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是结束,通过这两个变量我们就可以访问到所有的初始化函数了。这两个变量在那定义的呢?在一个连接器脚本文件里
-
.=ALIGN(4);
-
.initlist:{
-
__initlist_start=.;
-
*(.initlist)
-
__initlist_end=.;
-
}
这两个变量的值正好定义在.initlist区段的开始和结束地址,所以我们能通过这两个变量访问到所有的初始化函数。
与此类似,内核中也是用到这种方法,所以我们写驱动的时候比较独立,不用我们自己添加代码在一个固定的地方来调用我们自己的初始化函数和退出函数,连接器已经为我们做好了。先来分析一下module_init。定义如下:
-
#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__(".initcall"level".init")))=fn
如果某驱动想以func作为该驱动的入口,则可以如下声明:module_init(func);被上面的宏处理过后,变成__initcall_func6 __used加入到内核映像的".initcall"区。内核的加载的时候,会搜索".initcall"中的所有条目,并按优先级加载它们,普通驱动程序的优先级是6。其它模块优先级列出如下:值越小,越先加载。
-
#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末尾有个函数:
这个init_post中的第一句就是free_initmem();就是用来释放初始化代码和数据的。
-
voidfree_initmem(void)
-
{
-
if(!machine_is_integrator()&&!machine_is_cintegrator()){
-
free_area((unsignedlong)(&__init_begin),
-
(unsignedlong)(&__init_end),
-
"init");
-
}
-
}
接下来就是kernel内存管理的事了。
参考地址:http://blog.csdn.net/citytramper/archive/2006/02/16/600708.aspx
参考原文:http://blog.csdn.net/citytramper/archive/2006/04/15/664930.aspx
分享到:
相关推荐
来自网上-->module_init的简要分析和构建初始化函数表的实现图片解析
二者均是Python面向对象语言中的函数,__new__比较少用,__init__则用的比较多。 【异】 __new__是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例对象,是个静态方法。__init__是当实例对象创建...
Python 089.构造函数__init__.mp4
本文实例讲述了Python包,__init__.py功能与用法。分享给大家供大家参考,具体如下: 包: 为了组织好模块,将多个模块组合为一个包,所以包用于存放python模块 包通常是一个文件夹,当文件夹当作包使用时,文件夹...
linux 的c函数手册
Usart1_Init函数用于配置串口 1,设置了通信速率等;Clock_ini函数用于对上电后 RTC的状态进行判别。并作出相应的处理;Time_Show函数用于间隔一秒通过串口显示实时时钟;RTC_IRQHandler中断服务程序,用于处理秒...
解决Pytorch在Pycharm中没有代码提示的问题。首先把Pycharm升级到2019.1.1,然后把Pytorch升级到1.0.1.post2,然后把下载的__init__.pyi替换掉site-packages/torch/中的__init__.pyi就行。
单继承时super()和__init__()实现的功能是类似的 class Base(object): def __init__(self): print 'Base create' class childA(Base): def __init__(self): print 'creat A ', Base.__init__(self) class childB...
如果某类里没有__init__方法函数,通过类名字创建的实例对象为空,切没有初始化;如果有此方法函数,通常作为类的第一个方法函数,有点像C++等语言里的构造函数。 class Ca: def __init__(self, v): # 注意前后各两...
arm linux 从入口到start_kernel 代码分析 第四部分
Linux函数大全,包含常用函数说明,方便日常编程查询使用。
本文实例讲述了php使用curl_init()和curl_multi_init()多线程的速度比较。分享给大家供大家参考,具体如下: php中curl_init()的作用很大,尤其是在抓取网页内容或文件信息的时候,例如之前文章《php使用curl获取...
Linux C函数参考_常用数学函数篇 欢迎下载
/*********注册usb驱动函数框架**********/ static struct usb_driver skel_driver={ ...module_init(usb_skel_init); //驱动模块出口 module_exit(usb_skel_exit); //遵循GPL MODULE_LICENSE("GPL");
Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz delay_init(180); //初始化延时函数 uart_init(115200); //初始化USART LED_Init(); //初始化LED KEY_Init(); //初始化按键 SDRAM_Init(); //初始化SDRAM ...
7.9___init__.py_的用法|包、模块、函数与变量作用域|Python3.8入门_&_进阶_&_原生爬虫实战完全解读
通常在一个工程文件里面,我可能会发现在某个文件夹下面会有一个命名为__init__.py的py文件 why need init.py? 在一个工程文件下面,我们可能会有很多的py文件(模块),文件太多,管理很不方便,这时候我们就可以创建...
在 board/hang2440 目录下新建一个名为 boot_init.c 的文件,编写 colck_init 函数,同时加上一些声明和延时子函数,还有后面要用到的nand 相关操作函数
2440的main.c_之Main函数之Isr_Init分析这篇文章很详细的介绍了ARM中断系统的产生