系列文章目录
文章目录
一、模块化概念
c语言
按照功能将一个.c文件拆分为多个.c以及.h文件
stm32
对应每一个外设都有一个.c和.h文件,.c文件写模块的初始化以及功能函数,.h文件对应函数的声明,如果想使用该外设只需包含其头文件即可。
Linux
Linux 内核整体结构已经很庞大,包含了很多的组件,而对于我们工程师而言,有两种方法 将需要的功能包含进内核当中。
一:将所有的功能都编译进 Linux 内核。
二:将需要的功能编译成模块,在需要的时候动态地添加。 上述两种方式优缺点分析:第一种
:
优点:不会有版本不兼容的问题,不需要进行严格的版本检查
缺点:生成的内核会很大;要在现有的内核中添加新的功能,则要编译整个内核第二种
:
优点:模块本身不编译进内核,从而控制了内核的大小;模块一旦被加载,将和其它的部分 完全一样。
缺点:可能会有内核与模块版本不兼容的问题,导致内核崩溃;会造成内存的利用率比较低。
动态加载到内核
举例; QQ 在你电脑打开后 你现在电脑重启 他还打开吗
以模块的方式动态加载(调试阶段),重启后无效
还原性
加载的时候做了什么,卸载的时候就要还原什么
模块
2.2版本前模块生成xxx.o 2.2以后生成 xxx.ko文件
常用的命令
加载 insmod led.ko
查看 lsmod
卸载 rmmod led.ko
形象点 就好比 一个 USB的U盘
U盘是个独立的物件
我可以把U盘插到电脑上
电脑有反应(执行程序)
我也可以把U盘拔出来
电脑也有反应
二、单模块编程
1.概念
模块化编程首先它是模块,其次它是针对于内核运行的代码,不能跟以前的C语言一样直接搞个main,那么内核人家不识别,也不会去执行你所谓的main
那怎么办呢,按照人家内核的框架
编程人家才会执行 模块化编程就是学习内核的运行程序的框架
2.模块加载函数
static int __init XXX_init(void)
{
return 0;
}
moudle_init(XXX_init);
当#insmod xxx.ko时会调用 module_init所修饰的 XXX_init函数来完成一些初始化操作(xxx 在写代码时要根据实际模块进行修改)
Linux 内核的模块加载函数一般用__init 标识声明,用于告诉编译器相关函数或变量仅用于初始化,初始化结束后就释放这段内存。
XXX_init 函数的返回值为一个整形的数,如果执行成功,则返回 0,初始化失败时则返回错 误编码,Linux 内核当中的错误编码是负值,在<linux/errno.h>中定义
3.模块卸载函数
static void __exit XXX_exit(void)
{
}
moudle_exit(XXX_exit);
在用 rmmod 或 modprobe 命令卸载模块时,该函数被执行。完成与加载相反的工作。
模块的卸载函数和模块加载函数实现相反的功能,主要包括:
- 若模块加载函数注册了 XXX,则模块卸载函数注销 XXX
- 若模块加载函数动态分配了内存,则模块卸载函数释放这些内存
- 若模块加载函数申请了硬件资源,则模块卸载函数释放这些硬件资源
- 若模块加载函数开启了硬件资源,则模块卸载函数一定要关闭这些资源
4.模块的开源许可和声明
模块的开源许可协议
MODULE_LICENSE(“GPL”);
MODULE_xxx 可以写在模块的任何地方(但必须在函数外面),习惯上写在模块的最后
5.例子
模块化框架
#include <linux/kernel.h> //必不可少
#include <linux/module.h> //必不可少
static int __init hello_init(void)//两个下划线 注意此处是int
{
printk("hello_module is init!!!\n");//专用的打印函数printk
return 0;//返回值0必不可少
}
static void __exit hello_exit(void)//注意此处是void
{
printk("hello_module is exit!!!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
makefile:
obj-m += hallo.o
#在使用时kdir要修改为你自己的内核的*目录
KDIR:=/home/zmy/qudong/linux-3.5
all:
make -C $(KDIR) M=$(PWD) modules
#-C 选项的作用是指将当前工作目录转移到你所指定的位置。
#“M=”选项的作用是,当用户需要以某个内核为基础编译一个外部模块的话,
#需要在make modules 命令中加入“M=dir”,
#程序会自动到你所指定的dir目录中查找模块源码,将其编译,生成KO文件。
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.order
#使用make命令编译模块,由于makefile文件无法正确的处理带空格的路径名,
#确保路径没有空格符
#该命令是make modules命令的扩展,-C选项的作用是指将当前的工作目录转移到制定的 目录
#即(KDIR)目录,程序到(shellpwd)当前目录查找模块源码,将其编译,生成.ko文件
三、模块传参
传参单个变量,用这个函数。
module_param(name,type,perm)
name:表示参数的变量名字
type:表示参数的变量类型
注意!!!
bool :布尔类型
invbool:颠倒了值的bool类型;
charp :字符指针类型,内存为用户提供的字符串分配;
int :整型
long :长整型
short :短整型
uint :无符号整型
ulong :无符号长整型
ushort :无符号短整型
perm:数字权限 0777 0664 可读可写可执行
传参数组用这个函数
module_param_array(name,type,num,perm)
name:模块参数的名称
type: 模块参数的数据类型
num: 存放传入参数元素数量,使用时候这里要写一个变量的地址
记录参数个数
perm: 模块参数的访问权限
举例:
#include <linux/kernel.h>
#include <linux/module.h>
int tmp = 10;//定义单个变量
int buf[10]={0};//定义一个数组
static __init int hello_drv0_init(void)//模块加载函数
{
int i =0;//为了for循环定义的变量
printk("this is hellodrv0_init\n");
printk("tmp==%d\n",tmp);
for(i=0;i<10;i++)
{
printk("buf[%d]==%d\t",i,buf[i]);
}
return 0;
}
static __exit void hello_drv0_exit(void)//模块卸载函数
{
printk("this is hellodrv0_exit\n");
}
module_param(tmp,int,0664);//单个变量用这个函数
module_param_array(buf,int,&num,0664);//数组用这个函数
module_init(hello_drv0_init);
module_exit(hello_drv0_exit);
MODULE_LICENSE("GPL");
四、多模块编程
多模块意味着不只有一个模块,那么就拿两个模块来举例。
模块1调用模块2
模块1 需要声明外部函数
函数2要声明该函数可以被外部调用
头文件: #include <linux/stat.h>
EXPORT_SYMBOL( ) 参数为函数名。
作用:声明该函数可以被外部调用
模块1代码:模块1要调用模块2
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/stat.h> //头文件不能少
extern void hello_text0(void);//外部调用声明
extern void hello_text1(void);//外部调用声明
static int __init hello_drv1_init(void)
{
hello_text0();
printk(KERN_INFO"this is hellodrv1_init\n");
return 0;
}
static void __exit hello_drv1_exit(void)
{
hello_text1();
printk(KERN_INFO"this is hellodrv1_exit\n");
}
module_init(hello_drv1_init);
module_exit(hello_drv1_exit);
MODULE_LICENSE("GPL");
模块2代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/stat.h> //头文件不能少
void hello_text0(void)//被调用
{
printk("hello_text0\n");
}
void hello_text1(void)//被调用
{
printk("hello_text1\n");
}
static __init int hello_drv0_init(void)
{
printk("this is hellodrv0_init\n");
return 0;
}
static __exit void hello_drv0_exit(void)
{
printk("this is hellodrv0_exit\n");
}
module_init(hello_drv0_init);
module_exit(hello_drv0_exit);
EXPORT_SYMBOL(hello_text0);//模块内的函数如果要提供给其他模块使用,必须使用EXPORT_SYMBOL()导出
EXPORT_SYMBOL(hello_text1);
MODULE_LICENSE("GPL");
先加载模块2 再加载模块1
先卸载模块1 再卸载模块2
总结
个人学习使用。点个关注呗!谢谢。