0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

详解嵌入式Linux设备驱动篇module_init

Q4MP_gh_c472c21 来源:未知 作者:李倩 2018-04-18 14:50 次阅读

一直以来写linux驱动,都是按照固定格式,定义一个初始化和退出函数,书上告诉我这两个函数会被调用,至于为什么会被调用,在哪调用,一直不清楚。

偶然的一个机会,看到blob里面的代码,里面有一个初始化函数列表。按照一般的编程想法,各部分的初始化函数会在一个固定的函数里调用比如:

void init(void)

{

init_a();

init_b();

}

如果再加入一个初始化函数呢,那么再init_b()后面再加一行:

init_c();

这样确实能完成我们的功能,但这样有一定的问题,就是不能独立的添加初始化函数,每次添加一个新的函数都要修改init函数,blob中的初始化函数就是完全独立的,只要用一个宏来修饰一下:

void init_a(void)

{

}

__initlist(init_a, 1);

它是通过这个宏来实现初始化函数列表的呢?

先来看__initlist的定义:

#define __init __attribute__((unused, __section__(".initlist")))

#define __initlist(fn, lvl) /

static initlist_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还有其他的特性,比如:我们的初始化函数在完成初始化后,代码占用的空间会被释放,这又是为什么呢?今天晚了,下次再写。

linux kernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码一般都只执行一次,为了有更有效的利用内存,这些代码所占用的内存可以释放出来。

linux就是这样做的,对只需要初始化运行一次的函数都加上__init属性。在kernel初始化后期,释放所有这些函数代码所占的内存空间。它是怎么做到的呢?看过module_init和module_exit 的人知道,连接器把带__init属性的函数放在同一个section里,在用完以后,把整个section释放掉。

口说无凭,我们看源码,init/main.c中start_kernel是进入kernel的第一个c函数,在这个函数的最后一行是

rest_init();

static void rest_init(void)

{

kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);

unlock_kernel();

cpu_idle();

}

创建了一个内核线程,主函数init,代码如下:

static int init(void * unused)

{

lock_kernel();

do_basic_setup();

prepare_namespace();

/*

* Ok, we have completed the initial bootup, and

* we're essentially up and running. Get rid of the

* initmem segments and start the user-mode stuff..

*/

free_initmem();

unlock_kernel();

红色那行代码就是用来释放初始化代码和数据的。

void free_initmem(void)

{

#ifndef CONFIG_XIP_ROM

if (!machine_is_integrator()) {

free_area((unsigned long)(&__init_begin),

(unsigned long)(&__init_end),

"init");

}

#endif

}

接下来就是kernel内存管理的事了。

********************************************************************************************************

在Linux底下写过driver模块的对这个宏一定不会陌生。module_init宏在MODULE宏有没有定义的情况下展开的内容是不同的,如果这个宏没有定义,基本上表明阁下的模块是要编译进内核的(obj-y)。

1.在MODULE没有定义这种情况下,module_init定义如下:

#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 __used \

__attribute__((__section__(".initcall" level ".init"))) = fn

所以,module_init(x)最终展开为:

static initcall_t __initcall_##fn##id __used \

__attribute__((__section__(".initcall" level ".init"))) = fn

更直白点,假设阁下driver所对应的模块的初始化函数为int gpio_init(void),那么module_init(gpio_init)实际上等于:

static initcall_t __initcall_gpio_init_6 __used __attribute__((__section__(".initcall6.init"))) = gpio_init;

就是声明一类型为initcall_t(typedef int (*initcall_t)(void))函数指针类型的变量__initcall_gpio_init_6并将gpio_init赋值与它。

这里的函数指针变量声明比较特殊的地方在于,将这个变量放在了一名为".initcall6.init"节中。接下来结合vmlinux.lds中的

.initcall.init : AT(ADDR(.initcall.init) - (0xc0000000 -0x00000000)) {

__initcall_start = .;

*(.initcallearly.init) __early_initcall_end = .; *(.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 = .;

}

以及do_initcalls:

static void __init do_initcalls(void)

{

initcall_t *call;

for (call = __initcall_start; call < __initcall_end; call++)

do_one_initcall(*call);

/* Make sure there is no pending stuff from the initcall sequence */

flush_scheduled_work();

}

那么就不难理解阁下模块中的module_init中的初始化函数何时被调用了:在系统启动过程中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。

2.在MODULE被定义的情况下(大部分可动态加载的driver模块都属于此, obj-m),module_init定义如下:

#define module_init(initfn) \

static inline initcall_t __inittest(void) \

{ return initfn; } \

int init_module(void) __attribute__((alias(#initfn)));

这段宏定义关键点是后面一句,通过alias将initfn变名为init_module。前面那个__inittest的定义其实是种技巧,用来对initfn进行某种静态的类型检查,如果阁下将模块初始化函数定义成,比如,void gpio_init(void)或者是int gpio_init(int),那么在编译时都会有类似下面的warning:

GPIO/fsl-gpio.c: In function '__inittest':

GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type

通过module_init将模块初始化函数统一别名为init_module,这样以后insmod时候,在系统内部会调用sys_init_module()去找到init_module函数的入口地址。

如果objdump -t gpio.ko,就会发现init_module和gpio_init位于相同的地址偏移处。简言之,这种情况下模块的初始化函数在insmod时候被调用。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 连接器
    +关注

    关注

    96

    文章

    12631

    浏览量

    133124
  • Linux
    +关注

    关注

    87

    文章

    10990

    浏览量

    206733
  • 函数
    +关注

    关注

    3

    文章

    3868

    浏览量

    61308

原文标题:嵌入式Linux设备驱动篇module_init 详解

文章出处:【微信号:gh_c472c2199c88,微信公众号:嵌入式微处理器】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    linux内核使用链接脚本模仿module_init机制实战

    编写过设备驱动就会经常碰到module_init这个宏来定义驱动入口函数。这个宏定义了一个函数指针指向我们的驱动入口函数,等到上电的时候就将
    发表于 06-16 10:42 368次阅读
    <b class='flag-5'>linux</b>内核使用链接脚本模仿<b class='flag-5'>module_init</b>机制实战

    嵌入式Linux字符驱动LED灯设计

    嵌入式Linux字符驱动LED灯设计嵌入式Linux字符设备
    发表于 02-03 14:28

    嵌入式Linux应用开发详解 光盘

    `本书立足于嵌入式Linux平台,侧重于实际开发应用,是一本结合嵌入式Linux技术最新发展潮流和编者多年开发经验,精心编写的嵌入式
    发表于 06-02 10:26

    [分享资料]嵌入式Linux应用程序开发详解

    Linux的环境搭建,以及嵌入式Linux的I/O与文件系统的开发、进程控制开发、进程间通信开发、网络应用开发、基于中断的开发、设备驱动程序
    发表于 09-11 23:25

    转:嵌入式Linux应用程序开发详解

    。接着系统地讲解了嵌入式linux的环境搭建,以及嵌入式linux的i/o与文件系统的开发、进程控制开发、进程间通信开发、网络应用开发、基于中断的开发、
    发表于 06-12 11:11

    嵌入式驱动开发 Linux字符设备驱动

    1.嵌入式设备驱动概述2.字符设备驱动框架3.GPIO驱动程序设计实例4.中断处理和同步机制1.
    发表于 10-09 17:21

    嵌入式linux设备驱动开发详解SD

    嵌入式linux设备驱动开发详解SD,本资料大于20M,分2部分发
    发表于 11-05 17:21

    ARM嵌入式Linux系统开发详解

    例介绍了Linux设备驱动、网络设备驱动、Flash设备驱动
    发表于 09-14 08:57

    嵌入式Linux驱动开发

    一众嵌入式Linux驱动书中笔者最推崇宋宝华的《Linux设备驱动开发
    发表于 11-04 09:02

    linux驱动设备驱动开发详解

    1.《linux驱动设备驱动开发详解》 基于linux4.0 是目前主流的
    发表于 11-08 08:03

    Linux嵌入式驱动开发

    全部传送门Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)Linux嵌入式
    发表于 12-17 06:22

    IAR实现类linux驱动模块框架module_init的相关资料推荐

    其实在单片机上也能使用类linux驱动模块框架module_init(init_fun),从而给驱动管理提供了新的方式。boot.icf文件
    发表于 01-27 06:38

    Marvell10g驱动程序缺少MODULE_INITMODULE_EXIT怎么解决?

    为了查看驱动程序是否正在加载,我们将 printk() 添加到 probe 和 _init 函数。文本不会显示。 我注意到驱动程序缺少驱动程序通常具有的
    发表于 05-24 07:53

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

    几乎每个linux驱动都有个module_init(与module_exit的定义在Init.h (/include/
    发表于 05-05 14:43 5522次阅读
    <b class='flag-5'>linux</b><b class='flag-5'>驱动</b>的入口函数<b class='flag-5'>module_init</b>的加载和释放

    IAR 实现类linux驱动模块框架module_init(init_fun)

    其实在单片机上也能使用类linux驱动模块框架module_init(init_fun),从而给驱动管理提供了新的方式。boot.icf文件
    发表于 12-03 13:36 0次下载
    IAR 实现类<b class='flag-5'>linux</b><b class='flag-5'>驱动</b>模块框架<b class='flag-5'>module_init</b>(<b class='flag-5'>init</b>_fun)