《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #3 如何编写内核模块

简介: 本节书摘来自华章出版社《Linux内核精髓:精通Linux内核必会的75个绝技》一书中的第1章,第1.3节,作者 竹部 晶雄、平松 雅巳,更多章节内容可以访问云栖社区“华章计算机”公众号查看

HACK #3 如何编写内核模块

本节将介绍向Linux内核中动态添加功能的结构—内核模块的编写方法。
内核模块
Linux内核是单内核(monolithic kernel),也就是所有的内核功能都集成在一个内核空间内。但是内核具有模块功能,可以将磁盘驱动程序、文件系统等独立的内核功能制作成模块,并动态添加到内核空间或者删除。
内核模块是可以动态添加到Linux内核空间的二进制文件,文件扩展名为ko。
内核模块的编写方法大致有两种。一种是将内核源码树带有的功能编写为模块的方法(参考Hack #2),另一种是将内核源码树中所没有的特有功能编写为模块的方法。
通过内核配置编写模块
把内核源代码文件中CONFIG_*=m的项目所对应的驱动程序编写为模块。编写生成的模块一般安装在/lib/modules/内核版本/kernel下。
以RHEL6为例

# ls /lib/modules/2.6.32-71.29.1.el6.x86_64/kernel/
arch  crypto  drivers  fs  kernel  lib  mm  net  sound

编写特有的内核模块
下面将介绍如何编写内核源码树中所没有的特有内核模块。
以mymod模块为例说明,请将下面的代码以mymod.c为文件名保存。

#include <linux/module.h>
#include <linux/timer.h>
#include <linux/errno.h>

static int sec = 5;
module_param(sec, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(sec, "Set the interval.");

static void mymod_timer(unsigned long data);

static DEFINE_TIMER(timer, mymod_timer, 0, 0);

static void mymod_timer(unsigned long data)
{
    printk(KERN_INFO "mymod: timer\n");
    mod_timer(&timer, jiffies + sec * HZ);
}

static int mymod_init(void)
{
    printk(KERN_INFO "mymod: init\n");

    if (sec <= 0) {
        printk(KERN_INFO "Invalid interval sec=%d\n", sec);
        return -EINVAL;
    }

    mod_timer(&timer, jiffies + sec * HZ);

    return 0;
}

static void mymod_exit(void)
{
    del_timer(&timer);
    printk(KERN_INFO "mymod: exit\n");
}

module_init(mymod_init);
module_exit(mymod_exit);

MODULE_AUTHOR("Hiroshi Shimamoto");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My module");

在模块的源代码中包含(include)头文件linux/module.h。
名为module_int()和module_exit()的宏,可以调用回调(callback)函数来进行初始化和终止模块的处理。在模块的源文件中进行如下描述,就可以在添加模块时调用初始化函数,在删除模块时调用终止函数。
module_init(初始化函数名);
module_exit(终止函数名);
在这个例子模块的情形下调用的分别是mymod_init()和mymod_exit()。
初始化函数为了表示初始化已正常完成,需要返回0。按照Linux内核中的写法,发生错误(error)时将返回一个值为负数的错误代码。在这个例子中,如果设定值出错,则处理为-EINVAL(非法值)。
下面先用3个宏对模块进行定义,但在模块编写中并不是必需的。
image

这个例子模块还用到了模块参数。模块参数可以使用module_param()宏来生成。
module_param(参数名,参数类型,权限(permission));
在例子模块中,sec定义为int类型的模块参数。
另外,还可以使用MODULE_PARM_DESC()宏来对模块参数进行说明。
先简单介绍一下这个例子的运行过程。当添加模块时,会调用指定为初始化函数的mymod_init()。在mymod_init()中首先通过printk()输出:
mymod: init
然后确认模块参数sec是否正常。在模块参数sec的值为0以下的异常情形时,会返回EINVAL错误代码并终止程序。在判断模块参数sec正常后,将内核计时器设置为sec秒后启动超时(timeout)函数mymod_timer()。在每隔sec秒启动的mymod_timer()中,首先使用printk()输出:

mymod: timer

再次设置sec秒的内核计时器,然后终止。当删除模块时,会调用mymod_exit()函数,删除内核计时器,通过printk()输出:

mymod: exit

于是模块终止。
接下来需要准备编写模块所需的Makefile。由于是使用内核的创建框架来生成,因此Makefile的内容非常简单。

obj-m :=mymod.o

最后执行下列make命令,通过当前目录(current directory)的源代码和Makefile生成模块mymod.ko。

# make -C /lib/modules/'uname 杛'/build M='pwd'

通过使用modinfo命令,可以看到所生成模块mymod.ko的信息。从这里可以看到使用MODULE_*宏所指定的内容。

# modinfo mymod.ko
filename:       mymod.ko
description:    My module
license:        GPL
author:         Hiroshi Shimamoto
srcversion:     61A3BB7CFC0C89B8344F5A5
depends:        
vermagic:       2.6.32-71.29.1.el6.x86_64 SMP mod_unload modversions 
parm:           sec:Set the interval. (int)

添加内核模块
添加内核模块需要用到insmod命令或modprobe命令。
通过执行insmod命令把生成的mymod.ko模块添加进来。

# insmod mymod.ko

使用dmesg命令,可以看到例子模块mymod.ko的输出内容。

# dmesg | tail
       :
mymod: init

作为模块初始化函数mymod_init()所调用的printk()的输出内容会在最后一行显示。
使用lsmod可以显示目前添加到内核中的模块列表。

# lsmod
Module                Size   Used by
mymod                 1482   0
   :

可以看到,mymod行存在,模块已添加。
要将已添加的模块从内核空间删除时,可以使用rmmod命令。

# rmmod mymod

执行rmmod命令后,模块将从内核空间内删除,使用lsmod命令就不会再输出mymod行。
此外,使用dmesg命令还可以看到终止模块的处理中printk()输出的信息mymod: exit。

# dmesg | tail
       :
mymod: exit

下面针对模块参数作一些介绍。在添加模块后,就会在/sys/module下生成对应的目录和文件。

# ls /sys/module/mymod/

holders initstate notes parameters refcnt sections srcversion
可以确认在parameters下生成的模块mymod中所定义的参数sec。

# ls -l /sys/module/mymod/parameters/sec

-rw-r--r--. 1 root root 4096 May 15 06:34 /sys/module/mymod/parameters/sec
其内容应当是初始值5。

# cat /sys/module/mymod/parameters/sec

5
模块参数可以在使用insmod添加模块时对值进行指定。

# insmod mymod.ko sec=10

进行上述操作后,添加mymod.ko时模块参数sec就为10,默认间隔5秒的超时变成间隔10秒。
小结
本节介绍了内核模块的编写方法。编写特有内核模块是Kernel构建的入门级操作,你也可以尝试一下。
参考文献
Documentation/kbuild/modules.txt
—Hiroshi Shimamoto

相关文章
|
16天前
|
Linux C语言
Linux内核队列queue.h
Linux内核队列queue.h
|
3天前
|
Linux
Linux(23) Linux 4G模块不能获取IP排查思路
Linux(23) Linux 4G模块不能获取IP排查思路
11 0
|
9天前
|
算法 Linux 调度
深入理解Linux内核的进程调度机制
【4月更文挑战第17天】在多任务操作系统中,进程调度是核心功能之一,它决定了处理机资源的分配。本文旨在剖析Linux操作系统内核的进程调度机制,详细讨论其调度策略、调度算法及实现原理,并探讨了其对系统性能的影响。通过分析CFS(完全公平调度器)和实时调度策略,揭示了Linux如何在保证响应速度与公平性之间取得平衡。文章还将评估最新的调度技术趋势,如容器化和云计算环境下的调度优化。
|
14天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
20 3
|
21天前
|
负载均衡 算法 Linux
深度解析:Linux内核调度器的演变与优化策略
【4月更文挑战第5天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分——内核调度器。文章将首先回顾Linux内核调度器的发展历程,从早期的简单轮转调度(Round Robin)到现代的完全公平调度器(Completely Fair Scheduler, CFS)。接着,分析当前CFS面临的挑战以及社区提出的各种优化方案,最后提出未来可能的发展趋势和研究方向。通过本文,读者将对Linux调度器的原理、实现及其优化有一个全面的认识。
|
21天前
|
Ubuntu Linux
Linux查看内核版本
在Linux系统中查看内核版本有多种方法:1) 使用`uname -r`命令直接显示版本号;2) 通过`cat /proc/version`查看内核详细信息;3) 利用`dmesg | grep Linux`显示内核版本行;4) 如果支持,使用`lsb_release -a`查看发行版及内核版本。
36 6
|
24天前
|
Linux 内存技术
Linux内核读取spi-nor flash sn
Linux内核读取spi-nor flash sn
18 1
|
28天前
|
应用服务中间件 Linux PHP
Linux下安装php环境并且配置Nginx支持php-fpm模块
Linux下安装php环境并且配置Nginx支持php-fpm模块
29 0
|
5天前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。
|
2天前
|
监控 Linux Windows
50个必知的Linux命令技巧,你都掌握了吗?(下)
50个必知的Linux命令技巧,你都掌握了吗?(下)