第5讲 Linux驱动编写之“内核模块”操作
原创
©著作权归作者所有:来自51CTO博客作者leon_george的原创作品,请联系作者获取转载授权,否则将追究法律责任
第5讲、驱动编写之内核模块
1、内核模块的编写
2、内核模块的编译
* 静态编译
* 动态编译
3、模块加载和卸载命令
4、内核模块传参
知识回顾:
1.1 在内核中添加编译选项
make menuconfig //调用了Kconfig文件
make uImage //调用了Makefile
make
1.2、文件系统
基本概念
1.3 根文件系统制作
- 目录结构
- 系统启动时需要的文件
- init进程(PID:1)
- bin文件(shell命令和shell)
- lib库
- etc下的配置文件:(inittab 、init.d/rcS、profile、fstab、passwd、group、shadow)
- 制作文件系统工具:BusyBox
- bin下的所有命令
- linuxrc进程(init进程)
- etc下的配置文件模板
1.4 根文件系统挂载
- NFS挂载
- 服务器上NFS的服务器配置
- 网络畅通(ping)
- 内核支持NFS client功能
- uboot通过bootargs给内核传递参数,内核根据bootargs的参数,进行根文件系统的挂载
root=/dev/nfs nfsroot=/home/edu/rootfs rw ip=10.9.21.100:10.9.21.50:10.9.21.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,115200
- 制作根文件系统镜像包
- 利用Ext4工具包
mkfs_ext4.tar.gz
- 用
make_ext4fs
制作ext4文件系统镜像
tar xvf mkfs_ext4.tar.gz
cp make_ext4fs /bin/make_ext4fs
sudo chmod 777 $HOME/rootfs/ -R
make_ext4fs -s -l 314572800 -a root -L linux gtk.img /home/edu/rootfs
- 生成镜像包后,将镜像包拷贝到eMMC中,然后从eMMC中加载根文件系统。
setenv bootargs root=/dev/mmcblk0p8 rw rootfstype=ext4 init=/linucrc lcd=wy070ml tp=gslx680
saveenv
5.1、内核模块的编写
从内核源码的drivers目录中的驱动C文件中找到共同的函数:
- 分析模块初始化宏的调用逻辑
module_init(X)
#define//在iclude/linux/init.h中申明
#define
#define
而__define_initcall(level,fn,id)
的定义为:
#define __define_initcall(level,fn,id)\
static initcall_t _initcall_##fn##id __used\
__attribute__((__section__(".initcall" level ".init"))) = fn
所以:
__define_initcall("6",fn,6)就等于
static initcall_t _initcall_fn6 __used __attribute__((__section__(".initcall" 6 ".init"))) =
结论:
模块初始化宏module_init(fn)作用就是将fn这个函数地址,编译的时候统一放到init段中,在内核启动的时候或模块加载的时候被执行!
同理:模块卸载函数module_exit(fn)的作用就是将fn这个函数地址,编译的时候统一放到init段中,在模块被移除的时候被调用执行。
/*linux/init.h*/
/*linux/module.h*/
//内核模块所必须的三个要素:
typedef int(*initcall_t)(void);
typedef void(*exitcall_t)(void);
MODULE_LICENSE("GPL")
- 分析模块声明宏的调用逻辑
可有可无的声明:
MODULE_LICENSE()一般必须得有!!!
7 /*
98 * The following license idents are currently accepted as indicating free
99 * software modules
100 *
101 * "GPL" [GNU Public License v2 or later]
102 * "GPL v2" [GNU Public License v2]
103 * "GPL and additional rights" [GNU Public License v2 rights and more]
104 * "Dual BSD/GPL" [GNU Public License v2
105 * or BSD license choice]
106 * "Dual MIT/GPL" [GNU Public License v2
107 * or MIT license choice]
108 * "Dual MPL/GPL" [GNU Public License v2
109 * or Mozilla license choice]
110 *
111 * The following other idents are available
112 *
113 * "Proprietary" [Non free products]
114 *
115 * There are dual licensed components, but when running with Linux it is the
116 * GPL that is relevant so this is a non issue. Similarly LGPL linked with GPL
117 * is a GPL combined work.
118 *
119 * This exists for several reasons
120 * 1. So modinfo can show license info for users wanting to vet their setup
121 * is free
122 * 2. So the community can ignore bug reports including proprietary modules
123 * 3. So vendors can do likewise based on their own policies
124 */
#define
/*Author(s),use"Name <email>"or just"Name",formultiple
*authors use multipleMODULE_AUTHOR()statements/lines.
*/
#define
/* What your module does. */
#define
检查其调用逻辑:
#define//./module.h:92:
#define
结论:
- 编写内核模块的模板:
#include <linux/init.h>
#include<linux/module.h>
static int __init demo_init(void)
{
printk("---%s---%s---%d---\n",__FILE__,__func__,__LINE___);
return 0;
}
static void __exit demo_exit(void)
{
printk("---%s---%s---%d---\n",__FILE__,__func__,__LINE___);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
5.2 内核模块的编译
5.2.1、静态编译
- 定义:将驱动直接编译进内核,与内核形成一个整体运行,生命周期从内核启动开始动内核运行结束
- **方法:**在内核源码树中编译(配置成*之后,执行
make uImage
生成内核镜像,模块便加入到镜像文件中去了)
5.2.2、动态编译
- 内部编译:
- 定义:以模块的形式编译,在内核运行以后,通过
insmod
命令加载到内核里,和内核一起运行,通过rmmod卸载命令卸载模块。 - 优点:使用灵活,适合开发过程中使用
- 方法:内部编译:配置成’M’之后,执行
make modules
命令后,以模块的方式编译出来,
- 外部编译:
- 利用Makefile文件实现外部编译,具体使用方法可以查看帮助文档——
kernel-XXX/Documentation/kbuild/modules.txt
- 编辑好makefile文件后,当前目录下执行命令
make
之后,就可以看到.ko格式的模块文件了。 - 而后执行
insmod mod_name.ko
就可以加载进内核,执行rmmod mod_name
就可以卸载模块了。 - 执行
dmesg
命令,可以查看模块的加载卸载情况。
#Makefile文件
# 1.首先定义内核源码路径
#KDIR:=/lib/modules/$(shell uname-r)/build #x86架构
KDIR:=/home/edu/SAMBA_SHARE/BK2101/Driver/03_kernel/kernel-3.4.39 #armx架构
# 2. 然后定义自己定义的模块源码路径
PWD:=$(shell pwd)
# 3.添加自己模块的目标文件名
obj-m += demo.o
# 4.添加编译目标
modules:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
5.3、模块加载和卸载命令
- 加载:insmodxxx.ko
- 卸载:rmmodxxx
5.4、内核模块传参
- 头文件的位置:通用头文件一般位于
/include/linux/
下;体系结构相关的位于/arch/[arm/x86]/include/
下。 - 我们这个模块传参函数是通用的,所以在
/include/linux/moduleparam.h
下。
5.4.1 基本变量的传参
/*module_param-typesafe helperfora module/cmdline parameter
*@name:the variable to alter,and exposed parameter name.
*@type:the type of the parameter
*@perm:visibility in sys fs; 在sys目录下创建文件时用的文件权限。
*
*@value becomes the module parameter,or(prefixed byKBUILD_MODNAMEand a
*".")the kernel commandline parameter.Note that-is changed to _,so
*the user can use"foo-bar=1"evenforvariable"foo_bar".
*
*@perm is 0 if the the variable is not to appear in sysfs,or 0444
*for world-readable,0644 for root-writable,etc.Note thatifit
*is writable,you may need to use kparam_block_sysfs_write()around
*accesses(esp.charp,which can be kfreed when it changes).
*
*The @type is simply pasted to refer to a param_ops_##type and a
*param_check_##type:forconvenience many standard types are providedbut
*you can create your own by defining those variables.
*
*Standard types are:
*byte,short,ushort,int,uint,long,ulong
*charp:a character pointer
*bool:a bool,values0/1,y/n,Y/N.
*invbool:the above,only sense-reversed(N=true).
*/
#define module_param(name,type,perm)\
module_param_named(name,name,type,perm)
举例说明:
#include <linux/module.h>
#include <linux/moduleparam.h>
int num=10;
char *strp = "default";
module_param(num, int, 0644);
module_param(strp, charp, 0644);
static int __init demo_init(void)
{
printk(KERN_INFO"---num=%d---\n", num);
printk(KERN_INFO"---strp=%s---\n", strp);
printk(KERN_INFO"---%s---%s---%d--\n", __FILE__, __func__, __LINE__ );
return 0;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO"---%s---%s---%d--\n", __FILE__, __func__, __LINE__ );
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
insmod demo.ko num=21 strp="leon"
5.4.2 字符串变量的传参
/**
*module_param_string - a char array parameter
*@name: the name of the parameter //参数名
*@string: the string variable //字符串变量的名字
*@len: the maximum length of the string,incl.terminator
*@perm: visibility in sys fs.
*
*This actually copies the string when it's set(unlike type charp).
*@len is usually just sizeof(string).
*/
//一般我们将参数名name(用在命令行中给)和变量名string(用在代码中)都设置为一样;
//其涵义就是name从命令行中获取参数值,而后回来赋值给string
#define
//insmod demo.ko name="I am a string!"
//回到代码中后,string = name;
5.4.3 数组变量的传参
/**
*module_param_array-a parameter which is an arrayofsome type
*@name: the name of the array variable
*@type: the type,aspermodule_param()
*@nump: optional pointer filled in with the number written //数组元素个数
*@perm: visibility in sys fs
*
*Input and output areascomma-separated values. Commas inside values
*don't workproperly(eg.an arrayofcharp).
*
*ARRAY_SIZE(@name) is used to determine the number of elements in the
*array,so the definition must be visible.
*/
#define
//insmod demo.ko name=val1,val2,...
5.5、符号表导出
符号表文件:
//fn为需要导出的函数名,之后其它模块便可以调用fn了
#include <linux/export.h>
EXPORT_SYMBOL(fn);
EXPORT_SYMBOL_GPL(fn);
注意:
- 当模块之间相互间需要引用变量或函数的时候,需要使用符号导出,且需要按依赖关系顺序(被导出函数所在模块要先加载)加载,卸载的时候按逆顺序卸载。
- 模块生成的符号表在当前目录下的
Module.symvers
文件里; - 若某模块需要使用刚才模块导出的符号时,需要将该符号表文件复制到模块所在目录里才能编译成功。
5.6、printk
printk(打印级别"",...);
#define/* system is unusable */
#define/* action must be taken immediately */
#define/* critical conditions */
#define/* error conditions */
#define/* warning conditions */
#define/* normal but significant condition */
#define/* informational */
#define/* debug-level messages */
- 打印级别和控制台相关:
/proc/sys/kernel/printk
文件指示了当前系统的打印级别配置,即凡是比控制台打印级别高的(数字上小的打印级别高)都可以显示,否则只能通过dmesg
命令查看了:
控制台打印级别 默认级别 系统支持的最高打印级别 最低打印级别
7 7 1 7
5.7、模块操作相关命令
lsmod -查看内核加载的模块
insmod -加载模块
rmmod -卸载模块
modprob