您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 项目/工程管理 > LINUX内核模块编程
LINUX内核模块编程[转]ForewordTableofContents作者声明版本和注意感谢译者注作者声明《Linux内核驱动模块编程指南》最初是由OriPomerantz为2.2版本的内核编写的,后来,Ori将文档维护的任务交给了PeterJaySalzman,Peter完成了2.4内核版本文档的编写,毕竟Linux内核驱动模块是一个更新很快的内容。现在,Peter也无法腾出足够的时间来完成2.6内核版本文档的编写,目前该2.6内核版本的文档由合作者MichaelBurian完成。版本和注意Linux内核模块是一块不断更新进步的内容,在LKMPG上总有关于是否保留还是历史版本的争论。Michael和我最终是决定为每个新的稳定版本内核建立一个新的文档分支。也就是说LKMPG2.4.x专注于2.4的内核,而LKMPG2.6.x将专注于2.6的内核。我们不会在一篇文档中提供对旧版本内核的支持,对此感兴趣的读者应该寻找相关版本的文档分支。在文档中的绝大部分源代码和讨论都应该适用于其它平台,但我无法提供任何保证。其中的一个例外就是Chapter12,中断处理该章的源代码和讨论就只适用于x86平台。感谢感谢下列人士为此文档提供了他们宝贵的意见。他们是:IgnacioMartin,DavidPorter,DanielePaolo,Scarpazza和DimoVelev。译者注我,译者,名叫田竞,目前是一名在北京邮电大学就读的通信专业的大学生。自高中起我就是Linux的爱好者并追随至今。我喜欢Linux给我带来的自由,我想大家也一样。没有人不向往自由。我学习内核模块编写时最初阅读的是Orelly出版社的使用2.0版本的内核的书籍,但如同我预料的一样,书中的许多事例由于内核代码的变化而无法使用。这让想亲自体验内核模块的神秘的我非常苦恼。我在Linux文档计划在上海的镜像站ldp.linuxforum.net上找到了这篇文档。受原作者Ori的鼓励,基于上次完成的LKMPG2.4的,内容有稍许的改变和扩充。应该是目前最新的了。翻译的方式有所改变,在基于LDP认可的docbook格式上翻译,通过docbook2html转换为附件中的html文档。由于对docbook不是很熟悉,其中的一些标题尚未翻译,而且可能破坏了原有的tag,导致html出现一些错误显示,但总体来说很少。修改了很多2.4中的错别字。学习并分享学习的过程是我翻译的最终目的。Chapter1.IntroductionTableofContents什么是内核模块?内核模块是如何被调入内核工作的?什么是内核模块?现在,你是不是想编写内核模块。你应该懂得C语言,写过一些用户程序,那么现在你将要见识一些真实的东西。在这里,你会看到一个野蛮的指针是如何毁掉你的文件系统的,一次内核崩溃意味着重启动。什么是内核模块?内核模块是一些可以让操作系统内核在需要时载入和执行的代码,这同样意味着它可以在不需要时有操作系统卸载。它们扩展了操作系统内核的功能却不需要重新启动系统。举例子来说,其中一种内核模块时设备驱动程序模块,它们用来让操作系统正确识别,使用安装在系统上的硬件设备。如果没有内核模块,我们不得不一次又一次重新编译生成单内核操作系统的内核镜像来加入新的功能。这还意味着一个臃肿的内核。内核模块是如何被调入内核工作的?你可以通过执行lsmod命令来查看内核已经加载了哪些内核模块,该命令通过读取/proc/modules文件的内容来获得所需信息。这些内核模块是如何被调入内核的?当操作系统内核需要的扩展功能不存在时,内核模块管理守护进程kmod[1]执行modprobe去加载内核模块。两种类型的参数被传递给modprobe:一个内核模块的名字像softdog或是ppp。通用识别符像char-major-10-30。当传递给modprobe是通用识别符时,modprobe首先在文件/etc/modules.conf查找该字符串。如果它发现的一行别名像:aliaschar-major-10-30softdog它就明白通用识别符是指向内核模块softdog.o。然后,modprobe遍历文件/lib/modules/version/modules.dep来判断是否有其它内核模块需要在该模块加载前被加载。该文件是由命令depmod-a建立,保存着内核模块的依赖关系。举例来说,msdos.o依赖于模块fat.o内核模块已经被内核载入。当要加载的内核模块需要使用别的模块提供的符号链接时(多半是变量或函数),那么那些提供这些所需符号链接的内核模块就被该模块所依赖。最终,modprobe调用insmod先加载被依赖的模块,然后加载该被内核要求的模块。modprobe将insmod指向/lib/modules/version/[2]目录,该目录为默认标准存放内核模块的目录。insmod对内核模块存放位置的处理相当呆板,所以modprobe应该很清楚的知道默认标准的内核模块存放的位置。所以,当你想要载入一个内核模块时,你可以执行:insmod/lib/modules/2.5.1/kernel/fs/fat/fat.oinsmod/lib/modules/2.5.1/kernel/fs/msdos/msdos.o或只是执行modprobe-amsdos。Linux提供modprobe,insmodanddepmod在一个名为modutils或mod-utils的工具包内。在结束本章前,让我们来看一个/etc/modules.conf文件:#Thisfileisautomaticallygeneratedbyupdate-modulespath[misc]=/lib/modules/2.4.?/localkeeppath[net]=~p/mymodulesoptionsmydriverirq=10aliaseth0eepro用'#'起始的行为注释。空白行被忽略。以path[misc]起始的行告诉modprobe用/lib/modules/2.4.?/local替代搜寻misc内核模块的路径。正如你看到的,命令解释器shell的元字符也可以使用。以path[net]起始的行告诉modprobe在目录~p/mymodules搜索网络方面的内核模块。但是,在path[net]指令之前使用的keep指令告诉modprobe只是将该路径添加到标准搜索路径中,而不是像对待misc前面那样进行替换。以alias起始的的行使modprobe加载eepro.o当kmod以通用识别符'eth0'要求加载相应内核模块时。你不会发现像aliasblock-major-2floppy这样的别名行在文件/etc/modules.conf因为modprobe已经知道在绝大多数系统上安装的标准的设备的驱动模块。现在你已经知道内核模块是如何被调入的了。当你想写你自己的依赖于其它模块的内核模块时,还有一些内容没有提供。这个相对高级的问题将在以后的章节中介绍,当我们已经完成前面的学习后。在开始前在我们介绍源代码前,有一些事需要注意。系统彼此之间的不同会导致许多困难。顺利的编译并且加载你的第一个helloworld模块有时就会比较困难。但是当你跨过这道坎时,后面会顺利的多。内核模块和内核的版本问题为某个版本编译的模块将不能被另一个版本的内核加载如果内核中打开了CONFIG_MODVERSIONS选项。我们暂时不会讨论与此相关的内容。在我们进入相关内容前,本文档中的范例可能在该选项打开的情况下无法工作。但是,目前绝大多数的发行版是将该选项打开的。所以如果你遇到和版本相关的错误时,最好,重新编译一个关闭该选项的内核。使用X带来的问题强烈建议你在控制台下输入文档中的范例代码,编译然后加载模块,而不是在X下。模块不能像printf()那样输出到屏幕,但它们可以记录信息和警告,当且仅当你在使用控制台时这些信息才能最终显示在屏幕上。如果你从xterm中insmod一个模块,这些日志信息只会记录在你的日志文件中。除了查看日志文件你将无法得到输出信息。想要及时的获得这些日志信息,建议所有的工作都在控制台下进行。编译相关和内核版本相关的问题Linux的发行版经常给内核打一些非标准的补丁,这种情况回导致一些问题的发生。一个更普遍的问题是一些Linux发行版提供的头文件不完整。编译模块时你将需要非常多的内核头文件。墨菲法则之一就是那些缺少的头文件恰恰是你最需要的。我强烈建议从Linux镜像站点下载源代码包,编译新内核并用新内核启动系统来避免以上的问题。参阅LinuxKernelHOWTO获得详细内容。具有讽刺意味的是,这也会导致一些问题。gcc倾向于在缺省的内核源文件路径(通常是/usr/src/)下寻找源代码文件。这可以通过gcc的-I选项来切换。Notes[1]在早期的linux版本中,是一个名为kerneld的守护进程。[2]如果你在修改内核,为避免覆盖你现在工作的模块,你应该试试使用内核Makefile中的变量EXTRAVERSION去建立一个独立的模块目录。Chapter2.HelloWorldTableofContentsHello,World(part1):最简单的内核模块编译内核模块HelloWorld(part2)HelloWorld(part3):关于__init和__exit宏HelloWorld(part4):内核模块证书和内核模块文档说明从命令行传递参数给内核模块由多个文件构成的内核模块为已编译的内核编译模块Hello,World(part1):最简单的内核模块当第一个洞穴程序员在第一台洞穴计算机的墙上上凿写第一个程序时,这是一个在羚羊皮上输出`Hello,world'的字符串。罗马的编程书籍上是以`Salut,Mundi'这样的程序开始的。我不明白人们为什么要破坏这个传统,但我认为还是不明白为好。我们将从编写一系列的`Hello,world'模块开始,一步步展示编写内核模块的基础的方方面面。这可能是一个最简单的模块了。先别急着编译它。我们将在下章模块编译的章节介绍相关内容。Example2-1.hello-1.c/**hello-1.c-Thesimplestkernelmodule.*/#includelinux/module.h/*Neededbyallmodules*/#includelinux/kernel.h/*NeededforKERN_ALERT*/intinit_module(void){printk(1Helloworld1.\n);/**Anon0returnmeansinit_modulefailed;modulecan'tbeloaded.*/return0;}voidcleanup_module(void){printk(KERN_ALERTGoodbyeworld1.\n);}一个内核模块应该至少包含两个函数。一个“开始”(初始化)的函数被称为init_module()还有一个“结束”(干一些收尾清理的工作)的函数被称为cleanup_module(),当内核模块被rmmod卸载时被执行。实际上,从内核版本2.3.13开始这种情况有些改变。你可以为你的开始和结束函数起任意的名字。你将在以后学习如何实现这一点theSectioncalledHelloWorld(part2)。实际上,这个新方法时推荐的实现方法。但是,许多人仍然使init_module()和cleanup_module()作为他们的开始和结束函数。一般,init_module()要么向内核注册它可以处理的事物,要么用自己的代码替代某个内核函数(代码通常这样做然后再去调用原先的函数代码)。函数cleanup_module()应该撤消任何init_module()做的事,从而内核模块可以被安全的卸载。最后,任一个内核模块需要包含linux/module.h。我们仅仅需要包含linux/kernel.h当需要使用printk()记录级别的宏扩展时
本文标题:LINUX内核模块编程
链接地址:https://www.777doc.com/doc-3392067 .html