📄 (ldd) ch17-最新进展(转载).txt
字号:
(LDD) Ch17-最新进展(转载)
第十七章 最新进展
Linux一直在迅速地发展着,开发人员总是迫切希望改善核心内部,它们并不考虑向后兼
容性。这种自由开发导致了不同版本核心提供的设备驱动程序接口之间一定程度的不兼
容。不过,在应用级还保持着兼容,除了个别需要与核心特征进行低级交互的应用(象p
s)。
另一方面,设备驱动程序是直接链接到核心映象上的,因此必须与数据结构、全局变量
、以及由内核系统引出的函数发生的改变保持一致。在开发过程中,随着新特征的加入
,内部被修改;新的实现取代了就的实现,因为实践证明它们更快,更清晰。尽管不兼
容性要求程序员在写模块时要做一些额外的工作,我认为连续的开发是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社区中,模块化变的越来越重要,开发人员决定用一个更清晰的实现取代旧的。
头文件<linux/module.h>在2.1.18中完全重写了,一个新的API被引入。如你所期望的,
新的实现比旧的要容易使用。
为了加载你的模块,你将需要包modutils-2.1.34甚至更新版本(细节见Documentation/
Changes)。当与旧的核心一起使用时,这个包可以回到兼容模式,因此你可以用这个新
包替换modules-2.0.0,即使你经常在2.0和2.1之间切换。
引出符号
符号表的新接口比以前的要容易多了,它依赖于下面的宏:
EXPORT_NO_SYMBOLS;
这个宏与register_symtab(NULL)等价;它可以出现在一个函数的内部或外部,因为
这个宏与register_symtab(NULL)等价;它可以出现在一个函数的内部或外部,因为
它只是指导汇编器,而不产生实际代码。如果你想在Linux2.0上编译模块,这个宏应该
在init_module中被使用。
EXPORT_SYMTAB;
如果你打算引出一些符号,那么模块必须在包含<linux/module.h>之前定义这个宏
。
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)如何处
理这个问题的:
(代码384 #1)
上面的代码依赖于下面sysdep-2.1.h中的行:
(代码384 #2)
当使用2.1.18或更新的核心时,REGISTER_SYMTAB扩展为什么都不做,因为init_module
中没有什么需要做的;在函数外使用EXPORT_SYMBOL是引出模块符号唯一需要做的。
声明参数
核心模块的新的实现利用了ELF二进制格式的特征以获得更好的灵活性。更特别地,当构
核心模块的新的实现利用了ELF二进制格式的特征以获得更好的灵活性。更特别地,当构
造一个ELF目标文件时,你可以声明除“正文”、“数据”和“bss”之外的节。一个“
节”是一个连续的数据区域,与“段”的概念类似。
对于2.1,核心模块必须使用ELF二进制格式编译。事实上,2.1核心利用了ELF的节(见
“处理核心空间错误”),只能编译为ELF。因此模块的限制并不是个真正限制。使用EL
F允许信息域被存在目标文件中。好奇的读者可以使用objdump –section-headers来观
察节头,用objdump –section=.modinfo –full-contents来查看模块特定的信息。实
际上,.modinfo一节是用来存储模块信息的节,包含被称做“参数”的值,可以在加载
时修改。
当在2.1上编译时,一个参数可以用宏如下声明:
MODULE_PARM(variable, type-description);
当你在源文件中使用这个宏时,编译器被告知在目标文件中插入一个描述串;这个描述
表明variable是个参数,它的类型对应于type-description。insmod和modprobe查看目
标文件,保证你被允许修改variable,同时检查参数的实际类型。类型检查对防止不愉
快的错误非常重要,例如用一个串覆盖了一个整数,或错把长整数当成了短整数。
按我的观点,讲述宏的最好办法时给出几行示例代码。下面的代码属于一个想象的网卡
:
(代码385)
type-description串在头文件<linux/module.h>中被非常详细地介绍,并且为了你的方
便,它可以在整个核心源码中找到。
值得给出的一个技巧是如何参数化一个数组的长度,象上面的io。例如,设想网络驱动
程序支持的外围板子的数目有宏MAX_DEVICES表示,而不是硬写入的数字4。出于这个目
的,头文件<linux/module.h>定义了一个宏(__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一节,但它们目前没
有使用。请参考<linux/module.h>以获得更多的信息。
有使用。请参考<linux/module.h>以获得更多的信息。
/proc/modules
/proc/modules的格式在2.1.18中略有改变,而所有的模块化代码都被重写了。尽管这个
改变并不影响源码,你可能对其细节不感兴趣,因为/proc/modules在模块开发时经常被
检查。
新格式和旧的一样是面向行的,每行包含下面的域:
模块名
这个域与Linux2.0相同。
模块大小
这是个十进制数,以字节为单位(而不是内存页)报告长度。
这个模块的使用计数
如果模块没有使用计数,这个计数报告-1。这是和新的模块化代码一道引入的新特征;
如果模块没有使用计数,这个计数报告-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
方法。
原型的不同
四个文件操作表征一个新的原型;它们是:
long long (*llseek) (struct inode *, struct file *, long long, int);
long (*read) (struct inode *, struct fle *, char *, unsigned long);
long (*write) (struct inode *, struct file *, const char *, unsigned
long);
int (*release) (struct inode *, struct file *);
它们在2.0中的对应者是:
int (*lseek) (struct inode *, struct file *, off_t, int);
int (*read) (struct inode *, struct file *, char *, int);
int (*write) (struct inode *, struct file *, const char *, int);
void (*release) (struct inode *, struct file *);
如你所见的,其不同在于它们的返回值(它允许了更大的范围),还有count和offset参
数。头文件sysdep-2.1.h通过定义下面的宏处理这些不同:
read_write_t
read_write_t
这个宏扩展为参数count的类型以及read和write的返回值。
lssek_t
这个宏扩展为llseek的返回值类型。方法名字的改变(从lseek到llseek)并不
是个问题,因为你一般在file_operations中并不用名字对域赋值,而是声明一个静态结
构。
lseek_off_t
lseek的offset参数。
release_t
release方法的返回值;或为void或为int;
release_return( int return_value)
这个宏可以用来从release方法返回。它的参数用来返回一个错误代码:0表示成
功,负值表示失败。在比2.1.31老的核心中,这个宏扩展为return,因为这个方法返回v
oid。
oid。
用前面的宏,一个可移植的驱动程序原型是:
lseek_t my_lseek(struct inode *, struct file *, lseek_off_t, int);
read_write_t my_read(struct inode *, struct file *, char *,
count_t);
read_write_t my_write(struct inode *, struct file *, const char *,
count_t);
release_t my_release(struct inode *, struct file *);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -