Skip to content

内核模块

《Linux设备驱动程序》 - 第三版 的第2章,《Linux设备驱动开发详解》 - 宋宝华 的第4章,的读书笔记,本文中的所有代码可在GitHub仓库中找到

模块初探

模块"Hello World"实现了一个最简单的模块,其中"hello.c"定义了两个函数:

  • hello_init在模块被装载到内核时被调用
  • hello_exit在模块被移除时被调用
#include <linux/init.h>
#include <linux/module.h>

static int hello_init(void)
{
   printk(KERN_INFO "Hello World enter\n");
   return 0;
}
module_init(hello_init);

static void hello_exit(void)
{
   printk(KERN_INFO "Hello World exit\n ");
}
module_exit(hello_exit);

编译模块

编译模块需要准备内核树,可使用Ubuntu系统自带的默认内核目录/lib/modules/`uname -r`/build。对于"hello world"模块,

  • obj-m := hello.o配置指定了模块需要从目标文件hello.o中构造
  • make -C $(KDIR) M=$(PWD) modules命令将当前工作环境和内核构造系统相联系,完成了模块的构建

下面是完整的Makefile内容:

MODULE_NAME := hello

# 如果已经定义KERNELRELEASE,说明当前任务在内核构造系统中,可利用内建语句
ifneq (${KERNELRELEASE},)

obj-m := $(MODULE_NAME).o

else

# 当前系统的内核源代码目录
KDIR ?= /lib/modules/`uname -r`/build

# -C : 改变目录到指定的位置(即内核源代码目录),其中保存有内核的顶层makefile文件
# M= : 在构造"modules"目标之前返回到模块源代码目录,并指向obj-m变量中设定的模块.o文件
default:
    make -C $(KDIR) M=$(PWD) modules

clean:
    make -C $(KDIR) M=$(PWD) clean

endif

装载模块

insmodmodprobe可用于装载模块。modprobe命令更强大,会同时加载该模块所依赖的其他模块,因此需要在/lib/modules/<kernel-version>/modules.dep文件中指定依赖关系。

模块装载完成后,会在下面两个目录中加入相关信息:

  • /proc/modules
    • lsmod命令就是读取/proc/modules文件中的信息
  • /sys/module/<module-name>
# 装载模块
> sudo insmod hello.ko
> tail -n 1 /var/log/kern.log
Jun 19 12:09:34 ben-vm-base kernel: [181105.450980] Hello World enter

# 查看模块
> cat /proc/modules | grep hello
hello 16384 0 - Live 0x0000000000000000 (OE)
> lsmod | grep hello
hello                  16384  0
> ls /sys/module/hello/
coresize  holders  initsize  initstate  notes  refcnt  sections  srcversion  taint  uevent

# 卸载模块
> sudo rmmod hello.ko
> tail -n 2 /var/log/kern.log
Jun 19 12:09:34 ben-vm-base kernel: [181105.450980] Hello World enter
Jun 19 12:10:23 ben-vm-base kernel: [181154.345969] Hello World exit

模块参数

模块"book"通过module_param添加了模块参数,因此在装载时,利用book_name=<name> book_num=<num>即可自定义模块的参数内容,/sys/module/book/parameters中记录了参数的内容。

book.c
static char *book_name = "Linux Device Driver";
module_param(book_name, charp, S_IRUGO);

static int book_num = 4000;
module_param(book_num, int , S_IRUGO);
装载模块时传入参数
> sudo insmod book.ko book_name="Good_Book" book_num=1000

> cat /sys/module/book/parameters/book_name
Good_Book
> cat /sys/module/book/parameters/book_num
1000

模块符号

insmod使用公共内核符号表来解析模块中未定义的符号。内核符号表可通过cat /proc/kallsyms查看。

当模块装入内核后,可通过下面的宏导出的任何符号,导出的符号会变成内核符号表的一部分,供其他模块使用:

  • EXPORT_SYMBOL(name)
  • EXPORT_SYMBOL_GPL(name)

模块"symbol"中,"export"模块导出了hello_export函数,"import"模块使用了hello_export函数:

void hello_export(void)
{
    printk(KERN_INFO "Hello from another module");
}
EXPORT_SYMBOL(hello_export);
extern void hello_export(void);

static int import_init(void)
{
    hello_export();
    printk(KERN_INFO "Import module enter\n");
    return 0;
}
module_init(import_init);
# 先装载export, 再装载import
> sudo insmod export.ko
> sudo insmod import.ko
> tail -n 3 /var/log/kern.log
Jun 19 14:39:40 ben-vm-base kernel: [190111.168523] Export module enter
Jun 19 14:39:40 ben-vm-base kernel: [190111.185477] Hello from another module
Jun 19 14:39:40 ben-vm-base kernel: [190111.185481] Import module enter

# import依赖export
> modinfo import.ko
...
depends:        export
...

# 查看公共内核符号表
> cat /proc/kallsyms | grep hello_export
0000000000000000 r __kstrtab_hello_export       [export]
0000000000000000 r __kstrtabns_hello_export     [export]
0000000000000000 r __ksymtab_hello_export       [export]
0000000000000000 T hello_export [export]

# 先卸载import, 再卸载export
> sudo rmmod import.ko
> sudo rmmod export.ko
> tail -n 5 /var/log/kern.log
Jun 19 14:39:40 ben-vm-base kernel: [190111.168523] Export module enter
Jun 19 14:39:40 ben-vm-base kernel: [190111.185477] Hello from another module
Jun 19 14:39:40 ben-vm-base kernel: [190111.185481] Import module enter
Jun 19 14:41:49 ben-vm-base kernel: [190240.103068] Import module exit
Jun 19 14:41:49 ben-vm-base kernel: [190240.138433] Export module exit