一、什么是内核模块
1.linux kernel module
驱动程序在内核中,都是独立的模块,例如:beep驱动和LED驱动,beep和led之间没有任何联系,可以通过应用程序将两个驱动联系在一起。beep驱动和led驱动各自是独立的module。
说明:每个驱动程序都是一个独立的模块,每设计一个驱动程序,首先设计一个module,驱动程序是包含在module中的。
2、module编译后会生成一个*.ko
驱动程序:可以安装、可以卸载的
安装驱动:#insmod led_drv.ko
卸载驱动:#rmmod led_drv.ko
查看系统中,安装的module:#lsmod
注意:驱动是安装到内存中正在运行的内核上的。
二、设计一个最简单的module
1.参考内核源码
例子:/drivers/watchdog/mxp_wdt.c
2.设计Led driver module
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
//入口函数--->安装驱动
static int __init S5P6818_led_init(void)
{
printk(KERN_WARNING "S5P6818 led driver init \n");
return 0;
}
//出口函数--->卸载驱动
static void __exit S5P6818_led_exit(void)
{
printk(KERN_WARNING "S5P6818 led driver exit \n");
}
//驱动程序的入口:#insmod led_drv.ko -->module_init()-->S5P6818_led_init()
module_init(S5P6818_led_init);
//驱动程序的出口:#rmmod led_drv.ko --->module_exit()-->S5P6818_led_exit()
module_exit(S5P6818_led_exit);
//module的描述。#modinfo led_drv.ko
MODULE_AUTHOR("wslinkm@163.com");
MODULE_DESCRIPTION("LED driver for S5P6818");
MODULE_LICENSE("GPL");
MODULE_VERSION("V1.0");
三、Makefile
obj-m += led_drv.o
KERNELDIR:=/home/wsl/kernel/S5P6818/kernel
CROSS_COMPILE:=/home/wsl/kernel/S5P6818/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-
PWD:=$(shell pwd)
default:
$(MAKE) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.order .*.cmd *.ko *.mod.c *.symvers *.tmp_versions
说明:
1. obj-m += led_drv.o
将源程序的目标文件led_drv.o,编译成一个module(ko)
注意:+=,?=, := 区别?
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值
“=” 表示make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。例子:
x = foo
y = $(x) bar
x = end
在上例中,y的值将会是 end bar ,而不是 foo bar 。
“:=” 表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。
x := foo
y := $(x) bar
x := end
在上例中,y的值将会是 foo bar ,而不是 end bar 了。
2. KERNELDIR:=/home/wsl/kernel/S5P6818/kernel
内核源码的路径:内核的头文件和Makefile
3. CROSS_COMPILE:=/home/wsl/kernel/S5P6818/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-
交叉编译工具
4. PWD:=$(shell pwd)
当前路径
5. ( M A K E ) A R C H = a r m C R O S S C O M P I L E = (MAKE) ARCH=arm CROSS_COMPILE= (MAKE)ARCH=armCROSSCOMPILE=(CROSS_COMPILE) -C ( K E R N E L D I R ) M = (KERNELDIR) M= (KERNELDIR)M=(PWD) modules
向内核源码路径下的Makefile文件传递两个参数,并调用内核源码下的Makefile文件,使用该Makfile中的工具,回到当前路径下,将源程序编译成一个module。
四、驱动的调试
1. file
root@wsl-VirtualBox:/home/VMShareDir/CodeLinux/Linux_kernel/004module# file led_drv.ko
led_drv.ko: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), BuildID[sha1]=50316234980f9f06bc0c138bf8f43e42dced2f28, with debug_info, not stripped
2. size
root@wsl-VirtualBox:/home/VMShareDir/CodeLinux/Linux_kernel/004module# size led_drv.ko
text data bss dec hex filename
360 360 0 720 2d0 led_drv.ko
3. modinfo
root@wsl-VirtualBox:/home/VMShareDir/CodeLinux/Linux_kernel/004module# modinfo led_drv.ko
filename: /home/VMShareDir/CodeLinux/Linux_kernel/004module/led_drv.ko
version: V1.0
license: GPL
description: LED driver for S5P6818
author: wslinkm@163.com
srcversion: 753552DDFB9C4FE54972420
depends:
vermagic: 3.4.39-gec SMP preempt mod_unload ARMv7 p2v8
vermagic —>version magic(魔数):驱动可以安装的linux版本:3.4.39-gec,其中:-gec —>local version,配置内核的时候
ARMv7 ---->硬件的版本
GEC6818平台:
[root@GEC6818 /]#uname -a
Linux GEC6818 3.4.39-gec #37 SMP PREEMPT Tue Aug 1 13:53:02 CST 2017 armv7l GNU/Linux
驱动程序(ko)的版本与目标平台的版本要一致。
4. 驱动安装
[root@GEC6818 /tmp]#insmod led_drv.ko
[ 1870.798000] S5P6818 led driver init
5. 查看module
[root@GEC6818 /tmp]#lsmod
led_drv 768 0 - Live 0xbf039000 (O)
6. 卸载驱动
[root@GEC6818 /tmp]#rmmod led_drv.ko
[ 2038.228000] S5P6818 led driver exit
五、编译驱动时,对内核源码的要求
1、内核源码的版本要和驱动安装目标平台的版本一致
2、内核源码要针对目标平台的CPU架构配置过
3、内核源码必须要编译过。
六、printk
应用程序:stdio.h ---->printf()
驱动程序:kernel.h ---->printk()
printk()带有优先级的。
1、查看系统printk的优先级
#cat /proc/sys/kernel/printk
7 7 1 7
该文件有四个数字值,它们根据日志记录消息的重要性,定义将其发送到何处。关于不同日志级别的更多信息,请查阅syslog(2)联机帮助。上面显示的4个数据分别对应:
7—>控制台日志级别:优先级高于该值的消息将被打印至控制台
7—>默认的消息日志级别:将用该优先级来打印没有优先级的消息,printk(“gec6818 led driver init \n”);
1—>最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级)
7—>默认的控制台日志级别:控制台日志级别的缺省值
数值越小,优先级越高
2、printk优先级的含义
#define KERN_EMERG “<0>” /* system is unusable /
#define KERN_ALERT “<1>” / action must be taken immediately /
#define KERN_CRIT “<2>” / critical conditions /
#define KERN_ERR “<3>” / error conditions /
#define KERN_WARNING “<4>” / warning conditions /
#define KERN_NOTICE “<5>” / normal but significant condition /
#define KERN_INFO “<6>” / informational /
#define KERN_DEBUG “<7>” / debug-level messages */
3、如何解决printk输出看不看内容的问题?
设置printk的优先级
1)简单方法
开发板平台
#cat /proc/sys/kernel/printk
7 7 1 7
#echo 7 4 1 7 >/proc/sys/kernel/printk
永久生效,写入:/etc/profile
#echo 7 4 1 7 >/proc/sys/kernel/printk
2)printk加优先级
printk(KERN_WARNING "gec6818 led driver init \n");
printk("<4>" "gec6818 led driver exit \n");
3)配置linux内核,修改优先级—>一劳永逸
虚拟机平台
(1)使用默认的配置文件
root@wsl-VirtualBox:~/kernel/S5P6818/kernel# cp arch/arm/configs/GEC6818_defconfig .config
(2)make menuconfig–>配置内核
root@wsl-VirtualBox:~/kernel/S5P6818/kernel# make menuconfig
============================================================
报错:
root@wsl-VirtualBox:~/kernel/S5P6818/kernel# make menuconfig
scripts/kconfig/mconf Kconfig
Your display is too small to run Menuconfig!
It must be at least 19 lines by 80 columns.
/home/wsl/kernel/S5P6818/kernel/scripts/kconfig/Makefile:21: recipe for target ‘menuconfig’ failed
make[1]: *** [menuconfig] Error 1
Makefile:492: recipe for target ‘menuconfig’ failed
make: *** [menuconfig] Error 2
原因:terminal 窗口太小的原因
解决:把terminal变大整个屏幕大小就可以显示
============================================================
Kernel hacking —> (4) Default message log level (1-7)
(3)保存退出
(4)复制配置文件
root@wsl-VirtualBox:~/kernel/S5P6818/kernel#cp .config arch/arm/configs/GEC6818_defconfig
(5)编译内核
./mk -k
---->烧写内核镜像 out/release/boot.img
七、内核符号表
1、什么是内核符号表
内核符号表是内核中一个全局的总编,这个表中,声明了一些全局的函数,内核中的驱动程序就可以直接调用这些函数。
[root@GEC6818 /]#cat /proc/kallsyms
2、如何将一个函数名,申明到内核符号表中去
struct net_device_stats *eip_get_stats(struct net_device *dev)
{
return __ei_get_stats(dev);
}
EXPORT_SYMBOL(eip_get_stats);
EXPORT_SYMBOL()
EXPORT_SYMBOL_GPL() // 声明内核符号表中的函数,只有符合GPL协议的驱动才可以使用
如何让一个驱动符合GPL协议???
MODULE_LICENSE(“GPL”);
八、__init 和 __exit
__init用来修饰一个内核中一个初始化函数,初始化函数的特点是在系统初始化的时候,执行一次这样的函数,后面就不用了。
这个时候初始化函数所占用的内存可以释放掉。
内核的启动过程:
[ 0.000000] Memory: 1024MB = 1024MB total
[ 0.000000] Memory: 810820k/810820k available, 237756k reserved, 272384K highmem
[ 0.000000] Virtual kernel memory layout:
[ 0.000000] vector : 0xffff0000 - 0xffff1000 ( 4## 3、 kB)
[ 0.000000] fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB)
[ 0.000000] vmalloc : 0xef800000 - 0xfee00000 ( 246 MB)
[ 0.000000] lowmem : 0xc0000000 - 0xef600000 ( 758 MB)
[ 0.000000] pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
[ 0.000000] modules : 0xbf000000 - 0xbfe00000 ( 14 MB)
[ 0.000000] .text : 0xc0008000 - 0xc0a51018 (10533 kB)
[ 0.000000] .init : 0xc0a52000 - 0xc0a8f100 ( 245 kB) ---->内核初始化函数所占用的内存
[ 0.000000] .data : 0xc0a90000 - 0xc0b297d8 ( 614 kB)
[ 0.000000] .bss : 0xc0b297fc - 0xc0d09488 (1920 kB)
…
[ 3.927000] EXT4-fs (mmcblk0p2): recovery complete
[ 3.927000] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null)
[ 3.934000] VFS: Mounted root (ext4 filesystem) on device 179:2.
[ 3.940000] devtmpfs: mounted
[ 3.943000] Freeing init memory: 244K ---->释放了内存。
九、驱动程序和应用程序的区别
1、驱动程序有入口和出口,但是应用程序只有入口–main()
2、设计驱动程序的时候,只能使用内核源码提供的头文件–>/include
不能使用标准的C库:stdio.h,printf()
3、驱动程序是一个个独立的模块。各个驱动程序之间,一般是没有关系的。
4、编译方法
应用程序:arm-linux-gcc -o test test.c
驱动程序:
使用内核源码包提供的头文件、使用内核源码的编译工具:Makefile
5、驱动程序要求稳定、高效、精简。