3399模块 linux,如何编写简单的linux内核模块

如何编写简单的linux内核模块

获取Ring-0权限

尽管Linux系统为应用程序提供了强大而丰富的API,但是有的时候,这些还远远不够。当我们需要与硬件进行交互,或者需要访问系统中特权信息的时候,应用API就爱莫能助了,这时我们必须借助内核模块。

Linux内核模块是一段编译好的二进制代码,可以直接插入到Linux内核空间中,在ring 0级别运行,该级别不仅仅是x86-64处理器的最低层的运行级别,同时也是安全限制最少的一个级别。由于这里的代码完全不受限制,所以能够以令人难以置信的速度飞速运行,同时,它们还可访问系统中的任意内容。

来到内核世界

编写一个Linux内核模块并不是一件容易的事情。在修改内核的时候,您将面临数据丢失和系统损坏的风险。对于常规Linux应用程序来说,系统为它们提供了相应的安全网作为保护,但是内核代码却完全不是这样:内核代码一旦出现故障,将会锁定整个系统。

更糟糕的是,内核代码中出现的问题可能不会马上显现出来。如果内核模块加载后,系统立即被锁定的话,这还算是“最佳情况”。随着向模块添加的代码越来越多,我们将面临引入死循环和内存泄漏的风险。如果你不小心犯了这样的错误,随着机器的继续运行,这些代码占用的内存会持续增长。那么,最终会导致重要的内存结构,甚至缓冲区都被覆盖掉。

对于内核模块来说,传统应用程序的大部分开发范式都不适用。除了加载和卸载模块外,我们还需编写代码来响应系统事件,因为这里的代码并非以串行的模式运行。对于内核开发来说,我们要编写的是供应用程序使用的API,而非应用程序本身。

除此之外,我们在内核空间也无法访问各种标准库。虽然内核提供了一些常用的函数,比如printk(用作printf的替代品)和kmalloc(作用与malloc类似),但是大部分情况下,都需要我们亲自跟设备打交道。此外,在卸载模块时,我们必须亲自完成相关的清理工作,因为这里没有提供垃圾收集功能。

先决条件

在开始编写内核模块之前,我们需要确保已经准备好了得心应手的工具。最重要的是,你需要有一台Linux机器。虽然任何Linux发行版都可以满足我们的要求,但是在本文中,我使用的是Ubuntu 16.04 LTS,所以,如果你使用了其他版本的话,在安装的过程中,可能需要稍微调整一下相关的安装命令。

其次,你还需要一台单独的物理机器或虚拟机。虽然我更喜欢在虚拟机上完成这些工作,但是读者完全可以根据自己的喜好来作出决定。我不建议使用您的工作主机,因为一旦出错,就很可能会发生数据丢失的情况。同时,我们在编写内核模块的过程中,一般至少会锁定机器许多次,这个是不用怀疑的。内核出乱子的时候,最近更新的代码很可能还在向缓冲区中写入内容,所以,这就可能导致源文件损坏。如果在虚拟机上进行测试的话,就能够消除这种风险。

最后,您至少需要对C语言有一些基本的了解。由于C++运行时对于内核来说占用的空间太多了,因此,编写C代码对于内核开发来说是非常重要的。此外,为了与硬件进行交互,了解一些汇编语言方面的知识也是非常有帮助的。安装开发环境

在Ubuntu上,我们需要运行下列命令:

apt-get install build-essential linux-headers-`uname -r`

上面的命令将安装必要的开发工具,以及这个示例内核模块所需的内核头文件。

对于下面的示例内核模块,我们假设读者是以普通用户身份运行的,而不是root用户,但是,要求读者拥有sudo权限。对于非root用户来说,sudo在加载内核模块时是必须的,尽管这样有些麻烦,但我们希望尽可能以root之外的身份来完成内核模块开发工作。

踏上征程

从现在开始,我们就要开始编写代码了。好了,让我们先准备好工作环境:mkdir~/ src /lkm_example

cd~/ src /lkm_example

你可以启动自己最喜欢的编辑器(对于我来说,就是VIM),创建文件lkm_example.c,并输入以下代码:#include

#include

#include

MODULE_LICENSE(“GPL”);

MODULE_AUTHOR(“RobertW.OliverII”);

MODULE_DESCRIPTION(“A simple exampleLinuxmodule.”);

MODULE_VERSION(“0.01”);

staticint__init lkm_example_init(void){

printk(KERN_INFO“Hello,World!\n”);

return0;

}

staticvoid__exit lkm_example_exit(void){

printk(KERN_INFO“Goodbye,World!\n”);

}

module_init(lkm_example_init);

module_exit(lkm_example_exit);

现在,我们已经做好了一个最简单的内核模块,接下来,我会对一些重点内容加以详细说明:

·“includes”用于包含Linux内核开发所需的头文件。

· 根据模块的许可证的不同,MODULE_LICENSE可以设置为不同的值。要查看许可证的完整列表,请运行:grep“MODULE_LICENSE”-B27/usr/src/linux-headers-`uname - r`/include/linux/module.h

· 我们将init(加载)和exit(卸载)函数定义为static类型,并让它返回一个int型数据。

· 注意,这里要使用printk函数,而不是printf函数。此外,printk与printf使用的参数也各不相同。例如,KERN_INFO(这是一个标志,用以声明相应的消息记录等级)在定义的时候并没有使用逗号。

· 在文件的最后部分,我们调用了module_init和module_exit函数,来告诉内核哪些是加载函数和卸载函数。这样的话,我们就能够给这些函数自由命名了。

当目前为止,我们仍然无法编译这个文件:我们还需要一个Makefile文件。有了它,这个简单的示例模块就算就绪了。请注意,make会严格区分空格和制表符,因此,在应该使用tab的地方千万不要使用空格。obj-m+=lkm_example.o

all:

make-C/lib/modules/$(shell uname-r)/build M=$(PWD)modules

clean:

make-C/lib/modules/$(shell uname-r)/build M=$(PWD)clean

如果我们运行“make”,正常情况下应该成功编译我们的模块。最后得到的文件是“lkm_example.ko”。如果在此过程中出现了错误消息的话,请检查示例源文件中的引号是否正确,并确保没有意外粘贴为UTF-8字符。

现在,可以将我们的模块插入内核空间进行测试了。为此,我们可以运行如下所示的命令:sudo insmod lkm_example.ko

如果一切顺利的话,屏幕上面是不会显示任何内容的。这是因为,printk函数不会将运行结果输出到控制台,相反,它会把运行结果输出到内核日志。为了查看内核模块的运行结果,我们需要运行下列命令:sudo dmesg

正常情况下,这里应该看到带有时间戳前缀的“Hello,World!”行。这意味着我们的内核模块已经加载,并成功向内核日志输出了相关的字符串。我们还可以通过下面的命令,来检查该模块是否仍然处于加载状态:lsmod|grep“lkm_example”

要删除该模块,请运行下列命令:sudo rmmod lkm_example

如果您再次运行dmesg,则会在日志中看到字符串“Goodbye, World!”。同时,您也可以再次使用lsmod来确认它是否已被卸载。

正如你所看到的那样,这个测试工作流程有点繁琐而乏味,为了实现自动化,我们可以在Makefile文件末尾添加下列内容:test:

sudo dmesg-C

sudo insmod lkm_example.ko

sudo rmmod lkm_example.ko

dmesg

然后,运行下列命令:make test

这样的话,要想测试模块并查看内核日志的输出的话,就不必专门来运行相应的命令了。

现在,我们已经打造好了一个五脏俱全,但是没有什么用处的内核模块!

ab7653affab982b574eb7acc55df2e04.gif原    文:嘶吼

作    者:fanyeee

来源: https://sdk.cn/news/7844

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值