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

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

3天内不再提示

嵌入式2---在单片机里实现module_init机制

jf_49463572 来源:27熊熊嵌入式 作者:27熊熊嵌入式 2026-05-04 11:24 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

嵌入式2---在单片机里实现module_init机制

很多朋友在写单片机程序时,常会遇到这样的问题:所有模块的初始化函数(比如LED初始化、串口初始化、传感器初始化),都要手动在main函数里一一调用,不仅代码混乱、维护麻烦,而且新增或删除模块时,还要修改main函数,违背了“高内聚、低耦合”的原则。

其实在Linux系统中,module_init机制的核心思想也是一样的,Linux内核本身就是高度模块化的设计——驱动开发者只需通过module_init宏注册驱动初始化函数,无需修改内核核心代码,内核启动时会自动遍历所有注册的初始化函数,完成驱动加载;当模块卸载时,通过module_exit宏注册的退出函数会被自动调用,这也是Linux驱动“热插拔”特性的基础之一。

wKgZPGn1bo6AJ-NQAADtuhYLDSQ802.png  

module_init机制,简单说就是「自动注册、统一初始化」—— 每个模块的初始化函数,通过宏定义“注册”到系统中,程序启动后,自动遍历所有注册的初始化函数并执行。

可能有朋友会说:“单片机没有操作系统(比如Linux)里的module_init宏,怎么实现?” 其实原理很简单,核心就是利用「编译器特性」和「函数指针数组」,手动模拟出这一机制。今天就以STM32为例来介绍如何实现这一功能;

我们先分析一下linux 中module_init宏

定位到Linux内核源码中的include/linux/init.h,可以看到有如下代码:

#ifndef MODULE// 省略#definemodule_init(x) __initcall(x);// 省略#else
#definemodule_init(initfn)  intinit_module(void) __attribute__((alias(#initfn)));// 省略#endif

第一种情况:静态编译(#ifndefMODULE)。当MODULE未定义时,module_init(x)被宏定义为__initcall(x)。__initcall是Linux内核中用于标记静态初始化函数的宏,被该宏标记的函数会被放入内核的.initcall段中。其核心源码同样位于include/linux/init.h中,相关定义如下(只粘贴了相关的内容):

//...typedefint(*initcall_t)(void);//...#define__initcall(fn) device_initcall(fn)//...#definedevice_initcall(fn)   __define_initcall(fn, 6)//...#define__initcall(fn)  staticinitcall_t __initcall_##fn __used   __attribute__((__section__(".initcall.init"), __cold__)) = fn;//...#definedevice_initcall(fn)   __define_initcall(fn, 6)// ...#define__define_initcall(fn, id)  staticinitcall_t __initcall_##fn##id __used   __attribute__((__section__(".initcall"#id ".init"), __cold__)) = fn;

源码解析:__initcall宏的核心作用,是通过编译器__attribute__((__section__))指令,将初始化函数fn放入内核的.initcall相关段中(不同优先级对应不同子段)。其中__used属性确保函数不被编译器优化删除,__cold__属性标记该函数为冷函数(仅启动时调用,优化编译)。被该宏标记的函数,会被内核启动流程自动遍历执行,从而完成静态模块的初始化。这种方式下,模块会被直接编译到内核镜像中,内核启动时就完成初始化,无法动态卸载。

而内核启动时,正是通过do_initcalls函数来遍历并执行所有注册的静态初始化函数,该函数源码位于内核init/main.c中,核心实现如下:

staticvoid__initdo_initcalls(void){ intlevel;
 for(level =0; level < ARRAY_SIZE(initcall_levels) - 1; level++)    do_initcall_level(level);}

函数解析:do_initcalls函数是静态初始化的“总入口”,其核心逻辑是按优先级顺序遍历所有初始化层级。其中:

  • initcall_levels是一个数组,存储了前文提到的不同优先级.initcall段(如.initcall0.init、.initcall1.init等)的起始地址,对应pure_initcall、core_initcall等不同优先级的初始化宏。

  • ARRAY_SIZE(initcall_levels) - 1用于获取优先级层级总数,避免越界。

  • do_initcall_level(level)是底层执行函数,其核心实现(简化版,贴合内核源码逻辑)如下,传入优先级level后,会遍历该优先级下所有注册的初始化函数并依次调用,确保高优先级的初始化函数(如内核核心模块)先执行,低优先级函数(如设备驱动)后执行:

staticvoid__initdo_initcall_level(intlevel){ // 省略:参数校验、打印调试信息等冗余代码  initcall_t *fn;// 定义函数指针,用于遍历当前优先级的初始化函数
 // 遍历当前优先级level对应的.initcall段:从当前层级起始地址,到下一层级起始地址 for(fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)    do_one_initcall(*fn);// 调用单个初始化函数,完成该函数的执行}

函数解析:do_initcall_leveldo_initcalls函数的底层执行载体,专门负责处理单个优先级层级的初始化,核心逻辑拆解如下:

  • initcall_t *fn定义与前文一致的初始化函数指针,用于遍历当前优先级下的所有初始化函数。

  • initcall_levels[level]获取当前优先级level对应的.initcall段起始地址(比如level=1对应.core_initcall宏注册的函数所在段起始);initcall_levels[level+1]则是下一个优先级层级的起始地址,两者构成当前优先级的地址范围,确保只遍历当前层级的函数,不跨层级执行。

  • do_one_initcall(*fn)真正执行单个初始化函数的接口,传入当前遍历到的函数指针(*fn即具体的初始化函数),完成该模块的初始化;该函数内部会做函数合法性校验、执行状态判断等,确保初始化过程稳定。

do_initcalls函数的层级遍历,高优先级先执行,满足内核启动时“先核心、后外设”的初始化逻辑。

第二种情况:动态加载(#else分支)。当MODULE被定义时,module_init(initfn)会通过__attribute__((alias(#initfn)))定义一个别名函数init_module,该别名指向我们自己编写的初始化函数initfn。此时模块会被编译为.ko(内核模块)文件,后续可通过insmod命令动态加载到内核中——内核加载模块时,会自动调用init_module函数(即我们的初始化函数);卸载模块时,通过rmmod命令调用module_exit注册的退出函数,实现模块的热插拔,这也是Linux驱动动态开发的核心方式。

1.机制原理

module_init机制的核心,本质是把所有模块的初始化函数地址,存到一个数组里,程序启动后,循环调用这个数组里的所有函数。

用到两个关键知识点

  1. 函数指针:可以理解为“存放函数地址的变量”,通过函数指针,我们可以间接调用函数(比如void (*init_func)(void); 就是一个指向“无参数、无返回值”初始化函数的指针)。

  2. 编译器section特性:我们可以通过编译器指令,将所有注册的初始化函数指针,集中存放在指定的内存区域(section),后续只需找到这个区域的起始和结束地址,就能遍历所有函数。

流程:模块注册 → 函数指针存入指定section → 程序启动 → 遍历section中的函数指针 → 依次调用(完成所有模块初始化)。

下面我们讲实现方式:

我使用的是keil

我把我的代码贴出来

.h文件

typedefint(*initcall_t)(void);#define__init__attribute__((unused))#definemodule_init(fn) constinitcall_t __initcall_##fn __attribute__((section(".__initcall.0.b"), used)) = fn
.c文件
staticconstinitcall_t__initcall_sentinel_start  __attribute__((used,section(".__initcall.0.a"))) =0;
staticconstinitcall_t__initcall_sentinel_end  __attribute__((used,section(".__initcall.0.c"))) =0;

voidmodule_init_call(void){
constinitcall_t*call_start = &__initcall_sentinel_start; constinitcall_t*call_end = &__initcall_sentinel_end;while(call_start < call_end){if(*call_start)(*call_start)();call_start++;}}

在初始化函数下面添加 module_init宏如:

int test_init(void){return0;}module_init(test_init);

编译之后我们在.map文件里可以看到

wKgZPGn1bo6ACjmDAATYOUBAzQA904.png

初始化函数被放在的相应的区域里面

module_init_call函数放在了Reset_Handler,每次上电启动的时候就会调用

Reset_Handler  PROC        EXPORT Reset_Handler      [WEAK]   IMPORT SystemInit   IMPORT __mainIMPORT module_init_call        LDR  R0, =0xE000ED88  ; 使能浮点运算 CP10,CP11        LDR  R1,[R0]        ORR  R1,R1,#(0xF << 20)        STR  R1,[R0]        LDR  R0, =SystemInit        BLX  R0LDR  R0, =module_init_call        BLX  R0        LDR  R0, =__main        BX   R0        ENDP
大家可以根据不同的优先级设置多个数组,跟linux一个区遍历整个区域去做初始化参考链接:https://blog.csdn.net/weixin_37571125/article/details/78665184

https://zhuanlan.zhihu.com/p/615272622

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

    关注

    6078

    文章

    45591

    浏览量

    673962
  • 嵌入式
    +关注

    关注

    5210

    文章

    20680

    浏览量

    337363
  • Linux
    +关注

    关注

    88

    文章

    11822

    浏览量

    219600
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

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

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

    单片机嵌入式Internet技术的Web应用实现

    精简TCP/IP协议栈的实现,对数据的逐层打包、封帧、传送等流程[2];二是单片机Web应用服务的实现。 由单片机组成的
    发表于 11-24 18:10

    什么是嵌入式单片机?嵌入式单片机详情汇总

    嵌入式单片机,即嵌入式微控制器,指以微控制器为核心控制单元的嵌入到对象体系中的专用计算机系统,是应用十分广泛的一种嵌入式系统结构。
    发表于 11-13 09:39 6873次阅读

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

    linux就是这样做的,对只需要初始化运行一次的函数都加上__init属性。kernel初始化后期,释放所有这些函数代码所占的内存空间。它是怎么做到的呢?看过module_init
    的头像 发表于 04-18 14:50 6093次阅读

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

    几乎每个linux驱动都有个module_init(与module_exit的定义Init.h (/include/linux) 中)。没错,驱动的加载就靠它。为什么需要这样一个宏?
    发表于 05-05 14:43 6154次阅读
    linux驱动的入口函数<b class='flag-5'>module_init</b>的加载和释放

    单片机嵌入式的转化

    提到单片机很多人都很觉得不陌生,大街小巷上面电子产品都用到。近几年随着嵌入式的发展,做单片机的一帮家伙突然觉得大祸临头一般发现自己熟悉掌握的单片机慢慢被
    发表于 09-22 10:13 1574次阅读

    到底什么是嵌入式? 什么是单片机

    到底什么是嵌入式?什么是单片机
    的头像 发表于 02-25 16:13 1.6w次阅读

    怎么学习嵌入式单片机

    关于怎么学习嵌入式单片机,我从自身学生经历、工作经验和对于嵌入式单片机学习的建议三个方面回答:
    发表于 07-15 17:37 1483次阅读

    单片机嵌入式区别

    单片机嵌入式芯片平台片上资源价格应用场景不同开发模式技术特征芯片平台主流单片机平台:51、PIC、STM32、AVR、MSP430等主流嵌入式平台:ARM(最广泛)、PPC(老美、欧
    发表于 10-20 14:21 4次下载
    <b class='flag-5'>单片机</b>与<b class='flag-5'>嵌入式</b>区别

    单片机or嵌入式linux

    最近很多童鞋投票并咨询如何从单片机转做嵌入式Linux开发。看来读者圈中做单片机,RTOS的不少。尽管我目前从事Linux/Android方面的嵌入式开发工作,但是读书的时候也有5年左
    发表于 11-01 16:26 17次下载
    <b class='flag-5'>单片机</b>or<b class='flag-5'>嵌入式</b>linux

    单片机嵌入式的子类

    嵌入式系统设计的第一步是结合具体的应用,综合考虑系统对成本、性能、可扩展性、开发周期等各个方面的要求,确定系统的主控器件,并以之为核心搭建系统硬件平台。2 硬件组成上的区别单片机
    发表于 11-04 09:06 14次下载
    <b class='flag-5'>单片机</b>是<b class='flag-5'>嵌入式</b>的子类

    单片机到底是不是嵌入式

    01 问题很多同学一直纠结:我是学单片机呢还是学嵌入式呢?还有人说单片机也是嵌入式,到底对不对?嵌入式
    发表于 11-04 11:21 14次下载
    <b class='flag-5'>单片机</b>到底是不是<b class='flag-5'>嵌入式</b>?

    单片机嵌入式的区别

    ,价格低,应用领域大多为小家电,终端设备。 嵌入式片上资源丰富,价格高,应用领域广泛,基本可以适用于任何领域。开发模式 单片机一般都是裸机开发,程序规模较小,只有比较高端的芯片上才会使用RTOS
    发表于 11-15 12:36 15次下载
    <b class='flag-5'>单片机</b>和<b class='flag-5'>嵌入式</b>的区别

    1.单片机嵌入式的关系(3)

    单片机嵌入式的关系什么是单片机?什么是嵌入式单片机嵌入式的核心差异
    发表于 12-01 15:51 16次下载
    1.<b class='flag-5'>单片机</b>和<b class='flag-5'>嵌入式</b>的关系(3)

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

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