当前位置: 首页 > article >正文

Linux淘金记(一):module_init——初始化就该这么写

Linux淘金记(一):module_init——初始化就该这么写

  • 一、shi山般的初始化
  • 二、 释放函数名的本质
    • 第一步:把猫赶到一个笼子里,利用module_init
    • 第二步:遍历猫笼子,do_initcalls()
  • 三、在keil中实现module_init及万能初始化函数
    • 第一步、修改链接脚本
    • 第二步、实现module_init
    • 第三步、实现do_initcalls()
  • 四、在STM32测试一下
  • 五、优雅!

没头绪时,Linux源码必有解决方案。 ——伊曼努尔·康德

一、shi山般的初始化

写裸机时,初始化程序简单粗暴这么写:

#include "gpio.h"
#include "uart.h"
#include "led.h"
#include "spi.h"
...
int main(void)
{gpio_init();uart_init();led_init();spi_init();...
}

优雅点这么写:

#include "gpio.h"
#include "uart.h"
#include "led.h"
#include "spi.h"
...
void bsp_init(void)
{gpio_init();uart_init();led_init();spi_init();...
}int main(void)
{bsp_init();...
}

真的优雅么?bsp_init就是一个fen坑!
问题一:
所有init函数调用都耦合到bsp_init()里,意味着每加一个驱动程序就得在bsp_init追加这个调用。一千个设备就得扩到上千行,增删改查恶心得要吐,还容易丢三落四,制造bug。自己的shi还好,别人的呢?
问题二:
对嵌入式项目,不同的定制方案需要不同的bsp_init,你需要在为每个项目重写一遍bsp_init,无聊又浪费精力。
问题三:
有没有这样的初始化办法,每个工程的main函数都调用一个相同的bsp_init,每个驱动的c文件无需将自己的xxx_init函数添加到bsp_init,bsp_init会自动调用它们?

对于问题三,像是做梦。但是回头想想,咱们能想到的问题,写内核的神级前辈们就没想到吗?来看看Linux内核的方案。

二、 释放函数名的本质

有这么个公式:

xxx_init == 函数名 == 指针 == 数据

那么c文件中的xxx_init函数就是数据,数据要放到内存中,内存又可寻址。
诶?灵感来了,要把猫赶到一个笼子里去。把所有bsp_init要调用的函数名字放到一个已知地址的内存段中,然后bsp_init去遍历这个内存段,并通过每个函数指针调用这个函数原型。当然这个灵感不是笔者想的,是写内核的神人想的。

Linux内核是这么做的:

第一步:把猫赶到一个笼子里,利用module_init

Linux几乎每个驱动模块的结尾都要使用module_init这个宏:

#define module_init(x)	          __initcall(x);
#define __initcall(fn)            device_initcall(fn)
#define device_initcall(fn)		  __define_initcall(fn, 6)
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define ___define_initcall(fn, id, __sec) \static initcall_t __initcall_##fn##id __used \__attribute__((__section__(#__sec ".init"))) = fn;
#endif

展开:

#define module_init(x)  static initcall_t __initcall_##x##6 __used \__attribute__((__section__(".initcall6.init"))) = x;

例如module_init(led_init)就能展开为:

static initcall_t __initcall_led_initinit6 __used \
__attribute__((__section__(".initcall6.init"))) = led_init;

定义一个静态函数指针__initcall_led_initinit6指向初始化函数led_init,并且利用__attribute__把__initcall_led_initinit6链接到".initcall6.init"这个内存数据段里。简单来说就是把函数名丢到名为.initcall6.init的内存段里。如果每个驱动文件都这么做的话会出现这样:
在这里插入图片描述

第二步:遍历猫笼子,do_initcalls()

内核启动时会调用do_initcalls(),原型位于init/main.c:

static void __init do_initcalls(void)
{int level;for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)do_initcall_level(level);
}

循环调用do_initcall_level,原型(保留核心代码):

extern initcall_entry_t __initcall_start[];
extern initcall_entry_t __initcall0_start[];
extern initcall_entry_t __initcall1_start[];
extern initcall_entry_t __initcall2_start[];
extern initcall_entry_t __initcall3_start[];
extern initcall_entry_t __initcall4_start[];
extern initcall_entry_t __initcall5_start[];
extern initcall_entry_t __initcall6_start[];
extern initcall_entry_t __initcall7_start[];
extern initcall_entry_t __initcall_end[];static initcall_entry_t *initcall_levels[] __initdata = {__initcall0_start,__initcall1_start,__initcall2_start,__initcall3_start,__initcall4_start,__initcall5_start,__initcall6_start,__initcall7_start,__initcall_end,
};
/*...*/
static void __init do_initcall_level(int level)
{initcall_entry_t *fn;/* ... */for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)do_one_initcall(initcall_from_entry(fn));
}

最后do_one_initcall(保留核心代码):


int __init_or_module do_one_initcall(initcall_t fn)
{/* ... */ret = fn();return ret;/* ... */
}

遍历过程如图:
在这里插入图片描述
这样驱动文件中的xxx_init无需追加到do_initcalls中,只需在本文件使用module_init(xxx_init),然后在main函数调用do_initcalls。本质上这种做法是把在bsp_init函数中追加函数的工作交给了链接器。

三、在keil中实现module_init及万能初始化函数

第一步、修改链接脚本

1.找到MDK的链接脚本xxx.sct(xxx为工程名)的路径。注意这个文件需要编译工程后生成。
在这里插入图片描述
2.拷贝这个文件并重命名为module.sct。笔者这里把它放到了工程根目录下以免被误删。
在这里插入图片描述
3.链接器中选择module.sct,并打开编辑。
在这里插入图片描述
打开后如下:
在这里插入图片描述
4.追加自定义数据段_initcall6_init,这里模仿内核添加8个数据段:

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************LR_IROM1 0x08000000 0x00100000  {    ; load region size_regionER_IROM1 0x08000000 0x00100000  {  ; load address = execution address*.o (RESET, +First)*(InRoot$$Sections).ANY (+RO)}RW_IRAM1 0x20000000 0x00020000  {  ; RW data.ANY (+RW +ZI)}_initcall0_init +0 {.ANY (_initcall0_init)}_initcall1_init +0 {.ANY (_initcall1_init)}_initcall2_init +0 {.ANY (_initcall2_init)}_initcall3_init +0 {.ANY (_initcall3_init)}_initcall4_init +0 {.ANY (_initcall4_init)}_initcall5_init +0 {.ANY (_initcall5_init)}_initcall6_init +0 {.ANY (_initcall6_init)}_initcall7_init +0 {.ANY (_initcall7_init)}
}

完成!

第二步、实现module_init

创建一个init.h文件,module_init的宏基本照抄Linux内核:

#ifndef __INIT_H
#define __INIT_Htypedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);#define __init
#define __exit
#define __used  __attribute__((__used__))#define __define_initcall(fn, id) __used \static volatile initcall_t __initcall_##fn##id \__attribute__((__section__("_initcall" #id "_init"))) = fn#define pure_initcall(fn)		__define_initcall(fn, 0)
#define core_initcall(fn)		__define_initcall(fn, 1)
#define postcore_initcall(fn)   __define_initcall(fn, 2)
#define arch_initcall(fn)		__define_initcall(fn, 3)
#define subsys_initcall(fn)		__define_initcall(fn, 4)
#define fs_initcall(fn)			__define_initcall(fn, 5)
#define device_initcall(fn)		__define_initcall(fn, 6)
#define late_initcall(fn)		__define_initcall(fn, 7)#define __initcall(fn) device_initcall(fn)#define __exitcall(fn) \static volatile exitcall_t __exitcall_##fn__exit_call = fn#define module_init(x)	__initcall(x);
#define module_exit(x)	__exitcall(x);void __init do_initcalls(void);#endif  /* __INIT_H */

第三步、实现do_initcalls()

仿照内核,写init.c:

#include "init.h"#if defined ( __CC_ARM   )
extern unsigned int Image$$ER_IROM1$$Base;
extern unsigned int Image$$ER_IROM1$$Limit;
extern unsigned int Image$$ER_IROM1$$Length;   extern unsigned int Image$$RW_IRAM1$$Base;
extern unsigned int Image$$RW_IRAM1$$Limit;
extern unsigned int Image$$RW_IRAM1$$Length;     extern unsigned int Image$$RW_IRAM1$$ZI$$Base;
extern unsigned int Image$$RW_IRAM1$$ZI$$Limit;
extern unsigned int Image$$RW_IRAM1$$ZI$$Length;extern unsigned int Image$$_initcall0_init$$Base;
extern unsigned int Image$$_initcall0_init$$Limit;
extern unsigned int Image$$_initcall0_init$$Length;	extern unsigned int Image$$_initcall1_init$$Base;
extern unsigned int Image$$_initcall1_init$$Limit;
extern unsigned int Image$$_initcall1_init$$Length;extern unsigned int Image$$_initcall2_init$$Base;
extern unsigned int Image$$_initcall2_init$$Limit;
extern unsigned int Image$$_initcall2_init$$Length;extern unsigned int Image$$_initcall3_init$$Base;
extern unsigned int Image$$_initcall3_init$$Limit;
extern unsigned int Image$$_initcall3_init$$Length;extern unsigned int Image$$_initcall4_init$$Base;
extern unsigned int Image$$_initcall4_init$$Limit;
extern unsigned int Image$$_initcall4_init$$Length;extern unsigned int Image$$_initcall5_init$$Base;
extern unsigned int Image$$_initcall5_init$$Limit;
extern unsigned int Image$$_initcall5_init$$Length;extern unsigned int Image$$_initcall6_init$$Base;
extern unsigned int Image$$_initcall6_init$$Limit;
extern unsigned int Image$$_initcall6_init$$Length;extern unsigned int Image$$_initcall7_init$$Base;
extern unsigned int Image$$_initcall7_init$$Limit;
extern unsigned int Image$$_initcall7_init$$Length;#define __initcall0_start (initcall_t *)&Image$$_initcall0_init$$Base
#define __initcall1_start (initcall_t *)&Image$$_initcall1_init$$Base
#define __initcall2_start (initcall_t *)&Image$$_initcall2_init$$Base
#define __initcall3_start (initcall_t *)&Image$$_initcall3_init$$Base
#define __initcall4_start (initcall_t *)&Image$$_initcall4_init$$Base
#define __initcall5_start (initcall_t *)&Image$$_initcall5_init$$Base
#define __initcall6_start (initcall_t *)&Image$$_initcall6_init$$Base
#define __initcall7_start (initcall_t *)&Image$$_initcall7_init$$Base
#define __initcall_end    (initcall_t *)&Image$$_initcall7_init$$Limit#endif  /* #if defined ( __CC_ARM   ) */#define ARRAY_SIZE(a)  (sizeof(a)/sizeof(a[0]))
static initcall_t *initcall_levels[] = {__initcall0_start,__initcall1_start,__initcall2_start,__initcall3_start,__initcall4_start,__initcall5_start,__initcall6_start,__initcall7_start,__initcall_end,
};static int do_one_initcall(initcall_t fn)
{int ret;ret = fn();return ret;
}static void do_initcall_level(int level)
{initcall_t *fn;for (fn = initcall_levels[level]; fn < initcall_levels[level + 1]; fn++)do_one_initcall(*fn);
}void __init do_initcalls(void)
{int level;for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)do_initcall_level(level);
}

注意:这里MDK使用自己的armcc编译工具链,获取数据段地址的语法于gcc不同,MDK内置了全局变量表示内存段的信息:

extern unsigned int Image$$name$$Base;    //这个变量位于name段的起始
extern unsigned int Image$$name$$Limit;   //位于name段的结束地址加1,也是下一段的开始
extern unsigned int Image$$name$$Length;  //name段长度

其他函数提取核心代码。

四、在STM32测试一下

main函数无脑调用do_initcalls:

#include "stm32f4xx.h"
#include "stdio.h"
#include "console.h"
#include "init.h"int main(void)
{console_init();printf("start test!\r\n");do_initcalls();while(1) {}
}

添加两个测试模块文件module1.c和module2.c,利用module_init注册初始化函数:

#include "stdio.h"
#include "init.h"int module1_init(void)
{printf("module1 init!\r\n");return 0;
}module_init(module1_init);
#include "stdio.h"
#include "init.h"int module2_init(void)
{printf("module2 init!\r\n");return 0;
}module_init(module2_init);

编译,运行,完美!
在这里插入图片描述

五、优雅!

现在我们无需在main.c里include一堆头文件,然后bsp_init里加一堆init函数,只需要在main函数里调用do_initcalls,然后在每个需要初始化的.c文件内用module_init宏注册init函数。你甚至不用给这个.c文件做个.h文件,而且删除某个.c文件完全不会报错!最大的好处是我们可以在不同的项目上复用这个do_initcalls,只需更改链接脚本就可以。这就是松耦合的优雅。当然这块金子的价值远不止于此,期待日后的开发。

参考:
https://blog.csdn.net/weixin_39094034/article/details/123549216

http://www.lryc.cn/news/2417179.html

相关文章:

  • IOS-Moya简单使用-Swift
  • Myo肌电臂环中文入门指南及基于matlab获取Myo臂环信号
  • C++11重写muduo网络库——预备知识
  • 行业分析
  • 为什么有时候启动Activity需要加FLAG_ACTIVITY_NEW_TASK
  • 制作一个简单HTML中华传统文化网页设计源码(HTML+CSS)
  • GRUB4DOS使用大全
  • 《Head first Java》练习
  • asdasdasdasdasdas
  • 位图(bmp)文件格式分析
  • CPU架构名词解释
  • 计算机网络之网络基础网络通信原理(非常重要的网络基础知识,内含思维导图和详细图解)
  • 药娘化女仆公读《乡村振兴战略下传统村落文化旅游设计》晏嘤茹苦辉少许
  • 大家厚爱
  • maven冲突解决-enforcer插件介绍
  • 数学规划模型之线性规划
  • 操作系统实验二·生产者消费者问题
  • CodeProject SenseAI服务器:AI最简单的方法
  • 点对点 端到端的区别
  • 【论文阅读】HGT:Heterogeneous Graph Transformer
  • 网络分析——路径分析
  • [计算机效率] 磁盘优化及清理
  • 【已解决】ping: www.baidu.com: 未知的名称或服务
  • linux系统安装步骤
  • _Deallocate 造成 Exception:(_Ptr_user(_BIG_ALLOCATION_ALIGNMENT-1))==0
  • 《新人皮灯笼》里白扇子谋权篡位的暗线
  • 十进制二进制转换简单说明
  • 桌面精灵制作记录
  • linux 搭建webserver-Goahead
  • roundrobin来历_数据中心交换机横向虚拟化集群漫谈