Linux设备驱动会以内核模块的形式出现,学会编写Linux内核模块编程是学习Linux设备驱动的先决条件。
4.1 Linux内核模块简介
Linux内核的整体架构非常庞大,其包含的组件也非常多。怎样把需要的部分都包含在内核中呢?
一种方法是把所有需要的功能都编译到Linux内核中。这会导致两个问题,一是生成的内核会很大,二是如果要在现有的内核中新增或删除功能,将不得不重新编译内核。
有没有另一种机制可使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?
Linux提供了这样的机制,这种机制被称为模块(Module)。模块具有如下的特点:
模块本身不被编译入内核映像,从而控制了内核的大小。
模块一旦被加载,它就和内核中的其他部分完全一样。
为了使读者初步建立对模块的感性认识,先来看一个最简单的内核模块“Hello World”,如代码清单4.1所示,源代码文件:
#include <linux/init.h>
#include <linux/module.h>
#define DRIVER_AUTHOR "[email protected]"
#define DRIVER_DESC "A sample driver"
{
printk(KERN_INFO "----hello_init----\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "----hello_exit----\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_ALIAS(DRIVER_DESC);
Makefile文件:
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
else
obj-m := hello_world.o
endif
这个最简单的内核模块只包含内核模块加载函数、卸载函数和对GPL v2许可权限的声明以及一些描述信息,编译它会产生
hello_world.ko目标文件,通过“sudo insmod hello_world.ko”命令可以加载它,通过“sudo rmmod hello_world”命令可以卸载它,加载时输出“----hello_init----”,卸载时输出“----hello_exit----”。
内核模块中用于输出的函数是内核空间的printk()而不是用户空间的printf(),printk()的用法和printf()基本相似,但前者可定义输出级别。printk()可作为一种最基本的内核调试手段。
在Linux中,使用lsmod命令可以获得系统中已加载的所有模块以及模块之间的依赖关系,例如:
Module Size Used by
hello_world 16384 0
intel_rapl 20480 0
x86_pkg_temp_thermal 16384 0
intel_powerclamp 16384 0
coretemp 16384 0
kvm_intel 172032 0
joydev 20480 0
input_leds 16384 0
kvm 548864 1 kvm_intel
lsmod命令实际上是读取并分析“/proc/modules”文件,与上述lsmod命令结果对应的“/proc/modules”文件如下:
$ cat /proc/modules
hello_world 16384 0 - Live 0x0000000000000000 (OE)
intel_rapl 20480 0 - Live 0x0000000000000000
x86_pkg_temp_thermal 16384 0 - Live 0x0000000000000000
intel_powerclamp 16384 0 - Live 0x0000000000000000
coretemp 16384 0 - Live 0x0000000000000000
kvm_intel 172032 0 - Live 0x0000000000000000
joydev 20480 0 - Live 0x0000000000000000
input_leds 16384 0 - Live 0x0000000000000000
kvm 548864 1 kvm_intel, Live 0x0000000000000000
内核中已加载模块的信息也存在于/sys/module目录下,加载hello_world.ko后,内核中将包含/sys/module/hello_world目录,该目录下又有一个refcnt(引用计数)文件和v和一个sections(段)目录,如下:
ubuntu@ubuntu2018:/sys/module/hello_world$ cd sections/
ubuntu@ubuntu2018:/sys/module/hello_world/sections$ ls -al
total 0
drwxr-xr-x 2 root root 0 May 14 14:03 .
drwxr-xr-x 5 root root 0 May 14 14:00 ..
-r--r--r-- 1 root root 4096 May 14 14:03 .exit.text
-r--r--r-- 1 root root 4096 May 14 14:03 .gnu.linkonce.this_module
-r--r--r-- 1 root root 4096 May 14 14:03 .init.text
-r--r--r-- 1 root root 4096 May 14 14:03 .note.gnu.build-id
-r--r--r-- 1 root root 4096 May 14 14:03 .rodata.str1.1
-r--r--r-- 1 root root 4096 May 14 14:03 .strtab
-r--r--r-- 1 root root 4096 May 14 14:03 .symtab
在/sys/module/hello_world目录下运行“tree–a”可得到如下目录树:
xiezhi@ubuntu2018:/sys/module/hello_world$ tree -a
.
├── coresize
├── holders
├── initsize
├── initstate
├── notes
│ └── .note.gnu.build-id
├── refcnt
├── sections
│ ├── .exit.text
│ ├── .gnu.linkonce.this_module
│ ├── .init.text
│ ├── .note.gnu.build-id
│ ├── .rodata.str1.1
│ ├── .strtab
│ └── .symtab
├── srcversion
├── taint
├── uevent
└── version
3 directories, 16 files使用modinfo<模块名>命令可以获得模块的信息,包括模块作者、模块的说明、模块所支持的参数以及vermagic:
modinfo hello_world.ko
ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$ modinfo hello_world.ko
filename: /home/ubuntu/share/my_work/linux_drivers/three/hello_world.ko
alias: A sample driver
description: A sample driver
author: [email protected]
license: GPL v2
version: v1.0
srcversion: C630BE3949B6861A20E54C0
depends:
vermagic: 4.4.0-116-generic SMP mod_unload modversions retpoline
modprobe命令比insmod命令要强大,它在加载某模块时,会同时加载该模块所依赖的其他模块。使用modprobe命令加载的模块若以“modprobe -r filename”的方式卸载,将同时卸载其依赖的模块。模块之间的依赖关系存放在根文件系统的/lib/modules/<kernel-version>/modules.dep文件中,实际上是在整体编译内核的时候由depmod工具生成的,它的格式非常简单:
kernel/lib/cpu-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko
kernel/lib/pm-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko
kernel/lib/lru_cache.ko:
kernel/lib/cordic.ko:
kernel/lib/rbtree_test.ko:
kernel/lib/interval_tree_test.ko:
updates/dkms/vboxvideo.ko: kernel/drivers/gpu/drm/drm.ko
备注:
使用modprobe命令注意事项:
要想使用modprobe必须将ko模块文件拷贝到/lib/moudels/uname -r/kernel目录中,然后执行depmod -A,否则无法使用modprobe加载模块。
ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$sudo cp hello_world.ko /lib/modules/4.4.0-116-generic/kernel/
ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$sudo depmod -A
ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$sudo modprobe hello_world(加载内核模块)
ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$lsmod | grep hello_world
hello_world 16384 0
ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$ sudo modprobe -r hello_world(卸载内核模块)
ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$lsmod | grep hello_world