Lazy loaded image
🥳嵌入式 Linux开发
Linux驱动基础02-第一个内核模块
Words 1950Read Time 5 min
2024-9-11
2024-12-18
type
date
slug
category
icon
password

实验代码讲解

代码框架

Linux 内核模块的代码框架通常由模块加载、模块卸载函数、模块许可证证明,如上三者为必须,还由模块参数、模块导出符号和模块的其他相关信息组成。
上面hello module程序通过 module_init 宏定义在内核注册模块加载函数,module_exit 宏定义在内核注册内核卸载函数,MODULE_LICENSE 声明模块遵循的许可证。还通过<linux/module.h>提供的其他宏定义添加模块其他相关信息(作者、描述和别名)。

头文件

编写内核所需的头文件处于Linux源码中include的文件夹。
  • #include <linux/module.h>: 包含了内核加载module_init()/卸载module_exit()函数和内核模块信息相关函数的声明。
    •  
  • #include <linux/init.h>: 包含一些内核模块相关节区的宏定义
    • #include <linux/kernel.h>: 包含内核提供的各种函数,如printk(内核模块不能依赖与C库函数,因此不能用 printf 函数,需要使用单独的打印输出函数printk。)

    模块加载/卸载函数

    module_init

    返回值:
    • 0: 表示模块初始化成功,并会在/sys/module下新建一个以模块名为名的目录,如下图中的红框处
    • 非0: 表示模块初始化失败
    • __init 用于修饰原函数,__initdata 用于修饰变量,带有__init的修饰符,表示将该函数放到可执行文件的__init节区中,该节区的内容只能用于模块的初始化阶段, 初始化阶段执行完毕之后,这部分的内容就会被释放掉。
    • 宏定义module_init用于通知内核初始化模块的时候, 要使用哪个函数进行初始化。它会将函数地址加入到相应的节区section中, 这样的话,开机的时候就可以自动加载模块了

    module_exit

    与函数func_init区别在于,该函数的返回值是void类型,且修饰符也不一样, 这里使用的使用__exit,表示将该函数放在可执行文件的__exit节区, 当执行完模块卸载阶段之后,就会自动释放该区域的空间。
    类比于模块加载函数,__exit用于修饰函数,__exitdata用于修饰变量。 宏定义module_exit用于告诉内核,当卸载模块时,需要调用哪个函数。

    printk

    • printf:glibc实现的打印函数,工作于用户空间
    • printk:内核模块无法使用glibc库函数,内核自身实现的一个类printf函数,但是需要指定打印等级。
      • #define KERN_EMERG “<0>” 通常是系统崩溃前的信息
      • #define KERN_ALERT “<1>” 需要立即处理的消息
      • #define KERN_CRIT “<2>” 严重情况
      • #define KERN_ERR “<3>” 错误情况
      • #define KERN_WARNING “<4>” 有问题的情况
      • #define KERN_NOTICE “<5>” 注意信息
      • #define KERN_INFO “<6>” 普通消息
      • #define KERN_DEBUG “<7>” 调试信息
    查看当前系统printk打印等级:cat /proc/sys/kernel/printk, 从左到右依次对应当前控制台日志级别、默认消息日志级别、 最小的控制台级别、默认控制台日志级别。
    notion image
    打印内核所有打印信息:dmesg,注意内核log缓冲区大小有限制,缓冲区数据可能被覆盖掉。

    许可证

    内核模块许可证有 “GPL”,“GPL v2”,“GPL and additional rights”,“Dual SD/GPL”,“Dual MPL/GPL”,“Proprietary”

    相关信息声明

    函数
    说明
    MODULE_AUTHOR()
    描述模块的作者信息
    MODULE_DESCRIPTION()
    对模块的简单介绍
    MODULE_ALIAS()
    给模块设置一个别名

    模块加载卸载时机

    模块加载

    1. 开机时自动加载
        • 某些模块可能配置为在系统启动时自动加载。这通常在系统的启动脚本或配置文件中指定。
        • 例如,Linux 中可以通过 /etc/modules 文件或其他启动配置来实现。
    1. 手动加载
        • 系统管理员或用户可以在需要时手动加载模块,通常使用命令行工具(例如 Linux 中的 modprobeinsmod)。
    1. 设备驱动请求
        • 当一个设备被连接到系统时,系统可能会自动加载相应的驱动模块。

    模块卸载

    1. 手动卸载
        • 模块可以通过命令行工具手动卸载(例如 Linux 中的 rmmodmodprobe -r)。
    1. 依赖关系
        • 如果一个模块没有被其他模块或进程依赖,则可以被卸载。
    1. 系统关闭
        • 在系统关闭时,某些模块可能会被自动卸载。

    实验准备

    获取内核模块源码,将配套代码 linux_driver/module/hellomodule 解压到内核代码同级目录。
    • 第1行:该Makefile定义了变量KERNEL_DIR,来保存内核源码的目录。
    • 第3-5行: 指定了工具链并导出环境变量
    • 第6行:变量obj-m保存着需要编译成模块的目标文件名。
    • 第8行:’$(MAKE)modules’实际上是执行Linux顶层Makefile的伪目标modules。通过选项’-C’,可以让make工具跳转到源码目录下读取顶层Makefile。’M=$(CURDIR)’表明返回到当前目录,读取并执行当前目录的Makefile,开始编译内核模块。CURDIR是make的内嵌变量,自动设置为当前目录。

    上机演示

     
     
     
    上一篇
    Linux驱动基础01-驱动开发环境搭建
    下一篇
    Linux驱动基础03-字符设备

    Comments
    Loading...