1.1 简介
1.2 加载内核模块
1.3 最简单的模块
1.4 模块必要信息
1.4.1 内核模块必须至少包含的头文件:
1.4.2 内核模块必须至少有两个功能:
1.4.3 printk()日志记录
1.4.4 优先级
1.4.5 许可和模块文档
1.5 编译内核模块
1.6 实战
1.6.1 源代码文件: hello.c
1.6.2 Makefile文件: Makefile
1.6.3 使用make编译
1.6.4 使用modinfo查看新的模块文件
1,6.5 使用insmod加载内核模块
1.6.6 使用lsmod查看已加载的模块
1.6.7 使用rmmod卸载模块
1.6.8 使用dmesg查看日志
1.6.9 练习
1.6.10 其他信息
1.7 更多基础示例链接
1.7.1 将命令行参数传递给模块
1.7.2 跨越多个文件的模块
1.7.3 构建预编译内核的模块
1.7.4 与应用程序交互
2 make工具
2.1 DESCRIPTION
2.2 退出状态
2.3 选项
3. 其他内核编译方法及相关连接
3.1 编译Arch的内核模块
3.2 Arch构建系统
3.3 内核/传统编译
3.4 修补包
3.5 模块与程序
1. 内核模块编程
http://www.tldp.org/LDP/lkmpg/2.6/html/Linux内核模块编程指南 2007-05-18见2.6.4
1.1 简介
什么是内核模块?模块是可以根据需要加载和卸载到内核中的代码片段。它们扩展了内核的功能,而无需重启系统。例如,一种类型的模块是设备驱动程序,它允许内核访问连接到系统的硬件。还有很多的文件系统等。
1.2 加载内核模块
您可以通过运行lsmod来查看已经加载到内核中的模块,lsmod通过读取文件/proc/modules来获取其信息。执行modprobe来加载模块.modprobe以两种形式之一传递一个字符串:
> 模块名称,如softdog或ppp。
> 一个更通用的标识符,如char-major-10-30。
如果modprobe被赋予通用标识符,它首先在文件/etc/modprobe.conf中查找该字符串。如果找到如下的别名行:alias char-major-10-30 softdog
它知道通用标识符引用模块softdog.ko。
然后,modprobe查看文件/lib/modules/version/modules.dep,查看是否必须加载其他模块才能加载所请求的模块。该文件由depmod -a创建,包含模块依赖项。
例如,msdos.ko要求fat.ko模块已经加载到内核中。
最后,modprobe使用insmod首先将任何必备模块加载到内核中,然后加载所请求的模块。
modprobe将insmod指向/lib/modules/‘uname -r‘/, 模块的标准目录。
insmod对于模块的位置是相当愚蠢的,而modprobe知道模块的默认位置,知道如何找出依赖关系并以正确的顺序加载模块。
因此,例如,如果要加载msdos模块,则必须运行:
$ insmod /lib/modules/2.6.11/kernel/fs/fat/fat.ko
$ insmod /lib/modules/2.6.11/kernel/fs/msdos/msdos.ko
要么:
$ modprobe msdos
所以,insmod要求你传递完整的路径名并以正确的顺序插入模块,而modprobe只取名字,没有任何扩展名,并通过解析/lib找出它需要知道的所有内容/modules/version/modules.dep。
1.3 最简单的模块
http://www.tldp.org/LDP/lkmpg/2.6/html/x121.htmlExample 2-1. hello-1.c
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
int init_module(void)
{
printk(KERN_INFO "Hello world 1.\n");
return 0;
}
void cleanup_module(void)
{
printk(KERN_INFO "Goodbye world 1.\n");
}
1.4 模块必要信息
1.4.1 内核模块必须至少包含的头文件:
#include <linux/module.h> /* module_init() module_exit() 函数来注册模块入口和退出处理。#include <linux/kernel.h> /* Needed for KERN_INFO ,仅用于printk()日志级别的KERN_ALERT的宏扩展 */
#include <linux/init.h> /* Needed for the macros */
1.4.2 内核模块必须至少有两个功能:
- 一个“开始”(初始化)功能调用 的init_module()当模块insmoded到内核被称为,
- 以及“结束”(清理)函数调用在cleanup_module()这是刚刚称为在它被破坏之前。
这是通过 module_init()和module_exit()宏完成的。这些宏在linux / init.h中定义。
唯一需要注意的是,必须在调用宏之前定义init和cleanup函数,否则会出现编译错误。
1.4.3 printk()日志记录
由于代码运行在内核空间里面,不能直接使用用户空间的 print 函数,而要使用内核中的 printk 函数.作为日志记录机制,用于记录信息或发出警告。
每个printk() 语句都带有一个优先级,即您看到的<1>和KERN_ALERT。
有8个优先级,内核有宏,所以你不必使用神秘的数字,你可以在linux/kernel.h中查看它们(及其含义)。
如果未指定优先级,则将使用默认优先级DEFAULT_MESSAGE_LOGLEVEL。
花点时间阅读优先级宏。头文件还描述了每个优先级的含义。
在实践中,不要使用数字,如<4>。始终使用宏,如 KERN_WARNING。
如果优先级低于int console_loglevel,则会在当前终端上打印消息。
如果syslogd和klogd都在运行,那么该消息也将附加到/var/log/messages,无论它是否打印到控制台。
我们使用高优先级(如KERN_ALERT)来确保将printk()消息打印到控制台而不是仅记录到日志文件中。
编写实际模块时,您需要使用对当前情况有意义的优先级。
1.4.4 优先级
https://szosoft.blogspot.com/2019/06/linux-journal.html#1021cat /usr/lib/modules/5.1.15-arch1-1-ARCH/build/include/linux/kernel.h
cat /usr/lib/modules/5.1.15-arch1-1-ARCH/build/include/linux/printk.h
内核模块printk | no | (Key)journal |
KERN_EMERG | 0 | Emergency 紧急 |
KERN_ALERT | 1 | Alert 警报 |
KERN_CRIT | 2 | Critical 危急 |
KERN_ERR | 3 | Error 错误 |
KERN_WARNING | 4 | Warning 警告 |
KERN_NOTICE | 5 | Notice 注意 |
KERN_INFO | 6 | Informational 信息 |
KERN_DEBUG | 7 | Debug 调试 |
1.4.5 许可和模块文档
MODULE_LICENSE("GPL"); /* Get rid of taint message by declaring code as GPL. */MODULE_AUTHOR("Tom"); /* Who wrote this module? */
MODULE_DESCRIPTION("Test"); /* What does this module do */
MODULE_VERSION("0.0.1");
MODULE_SUPPORTED_DEVICE("testdevice"); /* 声明模块支持哪些类型的设备。 */
在内核2.4及更高版本中,设计了一种机制来识别在GPL(和朋友)下许可的代码,以便可以警告人们代码是非开源的。
这是通过MODULE_LICENSE()宏实现的。通过将许可证设置为GPL,可以防止打印警告。
此许可证机制在linux/module.h中定义并记录:
$ cat /usr/lib/modules/5.1.15-arch1-1-ARCH/build/include/linux/module.h |grep MODULE_
/*
* 目前接受以下许可证标识为免费软件模块
* "GPL" [GNU Public License v2 or later]
* "GPL v2" [GNU Public License v2]
* "GPL and additional rights" [GNU Public License v2 rights and more]
* "Dual BSD/GPL" [GNU Public License v2 or BSD license choice]
* "Dual MIT/GPL" [GNU Public License v2 or MIT license choice]
* "Dual MPL/GPL" [GNU Public License v2 or Mozilla license choice]
*
* 以下其他标识可供选择
* "Proprietary" [Non free products]
*/
1.5 编译内核模块
http://www.tldp.org/LDP/lkmpg/2.6/html/x181.html内核模块的编译需要与常规用户空间应用程序略有不同。
以前的内核版本要求我们关注这些设置,这些设置通常存储在Makefile中。虽然按层次结构组织,但许多冗余设置在次级Makefile中累积并使它们变大并且难以维护。
幸运的是,有一种新方法可以做这些事情,称为kbuild,外部可加载模块的构建过程现在完全集成到标准内核构建机制中。
要了解有关如何编译不属于官方内核的模块的更多信息(例如本指南中的所有示例),请参阅文件 linux/Documentation/kbuild/modules.txt.
编译时通过一个 Makefile 文件进行,把这个 Makefile 文件置于 hello.c 同一目录下.
Makefile对格式有要求。每一行文本除非顶头开始,如果需要格式编排,不能使用空格键来控制文本行缩进,必须使用Tab键。
1.6 实战
1.6.1 源代码文件: hello.c
/* hello.c - Demonstrates module documentation. */#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
#include <linux/init.h> /* Needed for the macros */
#define DRIVER_AUTHOR "Peter Jay Salzman <p@dirac.org>"
#define DRIVER_DESC "A sample driver"
static int __init init_hello(void)
{
printk(KERN_INFO "HelloWorld\n");
return 0;
}
static void __exit cleanup_hello(void)
{
printk(KERN_INFO "GoodbyeWorld\n");
}
module_init(init_hello);
module_exit(cleanup_hello);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR); /* Who wrote this module? */
MODULE_DESCRIPTION(DRIVER_DESC); /* What does this module do */
MODULE_SUPPORTED_DEVICE("testdevice");
1.6.2 Makefile文件: Makefile
obj-m += hello.oall:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
从技术角度来看,第一行确实是必要的,为了方便起见,添加了“全部”和“清洁”目标。
现在您可以通过发出命令make来编译模块。您应该获得类似于以下内容的输出:
1.6.3 使用make编译
$ makemake -C /lib/modules/5.1.15-arch1-1-ARCH/build M=/home/toma/ko modules
make[1]: Entering directory ‘/usr/lib/modules/5.1.15-arch1-1-ARCH/build‘
CC [M] /home/toma/ko/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/toma/ko/hello.mod.o
LD [M] /home/toma/ko/hello.ko
make[1]: Leaving directory ‘/usr/lib/modules/5.1.15-arch1-1-ARCH/build‘
请注意,内核2.6引入了一种新的文件命名约定:内核模块现在具有.ko 扩展名(代替旧的.o扩展名),可以轻松地将它们与传统的目标文件区分开来。
原因是它们包含一个额外的.modinfo部分,其中保留了有关该模块的其他信息。我们很快就会看到这些信息有什么用处。
有关内核模块的Makefile的更多详细信息,请参见 linux/Documentation/kbuild/makefiles.txt .
$ ls -l //编译后查看文件列表
name size
Makefile 154 //make文件
hello.c 786 //源文件
hello.ko 4528 //内核文件
hello.mod.c 646
hello.mod.o 2712
hello.o 2664 //目标文件
modules.order 30
Module.symvers 0
1.6.4 使用modinfo查看新的模块文件
$ modinfo hello.kofilename: /home/toma/ko/hello.ko
version: 0.0.1
description: Test
author: Tom
license: GPL
srcversion: BC3C3A49026E0297D738AE7
depends:
retpoline: Y
name: hello
vermagic: 5.1.15-arch1-1-ARCH SMP preempt mod_unload
1,6.5 使用insmod加载内核模块
$ sudo insmod ./hello.ko1.6.6 使用lsmod查看已加载的模块
$ lsmod |grep helloModule Size Used by
hello 16384 0
1.6.7 使用rmmod卸载模块
$ sudo rmmod hello$ lsmod |grep hello
1.6.8 使用dmesg查看日志
$ dmesg |tail...
[562050.818179] HelloWorld
[562458.113633] GoodbyeWorld
1.6.9 练习
练习1请参阅init_module()中 return语句上方的注释 ?将返回值更改为负值,重新编译并再次加载模块。怎么了?
$ sudo insmod ./hello-2.ko
insmod: ERROR: could not insert module ./hello-2.ko: Operation not permitted
$ dmesg |tail
...
[546135.110381] HelloWorld2.
练习2
将代码中许可的部分删除,再编译看看。
$ make
make -C /lib/modules/5.1.15-arch1-1-ARCH/build M=/home/toma/ko modules
make[1]: Entering directory ‘/usr/lib/modules/5.1.15-arch1-1-ARCH/build‘
CC [M] /home/toma/ko/hello-1.o
Building modules, stage 2.
MODPOST 1 modules
style="color: red; font-size: xx-small;">WARNING: modpost: missing MODULE_LICENSE() in /home/toma/ko/hello-1.o
see include/linux/module.h for more information
CC /home/toma/ko/hello-1.mod.o
LD [M] /home/toma/ko/hello-1.ko
make[1]: Leaving directory ‘/usr/lib/modules/5.1.15-arch1-1-ARCH/build‘
1.6.10 其他信息
现在看一下linux/drivers/char/Makefile的真实示例。正如你所看到的,有些东西被硬件连接到内核(obj-y)但是那些obj-m去了哪里?
那些熟悉shell脚本的人很容易发现它们。
对于那些没有的,你看到的obj - $(CONFIG_FOO)条目会扩展为obj-y或obj-m,具体取决于CONFIG_FOO变量是否已设置为y或m。
虽然我们在这里,但那些正是你在linux/.config文件中设置的那种变量,最后一次你说make menuconfig 或类似的东西。
1.7 更多基础示例链接
1.7.1 将命令行参数传递给模块
http://www.tldp.org/LDP/lkmpg/2.6/html/x323.html1.7.2 跨越多个文件的模块
http://www.tldp.org/LDP/lkmpg/2.6/html/x351.html1.7.3 构建预编译内核的模块
http://www.tldp.org/LDP/lkmpg/2.6/html/x380.html1.7.4 与应用程序交互
https://www.oschina.net/translate/writing-a-simple-linux-kernel-module2 make工具
2.1 DESCRIPTION
make实用程序将自动确定需要重新编译大型程序的哪些部分,并发出命令以重新编译它们。我们的示例显示了C程序,因为它们非常常见,但您可以使用make与任何编译语言,其编译器可以使用shell命令运行。
实际上,make并不仅限于程序。
您可以使用它来描述任何一些任务,其中某些文件必须在其他文件更改时自动从其他文件更新。
要准备使用make,您必须编写一个名为makefile的文件,该文件描述程序中文件之间的关系,以及用于更新每个文件的命令的状态。
在程序中,通常从目标文件更新可执行文件,而目标文件又通过编译源文件来完成。
一旦存在合适的makefile,每次更改一些源文件时,这个简单的shell命令:
make
足以执行所有必要的重新编译。
make程序使用makefile描述和文件的最后修改时间来决定需要更新哪些文件。
对于每个文件,它会发出makefile中记录的命令。
make执行makefile中的命令以更新一个或多个目标名称,其中name通常是程序。
如果不存在-f选项,make将按顺序查找 makefiles GNUmakefile, makefile, and Makefile, in that order.
(我们建议使用Makefile,因为它突出显示在目录列表的开头附近,紧邻其他重要文件,如README。)
检查的第一个名称,建议不要将GNUmakefile用于大多数makefile。
如果您具有特定于GNU make的makefile,则应使用此名称,并且其他版本的make不会理解该名称。
如果makefile为‘ - ‘,则读取标准输入。
2.2 退出状态
如果所有的makefile都被成功解析并且没有构建的目标失败,则GNU make退出状态为零。如果使用-q标志并且make确定需要重建目标,则将返回状态1。
如果遇到任何错误,将返回状态2。
2.3 选项
-b, -m | Ignored for compatibility. | 忽略兼容性. |
-B, --always-make | Unconditionally make all targets. | 无条件地制定所有目标. |
-C DIRECTORY, --directory=DIRECTORY | Change to DIRECTORY before doing anything. | 在做任何事之前改为DIRECTORY. |
-d | Print lots of debugging information. | 打印大量调试信息. |
--debug[=FLAGS] | Print various types of debugging information. | FLAGS可以用于所有调试输出(与使用-d相同), b用于基本调试,v用于更详细的基本调试,i用于显示隐式规则, j用于调用命令的详细信息,m用于在重新生成makefile时进行调试. 使用n禁用所有先前的调试标志. |
-e, --environment-overrides | Environment variables override makefiles. | 环境变量覆盖makefile. |
--eval=STRING | Evaluate STRING as a makefile statement. | 将STRING评估为makefile语句. |
-f FILE, --file=FILE, --makefile=FILE | Read FILE as a makefile. | 将FILE作为makefile读取. |
-h, --help | Print this message and exit. | 打印此消息并退出. |
-i, --ignore-errors | Ignore errors from recipes. | 忽略为重制文件而执行的命令中的所有错误. |
-I DIRECTORY, --include-dir=DIRECTORY | Search DIRECTORY for included makefiles. | 搜索DIRECTORY以获取包含的makefile. |
-j [N], --jobs[=N] | Allow N jobs at once; infinite jobs with no arg. | 一次允许N个工作;没有arg的无限工作. |
-k, --keep-going | Keep going when some targets can‘t be made. | 当一些目标无法制作时继续前进. |
-l [N], --load-average[=N],--max-load[=N] | Don‘t start multiple jobs unless load is below N. | 除非负载低于N,否则不要启动多个作业. |
-L, --check-symlink-times | Use the latest mtime between symlinks and target. | 使用符号链接和目标之间的最新mtime. |
-n, --just-print, --dry-run, --recon | Don‘t actually run any recipe; just print them. | 实际上不要运行任何配方;只需打印它们. |
-o FILE, --old-file=FILE, --assume-old=FILE | Consider FILE to be very old and don‘t remake it. | 即使FILE很老,也不要重新生成它. |
-O[TYPE], --output-sync[=TYPE] | Synchronize output of parallel jobs by TYPE. | 按TYPE同步并行作业的输出. 当与-j并行运行多个作业时,请确保将每个作业的输出收集在一起,而不是穿插其他作业的输出. 如果未指定type或是target,则将每个目标的整个配方的输出组合在一起. 如果type为line,则配方中每个命令行的输出将组合在一起. 如果type是recurse,则整个递归make的输出被组合在一起. 如果type为none,则禁用输出同步. |
-p, --print-data-base | Print make‘s internal database. | 打印通过读取makefile产生的数据库(规则和变量值); 然后像往常一样或以其他方式指定执行. 要打印数据库而不尝试重新创建任何文件,请使用: make -p -f/dev/null. |
-q, --question | Run no recipe; exit status says if up to date. | ``问题模式‘‘.不要运行任何命令,也不要打印任何东西;如果指定的目标已经是最新的,则返回退出状态为零,否则返回非零值. |
-r, --no-builtin-rules | Disable the built-in implicit rules. | 禁用内置隐式规则. |
-R, --no-builtin-variables | Disable the built-in variable settings. | 禁用内置变量设置. |
-s, --silent, --quiet | Don‘t echo recipes. | 无声操作;不要在执行时打印命令. |
-S, --no-keep-going, --stop | Turns off -k. | 取消-k选项的效果.这是永远不必要的, 除了在递归make中,-k可能通过MAKEFLAGS从顶级make继承, 或者如果你在环境中的MAKEFLAGS中设置-k. |
-t, --touch | Touch targets instead of remaking them. | 触摸目标而不是重新制作它们. 这用于假装命令已完成,以欺骗未来的make调用. |
--trace | Print tracing information. | 打印跟踪信息. |
-v, --version | Print the version number of make and exit. | 打印make和exit的版本号. |
-w, --print-directory | Print the current directory. | 打印当前目录. |
--no-print-directory | Turn off -w, even if it was turned on implicitly. | 关闭-w,即使它是隐式打开的. |
-W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE | Consider FILE to be infinitely new. | 认为FILE是无限新的. 没有-n,它几乎与在运行make之前在给定文件上运行touch命令相同,只是修改时间仅在make的想象中改变. |
--warn-undefined-variables | Warn when an undefined variable is referenced. | 引用未定义的变量时发出警告. |
3. 其他内核编译方法及相关连接
3.1 编译Arch的内核模块
https://wiki.archlinux.org/index.php/Compile_kernel_module首先,您需要安装构建依赖项,例如compiler(base-devel)和linux-headers。
3.2 Arch构建系统
https://wiki.archlinux.org/index.php/Kernel/Arch_Build_Systemhttps://www.kernel.org/doc/Documentation/kbuild/kconfig.txt
内核/Arch构建系统
该拱门构建系统可以用来构建基于官方的自定义内核的Linux软件包。
这种编译方法可以自动化整个过程,并且基于经过良好测试的软件包。
您可以编辑PKGBUILD以使用自定义内核配置或添加其他修补程序。
$ pacman -Ss asp
extra/asp 5-1 Arch Linux build source file management tool
安装的ASP封装和基devel的包组。
您需要一个干净的内核来开始自定义。通过运行以下命令从ABS获取最新的内核包文件到您的构建目录:
$ asp update linux
$ asp checkout linux
然后,从各自的源获取您需要的任何其他文件(例如,自定义配置文件,修补程序等)。
3.3 内核/传统编译
https://wiki.archlinux.org/index.php/Kernel/Traditional_compilation#Download_the_kernel_source安装核心包
安装base-devel软件包组,其中包含必要的软件包,例如make和gcc。
还建议安装以下软件包,如默认的Arch内核PKGBUILD中所列:xmlto,kmod,inetutils,bc,libelf,git
3.4 修补包
https://wiki.archlinux.org/index.php/Patching_packages#Applying_patches本文介绍如何创建以及如何在Arch Build System(ABS)中将补丁应用于包。
一个补丁描述了一组针对一个或多个文件线路的变化。补丁通常用于自动更改源代码。
3.5 模块与程序
http://www.tldp.org/LDP/lkmpg/2.6/html/x427.html程序 模块
用户空间 内核空间
printf() printk()
main()开始 init_module或 通过module_init调用指定的函数 开始
cleanup_module或 使用module_exit调用指定的函数 结束