📄 (ldd) ch02-编写和运行模块(转载).txt
字号:
(代码)
文件中的每一项是有驱动程序锁定的范围(以十六进制表示)。在这些被释放前,其他
驱动程序不允许访问这些端口。
避免冲突有两个途径。首先,向系统增加新设备的用户检查/proc/ioports,然后在配置
新设备使用空闲端口――这种方法假设设备可以通过跳线进行配置。然后,当软件驱动
程序初始化自己时,它能自动探测新设备而对其他设备无害:驱动程序不会探测已由其
他驱动程序使用的I/O端口。
事实上,基于I/O注册的冲突避免对于模块化驱动程序很合适,但对于连编到内核里的驱
动程序来说却可能失败。尽管我们不涉及这种驱动程序,但还是很是必要注意到,对于
一个在启动时初始化自己的驱动程序来说,由于它要使用之后会被注册的端口,很可能
会造成对其他设备的误配置。虽然如此,还是没有办法让一个符合规范的驱动程序与已
配置好的硬件交互,除非以前加载的驱动程序不注册它的端口。基于以上原因,探测ISA
设备是件很危险的事,而且如果随正式Linux内核发行的驱动程序为了因与尚未加载的模
设备是件很危险的事,而且如果随正式Linux内核发行的驱动程序为了因与尚未加载的模
块对应的设备交互,拒绝在模块加载时执行探测功能。
设备探测的问题是因为只有一种方法标别设备,即通过写目标端口然后再读的方法――
处理器(而且是任何程序)只能查看数据线上的电子信号。驱动程序编写者知道一旦设
备连接到某个特定的端口上,它就会响应相应的查询代码。但是如果另一个设备连到了
端口上,程序仍然会写这个设备,但天知道它会怎么响应这个异常的探测操作。有时可
以通过读外设的BIOS,查看一个已知的字串来避免端口探测;已有若干SCSI设备使用了
这种技术,但并不是每个设备都要有自己的BIOS。
一个符合规范的驱动程序应该调用check_region查看是否某个端口区域已由其他驱动程
序锁定,之后就用request_region将端口锁住,当驱动程序不再使用端口时调用release
_region释放端口。这些函数的原型在<linux/ioports.h>中。
注册端口的典型顺序如下所示(函数skull_probe_hw包含了所有设备相关代码,这里没
有出现):
(代码)
在cleanup_module里释放端口:
(代码)
系统也使用了一套类似的请求/释放策略维护中断,但注册/注销中断比处理端口复杂,
整个过程的详细解释将放在第9章“中断处理”中介绍。
与前面讲到的关于设施的注册/注销相似,对资源的请求/释放方法也适合使用已勾勒的
基于goto的实现框架。
对于编写PCI设备驱动程序的人来说,不存在这里所讲的探测问题。我将在第15章“外部
总线简介”中介绍。
ISA内存
本节技术性很强,如果你对处理硬件问题不是很有把握,可以简单跳过这节。
在Intel平台上,ISA槽上的目标设备可能会提供片上内存,范围在640KB到1MB之间(0xA
0000到0xFFFFF);这也是设备驱动程序可以使用的一类资源。
这种内存部件反映了8086处理器那个时代,当时8086的寻址只有一兆的大小。PC设计人
员决定,低端的640KB当做RAM,而保留另外的384KB用于ROM和内存映射设备。今天,即
便是最强力的个人电脑也还有这个在第一兆字节里的空洞。Linux的PC版保留了这片内存
,根本不考虑使用它。本节给出的代码可以让你访问这个区域的内存,但它仅限于x86平
台,而且Linux内核要至少是2.0.x的,x是多少都可以。2.1版改变了物理内存的访问方
式,比如,640KB-1MB这段范围内的I/O内存就不能再这样访问。访问I/O内存的正确方式
是第18章“硬件管理”“低1M内的ISA内存”小节中的内容,这超出了本章的范围。
是第18章“硬件管理”“低1M内的ISA内存”小节中的内容,这超出了本章的范围。
尽管内核提供了端口和中断的请求/释放机制,当前它还是没能提供给I/O内存类似的机
制,所以你得自己做了。如果我能理解Linus是如何看待PC体系结构的化,这里给的方法
就不会变化了。
有时某个驱动程序需要在初始化时探测ISA内存;例如,我需要告诉视频截取器(frame
grabber)在哪映射截取的图象。问题是,如果没有探测方法,我将无法辨别那段范围内
哪块内存正在使用。人们需要能够辨别3种不同的情况:映射了RAM,有ROM(例如,VGA
BIOS),或者那段区域空闲。
skull样例给出一种处理这些内存的方法,但由于skull和物理设备无关,它打印完640KB
-1MB这段内存区域的信息后就退出了。然而,有必要谈一谈用于分析内存的代码,因为
它必须处理一些竞争条件。竞争条件就是这样一种情形,两个任务可以竞争同一个资源
,而且未同步的操作可能会损坏系统。
尽管驱动程序编写者无需处理多任务,我们还是必须记住,中断可能在你的代码中间发
生,而且中断处理函数可能会不提醒你就修改全局量。尽管内核提供了许多工具处理竞
争条件,下面给得出的简单规则阐述了处理这个问题的方法;对这个问题的彻底对策将
在第9章的“竞争条件”小节中给出。
l 如果仅仅是读取共享的量,而不是写,将其声明为volatile,要求编译器不对
其进行优化。这样,编译好的代码在每次源码读取它时读取这个量了。
其进行优化。这样,编译好的代码在每次源码读取它时读取这个量了。
l 如果代码需要检查和修改这个值,必须在操作期间关闭中断,这样可以防止其
他进程在我们检查过这个值后,但恰恰又在我们修改这个量之前修改这个量。
我们建议采用如下关闭中断的顺序:
(代码)
这里cli代表“clear interrupt flag(清除中断标志)”。上面出现的函数都定义在<a
sm/system.h>中。
应该避免使用经典的cli和sti序列,因为有时你无法在关闭中断前断定中断是否打开了
。如果此时调用sti就是产生很不规则的错误出现,很难追踪这样的错误。
由于那段内存只能通过写物理内存和读取检查才能标别,而且如果测试期间有中断的化
,有可能会被其他程序修改,因此检查RAM段的代码同时利用了volatile声明和cli。下
面的这段代码并不是很简单,如果一个设备正在象它的内存写数据,而这段代码又在扫
描这段区域,它就会误认为这段区域是空闲区。好在这样的情况很少发生。
在下面的源代码中,每个printk都带有一个KERN_INFO前缀。这个符号拼接在格式字串前
面做消息的优先级,它定义在<linux/kernel.h>中。这个符号展开后与本章开始的hello
..c中使用的<1>字串很相似。
..c中使用的<1>字串很相似。
(代码)
如果你在探测时注意恢复你所修改的字节,探测内存不会造成与其他设备的冲突。*
作为一个细心的读者,你可能会知道在15MB-16MB地址域内的ISA内存是怎么回事。很不
幸,那是个更棘手的问题,我们将在第8章的“1M以上的ISA内存”小节中讨论。
自动和手动配置
根据系统的不同,驱动程序需要了解的若干参数也会随之变化。例如,设备必须了解硬
件的I/O地址或内存区域。
注意,本节所讨论的大部分问题并不适用于PCI设备(第15章介绍)。
根据设备的不同,除了I/O地址外,还有一些其他参数会影响系统的驱动程序的行为,如
设备的品牌和发行号。驱动程序为了正确地工作有必要了解这些参数的具体值。用正确
的数值设置驱动程序(即,配置它)是一项需要在初始化期间完成的复杂的任务。
基本说来,有两种方式可以获得这些正确的数值:或者是用户显式地给出它们,或者是
驱动程序自己探测。无疑,自动探测是最好的驱动程序配置方法,而用户配置则是最好
实现的;作为驱动程序编写者的一种权衡,他应该尽可能地实现自动配置,但又允许用
户配置作为一种可选的方式替代自动配置。这种配置方法的另一个好处就是,在开发期
户配置作为一种可选的方式替代自动配置。这种配置方法的另一个好处就是,在开发期
间可以给定参数,从而不用自动探测,可以在以后实现它。
insmod在加载时接受命令行中给定的整数和字串值,可以给参数赋值。这条命令可以修
改在模块中定义的全局变量。例如,如果你的源码中包含了这些变量:
(代码)
那么你就可以使用如下命令加载模块:
(代码)
例子里使用了printk,它可以显式,当init_module被调用时,赋值已经发生了。注意,
insmod可以给任何整型或字符指针变量赋值,不管它们是否是公共符号表中的一部分。
但对于声明为数组的串是不能在加载时赋值的,因为它已经在编译时解析出来了,以后
就不能修改了。
自动配置可以设计为按如下方式工作:“如果配置变量是默认值,就执行自动探测;否
则,保留当前值。”为了让这种方法可以工作,“默认”值应该不是任何用户可以在加
载时设定的值。
下面这段代码给出了skull是如何自动探测设备的端口地址的。在这个例子中,使用自动
探测查找多个设备,而手动配置只限于一个设备。注意,函数skull_detect在上面已经
探测查找多个设备,而手动配置只限于一个设备。注意,函数skull_detect在上面已经
给出了,而skull_init_board负责完成设备相关的初始化工作,这里没有给出。
(代码)
为了方便用户在insmod命令行中给出相应的参数,而且如果这些符号不会放到主符号表
中的话,实际使用的驱动程序可以去掉配置变量的前缀(在本例中就是skull_)。如果
它们确实要放到主符号表中,好的办法就是声明两个符号:一个没有前缀,在加载时赋
值,一个有前缀,用register_symtab放到符号表中。
在用户空间编写驱动程序
到现在为止,一个首次接触内核问题的Unix程序员困难会对编写模块非常紧
--
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -