编写一个内核模块

目录

Kernel headers

内核模块的头文件所在路径

Module macros

Entry and exit points

Return values

The __init and __exit keywords

模块参数

 步入内核的第一步,通过插入模块编写内核代码

// /root/test.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

MODULE_AUTHOR("<insert your name here>");
MODULE_DESCRIPTION("LLKD book:ch4/helloworld_lkm: hello, world, our first LKM");
MODULE_LICENSE("Dual MIT/GPL");
MODULE_VERSION("0.1");

static int __init helloworld_lkm_init(void)
{
    printk(KERN_INFO "Hello, world\n");
    return 0; /* success */
}

static void __exit helloworld_lkm_exit(void)
{
    printk(KERN_INFO "Goodbye, world\n");
}

module_init(helloworld_lkm_init);
module_exit(helloworld_lkm_exit);

编写Makefile

[root@ecs-3370 ~]# cat Makefile 
PWD := $(shell pwd)
obj-m += test.o
 
# Enable the pr_debug() as well (rm the comment from the line below)
#EXTRA_CFLAGS += -DDEBUG
#CFLAGS_printk_loglvl.o := -DDEBUG
 
all:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
install:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules_install
clean:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean

Makefile中的内容详情请参考内核模块Makefile文件详解_巭犇的博客-CSDN博客

Kernel headers

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

我们对一些头文件使用#include。与用户空间“C”应用程序的开发不同,这些是内核头文件,

[root@ecs-3370 ~]# ll /lib/modules/3.10.0-1062.el7.x86_64/
total 3304
lrwxrwxrwx.  1 root root     39 Aug 23 16:45 build -> /usr/src/kernels/3.10.0-1062.el7.x86_64
drwxr-xr-x.  2 root root   4096 Aug  8  2019 extra
drwxr-xr-x. 12 root root   4096 Aug 23 16:45 kernel
-rw-r--r--   1 root root 852950 Sep 14 09:57 modules.alias
-rw-r--r--   1 root root 813960 Sep 14 09:57 modules.alias.bin
-rw-r--r--.  1 root root   1333 Aug  8  2019 modules.block
-rw-r--r--.  1 root root   7357 Aug  8  2019 modules.builtin
-rw-r--r--   1 root root   9425 Sep 14 09:57 modules.builtin.bin
-rw-r--r--   1 root root 272311 Sep 14 09:57 modules.dep
-rw-r--r--   1 root root 380741 Sep 14 09:57 modules.dep.bin
-rw-r--r--   1 root root    361 Sep 14 09:57 modules.devname
-rw-r--r--.  1 root root    140 Aug  8  2019 modules.drm
-rw-r--r--.  1 root root     69 Aug  8  2019 modules.modesetting
-rw-r--r--.  1 root root   1787 Aug  8  2019 modules.networking
-rw-r--r--.  1 root root  97132 Aug  8  2019 modules.order
-rw-r--r--   1 root root    569 Sep 14 09:57 modules.softdep
-rw-r--r--   1 root root 399062 Sep 14 09:57 modules.symbols
-rw-r--r--   1 root root 488237 Sep 14 09:57 modules.symbols.bin
lrwxrwxrwx.  1 root root      5 Aug 23 16:45 source -> build
drwxr-xr-x.  2 root root   4096 Aug  8  2019 updates
drwxr-xr-x.  2 root root   4096 Aug 23 16:45 vdso
drwxr-xr-x.  2 root root   4096 Aug  8  2019 weak-updates

请注意名为build的符号连接或软链接。它指向本系统上内核的位置,正如您将看到的,我们将把这些信息提供给用于构建内核模块的Makefile。(此外,有一些系统有一个类似的软链接,称为source)。

[root@ecs-3370 ~]# ll /usr/src/kernels/3.10.0-1062.el7.x86_64
total 4772
drwxr-xr-x.  32 root root    4096 Aug 23 16:45 arch
drwxr-xr-x.   3 root root    4096 Aug 23 16:45 block
drwxr-xr-x.   4 root root    4096 Aug 23 16:45 crypto
drwxr-xr-x. 119 root root    4096 Aug 23 16:45 drivers
drwxr-xr-x.   2 root root    4096 Aug 23 16:45 firmware
drwxr-xr-x.  75 root root    4096 Aug 23 16:45 fs
drwxr-xr-x.  28 root root    4096 Aug 23 16:46 include
drwxr-xr-x.   2 root root    4096 Aug 23 16:46 init
drwxr-xr-x.   2 root root    4096 Aug 23 16:46 ipc
-rw-r--r--.   1 root root     505 Aug  8  2019 Kconfig
drwxr-xr-x.  13 root root    4096 Aug 23 16:46 kernel
drwxr-xr-x.  10 root root    4096 Aug 23 16:46 lib
-rw-r--r--.   1 root root   51289 Aug  8  2019 Makefile
-rw-r--r--.   1 root root    2305 Aug  8  2019 Makefile.qlock
drwxr-xr-x.   2 root root    4096 Aug 23 16:46 mm
-rw-r--r--.   1 root root 1140292 Aug  8  2019 Module.symvers
drwxr-xr-x.  61 root root    4096 Aug 23 16:46 net
drwxr-xr-x.  15 root root    4096 Aug 23 16:46 samples
drwxr-xr-x.  13 root root    4096 Aug 23 16:46 scripts
drwxr-xr-x.   9 root root    4096 Aug 23 16:46 security
drwxr-xr-x.  24 root root    4096 Aug 23 16:46 sound
-rw-r--r--.   1 root root 3594971 Aug  8  2019 System.map
drwxr-xr-x.  20 root root    4096 Aug 23 16:46 tools
drwxr-xr-x.   2 root root    4096 Aug 23 16:46 usr
drwxr-xr-x.   4 root root    4096 Aug 23 16:46 virt
-rw-r--r--.   1 root root      41 Aug  8  2019 vmlinux.id

内核模块的头文件所在路径

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

 编译器通过在/lib/modules/$(uname -r)/build/include/下查找以上的头文件init.h、kerne.h、module.h(实际就是内核源码目录下的include目录)。

Module macros

  • MODULE_AUTHOR(): Specifies the author(s) of the kernel module
  • MODULE_DESCRIPTION(): Briefly describes the function of this LKM
  • MODULE_LICENSE(): Specifies the license(s) under which this kernel module is released
  • MODULE_VERSION(): Specifies the (local) version of the kernel module

在没有源代码的情况下通过modinfo指令就可以读模块的这些属性信息

Entry and exit points

别忘了,内核模块毕竟是以内核特权运行的内核代码。它不是一个应用程序,因此没有像我们熟悉的main()函数那样的入口点(我们非常熟悉并且喜欢它)。当然,这引出了一个问题:内核模块的入口和出口是什么?请注意,在简单内核模块的底部,有以下几行:

module_init(helloworld_lkm_init);
module_exit(helloworld_lkm_exit);

module_[init|exit]()代码是分别指定入口点和出口点的宏。每个函数的参数都是一个函数指针。使用现代C编译器,我们可以只指定函数的名称。因此,在我们的代码中,以下内容适用:

  • The helloworld_lkm_init() function is the entry point.
  • The helloworld_lkm_exit() function is the exit point.

Return values

Notice the signature of the init and exit functions is as follows:

static int __init <modulename>_init(void);
static void __exit <modulename>_exit(void);

As a good coding practice, we have used the naming format for the functions as __[init|exit](), where is replaced with the name of the kernel module. You will realize that this naming convention is just that - it's merely a convention that is, technically speaking, unnecessary, but it is intuitive and thus helpful. Clearly, neither routine receives any parameter.

作为一种良好的编码实践,我们将函数的命名格式用作__[init|exit](),其中替换为内核模块的名称。您将认识到,这个命名约定只是一种,从技术上讲,它只是一种不必要的约定,但它是直观的,因此很有帮助。显然,两个例程都没有收到任何参数。

Marking both functions with the static qualifier implies that they are private to this kernel module. That is what we want.

static标记这两个函数意味着它们是这个内核模块的私有函数。初始化函数应当声明成静态的, 因为它们不会在特定文件之外可见; 没有硬性规定这个, 然而, 因为没有函数能输出给内核其他部分, 除非明确请求。

The __init and __exit keywords

A niggling leftover: what exactly are the __init and __exit macros we see within the preceding function signatures? These are merely memory optimization attributes inserted by the linker.

一个小问题:我们在前面的函数签名中看到的__init和__exit宏到底是什么?这些仅仅是链接器插入的内存优化属性。

The __init macro defines an init.text section for code. Similarly, any data declared with the __initdata attribute goes into an init.data section. The whole point here is the code and data in the init function is used exactly once during initialization. Once it's invoked, it will never be called again; so, once called, it is then freed up (via free_initmem()).

__init宏定义init.text代码部分类似地,使用__initdata属性声明的任何数据都会进入init。数据段。这里的重点是初始化过程中只使用一次init函数中的代码和数据。一旦它被调用,就再也不会被调用;因此,一旦被调用,它就会被释放(通过freeinitmem()),声明中的 __init 标志可能 看起来有点怪; 它是一个给内核的暗示, 给定的函数只是在初始化使用. 模块加载者在模 块加载后会丢掉这个初始化函数, 使它的内存可做其他用途。

The deal is similar with the __exit macro, though, of course, this only makes sense with kernel modules. Once the cleanup function is called, all the memory is freed. If the code were instead part of the static kernel image (or if module support were disabled), this macro would have no effect.

该协议与__exit宏类似,当然,这只对内核模块有意义。一旦调用了cleanup函数,就会释放所有内存。如果代码是静态内核映像的一部分(或者禁用了模块支持),则此宏将不起作用。

Fine, but so far, we have still not explained some practicalities: how exactly can you get the kernel module object into kernel memory, have it execute, and then unload it, plus several other operations you might wish to perform. Let's discuss these in the following section.

很好,但到目前为止,我们还没有解释一些实用性:如何确切地将内核模块对象放入内核内存,执行它,然后卸载它,以及您可能希望执行的其他几个操作。让我们在下一节讨论这些问题。

moudle_init 是强制的. 这个宏定义增加了特别的段到模块目标代码中, 表明在哪里 找到模块的初始化函数. 没有这个定义, 你的初始化函数不会被调用

moudle_exit 声明对于使得内核能够找到你的清理函数是必要的. 如果你的模块没有定义一个清理函数, 内核不会允许它被卸载.

模块参数

[root@ecs-3370 tmp]# insmod test.ko howmany=10 whom="mom"
。。。
#include <linux/moduleparam.h>

static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO); 

。。。
    printk(KERN_INFO "howmainy is %i",howmany);
。。。

参数用 moudle_param 宏 定义来声明, 它定义在 moduleparam.h. module_param 使用了 3 个参数: 变量名, 它的 类型, 以及一个权限掩码用来做一个辅助的 sysfs 入口. 这个宏定义应当放在任何函数之 外, 典型地是出现在源文件的前面.

模块参数支持许多类型:

bool invbool charp  int long short uint ulong ushort...

最后的 module_param 字段是一个权限值; 你应当使用 中定义的值. 这 个值控制谁可以存取这些模块参数在 sysfs 中的表示. 如果 perm 被设为 0, 就根本没有 sysfs 项. 否则, 它出现在 /sys/module下面, 带有给定的权限. 使用 S_IRUGO 作为 参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR 允许 root 来改变参数. 注意, 如果一个参数被 sysfs 修改, 你的模块看到的参数值也改变了, 但是你的模块没有任何其 他的通知. 你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.

本文翻译自

《Linux-Kernel-Programming-Kaiwan-N-Billimoria》

《linux-device-driver-development》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值