内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的最大优点是效率高,因为所有的内容都集成在一起,但其缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。
一、 什么是模块
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。
二、 编写一个简单的模块
模块和内核都在内核空间运行,模块编程在一定意义上说就是内核编程。因为内核版本的每次变化,其中的某些函数名也会相应地发生变化,因此模块编程与内核版本密切相关。以下例子针对2.6内核
1.程序举例
hellomod.c
// hello world driver for Linux 2.6
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
/* 必要的头文件*/
static int __init lkp_init( void )
{
printk(“<1>Hello,World! from the kernel space…\n”);
return 0;
}
static void __exit lkp_cleanup( void )
{
printk(“<1>Goodbye, World! leaving kernel space…\n”);
}
module_init(lkp_init);
module_exit(lkp_cleanup);
MODULE_LICENSE(“GPL”);
.说明
第4行:
所有模块都要使用头文件module.h,此文件必须包含进来。
第5行:
头文件kernel.h包含了常用的内核函数。
第6行:
头文件init.h包含了宏_init和_exit,它们允许释放内核占用的内存。
建议浏览一下该文件中的代码和注释。
第9-12行:
这是模块的初始化函数,它必需包含诸如要编译的代码、初始化数据结构等内容。
第11行使用了printk()函数,该函数是由内核定义的,功能与C库中的printf()类似,
它把要打印的信息输出到终端或系统日志。字符串中的<1>是输出的级别,
表示立即在终端输出。
第15-18行:
这是模块的退出和清理函数。此处可以做所有终止该驱动程序时相关的清理工作。
第20行:
这是驱动程序初始化的入口点。对于内置模块,内核在引导时调用该入口点;
对于可加载模块则在该模块插入内核时才调用。
第21行:
对于可加载模块,内核在此处调用module_cleanup()函数,而对于内置的模块,
它什么都不做。
第22行:
提示可能没有GNU公共许可证。有几个宏是在2.4版的内核中才开发的(详情参见modules.h)。
函数module_init()和cleanup_exit()是模块编程中最基本也是必须的两个函数。
module_init()向内核注册模块所提供的新功能,
而cleanup_exit()注销由模块提供的所有功能。
模块编程属于内核编程,因此,除了对内核相关知识有所了解外,还需要了解与模块相关的知识。
1.应用程序与内核模块的比较
为了加深对内核模块的了解,表一给出应用程序与内核模块程序的比较。
表一 应用程序与内核模块程序的比较
|
C语言应用程序 |
内核模块程序 |
使用函数 |
Libc库 |
内核函数 |
运行空间 |
用户空间 |
内核空间 |
运行权限 |
普通用户 |
超级用户 |
入口函数 |
main() |
module_init() |
出口函数 |
exit() |
module_exit() |
编译 |
Gcc –c |
Makefile |
连接 |
Gcc |
insmod |
运行 |
直接运行 |
insmod |
调试 |
Gdb |
kdbug, kdb,kgdb等 |
从表一我们可以看出,内核模块程序不能调用libc库中的函数,它运行在内核空间,且只有超级用户可以对其运行。另外,模块程序必须通过module_init()和module-exit()函数来告诉内核“我来了”和“我走了”。
2.内核符号表(如果对以下第2~4点理解上有困难,可以越过)
如 前所述,Linux内核是一个整体结构,像一个圆球,而模块是插入到内核中的插件。尽管内核不是一个可安装模块,但为了方便起见,Linux把内核也看作 一个“母”模块。那么模块与模块之间如何进行交互呢,一种常用的方法就是共享变量和函数。但并不是模块中的每个变量和函数都能被共享,内核只把各个模块中 主要的变量和函数放在一个特定的区段,这些变量和函数就统称为符号。到低哪些符号可以被共享? Linux内核有自己的规定。对于内核这个特殊的母模块,在kernel/ksyms.c中定义了从中可以“移出”的符号,例如进程管理子系统可以“移出”的符号定义如下:
/* 进程管理 */
EXPORT_SYMBOL(do_mmap_pgoff);
EXPORT_SYMBOL(do_munmap);
EXPORT_SYMBOL(do_brk);
EXPORT_SYMBOL(exit_mm);
…
EXPORT_SYMBOL(schedule);
EXPORT_SYMBOL(jiffies);
EXPORT_SYMBOL(xtime);
…
你可能对这些变量和函数已经很熟悉。其中宏定义EXPORT_SYMBOL()本身的含义是“移出符号”。为什么说是“移出”呢?因为这些符号本来是内核内部的符号,通过这个宏放在一个公开的地方,使得装入到内核中的其他模块可以引用它们。
实际上,仅仅知道这些符号的名字是不够的,还得知道它们在内核地址空间中的地址才有意义。因此,内核中定义了如下结构来描述模块的符号:
struct module_symbol
{
unsigned long value; /*符号在内核地址空间中的地址*/
const char *name; /*符号名*/
};
我们可以从/proc/ksyms文件中读取所有内核模块“移出”的符号,这所有符号就形成内核符号表,其格式如下:
内存地址 符号名 [所属模块]
在模块编程中,可以根据符号名从这个文件中检索出其对应的地址,然后直接访问该地址从而获得内核数据。第三列“所属模块”指符号所在的模块名,对于从内核这一母模块移出的符号,这一列为空。
模块加载后,2.4内核下可通过 /proc/ksyms、 2.6 内核下可通过/proc/kallsyms查看模块输出的内核符号
3.模块依赖
如前所述,内核符号表记录了所有模块可以访问的符号及相应的地址。当一个新的模块被装入内核后,它所申明的某些符号就会被登记到这个表中,而这些符号可能被其他模块所引用,这就引出了模块依赖这个问题。
一个模块A引用另一个模块B所移出的符号,我们就说模块B被模块A引用,或者说模块A依赖模块B。如果要链接模块A,必须先链接模块B。这种模块间相互依赖的关系就叫模块依赖。
4.模块引用计数器
为 了确保模块安全地卸载,每个模块都有一个引用计数器。当执行模块所涉及的操作时就递增计数器,在操作结束时就递减这个计数器;另外,当模块B被模块A引用 时,模块B的引用计数就递增,引用结束,计数器递减。什么时候可以卸载这个模块?当然只有这个计数器值为0的时候,例如,当一个文件系统还被安装在系统上 时就不能将其卸载,当这个文件系统不再被使用时,引用计数器就为0,于是可以卸载。
四.模块编译
Linux 中最重要的软件开发工具是 GCC。GCC 是 GNU 的 C 和 C++ 编译器。但是,在大型的开发项目中,通常有几十到上百个的源文件,如果每次均手工键入 gcc 命令进行编译的话,则会非常不方便。因此,人们通常利用 make 工具来自动完成编译工作。利用这种自动编译可大大简化开发工作,避免不必要的重新编译。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。
1.编译工具make
实际上,make 工具通过一个称为 Makefile 的文件来完成并自动维护编译工作。Makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。下面给出2.6 内核模块的Makefile模板(请参看Makefile的写法)
# Makefile2.6 obj-m += hellomod.o # 产生hellomod 模块的目标文件 CURRENT_PATH := $(shell pwd) #模块所在的当前路径 LINUX_KERNEL := $(shell uname -r) #Linux内核源代码的当前版本 LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL) #Linux内核源代码的绝对路径 all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #编译模块了 clean: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #清理 |
注意: 在每个命令前(例如make命令前)要键入一个制表符(按TAB键产生)
有了Makefile,执行make命令,会自动形成相关的后缀为.o和.ko文件。
到此,模块编译好了,该把它插入到内核了:
如:$insmod hellomod.ko
当然,要以系统员的身份才能把模块插入。
成功插入后,可以通过dmesg命令查看,屏幕最后几行的输出就是你程序中输出的内容:Hello,World! from the kernel space…
当模块不再需要时,可以通过rmmod命令移去,例如
$rmmod hellomod
modutils是管理内核模块的一个软件包。可以在任何获得内核源代码的地方获取Modutils(modutils-x.y.z.tar.gz)源代码,然后选择最高级别的patch.x.y.z等于或小于当前的内核版本,安装后在/sbin目录下就会有insomod、rmmod、ksyms、lsmod、modprobe等实用程序。当然,通常我们在加载Linux内核时,modutils已经被载入。
1.Insmod命令
调用insmod程序把需要插入的模块以目标代码的形式插入到内核中。在插入的时候,insmod自动调用init_module()函数运行。注意,只有超级用户才能使用这个命令,其命令格式为:
# insmod[path] modulename.c
2. rmmod命令
调用rmmod程序将已经插入内核的模块从内核中移出,rmmod会自动运行cleanup_module()函数,其命令格式为:
#rmmod[path] modulename.c
3.lsmod命令
调用lsmod程序将显示当前系统中正在使用的模块信息。实际上这个程序的功能就是读取/proc文件系统中的文件/proc/modules中的信息,其命令格式为:
#lsmod
4.ksyms命令
ksyms这个程序用来显示内核符号和模块符号表的信息。与lsmod相似,它的功能是读取/proc文件系统中的另一个文件/proc/kallsyms。
分享到:
相关推荐
1-IA32寄存器简介 2-IA32内存寻址机制 3-X86寻址演变 4-内存寻址(很详尽) 5-Linux中的汇编语言 6-段机制及Linux中的实现 7-分页机制及Linux实现 8-内核空间和用户空间
"Linux内核之旅"是一次深入理解Linux内核的探索过程,而"Sagalinux"可能是一个与这次旅程相关的项目或教程名称,它可能是为了帮助学习者更好地理解和实践Linux内核的工作原理。 Linux内核主要由以下几个关键部分...
欢迎访问Linux内核之旅开源社区网站 网站入口: 十多年前,陈老师和她的学生康华、陈逸飞等创办了“Linux内核之旅”( 网站,并撰写了电子杂志的系列文章。近年来,有把这个网站与微信平台打通的愿望,多年积累,...
[Linux内核之旅] - 2015-11-12 Linux内核之旅—微信平台开篇.html | [Linux内核之旅] - 2015-11-13 hurlex — x86架构的内核Demo实现.html | [Linux内核之旅] - 2015-11-13 《Linux内核设计与实现》章节节选—操作...
D:\linux-doc\linux-kernel-travel 的目录 Advanced.Linux.Programming.pdf Building.Embedded.Linux.Systems.pdf Linux.Kernel.Development.Second.Edition.chm Linux_Debugging_and_Performance_Tuning_Tips_and_...
readyreadyreadtreadyreadyreadtreadyreadyreadtreadyreadyreadtreadyreadyreadtreadyreadyreadtreadyreadyreadt
这份"Linux内核资料分析"压缩包提供了丰富的资源,帮助你开启探索Linux内核之旅。 首先,Linux内核源码是了解其工作方式的最直接途径。源码分析可以帮助我们理解如何实现进程管理、内存分配、设备驱动、文件系统...
"Linux专家之路"不仅是一条技术探索之旅,也是一次理解操作系统本质的旅程。这个过程中,你会遇到诸如中断处理、调度算法、数据结构设计等方面的挑战,但同时也将收获对系统运行机制的深刻认识。通过不断实践和学习...
5. 《Linux内核之旅》:作者Jason Kolb通过一系列的小故事,带领读者探索Linux内核的奥秘,使得学习过程更加生动有趣。 6. 《Linux内核调试》:由M. Tim Jones撰写,该书专注于Linux内核的调试技术,包括使用GDB、...
《Linux内核源代码分析》是由冯锐翻译的一部深入探讨Linux内核源代码的著作。本书主要针对那些想要理解Linux内核工作机制...通过冯锐的翻译,相信读者可以更轻松地踏上这段探索之旅,为后续章节的学习打下坚实的基础。
《Linux Kernel 四库全书》是一套专为Linux内核爱好者和专业人士设计的深度学习资料,涵盖了Linux内核的...这是一次全面而深入的Linux内核之旅,对于立志成为系统级程序员或系统管理员的人来说,是不可多得的学习资源。
《LINUX内核源代码情景分析》一书深入探讨了Linux操作系统的核心——内核的源代码,为读者提供了一个理解其工作原理的详尽指南。...这是一次深入学习之旅,将帮助技术人士提升自己的技能,更好地应对复杂的技术挑战。
本书开篇便引人深思地提出,今日的选择与行动皆由过去的无数偶然事件塑造而成,这种哲学视角贯穿全书,鼓励读者以开放的心态拥抱内核学习之旅。 #### 方法论概览:从理论到实践 1. **全面了解**:本书首倡全面理解...
正如书中所述,每一个编程领域的学习之旅往往始于“Hello, World”程序,这是为了确保环境正确设置并能够执行基本操作。在Linux内核编程中,编写一个简单的内核模块并使其运行,同样是一个重要的起点。这个模块通常...
《LINUX内核源代码导读》是李云华著作的一本深入解析Linux内核源代码的书籍,旨在帮助读者理解并...这是一次挑战性的学习之旅,但对于那些想要深入计算机操作系统底层的程序员和系统管理员来说,无疑是一份宝贵的资源。
在Linux内核开发中,`Makefile`扮演着至关重要的角色,它是构建和编译内核的核心。...虽然本文并未深入探讨配置系统的实现细节,但对于初学者来说,掌握如何编写和使用`Makefile`已经足够开始内核开发之旅。
学习Linux内核的实验代码仓库 仓库的作用 在我们实验室学习Linux内核各部分的过程中,写过很多实验代码。现在将这些代码整理在这里,并且会一直保持更新,欢迎所有Linuxer一起来交流学习。 如何上传自己的代码 您...
### Linux内核源码编译入门技术解析 #### 一、Linux内核源码的文件组织 对于初学者而言,了解Linux内核源码的基本结构是至关重要的第一步。...希望本文能够帮助初学者更好地入门Linux内核源码的学习之旅。
《深入分析Linux内核》是一本深度探讨Linux操作系统内核的专业书籍,旨在帮助读者理解Linux内核的工作原理和实现机制。作者陈莉君是Linux领域的知名专家,她的站点http://www.kerneltravel.net提供了丰富的学习资源...