module_init函数的功能,为什么module_init可以传入函数参数,这个函数为什么是static int __init 内核模块编译

这与内核模块开发有关

内核模块开发

Linux 内核管理着计算机的所有资源,为上层应用程序提供统一的编程接口( 系统调用 ),屏蔽底层设备的差异。由于不同用户物理设备以及对内核功能需求的差异性,需要一种类似插件的运行机制——编写插件实现所需功能,然后嵌入到内核中,与内核形成一个整体。

Linux内核模块 便是这样的插件。作为 Linux 内核的扩展手段,可以在运行时动态加载和卸载。那么,一个内核模块是怎么编写的呢?与普通程序编写有什么区别呢?

程序开发经常以 hello world 程序入门,因为这是最简单的。本文也通过一个最简单的内核模块,演示如何一步步编写一个内核模块。

结构

内核模块与普通程序不同,没有执行流。可以这样理解,内核模块实现一些函数,作为回调函数注册到内核中。在内核加载/卸载时,或者其他应用程序调用系统调用时,注册的回调函数才得到调用。

有两个最基本的回调函数 init 和 exit ,分别由 module_init 和 module_exit 登记,分别在模块加载和卸载的时候执行。下面实现的 hello world 内核模块,将只实现这两个最基本的函数。

内核模块支持 参数 ,用户借此控制内核模块的行为。参数有默认值,可以在加载时指定,也可以在通过 proc 伪文件系统等手段动态修改。

此外,内核模块还需要带一些描述信息,包括 许可证 、 作者 、 描述 以及 版本 等等。描述信息由 MODULE 系列宏指定, _modinfo 等命令可以展示这些信息。

编码

开干了!新建一个文件, hello.c 用于实现一个玩具型内核模块—— hello 模块。

hello 模块只实现 init 和 exit 函数,分别在加载和卸载时往内核日志输出一条记录。同时,也演示了参数的使用方式——通过参数控制日志输出内容。

/**
 * FileName:   hello.c
 * Author:     Chen Yanfei
 * @contact:   fasionchan@gmail.com
 * @version:   $Id$
 *
 * Description:
 *
 * Changelog:
 *
 **/
// 引入相关内核头文件
#include <linux/module.h>
// 内核模块信息,包括许可证、作者、描述和版本等
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Fasion Chan");
MODULE_DESCRIPTION("An hello worlk module for demonstration");
MODULE_VERSION("1.0");
// 内核模块参数,加载时指定或者动态指定,以此控制模块行为
static char *name = "world";
module_param(name, charp, S_IRUGO);
MODULE_PARM_DESC(name, "Whom this module say hello to");
// 初始化函数,在加载时调用,分配资源准备执行环境
// 这里只是往内核日志输出一行记录
static int __init hello_init(void)
{
        printk(KERN_INFO "HELLO: Hello %s, this is hello module speaking\n", name);
        return 0;
}
// 清理函数,在卸载时调用,回收资源销毁执行环境
static void __exit hello_exit(void)
{
        printk(KERN_INFO "HELLO: Goodbye %s", name);
}
// 登记初始化函数及清理函数
module_init(hello_init);
module_exit(hello_exit);
编译

内核模块代码已经写好了,怎样知道写得对不对呢?编译运行一下不就知道了?可问题是如何编译呢?跟普通程序一样吗?用 gcc 来编译?

没错,内核模块用 gcc 来编译,但不会直接运行 gcc 。我们需要先准备一个 Makefile ,内容如下:

obj-m+=hello.o
all:
        make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules

好了之后,我们再命令行下运行 make 即可:

$ ls
hello.c  Makefile
$ make
make -C /lib/modules/3.16.0-4-amd64/build/ M=/home/fasion/hello modules
make[1]: Entering directory '/usr/src/linux-headers-3.16.0-4-amd64'
make[1]: Entering directory `/usr/src/linux-headers-3.16.0-4-amd64'
  CC [M]  /home/fasion/hello/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/fasion/hello/hello.mod.o
  LD [M]  /home/fasion/hello/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-3.16.0-4-amd64'
$ ls
hello.c  hello.ko  hello.mod.c  hello.mod.o  hello.o  Makefile  modules.order  Module.symvers

看到 hello.ko 文件生成,意味着编译大功告成了!该文件就是二进制内核模块目标文件,其他文件的作用不再深入讨论。

顺便提一句, Makefile 中分成 all 和 clean 两节。其中 all 用来编译, clean 用来清理编译环境:

$ make clean
make -C /lib/modules/3.16.0-4-amd64/build/ M=/home/fasion/hello clean
make[1]: Entering directory '/usr/src/linux-headers-3.16.0-4-amd64'
make[1]: Entering directory `/usr/src/linux-headers-3.16.0-4-amd64'
  CLEAN   /home/fasion/hello/.tmp_versions
  CLEAN   /home/fasion/hello/Module.symvers
make[1]: Leaving directory '/usr/src/linux-headers-3.16.0-4-amd64'
$ ls
hello.c  Makefile

可以看到,运行了 make clean 后,所有编译生成的文件都清理掉了,只剩源码文件和 Makefile

运行

内核模块编译完成后,要怎么使用呢?跟普通程序直接运行不同,内核模块需要加载到内核里面发挥作用。使用 insmod 命令加载内核模块:

$ ls
hello.c  hello.ko  hello.mod.c  hello.mod.o  hello.o  Makefile  modules.order  Module.symvers
$ sudo insmod hello.ko
$ dmesg
[   14.226777] 00:00:00.000718 main     OS Product: Linux
[   14.226814] 00:00:00.000779 main     OS Release: 3.16.0-4-amd64
[   14.226845] 00:00:00.000814 main     OS Version: #1 SMP Debian 3.16.7-ckt20-1+deb8u4 (2016-02-29)
[   14.226873] 00:00:00.000844 main     OS Service Pack: #1 SMP Debian 3.16.7-ckt20-1+deb8u4 (2016-02-29)
[   14.226912] 00:00:00.000872 main     Executable: /opt/VBoxGuestAdditions-5.0.8/sbin/VBoxService
00:00:00.000873 main     Process ID: 716
00:00:00.000874 main     Package type: LINUX_64BITS_GENERIC
[   14.232015] 00:00:00.005962 main     5.0.8 r103449 started. Verbose level = 0
[ 4368.359895] HELLO: Hello world, this is hello module speaking

内核模块加载后,用 dmesg 命令看到,内核模块初始化时输出的一条内核日志。

加载内核模块时怎么指定参数的值呢?下面演示移除内核模块,并以 fasion 为 name 参数值重新挂载:

$ sudo rmmod hello
$ sudo insmod hello.ko name=fasion

操作完成后,用 dmesg 可以看到以下内核日志:

4368.359895] HELLO: Hello world, this is hello module speaking
[ 4812.067761] HELLO: Goodbye world
[ 4841.011892] HELLO: Hello fasion, this is hello module speaking

第二次挂载时, name 参数的值不是默认值 world ,而是 fasion 了。

跟内核模块相关还有 insmod 、 rmmod 、 lsmod 、 modinfo 、 modprobe 等命令,用法请参考man文档。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值