第5讲、驱动编写之内核模块

1、内核模块的编写

2、内核模块的编译

​ * 静态编译 * 动态编译 ​

3、模块加载和卸载命令

4、内核模块传参

知识回顾:

1.1 在内核中添加编译选项

make menuconfig    //调用了Kconfig文件
make uImage //调用了Makefile
make

1.2、文件系统

基本概念

1.3 根文件系统制作

  1. 目录结构
  2. 系统启动时需要的文件
  • init进程(PID:1)
  • bin文件(shell命令和shell)
  • lib库
  • etc下的配置文件:(inittab 、init.d/rcS、profile、fstab、passwd、group、shadow)
  1. 制作文件系统工具:BusyBox
  • bin下的所有命令
  • linuxrc进程(init进程)
  • etc下的配置文件模板

1.4 根文件系统挂载

  1. 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​
  1. 制作根文件系统镜像包
  • 利用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
  • 制作ext4镜像
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文件中找到共同的函数:

  1. 分析模块初始化宏的调用逻辑
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")

  1. 分析模块声明宏的调用逻辑

可有可无的声明:

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

结论

  作者传入的信息最后都被存入到一个数组中去了。

  1. 编写内核模块的模板:
#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讲 Linux驱动编写之“内核模块”操作_内核模块的编译安装等操作

5.2.1、静态编译

  • 定义:将驱动直接编译进内核,与内核形成一个整体运行,生命周期从内核启动开始动内核运行结束
  • **方法:**在内核源码树中编译(配置成*之后,执行​​make uImage​​生成内核镜像,模块便加入到镜像文件中去了)

5.2.2、动态编译

  1. 内部编译:
  • 定义:以模块的形式编译,在内核运行以后,通过​​insmod​​命令加载到内核里,和内核一起运行,通过rmmod卸载命令卸载模块。
  • 优点:使用灵活,适合开发过程中使用
  • 方法:内部编译:配置成’M’之后,执行​​make modules​​命令后,以模块的方式编译出来,
  • 第5讲 Linux驱动编写之“内核模块”操作_linux_02

  1. 外部编译
  • 利用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、符号表导出

符号表文件:

第5讲 Linux驱动编写之“内核模块”操作_内核模块的编译安装等操作_03

//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