| Gzu521.com我的学习网 |
|
.GeC#h2(资/料来.源,于:gzu521学;习/网:电脑课堂;LINUX教程 ]gzu521.com.GeC#h2 linux一直在迅速地发展着,开发人员总是迫切希望改善核心内部,它们并不考虑向后兼容性。这种自由开发导致了不同版本核心提供的设备驱动程序接口之间一定程度的不兼容。不过,在应用级还保持着兼容,除了个别需要与核心特征进行低级交互的应用(象ps)。 另一方面,设备驱动程序是直接链接到核心映象上的,因此必须与数据结构、全局变量、以及由内系统引出的函数发生的改变保持一致。在开发过程中,随着新特征的加入,内部被修改;新的实现取代了就的实现,因为实践证明它们更快,更清晰。尽管不兼容性要求程序员在写模块时要做一些额外的工作,我认为连续的开发是linux社区的成功点:严格的先后兼容性最终证明是有害的。 这一章讲述2.0.x和2.1.43之间的不同,这些将会与即将推出的2.2发布类似。linus在前几个2.1版本中引入了最重要的改变,这样核心就可以多经历几个2.1版本,使得驱动程序的作者有足够的时间在开发被锁定以发布稳定的2.2之前来稳定驱动程序。下面的小节介绍驱动程序是如何处理2.0和2.1.43之间的不同的。我已经修改了本书介绍的所有示例代码,使得它们可以同时在2.0和2.1.43上编译和运行,以及这之间的大多数版本。驱动程序的新版本可以从o’reilly的ftp站点上在线例子的v2.1目录下得到。2.0和2.1之间的兼容性通过头文件sysdep-2.1.h获得,它可以与你自己的模块集成。我选择不把兼容性扩展到1.2避免了给c代码加载太多的条件,而且1.2-2.0的不同已经在前面的章节解释过了。在我将要写完这本书时,我了解到从2.1.43起又引入了一些小的不兼容性;我不打算对之加以评述,因为我不能保证对这些最新版本的完全支持。 注意在本章我不会讲述2.1开发系列引入的所有新东西。我要做的只是移植2.0模块,使之可以在2.0和2.1核心上运行。利用2.1的特征意味着放弃对不具有这些特征的2.0发布的支持。2.0版本仍是本书的重点。在写sysdep-2.1.h时,我已努力使你熟悉新的api,我引入的宏用来使2.1的代码可以在2.0上跑,而不是相反。 本章以重要性逐渐降低的顺序介绍不兼容性;最重要的不同首先被介绍,次要的细节则在后面介绍。 模块化 在linux社区中,模块化变的越来越重要,开发人员决定用一个更清晰的实现取代旧的。头文件在2.1.18中完全重写了,一个新的api被引入。如你所期望的,新的实现比旧的要容易使用。为了加载你的模块,你将需要包modutils-2.1.34甚至更新版本(细节见documentation/ changes)。当与旧的核心一起使用时,这个包可以回到兼容模式,因此你可以用这个新包替换modules-2.0.0,即使你经常在2.0和2.1之间切换。 引出符号 符号表的新接口比以前的要容易多了,它依赖于下面的宏: export_no_symbols;这个宏与register_symtab(null)等价;它可以出现在一个函数的内部或外部,因为它只是指导汇编器,而不产生实际代码。如果你想在linux2.0上编译模块,这个宏应该在init_module中被使用。 export_symtab;如果你打算引出一些符号,那么模块必须在包含之前定义这个宏。 export_symbol(name);这个宏表明你想引出这个符号名。它必须在任何函数之外使用。 export_symbol_novers(name)使用这个宏而不是export_symbol()强制丢弃版本信息,即使是编译带有版本支持的代码。这对避免一些不必要的重编译很有用。例如,memset函数将总以同样的方式工作;引出符号而不带版本信息允许开发者改变实现(甚至使用的数据类型)而不需insmod标出不兼容性。在模块化的代码中不大可能需要这个宏。 如果这些宏都没有在你的源码中使用,那么所有的非静态符号都被引出;这与在2.0中一如果这些宏都没有在你的源码中使用,那么所有的非静态符号都被引出;这与在2.0中一样。如果这个模块是从几个源文件生成的,你可以从任何源文件引出符号,而且还可以在模块的范围中共享任何符号。如你所看到的,引出符号表的新方法解决了一些问题,但这个创新也引入了一个重要的不兼容性:一个引出了一些符号的模块,如果想同时在2.0和2.1上编译运行,则必须用条件编译来包含两个实现。下面是export模块(v2.1/misc-modules/export.c)如何处理这个问题的当使用2.1.18或更新的核心时,register_symtab扩展为什么都不做,因为init_module中没有什么需要做的;在函数外使用export_symbol是引出模块符号唯一需要做的。 核心模块的新的实现利用了elf二进制格式的特征以获得更好的灵活性。更特别地,当构核心模块的新的实现利用了elf二进制格式的特征以获得更好的灵活性。更特别地,当构造一个elf目标文件时,你可以声明除“正文”、“数据”和“bss”之外的节。一个“节”是一个连续的数据区域,与“段”的概念类似。 对于2.1,核心模块必须使用elf二进制格式编译。事实上,2.1核心利用了elf的节(见“处理核心空间错误”),只能编译为elf。因此模块的限制并不是个真正限制。使用elf允许信息域被存在目标文件中。好奇的读者可以使用objdump –section-headers来观察节头,用objdump –section=.modinfo –full-contents来查看模块特定的信息。实际上,.modinfo一节是用来存储模块信息的节,包含被称做“参数”的值,可以在加载时修改。 当在2.1上编译时,一个参数可以用宏如下声明:module_parm(variable, type-description);当你在源文件中使用这个宏时,编译器被告知在目标文件中插入一个描述串;这个描述表明variable是个参数,它的类型对应于type-description。insmod和modprobe查看目标文件,保证你被允许修改variable,同时检查参数的实际类型。类型检查对防止不愉快的错误非常重要,例如用一个串覆盖了一个整数,或错把长整数当成了短整数。按我的观点,讲述宏的最好办法时给出几行示例代码。下面的代码属于一个想象的网卡type-description串在头文件中被非常详细地介绍,并且为了你的方便,它可以在整个核心源码中找到。值得给出的一个技巧是如何参数化一个数组的长度,象上面的io。例如,设想网络驱动程序支持的外围板子的数目有宏max_devices表示,而不是硬写入的数字4。出于这个目的,头文件定义了一个宏(__module_string),它用c预处理器将一个宏“字符串化”。这个宏可以如下使用: int io[max_devices+1]={0,}; module_parm(io, “1-” __module_string(max_devices) “i”); 在前一行中,被“字符串化”的值与其他串接在一起构成目标文件中有意义的串。scull示例模块也用module_parm来声明它的参数(scull_major和其他整数变量)。这在linux2.0上编译时可能会出问题,那里这个宏未定义。我选择的简单的修正是在sysdep-2.1.h中定义module_parm,这样在与2.0头文件编译时,它扩展为空语句。其它有意义的值可以象module_author()一样 存在模块的.modinfo一节,但它们目前没有使用。请参考以获得更多的信息。 /proc/modules /proc/modules的格式在2.1.18中略有改变,而所有的模块化代码都被重写了。尽管这个改变并不影响源码,你可能对其细节不感兴趣,因为/proc/modules在模块开发时经常被检查。 新格式和旧的一样是面向行的,每行包含下面的域: 模块名:这个域与linux2.0相同。 模块大:小这是个十进制数,以字节为单位(而不是内存页)报告长度。这个模块的使用计数如果模块没有使用计数,这个计数报告-1。这是和新的模块化代码一道引入的新特征;你可以写一个模块,它的去除可以有一个函数控制而不是使用计数。这个函数判断模块是否能够被卸载。例如,ipv6模块就使用这个特征。 可选标志 标志是文本串,每个都由括号包含,并由空格分隔。参考本模块的模块列表。这个列表整体被包含在方括号内,表中的单个名字由空格隔开。 下面是/proc/modules在2.1.43中的可能内容: morgana% cat /proc/modules ipv6 57164 -1 netlink 3180 0 [ipv6] floppy 45960 1 (autoclean) floppy 45960 1 (autoclean) monitor 516 0 (unused) 在这个屏幕快照中,ipv6没有使用计数,并依赖于netlink;floppy已经被kerneld加载,由“autoclean”标志给出,monitor是我的一个小工具,控制一些状态灯,并在系统终止时关掉我的计算机。如你所看到的,它是“unused”,我并不关心它的使用计数。 文件操作 有几个文件操作在2.1里与2.0有不同的原型。这主要是出于处理大小不能放入32位的文件的需要。其不同由头文件sysdep-2.1.h处理,它根据使用的核心版本定义了几个伪类型。文件操作中引入的仅有的显著创新是poll方法,它用完全不同的实现代替了select方法。 |
责任编辑:gzu521