一、简介:
Linux内核是一个整体结构,但是通过内核模块的方式向开发人员提供了一种动态加载程序到内核的能力。通过内核模块,开发人员可以访问内核的资源,内核还向开发人员提供了访问底层硬件和总线的接口。因此,Linux系统的驱动是通过内核模块实现的。
Linux内核模块是一种可以被内核动态加载和卸载的可执行程序。通过内核模块可以扩展内核的功能,通常内核模块被用于设备驱动、文件系统等。如果没有内核模块,需要向内核添加功能就需要修改代码、重新编译内核、安装新内核等步骤,不仅繁琐,而且容易出错,不易于调试。
二、常用的模块操作命令:
(1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表
(2)insmod(install module,安装模块),功能是向当前内核中去安装一个模块,用法是insmod xxx.ko
(3)modinfo(module information,模块信息),功能是打印出一个内核模块的自带信息。
(4)rmmod(remove module,卸载模块),功能是从当前内核中卸载一个已经安装了的模块,用法是rmmod xxx(注意卸载模块时只需要输入模块名即可,不能加.ko后缀)
三、编写一个最简单的的内核模块
module_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
// 模块安装函数
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
}
module_init(chrdev_init);//注册模块的初始化函数
module_exit(chrdev_exit);//注册模块的退出函数
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("liyijun"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("clark"); // 描述模块的别名信息
module_init的通俗理解:
模块源代码中用module_init宏声明了一个函数(在我们这个例子里是chrdev_init函数),作用就是指定chrdev_init这个函数和insmod命令绑定起来,也就是说当我们insmod module_test.ko时,insmod命令内部实际执行的操作就是帮我们调用chrdev_init函数。模块安装时insmod内部除了帮我们调用module_init宏所声明的函数外,实际还做了一些别的事(譬如lsmod能看到多了一个模块也是insmod帮我们在内部做了记录),但是我们就不用管了。
module_init的深入理解:
http://www.seotest.cn/jishu/34634.html
https://www.cnblogs.com/sky-heaven/p/10344826.html
https://blog.csdn.net/anglexuchao/article/details/80391928
https://www.jianshu.com/p/a783474efb44
四、编译内核模块
Makefile
#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
KERN_VER = $(shell uname -r)
KERN_DIR = /lib/modules/$(KERN_VER)/build
obj-m += module_test.o
all:
make -C $(KERN_DIR) M=`pwd` modules
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
Makefile解释:
1、uname -r : 显示操作系统的发行版号,所以在我的计算机上KERN_VER变量就是4.15.0-64-generic
KERN_DIR 变量就是 /lib/modules/4.4.0-62-generic/build
2、obj-m += module_test.o,这一行就表示我们要将module_test.c文件编译成一个模块 ,不会编译到内核,但是会生成一个独立的 "module_test.ko" 文件;
假如是obj-y则表示把module_test.o文件编译进内核;
3、make -C $(KERN_DIR) M=`pwd` modules 该命令是make modules命令的扩展,用来实际编译模块,大致意思就是:利用make -C(进入到某个目录下编译)进入到我们指定的内核源码树目录下,然后在源码目录树下借用内核源码中定义的模块编译规则(makefile)去编译这个模块,M=`pwd`表明然后返回到当前目录继续读入、执行当前的Makefile。
https://www.cnblogs.com/liulipeng/archive/2013/11/05/3408238.html
编译完成之后,生成module_test.ko和几个临时文件:
五、安装与卸载内核模块
在执行完安装命令后并没有将chrdev_init helloworld init信息打印,这是因为ubuntu中这个printk的打印级别控制没法实践,ubuntu中不管你把级别怎么设置都不能直接打印出来,必须dmesg命令去查看。
关于tail命令参考以下:
https://www.runoob.com/linux/linux-comm-tail.html
我们对模块进行卸载:
六、内核模块的传参
驱动程序常需要在加载的时候提供一个或者多个参数,内模块提供了设置参数的能力。通过module_param()宏可以为内核模块设置一个参数。定义如下:
module_param(参数名称,类型,属性)其中,参数名称是加载内核模块时使用的参数名称,在内核模块中需要有一个同名的变量与之对应;类型是参数的类型,内核支持C语言常用的基本类型;属性是参数的访问权限。
代码示例:
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/moduleparam.h>
static int initValue = 0;
static char* initName = NULL;
module_param(initValue,int,S_IRUGO);
module_param(initName,charp,S_IRUGO);
// 模块安装函数
static int __init chrdev_init(void)
{
printk(KERN_INFO "initValue = %d,initName = %s\n",initValue,initName);
printk(KERN_INFO "chrdev_init helloworld init\n");
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
}
module_init(chrdev_init);//注册模块的初始化函数
module_exit(chrdev_exit);//注册模块的退出函数
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("liyijun"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("clark"); // 描述模块的别名信息
Makefile文件同上
安装模块的时候一同传递参数:
使用modinfo命令查看内核模块的信息:
REF:
朱友鹏课件
《ARM嵌入式linux系统开发详解》