如何在 Linux 中使用可加载内核模块?

可加载内核模块是可以根据需要加载和卸载到内核中的代码片段。 它们提供了一种无需重建或重新启动系统即可扩展内核功能的方法。 在本文中,让我们更好地了解这些内核模块。

目录

什么是可加载内核模块?

可加载内核模块 (LKM) 是一段可以根据需要加载和卸载到内核中的代码。 LKM 通常用于添加对新硬件(作为设备驱动程序)或文件系统的支持,或者用于添加系统调用。

在 Linux 中,术语“模块”是指可加载的内核对象。 模块可以静态编译到内核中,也可以在运行时动态加载。 动态加载通常是首选,因为它允许更大的灵活性(可以根据需要加载和卸载模块)并且更容易调试内核(如果模块导致问题,可以简单地删除它而无需重新编译整个内核)。

可加载内核模块的概念最初是在 Unix System V Release 4 (SVR4) 中引入的,它们主要用于设备驱动程序。 在 Linux 中,这个想法被采纳和扩展,LKM 现在用于各种用途。

使用可加载内核模块的优势

使用 LKM 有几个优点:

  • 它们允许代码重用——如果多个设备需要支持相同的驱动程序或文件系统,则可以将该代码放在一个模块中,然后所有这些设备都可以使用该模块。 这减少了代码重复和混乱。
  • 它们可以在不需要重新编译内核的情况下添加或删除功能——这使得调试变得更加容易,因为可以简单地删除有问题的模块,而不是每次需要更改或修复某些东西时都必须重新编译一个全新的内核。
  • 它们提高了安全性——由于模块不是主内核映像的一部分,因此如果受到损害,它们就不能直接攻击系统的其他部分。 这种隔离可以保护系统的关键部分免受次要组件中的漏洞的攻击。

如何使用可加载内核模块?

如上所述,LKM 可用于多种用途,例如设备驱动程序、文件系统、系统调用、网络驱动程序、TTY 线路指令、有用的解释器等。下面列出了一些常见用途:

设备驱动程序

LKM 最常见的用途之一是编写设备驱动程序。 设备驱动程序是一种软件,它允许您的操作系统与硬件设备(如打印机或显卡)进行通信。 如果没有设备驱动程序,您的计算机将无法正确(或根本无法)使用这些设备。

文件系统驱动程序

LKM 的另一个常见用途是编写文件系统驱动程序。 文件系统是您的操作系统用来在磁盘上存储文件的工具(例如,ext3/4),并且每种类型的文件系统都有自己的驱动程序,它告诉操作系统如何在该特定类型的文件系统上读取/写入数据。

系统调用

系统调用是用户空间程序与内核交互的方式; 它们为程序从内核请求服务(例如读/写文件或创建进程)提供了一种方式。 许多系统调用是通过 LKM 实现的; 添加新的系统调用通常需要创建一个新的 LKM。

网络驱动程序

网络驱动程序是 LKM 的另一个常见用途。 如今,大多数计算机都有某种形式的网络连接(以太网、WiFi 等),每种类型的网络都有自己的驱动程序来处理计算机和网络之间的通信。 添加新类型的网络通常需要创建新的 LKM。

TTY 线路说明

TTY(电传打字机)线路指令是用户空间程序与调制解调器或终端等串行设备交互的方式。 TTY 线路指令通常通过 LKM 实现。

有用的口译员

许多解释型语言(例如 Perl、Python 和 Ruby)可用于编写 LKM。 这对于编写需要直接与内核交互的系统管理实用程序或其他工具很有用。

编译内核模块

为了使用可加载的内核模块,它们必须首先被编译。 编译模块的过程类似于编译常规程序; 但是,有一些重要的区别。

首先,您需要一份内核源代码的副本。 这是因为模块需要知道它将与之交互的内核的内部结构; 如果不访问源代码,就无法编译工作模块。 幸运的是,大多数主要的 Linux 发行版都通过其软件包存储库提供内核源代码。

为了 example,在基于 Debian 的系统(例如 Ubuntu )上,您可以安装 linux-source 包; 在基于 Red Hat 的系统上(例如 Fedora ),您可以安装 kernel-devel 包。 一旦你安装了合适的源代码包,你会在 /usr/src 目录(在基于 Debian 的系统上)或 /usr/share/doc/kernel-$version(在基于 RedHat 的系统上)找到内核源代码,其中 $ version 是您当前内核的版本。

内核源

其次,您需要安装编译器工具链,以便您可以将代码编译成二进制目标文件 (.o)。 在基于 Debian 的系统上,这可以通过安装 build-essential 来完成; 在基于 Red Hat 的系统上,默认情况下应该已经安装了 GCC。 如果没有,可以使用 yum install GCC 安装。

在 Ubuntu 中安装 build-essential

设置好编译器工具链后,还应确保已安装; 这通常可以通过您的发行版的包存储库获得

apt install make

第四,一旦你的模块被获取并成功编译成一个没有依赖关系的目标文件,那么它就可以插入内核来查看它是否正常工作,以及它对内核空间和其他已经在其中运行的模块有什么影响甚至考虑将这个新模块之前的内核空间引入用户空间或应用程序空间。

将参数从用户空间传递到内核空间的内核模块示例

#include <linux/module.h> 
#include <linux/kernel.h> 
static int my_int = 0; 
module_param(my_int, int, S_IRUSR | S_IWUSR); 
MODULE_PARM_DESC(my_int, "An integer"); 
static char *mystring = "default"; 
module_param(mystring, charp, 0000); 
MODULE_PARM (mystring , “s”) ; 
/* same as above */ 
MODULE _ PARM _ DESC ( my string , “A character string” ); 
static int __init hello2 _ init ( void ) { printk("Hello world 2"); 
printk("My Int is %d and My String is %s", myInt, myString); 
return 0; } 

static void __exit hello2 _ exit ( void ) { printk("Goodbye world 2"); } 
module _ init ( hello2 _ init ); 
module _ exit ( hello2 _ exit ); 
MODULE NAME ("hello-lkm-params") ; 
AUTHOR ("Dee") ; 
DESCRIPTION ("This is a simple example to show how to pass parameters from userspace to kernel space by insmod or modprobe!"); 
MODULE_LICENSE("GPL");
  • 上面的代码展示了如何使用 insmod 或 modprobe 将参数从用户空间传递到内核空间。 my_int 和 mystring 变量被声明为全局变量。 module_param() 宏用于指定可以从用户空间传入的参数。 S_IRUSR 和 S_IWUSR 标志表明该参数可由用户空间读写。 MODULE _ PARM _ DESC 宏用于提供参数的描述以供 modinfo 使用。
  • hello2_init() 函数在模块加载时被调用,它会在内核日志中打印一条消息,表明模块已经加载。 它还打印 myInt 和 mystring 的值。
  • hello2_exit() 函数在模块卸载时调用,它会在内核日志中打印一条消息,指示模块已卸载。
  • module_init() 和 module_exit() 宏分别用于指定在加载和卸载模块时应该调用的函数。
  • MODULE_NAME 宏用于为模块提供名称。 MODULE_LICENSE 宏用于指定发布模块的许可证。

如何在 Linux 中使用可加载内核模块?

有两种方法可以将模块加载到 Linux 内核中:使用 insmod 或 modprobe。 insmod 用于手动加载单个模块,而 modprobe 可用于手动和自动加载/卸载多个模块。

要使用任一程序,您必须首先拥有一个已编译的模块(通常带有 .ko 后缀)。 为了 example,假设您有一个名为 my_module.ko 的模块。 要使用 insmod 加载它,您可以输入:

sudo insmod my_module.ko 

要卸载它,您可以输入:

sudo rmmod my_module 

如果你想在系统启动时自动加载你的模块,你可以在 /etc/modules 中为它添加一个条目:

my_module 

这将导致 modprobe 在系统启动时自动加载您的模块(或者当 modprobe 不带参数运行时)。 您还可以通过在模块名称后添加它们来指定应该传递给模块的选项,用空格分隔:

my_module option1=value1 option2=value2 ... 

结论

总之,可加载内核模块是一种功能强大且有用的工具,可用于多种用途。 它们允许代码重用、提高安全性并使调试更容易。 如果您正在开发需要直接与内核交互的软件,或者您正在编写设备驱动程序或文件系统驱动程序,那么您可能需要使用 LKM。