01 - 模块机制

---- 整理自 王利涛老师 课程
实验环境:宅学部落 www.zhaixue.cc

1. 可加载模块

1.1 Linux内核的模块机制

  • LKM:Loadable Kernel Module
    • 内核模块化、高度可定制化和可裁剪
    • 适配不同的架构、硬件平台
    • 支持运行时动态加载或卸载一个模块
    • 不需要重新编译、重启内核

1.2 实验:hello 模块

  • 目标:一个内核模块的编译和运行,动态加载和卸载
  1. 新建 hello.c 文件,内容如下:
#include <linux/init.h>
#include <linux/module.h>

static int __init hello_init(void) {
    printk("Hello world\n");
    return 0;
}

static void __exit hello_exit(void) {
    printk("Goodbye world\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
EXTRA_CFLAGS += -DDEBUG 
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
  • M=$(PWD):需要编译的模块源文件地址
  • -C 选项:此选项指定内核源码的位置,make 在编译时将会进入内核源码目录,执行编译,编译完成时返回。

在这里插入图片描述

1.3 内核模块的构成

  • hello_init
    • 模块加载入口函数,主要完成模块初始化工作
    • 使用 __init 声明,使用 module_init 指定
    • 模块被加载到内核时,入口函数自动被内核执行
    • 返回值:errno
    • 应用层可根据返回值,使用 perror 进行解析
  • hello_exit
    • 模块卸载函数,模块卸载时该函数自动被内核执行
    • 使用 __exit 声明,使用 module_exit 指定
    • 主要完成结束模块运行的相关工作、清理各种资源
    • 返回类型:void
  • insmod
  • lsmod
  • rmmod

2. 内核许可声明

  • 用来描述内核的许可权限:内核以 GPL 发布
  • 模块不声明 LICENSE,内核会有 (kernel tainted) 警告
  • 内核状态此时是 受污染的(dirty)
  • 内核受污染后,一些调试、打印功能可能会失效

2.1 协议分类

在这里插入图片描述

2.2 内核污染(kernel tainted)

2.2.1 cat /proc/sys/kernel/tainted

#include <linux/init.h>
#include <linux/module.h>

static int __init hello_init(void) {
    printk("Hello world\n");
    return 0;
}

static void __exit hello_exit(void) {
    printk("Goodbye world\n");
}

module_init(hello_init);
module_exit(hello_exit);
// MODULE_LICENSE("GPL"); // 模块不声明 LICENSE
MODULE_AUTHOR("uuxiang");
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
EXTRA_CFLAGS += -DDEBUG 
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
  • 模块不声明 LICENSE,内核会有 (kernel tainted) 警告

在这里插入图片描述

2.2.2 内核被污染的原因

在这里插入图片描述

3. 模块签名机制

  1. 内核编译时配置打开如下选项:make menuconfig

在这里插入图片描述

CONFIG_MODULE_SIG
CONFIG_MODULE_SIG_FORCE
CONFIG_MODULE_SIG_ALL
  1. 未签名的 hello.ko

在这里插入图片描述

  1. 查看签名需要的文件:

在这里插入图片描述

  • certs/signing_key.x509 公钥
  • certs/signing_key.pem 私钥
  • scripts/sign-file 签名工具
  1. 手工给模块签名

在这里插入图片描述

  1. 补充:清除模块签名

在这里插入图片描述

4. 两种编译方式

4.1 将模块编译进内核

进入如下目录 drivers/char,新建 hello.c 文件(内容同上),并修改 KconfigMakefile

// linux-5.10.4\drivers\char\hello.c
#include <linux/init.h>
#include <linux/module.h>

static int __init hello_init(void)
{
    printk("Hello world\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk("Goodbye world\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");
# linux-5.10.4\drivers\char\Kconfig
...
config HELLO
	tristate "A hello module test"
	default m
	help
		a simple kernel module test
...
# linux-5.10.4\drivers\char\Makefile
...
obj-${CONFIG_HELLO}		+= hello.o
...

在这里插入图片描述
在这里插入图片描述

运行新的内核,从开机 log 中打印我们 hello 模块中的信息:

在这里插入图片描述

4.2 out-of-tree 编译

ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
EXTRA_CFLAGS += -DDEBUG 
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif

4.3 模块的 Makefile 分析

  • Kbuild 示例:
obj-y := hello.o # Static Build
obj-m := hello.o # Module Build
obj-{CONFIG_HELLO} := hello.o # Conditional Build
hello-objs := hello.c sub.c # Multi-File Object 
  • 示例:将 Kbuild 和 Makefile 分开:
# Kbuild
obj-m := hello.o
ifneq ($(KERNELRELEASE),)

else
EXTRA_CFLAGS += -DDEBUG 
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif

在这里插入图片描述

5. 模块参数

5.1 如何给模块传参数?

#define module_param(name, type, perm) module_param_named(name, name, type, perm)

  • name:要传递的参数,对应模块中的全局变量
  • type:要传递的参数类型,要和全局变量类型一致
  • perm:读写权限
    • /sys/module/hello/parameters/xx 参数节点
    • 0666:读写权限
    • 0444:只读权限,无法对这个文件节点执行写的操作
    • 4 - 读,2 - 写,1 - 执行
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>

static int num = 10;
module_param(num, int, 0660);

static int __init hello_init(void)
{
    printk("Hello world\n");
    printk("num = %d\n", num);
    return 0;
}

static void __exit hello_exit(void)
{
    printk("Goodbye world\n");
    printk("num = %d\n", num);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");

在这里插入图片描述

5.2 通过 uboot 给模块传参

  1. 需要使用编译进内核中的 hello 模块
// linux-5.10.4\drivers\char\hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>

static int num = 10;
module_param(num, int, 0660);

static int __init hello_init(void)
{
    printk("Hello world\n");
    printk("num = %d\n", num);

    return 0;
}

static void __exit hello_exit(void)
{
    printk("Goodbye world\n");
    printk("num = %d\n", num);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");

在这里插入图片描述

  1. 编译 uboot 时,make menuconfig,在 bootargs 中添加 hello.num = 123

在这里插入图片描述

  1. 完整开机 log 如下:

在这里插入图片描述

6. EXPORT_SYMBOL

6.1 用户空间的模块化编程

函数的实现:math.c/int add(int a, int b)
函数的声明:math.h/int add(int a, int b);

#ifndef __MATH_H
#define __MATH_H

int add(int a, int b);
int sub(int a, int b);

#endif
int add(int a, int b) {
    return a + b;
}

int sub(int a, int b) {
    return a - b;
}
#include <stdio.h>
#include "math.h"

int main(void)
{
    int sum = 0;
    sum = add(3, 4);
    printf("sum = %d\n", sum);
    
    return 0;
}

6.2 内核空间的模块化编程

  • 模块的封装:static、EXPORT_SYMBOL
  • 函数的声明:头文件
  • 按不同的协议导出符号
    • EXPORT_SYMBOL
    • EXPORT_SYMBOL_GPL

6.3 编程实验:hello 模块调用 math 模块

  • KBUILD_EXTRA_SYMBOLS 用来告诉内核当前 module 需要引用另外一个 module 导出的符号
# drivers\05export_symbol\hello\Makefile
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
EXTRA_CFLAGS += -DDEBUG
# KBUILD_EXTRA_SYMBOLS 用来告诉内核当前module需要引用另外一个module导出的符号
KBUILD_EXTRA_SYMBOLS += /home/code_folder/uboot_linux_rootfs/kernel/drivers/05export_symbol/math/Module.symvers
export KBUILD_EXTRA_SYMBOLS
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
#include <linux/init.h>
#include <linux/module.h>

extern int num;

static void hello_probe(void)
{
    extern int add(int a, int b);
    int sum = add(3, 4);
    printk("sum = %d\n", sum);
}

static int __init hello_init(void)
{
    printk("num = %d\n", num);
    hello_probe();
    return 0;
}

static void __exit hello_exit(void)
{
    printk("Goodbye hello_module\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");
# drivers\05export_symbol\math\Makefile
ifneq ($(KERNELRELEASE),)
obj-m := math.o
else
EXTRA_CFLAGS += -DDEBUG 
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
#include <linux/init.h>
#include <linux/module.h>

int num = 10;
module_param(num, int, 0660);
EXPORT_SYMBOL(num);

int add(int a, int b)
{
    return a + b;
}
EXPORT_SYMBOL(add);

static int __init math_init(void)
{
    printk("Hello math_module\n");
    return 0;
}

static void __exit math_exit(void)
{
    printk("Goodbye math_module\n");
}

module_init(math_init);
module_exit(math_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");

在这里插入图片描述

9. 模块的版本控制

  • 解决内核模块和内核之间的接口一致性问题
  • 根据函数参数、返回值类型等生成 CRC 校验码
  • 当内核和模块双方的校验码相等,则为相同接口
  • 内核启动版本控制功能:CONFIG_MODVERSIONS

9.1 实验:版本控制实验

  • 使用打开 CONFIG_MODVERSIONS 的内核和不开 CONFIG_MODVERSIONS 编译内核后编译的 hello.ko,进行版本控制的实验。

在这里插入图片描述

9.2 分析

  • 相关的几个文件
    hello.mod.c
    hello.ko:__versions section
    内核中:Modules.symvers
    模块中:Modules.symvers

9.2.1 未开 CONFIG_MODVERSIONS

// hello.mod.c
#include <linux/module.h>
#define INCLUDE_VERMAGIC
#include <linux/build-salt.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>

BUILD_SALT;

MODULE_INFO(vermagic, VERMAGIC_STRING);
MODULE_INFO(name, KBUILD_MODNAME);

__visible struct module __this_module
__section(".gnu.linkonce.this_module") = {
	.name = KBUILD_MODNAME,
	.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
	.exit = cleanup_module,
#endif
	.arch = MODULE_ARCH_INIT,
};

#ifdef CONFIG_RETPOLINE
MODULE_INFO(retpoline, "Y");
#endif

MODULE_INFO(depends, "");

在这里插入图片描述

内核中 Modules.symvers:

在这里插入图片描述

模块中 Modules.symvers:

在这里插入图片描述

9.2.2 打开 CONFIG_MODVERSIONS

在这里插入图片描述

在这里插入图片描述

hello.ko 模块中调用的内核中的接口函数以及 CRC 校验码,当模块被加载到内核中运行的时候,内核就会 根据调用的比如 printk 的校验码和内核中保存的 printk 的校验码进行比对,比对成功,模块才可以加载到内核中运行。

  • 内核中 Modules.symvers:

在这里插入图片描述

反汇编:

在这里插入图片描述

在这里插入图片描述

我们自己编译的模块 hello.ko,使用 EXPORT_SYMBOL 导出的时候,也会生成校验码,别的模块在调用时也会进行校验。

在这里插入图片描述

10. 模块的头文件

#include <linux/xx.h>
#include <asm/xx.h>
#include <plat/xx.h>
#include <mach/xx.h>
#include "usb.h"
  • 头文件分类

    • 内核专用头文件:include/linux
    • 和CPU架构相关:arch/$(ARCH)/include
    • 板级硬件相关:
      • arch/$(ARCH)/plat-xx/include
      • arch/$(ARCH)/mach-xx/include
  • 通过 gcc -l 指定头文件路径

#include <module1/module1.h>
#include <module2/module2.h>
#include <module3/module3.h>

├── a.out
├── inc
│ ├── module1
│ │ └── module1.h
│ ├── module2
│ │ └── module2.h
│ └── module3
│ └── module3.h
├── main.c
├── module1
│ └── module1.c
├── module2
│ └── module2.c
└── module3
└── module3.c
  • 内核中的头文件路径

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

11. 多文件构成的模块

  • 编程实验:
  1. 一个复杂模块往往由多个 C 文件构成
  2. 模块内部接口的封装和引用
  3. 模块如何封装
  4. 模块间如何引用
  5. 头文件
  6. Makefile 的写法
ifneq ($(KERNELRELEASE),)
obj-m := files_in_hello.o
files_in_hello-objs := main.o add.o sub.o
else
EXTRA_CFLAGS += -DDEBUG 
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif

在这里插入图片描述

在这里插入图片描述

12. 模块间的依赖

#ifndef __ADD_H__
#define __ADD_H__

int add(int a, int b);

#endif
#ifndef __SUB_H__
#define __SUB_H__

int sub(int a, int b);

#endif
#include <linux/init.h>
#include <linux/module.h>

int add(int a, int b) {
    return a + b;
}
EXPORT_SYMBOL(add);

static int __init add_init(void) {
    printk("add module init\n");
    return 0;
}

static void __exit add_exit(void) {
    printk("add module exit\n");
}

module_init(add_init);
module_exit(add_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");
#include <linux/init.h>
#include <linux/module.h>

int sub(int a, int b) {
    return a - b;
}
EXPORT_SYMBOL(sub);

static int __init sub_init(void) {
    printk("sub module init");
    return 0;
}

static void __exit sub_exit(void) {
    printk("sub module exit\n");
}

module_init(sub_init);
module_exit(sub_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");
#include <linux/init.h>
#include <linux/module.h>
#include "add.h"
#include "sub.h"

static int __init hello_init(void) {
    int result;
    int sum;
    printk("hello module init\n");
    sum = add(3, 4);
    printk("sum = %d\n", sum);

    result = sub(5, 3);
    printk("result = %d\n", result);
    return 0;
}

static void __exit hello_exit(void) {
    printk("hello module exit\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");
ifneq ($(KERNELRELEASE),)
obj-m := dep_modules.o
obj-m += add.o
obj-m += sub.o
else
EXTRA_CFLAGS += -DDEBUG 
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif

在这里插入图片描述
在这里插入图片描述

  • 先加载依赖的 ko,再加载 dep_modules.ko ,卸载顺序相反。

在这里插入图片描述

  • 生成模块间的依赖关系:depmod -a
    depmod 会解析 /lib/modules/$(kernel_version) 下的所有内核模块,通过各个模块 EXPORT_SYMBOL 和引用的符号,生成一个 模块依赖关系表/lib/modules/$(kernel_version)/modules.dep.bb
modprobe hello # 加载
modprobe –r hello # 卸载

在这里插入图片描述

13. 编写一个字符驱动

readwrite
sys_readsys_write
普通文件设备文件
文件系统设备模型
inode设备号
块设备驱动字符设备驱动
  • 编程实验:实现一个最简单的字符驱动
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>

static char hello_buf[512] = "0";

static int hello_open(struct inode *inode, struct file *fp)
{
    return 0;
}

static int hello_release(struct inode *inode, struct file *fp)
{
    return 0;
}

static ssize_t hello_read(struct file *fp, char __user *buf, 
                           size_t size, loff_t *pos)
{
    unsigned long p = *pos;
    unsigned int count = size;

    if (p >= 512) {
        return -1;
    }

    if (count > 512) {
        count = 512 - p;
    }

    if (copy_to_user(buf, hello_buf + p, count) != 0) {
        printk("read error!\n");
        return -1;
    }

    return count;
}

static ssize_t hello_write(struct file *fp, const char __user *buf, 
                            size_t size, loff_t *pos)
{
    unsigned long p = *pos;
    unsigned int count = size;

    if (p >= 512) {
        return -1;
    }

    if(count > 512) {
        count = 512 - p;
    }

    if(copy_from_user(hello_buf, buf + p, count) != 0) {
        printk("write error!\n");
        return -1;
    }

    return count;
}

static const struct file_operations hello_fops = {
    .owner   = THIS_MODULE,
    .read    = hello_read,
    .write   = hello_write,
    .open    = hello_open,
    .release = hello_release,
};

static int __init hello_init(void)
{
    int ret;

    ret = register_chrdev(222, "hello", &hello_fops);
    if (ret < 0) {
        printk("Register char module: hello_char failed..\n");
        return 0;
    } else {
        printk("Register char module: hello_char success!\n");
    }
    
    return 0;
}

static void __exit hello_exit(void)
{
    printk("Goodbye char module: hello_char!\n");
    unregister_chrdev(222, "hello_char");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuuuu");

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    int fd;
    int i, ret;
    char write_buf[10] = "aaaabbbb";
    char read_buf[10] = "0";

    fd = open("/dev/hello", O_RDWR);
    if(fd == -1) {
        printf("cannot open file..\n");
        exit(1);
    }
    
    if ((ret = write(fd, write_buf, 10)) < 0) {
        printf("write failed\n");
        return -1;
    }

    lseek(fd, 0, SEEK_SET);
    if ((ret = read(fd, read_buf, 10)) < 10) {
        printf("read error!\n");
        exit(1);
    }

    printf("%s\n", read_buf);

    close(fd);

    return 0;
}
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
EXTRA_CFLAGS += -DDEBUG 
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif

在这里插入图片描述

14. 模块的运行过程

  • 模块的分类
    • 可加载模块:源码外编译,动态加载、动态卸载
    • 内置模块:直接编译进内核,随内核启动初始化
  • 使用 dump_stack() 打印函数调用栈
#include <linux/init.h>
#include <linux/module.h>

static int __init hello_init(void)
{
    printk("Hello world\n");
    dump_stack();
    return 0;
}

static void __exit hello_exit(void)
{
    printk("Goodbye world\n");
    dump_stack();
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuuuu");

在这里插入图片描述

在这里插入图片描述

15. 模块机制实现分析

  • 分析之前的准备
    C 语言与链接脚本、Makefile、Kbuild 的交互
    C 语言如何引用链接脚本中定义的符号
    C 语言如何使用 Makefile 中定义的符号
    C 语言如何使用 kbuild 配置变量
hello.c
	|--> module_init(hello_init);
	
	linux-5.10.4/include/linux/module.h
		|--> #define module_init(x)	__initcall(x);
	
		linux-5.10.4/include/linux/init.h
			|--> #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;
									    
linux-5.10.4/arch/arm/kernel/vmlinux.lds
	|--> __initcall6_start = .; KEEP(*(.initcall6.init))
hello.c
	|--> module_init(hello_init);
	
	linux-5.10.4/include/linux/module.h
		|--> __initcall(hello_init);
	
		linux-5.10.4/include/linux/init.h
			|--> device_initcall(hello_init)
				|--> __define_initcall(hello_init, 6)
					|--> ___define_initcall(hello_init, 6, .initcall6)
						|--> static initcall_t __initcall_hello_init6 __used \
							__attribute__((__section__(".initcall6.init"))) = hello_init;
						// hello_init 放在 .initcall6.init 段中

linux-5.10.4/arch/arm/kernel/vmlinux.lds
	|--> __initcall6_start = .; KEEP(*(.initcall6.init))

15.1 attribute 和 section

  1. 补充一下 __attribute__ 机制,GNU C 的一大特色就是 __attribute__ 机制。__attribute__ 可以设置函数属性、变量属性和类型属性,也就是通过给函数或者变量声明属性值,以便让编译器能够对要编译的程序进行优化处理。具体可自行查阅。
  2. 对于 section 这个关键字,我们可以通过它将指定的变量定义到指定的段中。示例如下:
attribute((section(“section_name”)))
// 其作用是将作用的函数或数据放入指定的名为"section_name"对应的段中。

15.2 分析 hello_init

static initcall_t __initcall_hello_init6 __used __attribute__((__section__(".initcall6.init"))) = hello_init;
/*
精简一下就是下面这样,类型为initcall_t的static的变量__initcall_hello_init6,被初始化为hello_init
*/
static initcall_t __initcall_hello_init6 = hello_init;
// 剩余的__used,原型如下:
#define __used __attribute__((__used__)) //attribute((used))是GCC编译器的一个指令,
										 // 用来告诉编译器一个变量或函数可能会被用到,避免编译器在优化代码时将其删去。
										 // 一般情况下,编译器会删去未被使用过的变量或函数,以减少代码大小和提高程序效率。
/*
剩余的__attribute__((__section__(".initcall6.init"))):
将作用的变量__initcall_hello_init6放入到 .initcall6.init段中
*/

在这里插入图片描述

15.3 分析 module_init 调用流程

15.3.1 整体流程

linux-5.10.4\init\main.c
|--> start_kernel(void)
	|--> arch_call_rest_init(void)
		|--> rest_init(void)
			|--> pid = kernel_thread(kernel_init, NULL, CLONE_FS);
				|--> kernel_init(void *unused)
					|--> kernel_init_freeable(void)
						|--> do_basic_setup(void)
							|--> do_initcalls(void)
								|--> do_initcall_level(level, command_line)
									|--> do_one_initcall(initcall_from_entry(fn))

15.3.2 do_initcalls

static void __init do_initcalls(void)
{
	int level;
	size_t len = strlen(saved_command_line) + 1;
	char *command_line;

	command_line = kzalloc(len, GFP_KERNEL);
	if (!command_line)
		panic("%s: Failed to allocate %zu bytes\n", __func__, len);
	
	// 遍历数组initcall_levels,每次执行do_initcall_level
	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
		/* Parser modifies command_line, restore it each time */
		strcpy(command_line, saved_command_line);
		do_initcall_level(level, command_line);
	}

	kfree(command_line);
}

上述代码 do_initcalls 中的 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,
};

数组中的 __initcall6_start 是不是有点眼熟,和我们前面分析的 linux-5.10.4/arch/arm/kernel/vmlinux.lds有关:

linux-5.10.4/arch/arm/kernel/vmlinux.lds
	|--> __initcall6_start = .; KEEP(*(.initcall6.init))

15.3.3 do_initcall_level

do_initcalls 中的 do_initcall_level 函数如下:

static void __init do_initcall_level(int level, char *command_line)
{
	initcall_entry_t *fn;

	parse_args(initcall_level_names[level],
		   command_line, __start___param,
		   __stop___param - __start___param,
		   level, level,
		   NULL, ignore_unknown_bootoption);

	trace_initcall_level(initcall_level_names[level]);
	for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
		do_one_initcall(initcall_from_entry(fn));
}

对于当前的这个 level 优先级,通过 fn 遍历数组 initcall_levels[level],例如 对于level6,这里就会遍历 initcall_levels[6],也就是__initcall6_start。

15.3.4 do_one_initcall(initcall_from_entry(fn))

fn 就是 __initcall6_start 中的元素,根据链接脚本(如下所示),从而获取到 hello_init 来执行。

linux-5.10.4/arch/arm/kernel/vmlinux.lds
	|--> __initcall6_start = .; KEEP(*(.initcall6.init))
// linux-5.10.4\include\linux\init.h
static inline initcall_t initcall_from_entry(initcall_entry_t *entry)
{
	return *entry;
}

// linux-5.10.4\init\main.c
int __init_or_module do_one_initcall(initcall_t fn)
{
	...
	ret = fn();
	...
}
  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
LoRa01模块是一款用于构建低功耗、长距离无线通信网络的设备。它采用全球通用的LoRaWAN协议,可实现远距离传输,节能耗,广覆盖范围等特点。在使用LoRa01模块时,常常需要与STM32微控制器进行配合。下面是一些常见的STM32代码示例,用于驱动和控制LoRa01模块。 1. 配置串口:首先,要确定与LoRa01模块相连的串口引脚。可以使用STM32引脚配置工具进行配置,并设置相应的引脚模式和中断。 2. 初始化LoRa模块:使用STM32的串口通信功能,通过配置好的串口向LoRa模块发送初始命令和参数,例如设置工作模式、信道、传输速率等。 3. 发送数据:通过STM32的串口,将要发送的数据传输到LoRa模块缓冲区,并发送给目标节点。可以使用STM32的DMA或中断方式来实现数据的高效传输。 4. 接收数据:通过STM32的串口接收缓冲区,读取来自LoRa模块的数据。可以使用中断或DMA方式来获取数据,然后进行相应的处理。 5. 错误处理:在代码中添加错误处理机制,例如检查发送和接收数据时的错误标志,并根据不同的错误情况进行相应的处理,如重传或重新初始化LoRa模块等。 6. 低功耗优化:利用STM32的低功耗特性,例如使用低功耗模式和时钟树优化。还可以通过配置LoRa模块的传输功率和传输速率来降低功耗。 这些是一些常见的STM32代码示例,用于与LoRa01模块配合使用。根据具体的应用需求和硬件环境,还可以根据需要进行相应的配置和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

uuxiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值