Linux内核模块简介
众所周知,Linux系统已经成了应用最为广泛的操作系统。小到日常经常用到的电子设备,比如,智能手机、智能手表等,大到互联网公司的数据中心,都可以见到Linux的身影。Linux之所以如此成功,其中一个比较关键的因素就是她非常优秀的内核设计。这种设计可以使其适应各种场景的需求,加上天生开源的特质,想不火都难。
今天,本文所要介绍的就是Linux内核相关开发中一个比较基础的概念:Linux Kernel Module(LKM)。大家,可能都知道,Linux是一个跨平台的操作系统,市面上几乎所有的硬件平台,Linux应该都是支持的。即便,暂时不支持,也可以经过少许的适配,就可以使Linux系统完美的运行起来。那么,LKM在硬件平台的适配中就扮演着十分钟的角色,有数据表面,Linux内核源代码中有超过50%都是设备驱动相关的代码,而设备驱动一般都是以内核模块的形式存在。
Linux内核是宏内核,简单来说就是内核就是一个整体,可以理解成为一个可执行程序,内核包揽了所有的系统功能,比如,进程调度、内核管理、文件系统、设备管理等等。这样势必出现一个问题,那就是如果开启所有的功能,会使得内核变得十分的臃肿。而LKM,可以从一定程度上,解决这个问题。通过将内核功能模块实现为LKM,可以对内核进行精简,只保留最为核心的功能,然后,其他功能模块,可以随着后续需求,进行动态的加载。Linux内核通过LKM可以动态的管理内核功能,使内核功能就要U盘一样,支持热插拔,这样做不仅可以节约内核运行时的硬件要求,同时可以满足内核功能的实时扩展性。
Linux内核实现了一整套管理LKM的框架,用来加载、卸载、管理模块间的关系。LKM本质上就是一段可以被链接到内核空间的代码片段,其格式和内核一样,都是ELF格式,其为内核提供访问该模块的入口和出口,而后内核模块管理框架,就会自动的将其链接到内核空间,从而完成内核功能的扩展。
下面,就通过一个实例,简单的介绍一下LKM。
基本实例
LKM既然是内核代码片段,那么其运行的环境就是内核环境,而内核环境有其独特开发标准和开发库。Linux用户空间开发一般的都是基于libc库,比如大名鼎鼎的glibc。而内核环境不依赖于glibc,所以LKM不会依赖于libc开发,而是基于内核开发框架。下面以一个实例来说明一下,LKM的开发方式。
与大多数入门程序示例一样,这里我们也是实现一个打印“hello,world"的LKM。
#include <linux/init.h> #include <linux/module.h> static int __init module_init_func(void) { printk("hello, world.\n"); return 0; } static void __exit module_exit_func(void) { return; } MODULE_LICENSE("GPL v2"); MODULE_VERSION("v0.1"); MODULE_AUTHOR("lhl"); MODULE_DESCRIPTION("LKM, helloworld."); module_init(module_init_func); module_exit(module_exit_func);
上面代码的基本含义如下:
- init.h和module.h是编写内核模块必须包含的两个头文件,里面包含LKM基本的数据结构和主要的API。
- module_init_func和module_exit_func分别对应LKM的入口和出口,而module_init和module_exit用于将这两个函数注册到内核中。
- 宏MODULE_LICENSE用于声明内核模块所遵循的协议,一般有GPL、GPL v2、GPL and additional rights、Dual BSD/GPL等。
- 宏MODULE_VERSION用于声明内核模块的版本信息。
- 宏MODULE_AUTHOR用于声明LKM的作者信息。
- 宏MODULE_DESCRIPTION用于声明LKM的基本功能描述。
内核模块的编译分为两种:单独编译和嵌入到内核代码中编译。一般情况下,LKM的编译选择单独编译,这种编译方式有两种优势:
- 编译速度快;
- 便于代码的维护。
单独编译需要提前构建内核编译环境,构建方式有两种:
- 下载内核源码,编译完成后,执行make modules_install安装。
- 如果是Ubuntu系统,可以使用sudo apt-get install linux-headers-$(uname -r)安装适配于当前内核的构建环境。
内核模块的构建需要编写Makefile文件,我们把上面的helloworld保存为hellword.c,相应的Makefile文件的内容如下:
obj-m:=helloworld.o KERS :=/lib/modules/$(shell uname -r)/build all: make -C $(KERS) M=$(shell pwd) modules clean: make -C $(KERS) M=$(shell pwd) clean
上述代码的基本含义如下:
- obj-m:用于定义内核模块的二进制名称,注意,该文件名称与内核模块的c程序文件名进行对应。
- KERS:用于声明内核编译环境的位置。
- all:用于定义目标文件的编译规则,make -C (KERS)表示进入内核编译环境,M=(KERS)表示进入内核编译环境,M=(KERS)表示进入内核编译环境,M=(shell pwd)指定当前LKM的路径,modules表示进行LKM的编译。
- clean:与all目标类似,这里用于定义清理LKM编译过程中产生的文件,包括.ko文件。
好了,准备好了编译内核模块的所有文件,执行make,可以看到下面的编译过程。 $ make
make -C /lib/modules/5.3.0-45-generic/build M=/home/lhl/learn/LKM/example modules make[1]: 进入目录“/usr/src/linux-headers-5.3.0-45-generic” CC [M] /home/lhl/learn/LKM/example/helloworld.o Building modules, stage 2. MODPOST 1 modules CC /home/lhl/learn/LKM/example/helloworld.mod.o LD [M] /home/lhl/learn/LKM/example/helloworld.ko make[1]: 离开目录“/usr/src/linux-headers-5.3.0-45-generic”
helloworld.ko就是最终生成的内核模块,使用sudo insmod helloworold.ko,安装该内核模块,然后,执行dmesg,可以看到内核日志:
$ sudo insmode helloworld.ko $ dmesg ...... [18813.852210] hello, world. ...... $ sudo rmmod helloworld.ko
通过sudo rmmod helloworld,可以卸载内核模块。
还可以通过,modinfo可以查看LKM的基本信息:
$ modinfo helloworld.ko filename: /home/lhl/learn/LKM/example/helloworld.ko description: LKM, helloworld. author: lhl version: v0.1 license: GPL v2 srcversion: 44ADBB2239DA10943034DBE depends: retpoline: Y name: helloworld vermagic: 5.3.0-45-generic SMP mod_unload
总结
上面就是对于LKM的简单介绍,对于一个小白足可以完成一个内核模块,后续文章还会深入分析LKM的具体的实现机制。