3-Linux驱动开发-简单内核模块代码详解
单一模块hellomodule
代码结构:模块加载函数(必须) moudel_init模块卸载函数(必须) module_exit模块许可证声明(必须) MODULE_LICENSE模块参数模块导出符号模块的其他相关信息
hellomodule.c
//以下头文件都包含在以下目录
///home/kun/kernal_code/ebf_linux_kernel-ebf_4.19_star/include/linux
#include <linux/module.h>//包含内核模块信息声明的相关函数
#include <linux/init.h>//包含了 module_init() 和 module_exit() 函数的声明
#include <linux/kernel.h>//包含内核提供的各种函数,如 printkstatic int __init hello_init(void){printk(KERN_EMERG "[ KERN_EMERG ] Hello Module Init\n");printk( "[ default ] Hello Module Init\n");return 0;
}
/*
初始化函数 (hello_init)
static: C 语言关键字,表示此函数只在本文件内可见。
int: 它必须返回一个 int 值。0表示初始化成功。如果返回非 0 值,insmod 命令将会失败。
__init:宏,目的告诉编译器,这个函数(hello_init)只在初始化时使用一次。内核加载成功后,会把这个函数占用的内存释放。
printk:内核版的 printf。
KERN_EMERG:日志级别,紧急情况(Emergency),当日志级别非常高时,内核日志不仅会进入 dmesg,还会被推送到所有当前活动的终端(Tty)上。
printk 函数#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>”调试信息
*/static void __exit hello_exit(void)
{printk("[ default ] Hello Module Exit\n");
}
/*
退出函数 (hello_exit)
卸载 (rmmod) 时执行的函数。
__exit:宏。它告诉编译器,这个函数只在模块卸载时才需要。
*/module_init(hello_init);//宏,注册hello_init函数,告诉内核:当这个 .ko 文件被 insmod 加载时,请调用 hello_init 函数。
module_exit(hello_exit);//宏,注册hello_exit函数,告诉内核:当这个模块被 rmmod 卸载时,请调用 hello_exit 函数。MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("hello world module");
MODULE_ALIAS("test_module");
/*
模块元数据 (Module Metadata):描述信息,明模块的作者、功能和许可证
MODULE_LICENSE("GPL2"):模块的许可证,表示模块代码接受的软件许可协议,Linux 内核遵循 GPL V2 开源协议,内
核模块与 linux 内核保持一致即可。
MODULE_AUTHOR / MODULE_DESCRIPTION,描述作者和功能
MODULE_ALIAS("test_module"):别名允许您使用 modprobe(insmod 的智能版本)通过别名 test_module 来加载 hellomodule.ko。
*/
对应Makefile
KERNEL_DIR=/home/kun/kernal_code/ebf_linux_kernel-ebf_4.19_star/build_image/build
ARCH=arm
CROSS_COMPILE=arm-none-linux-gnueabihf-
export ARCH CROSS_COMPILE
#m 代表 Module(模块),请求编译一个内核模块
obj-m := hellomodule.o
#$(MAKE)等于 make 命令
#-C(Change Directory)切换目录,切换到KERNEL_DIR
#M= 代表Module(要编译的模块) $(CURDIR) 是一个内置变量,代表当前所在的目录/hellomodule
#modules,编译成的具体目标
all:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules.PHONE:clean copyclean:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean copy:sudo cp *.ko /home/embedfire/workdir
使用内核模块
#插入这个内核模块 insmod仅用于测试用
debian@lubancat:/mnt/host_projects$ sudo insmod hellomodule.ko
#插入成功的默认内容输出
Message from syslogd@lubancat at Nov 13 21:12:09 ...kernel:[ 666.015524] [ KERN_EMERG ] Hello Module Init
#利用管道查看此内核module
debian@lubancat:/mnt/host_projects$ lsmod | grep hellomodule
hellomodule 16384 0
#删除这个内核模块
debian@lubancat:/mnt/host_projects$ sudo rmmod hellomodule.ko
系统自动加载模块
#复制 .ko 文件到官方目录
#挂载位置文件放入/lib/modules/$(uname -r)/
#$(...) (命令替换) uname -r 内核版本字符串
sudo cp hellomodule.ko /lib/modules/$(uname -r)/#注册更新模块数据库
sudo depmod -a #全局系统命令#添加到开机自启列表
sudo nano /etc/modules
#写入hellomodule#重启后查看
lsmod | grep hellomodule
内核模块传参与符号共享
函数说明
extern
声明一个变量static int itype=0;
阻止任何C文件通过 C 语言的链接器直接看到或访问这个变量。module_param(name, type, permission)
加载时给模块传递数值,暴露接口给用户,加载时输入
permission规则一共四位开头0代表八进制。后面是三个组Owner,Group,Others权限数字r (Read - 读) = 4w (Write - 写) = 2x (Execute - 执行) = 1EXPORT_SYMBOL()
把函数或变量的内存地址,放入内核的全局符号表中,用于模块间通信,允许其他模块调用。
calculation.h
//目的就是告诉别处有定义,编译时候不要报错
#ifndef __CALCULATION_H__
#define __CALCULATION_H__
//这个变量在别处定义
extern int itype;
//声明函数原型
int my_add(int a, int b);
int my_sub(int a, int b);
#endif
calculation.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>#include "calculation.h"static int __init calculation_init(void){printk(KERN_ALERT "calculation init!\n");printk(KERN_ALERT "itype+1 = %d, itype-1 = %d\n", my_add(itype,1), my_sub(itype,1)); return 0;}static void __exit calculation_exit(void)
{printk(KERN_ALERT "calculation exit!\n");
}module_init(calculation_init);
module_exit(calculation_exit);MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("calculation module");
MODULE_ALIAS("calculation_module");
parametermodule.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>static int itype=0;
module_param(itype,int,0);static bool btype=0;
module_param(btype,bool,0700);static char ctype=0;
module_param(ctype,byte,0);static char *stype=0;
module_param(stype,charp,0644);static int __init param_init(void){printk(KERN_ALERT "param init!\n");printk(KERN_ALERT "itype=%d\n",itype);printk(KERN_ALERT "btype=%d\n",btype);printk(KERN_ALERT "ctype=%d\n",ctype);printk(KERN_ALERT "stype=%s\n",stype);return 0;
}static void __exit param_exit(void)
{printk(KERN_ALERT "module exit!\n");
}EXPORT_SYMBOL(itype);int my_add(int a, int b){return a+b;}EXPORT_SYMBOL(my_add);int my_sub(int a, int b){return a-b;}EXPORT_SYMBOL(my_sub);module_init(param_init);
module_exit(param_exit);MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("module_param");
MODULE_ALIAS("module_param");
make后生成了相关文件

calculation.ko依赖parametermodule.ko中的参数itype和定义的函数(EXPORT_SYMBOL暴露的)
板子上使用时:
#要先加载parametermodule.ko再加载calculation.ko, 卸载类似出栈,module_param的传参
sudo insmod parametermodule.ko itype=123 btype=1 ctype=200 stype=abc
sudo insmod calculation.ko
