⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 (ldd) ch02-编写和运行模块(转载).htm

📁 html格式
💻 HTM
📖 第 1 页 / 共 4 页
字号:
      color=#ffffff size=3>
      <P><BR>正如函数register_symtab的名字所暗示,它用来在内核主符号表中注册符号表。这种方<BR>法要比通过静态和全局变量的方法清晰的多,这样程序员就可以把关于哪些开放给其他<BR>模块,哪些不开放的信息集中存放。这种方法比在源文件中到处堆放static声明要好的<BR>多。<BR>&nbsp;<BR>如果模块在初始化过程中调用了register_symtab,全局变量就不再是开放的了;只有那<BR>些显式罗列在符号表中的符号才开放给内核。<BR>&nbsp;<BR>填写一个符号表是项挺复杂的工作,但内核开发人员已经写好了头文件简化这项工作。<BR>下面若干行代码演示了如何声明和开放一个符号表:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>有兴趣的读者可以看看&lt;linux/symtab_begin.h&gt;,但它可是内核中最难懂的头文件之一<BR>。事实上,仅想好好使用宏X的话,根本没必要读董它。<BR>&nbsp;<BR>由于register_symtab是在模块加载到内核后被调用的,它可以覆盖模块静态或全局声明<BR>的符号。此时,register_symtab用显式符号表替代模块默认开放的公共符号。<BR>&nbsp;<BR>这种覆盖是可能的,因为insmod命令处理传递给系统调用sys_init_module的全局符号表<BR>,然后在调用init_module之前注册这个符号表。因此这之后的任何一次显式调用regist<BR>er_symtab都会替换相应模块的符号表。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>er_symtab都会替换相应模块的符号表。<BR>&nbsp;<BR>如果你的模块不需要开放任何符号,而且你也不想把所有的东西都声明成static的,在i<BR>nit_module里加上下面一行语句就可以了。这次对register_symtab的调用通过注册一个<BR>空表覆盖了模块默认的符号表:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>如果源文件不想给堆叠在其上的模块提供什么接口,用上面那行语句隐藏所有的符号总<BR>是不错的。<BR>&nbsp;<BR>当模块从内&nbsp;诵对厥&nbsp;,它所声明的所有公共符号也就自动?主符号表中注销了。不过是<BR>全局符号还是显式符号表,这一点都适用。<BR>&nbsp;<BR>初始化和终止<BR>正如前面已述,init_module向内核注册模块所能提供的所有设施。这里我使用了“设施<BR>”,我的意思是指新功能,是一整个设备驱动程序或新软件抽象,是一个可以由应用程<BR>序使用的新功能。<BR>&nbsp;<BR>通过调用内核函数完成新设施的注册。传递的参数通常为一个指向描述这个新设施的数<BR>据结构和要注册的设施名称。这个数据结构通常会包含一些指向模块函数的指针,这就<BR>是模块体内的函数是被调用的机制。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>除了用来标别模块类别(如字符和块设备驱动程序)的“主”设施之外,模块还可以注<BR>册如下项目:<BR>&nbsp;<BR>其他设备<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;由于这类设施仅仅用于总线型鼠标,这些设备曾一度称为鼠标设备。它们都是些<BR>不完整的设备,通常要比那些功能健全的设备简单。<BR>&nbsp;<BR>串行端口<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;可以在运行时向系统里加入串口设备驱动程序;这也是支持PCMCIA调治解调器的<BR>机制。<BR>&nbsp;<BR>行律<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;行律是处理终端数据流的软件层。模块可以注册新行律,以非标准方式处理终端<BR>事务。例如,模块kmouse就使用行律从串口鼠标中偷取数据。<BR>&nbsp;<BR>终端设备驱动程序<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;终端设备驱动程序一组实现终端底层数据处理的函数。控制台和串口设备驱动程<BR>序为了创建终端设备,它们都要注册自己的驱动程序。而多端口串口则有自己的驱动程<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>序为了创建终端设备,它们都要注册自己的驱动程序。而多端口串口则有自己的驱动程<BR>序。<BR>&nbsp;<BR>/proc文件<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/proc包含了用来访问内核信息的文件。由于它们也可以用来调试,第4章的“使<BR>用/proc文件系统”将讲解/proc文件。<BR>&nbsp;<BR>二进制文件格式<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;对于每个可执行文件,内核扫描“二进制文件格式”列表并按相应的格式执行它<BR>。模块可以实现新的格式,Java模块就是这样做的。<BR>&nbsp;<BR>Exec域<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;为了提供与其他流行Unix系统的兼容,必须修改内核的某些内部表格。一个“执<BR>行域”就是一组从其他操作系统约定到Linux系统的映射。例如,模块可以定义执行SCO<BR>二进制文件的执行域。<BR>&nbsp;<BR>符号表<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个已在前面的“注册符号表”小节中介绍了。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>上面这些项目都不是前一章所考虑的设备类型,而且都支持那些通常集成到驱动程序功<BR>能中的设施,如/proc文件和行律。之所以鼠标和其他设备驱动程序都没有象“完整”字<BR>符设备那样管理,这主要是为了方便。过一会儿,当你读到第3章“字符设备”的“主从<BR>设备号”小节时,原因就明了了。<BR>&nbsp;<BR>还可以将模块注册为某些驱动程序的附件,但这样做就太特殊了,这里就不作讨论了;<BR>它们都使用了“注册符号表”中讲到的堆叠技术。如果你想做更深一步的探究,你可以<BR>在内核源码中查查register_symtab,并且找找不同驱动程序的入口点。大部分注册函数<BR>都是以register_开始的,这样你就可以用“register_”在/proc/ksyms找找它们了。<BR>&nbsp;<BR>init_module中的错误处理<BR>如果你注册时发生什么错误,你必须取消失败前所有已完成的注册。例如,如果系统没<BR>有足够内存分配新数据结构时,可能会发生错误。尽管这不太可能,但确实会发生,好<BR>的程序代码必须为处理这类事件做好准备。<BR>&nbsp;<BR>Linux不为每个模块保留它都注册了那些设施,因此当init_module在某处失败时,模块<BR>必须统统收回。如果你在注销你已经注册的设施时失败了,内核就进入一种不稳定状态<BR>:卸载模块后,由于它们看起来仍然是“忙”的,你再也不能注册那些设施了,而且你<BR>也无法注销它们了,因为你必须使用你注册时的那个指针,而你不太可能得到那个指针<BR>了。恢复这种情况非常复杂,通常,重新启动是最好的解决方法。<BR>&nbsp;<BR>我建议你用goto语句处理错误恢复。我讨厌使用goto,但以我个人来看,这是一个它有<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>我建议你用goto语句处理错误恢复。我讨厌使用goto,但以我个人来看,这是一个它有<BR>所做为的地方(而且,是唯一的地方)。在内核里,通常都会象这里处理错误那样使用g<BR>oto。<BR>&nbsp;<BR>下面这段样例在成功和失败时都能正确执行:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>返回值(err)是一个错误编码。在Linux内核里,错误编码是一个负值,在&lt;linux/errn<BR>o.h&gt;中定义。如果你不使用其他函数返回的错误编码而要生成自己的,你应该包含&lt;linu<BR>x/errno.h&gt;,这样就可以使用诸如-ENODEV,-ENOMEM之类的符号值。总是返回相应的错<BR>误编码是种非常好的习惯,因为这样一来用户程序就利用perror或相似的方法把它们转<BR>换成有意义的字符串了。<BR>&nbsp;<BR>很明显,cleanup_module要取消所有init_module中完成的注册。<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>使用计数<BR>为了确定模块是否可以安全地卸载,系统为每个模块保留了一个使用计数。由于模块忙<BR>的时候是不能卸载模块的,系统需要这些信息:当文件系统还被安装在系统上时就不能<BR>删除这个文件系统类型,而且你也不能在还有程序使用某个字符设备时就去掉它。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>如果忘了更新使用计数,你就不能再卸载模块了。在开发期间这种情况很可能发生,所<BR>以你一定要牢记。例如,如果进程因你的驱动程序引用了NULL指针而终止,驱动程序就<BR>不可能区关闭设备,使用计数也就无法回复到0。一种可能的解决方法就是在调试期间完<BR>全不使用使用计数,将MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT重新定义为空操作。另<BR>一个解决方法就是利用其他方法将计数强制复位为0(在第5章的“使用ioctl参数”小节<BR>中介绍)。在编写成品模块时,决不能投机取巧。然而在调试时期,有时候忽略一些问<BR>题可以节省时间,是可以接受的。<BR>&nbsp;<BR>使用计数的当前值可以在/proc/modules中每一项的第3个域中找到。这个文件显式系统<BR>中当前共加载了那些模块,每一项对应一个模块。其中的域包括,模块名,模块使用的<BR>页面数和当前使用计数。这是一个/proc/modules样例:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>(autoclean)标志表明模块由kerneld管理(见第11章)。较新的内核中又加入了一些新<BR>的标志,除了一件事外,/proc/modules的基本结构完全相同:在内核2.1.18和更新的版<BR>本中,长度用字节计而不是页面计。<BR>&nbsp;<BR>卸载<BR>要卸载一个模块就要使用rmmod命令。由于无需连编,它的任务远比加载简单。这个命令<BR>调用系统调用delete_module,如果使用计数为0它又调用模块的cleanup_module。<BR>&nbsp;<BR>cleanup_module实现负责注销所有由模块已经注册了的项目。只有符号表是自动删除的<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>cleanup_module实现负责注销所有由模块已经注册了的项目。只有符号表是自动删除的<BR>。<BR>&nbsp;<BR>使用资源<BR>模块不使用资源是无法完成自己的任务的,这些资源包括内存,I/O端口和中断,如果你<BR>要用DMA控制器的话,还得有DMA通道。<BR>&nbsp;<BR>做为一个程序员,你一定已经习惯了内存分配管理,在这方面编写内核代码没什么区别<BR>。你的程序使用kmalloc分配内存,使用kfree释放内存。除了kmalloc多一个参数,优先<BR>级,外,它们和malloc,free很相似。很多情况下,用优先级GFP_KERNEL就可以了。缩<BR>写GFP代表“Get&nbsp;Free&nbsp;Page(获取空闲页面)。”<BR>&nbsp;<BR>与此不同,获取I/O端口和中断乍听起来怪怪的,因为程序员一般同用显式的指令访问它<BR>们,不必让操作系统了解这些。“分配”端口和中断与分配内存不同,因为内存是从一<BR>个资源池中分配,并且每个地址的行为是一样的;I/O端口都各有自己的作用,而且驱动<BR>程序需要在特定的端口上工作,而不能随便使用某个端口。<BR>&nbsp;<BR>端口<BR>对于大多数驱动程序而言,它们的典型工作就是读写端口。不管是初始化还是正常工作<BR>的时候,它们都是这样的。为了避免其他驱动程序的干扰,必须保证设备驱动程序以独<BR>占方式访问端口――如果一个模块探测因自己的硬件而写某个端口,而恰巧这个端口又<BR>是属于另一个设备的,这之后一定会发生点怪事。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>为了防止不同设备间的干扰,Linux的开发者决定实现端口的请求/释放机制。然而,未<BR>授权的对端口的访问并不会产生类似于“段失效”那样的错误――硬件无法支持端口注<BR>册。<BR>&nbsp;<BR>从文件/proc/ioports可以以文本方式获得已注册的端口信息,就象下面的样子:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>文件中的每一项是有驱动程序锁定的范围(以十六进制表示)。在这些被释放前,其他<BR>驱动程序不允许访问这些端口。<BR>&nbsp;<BR>避免冲突有两个途径。首先,向系统增加新设备的用户检查/proc/ioports,然后在配置<BR>新设备使用空闲端口――这种方法假设设备可以通过跳线进行配置。然后,当软件驱动<BR>程序初始化自己时,它能自动探测新设备而对其他设备无害:驱动程序不会探测已由其<BR>他驱动程序使用的I/O端口。<BR>&nbsp;<BR>事实上,基于I/O注册的冲突避免对于模块化驱动程序很合适,但对于连编到内核里的驱<BR>动程序来说却可能失败。尽管我们不涉及这种驱动程序,但还是很是必要注意到,对于<BR>一个在启动时初始化自己的驱动程序来说,由于它要使用之后会被注册的端口,很可能<BR>会造成对其他设备的误配置。虽然如此,还是没有办法让一个符合规范的驱动程序与已<BR>配置好的硬件交互,除非以前加载的驱动程序不注册它的端口。基于以上原因,探测ISA<BR>设备是件很危险的事,而且如果随正式Linux内核发行的驱动程序为了因与尚未加载的模<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>设备是件很危险的事,而且如果随正式Linux内核发行的驱动程序为了因与尚未加载的模<BR>块对应的设备交互,拒绝在模块加载时执行探测功能。<BR>&nbsp;<BR>设备探测的问题是因为只有一种方法标别设备,即通过写目标端口然后再读的方法――<BR>处理器(而且是任何程序)只能查看数据线上的电子信号。驱动程序编写者知道一旦设<BR>备连接到某个特定的端口上,它就会响应相应的查询代码。但是如果另一个设备连到了<BR>端口上,程序仍然会写这个设备,但天知道它会怎么响应这个异常的探测操作。有时可<BR>以通过读外设的BIOS,查看一个已知的字串来避免端口探测;已有若干SCSI设备使用了<BR>这种技术,但并不是每个设备都要有自己的BIOS。<BR>&nbsp;<BR>一个符合规范的驱动程序应该调用check_region查看是否某个端口区域已由其他驱动程<BR>序锁定,之后就用request_region将端口锁住,当驱动程序不再使用端口时调用release<BR>_region释放端口。这些函数的原型在&lt;linux/ioports.h&gt;中。<BR>&nbsp;<BR>注册端口的典型顺序如下所示(函数skull_probe_hw包含了所有设备相关代码,这里没<BR>有出现):<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>在cleanup_module里释放端口:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>系统也使用了一套类似的请求/释放策略维护中断,但注册/注销中断比处理端口复杂,<BR>整个过程的详细解释将放在第9章“中断处理”中介绍。<BR>&nbsp;<BR>与前面讲到的关于设施的注册/注销相似,对资源的请求/释放方法也适合使用已勾勒的<BR>基于goto的实现框架。<BR>&nbsp;<BR>对于编写PCI设备驱动程序的人来说,不存在这里所讲的探测问题。我将在第15章“外部<BR>总线简介”中介绍。<BR>&nbsp;<BR>ISA内存<BR>本节技术性很强,如果你对处理硬件问题不是很有把握,可以简单跳过这节。<BR>&nbsp;<BR>在Intel平台上,ISA槽上的目标设备可能会提供片上内存,范围在640KB到1MB之间(0xA<BR>0000到0xFFFFF);这也是设备驱动程序可以使用的一类资源。<BR>&nbsp;<BR>这种内存部件反映了8086处理器那个时代,当时8086的寻址只有一兆的大小。PC设计人<BR>员决定,低端的640KB当做RAM,而保留另外的384KB用于ROM和内存映射设备。今天,即<BR>便是最强力的个人电脑也还有这个在第一兆字节里的空洞。Linux的PC版保留了这片内存<BR>,根本不考虑使用它。本节给出的代码可以让你访问这个区域的内存,但它仅限于x86平<BR>台,而且Linux内核要至少是2.0.x的,x是多少都可以。2.1版改变了物理内存的访问方<BR>式,比如,640KB-1MB这段范围内的I/O内存就不能再这样访问。访问I/O内存的正确方式<BR>是第18章“硬件管理”“低1M内的ISA内存”小节中的内容,这超出了本章的范围。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>是第18章“硬件管理”“低1M内的ISA内存”小节中的内容,这超出了本章的范围。<BR>&nbsp;<BR>尽管内核提供了端口和中断的请求/释放机制,当前它还是没能提供给I/O内存类似的机<BR>制,所以你得自己做了。如果我能理解Linus是如何看待PC体系结构的化,这里给的方法<BR>就不会变化了。<BR>&nbsp;<BR>有时某个驱动程序需要在初始化时探测ISA内存;例如,我需要告诉视频截取器(frame<BR>grabber)在哪映射截取的图象。问题是,如果没有探测方法,我将无法辨别那段范围内<BR>哪块内存正在使用。人们需要能够辨别3种不同的情况:映射了RAM,有ROM(例如,VGA<BR>BIOS),或者那段区域空闲。<BR>&nbsp;<BR>skull样例给出一种处理这些内存的方法,但由于skull和物理设备无关,它打印完640KB<BR>-1MB这段内存区域的信息后就退出了。然而,有必要谈一谈用于分析内存的代码,因为<BR>它必须处理一些竞争条件。竞争条件就是这样一种情形,两个任务可以竞争同一个资源<BR>,而且未同步的操作可能会损坏系统。<BR>&nbsp;<BR>尽管驱动程序编写者无需处理多任务,我们还是必须记住,中断可能在你的代码中间发<BR>生,而且中断处理函数可能会不提醒你就修改全局量。尽管内核提供了许多工具处理竞<BR>争条件,下面给得出的简单规则阐述了处理这个问题的方法;对这个问题的彻底对策将<BR>在第9章的“竞争条件”小节中给出。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果仅仅是读取共享的量,而不是写,将其声明为volatile,要求编译器不对<BR>其进行优化。这样,编译好的代码在每次源码读取它时读取这个量了。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>其进行优化。这样,编译好的代码在每次源码读取它时读取这个量了。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果代码需要检查和修改这个值,必须在操作期间关闭中断,这样可以防止其<BR>他进程在我们检查过这个值后,但恰恰又在我们修改这个量之前修改这个量。<BR>&nbsp;<BR>我们建议采用如下关闭中断的顺序:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>这里cli代表“clear&nbsp;interrupt&nbsp;flag(清除中断标志)”。上面出现的函数都定义在&lt;a<BR>sm/system.h&gt;中。<BR>&nbsp;<BR>应该避免使用经典的cli和sti序列,因为有时你无法在关闭中断前断定中断是否打开了<BR>。如果此时调用sti就是产生很不规则的错误出现,很难追踪这样的错误。<BR>&nbsp;<BR>由于那段内存只能通过写物理内存和读取检查才能标别,而且如果测试期间有中断的化<BR>,有可能会被其他程序修改,因此检查RAM段的代码同时利用了volatile声明和cli。下<BR>面的这段代码并不是很简单,如果一个设备正在象它的内存写数据,而这段代码又在扫<BR>描这段区域,它就会误认为这段区域是空闲区。好在这样的情况很少发生。<BR>&nbsp;<BR>在下面的源代码中,每个printk都带有一个KERN_INFO前缀。这个符号拼接在格式字串前<BR>面做消息的优先级,它定义在&lt;linux/kernel.h&gt;中。这个符号展开后与本章开始的hello<BR>..c中使用的&lt;1&gt;字串很相似。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>..c中使用的&lt;1&gt;字串很相似。<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>如果你在探测时注意恢复你所修改的字节,探测内存不会造成与其他设备的冲突。*<BR>&nbsp;<BR>作为一个细心的读者,你可能会知道在15MB-16MB地址域内的ISA内存是怎么回事。很不<BR>幸,那是个更棘手的问题,我们将在第8章的“1M以上的ISA内存”小节中讨论。<BR>&nbsp;<BR>自动和手动配置<BR>根据系统的不同,驱动程序需要了解的若干参数也会随之变化。例如,设备必须了解硬<BR>件的I/O地址或内存区域。<BR>&nbsp;<BR>注意,本节所讨论的大部分问题并不适用于PCI设备(第15章介绍)。<BR>&nbsp;<BR>根据设备的不同,除了I/O地址外,还有一些其他参数会影响系统的驱动程序的行为,如<BR>设备的品牌和发行号。驱动程序为了正确地工作有必要了解这些参数的具体值。用正确<BR>的数值设置驱动程序(即,配置它)是一项需要在初始化期间完成的复杂的任务。<BR>&nbsp;<BR>基本说来,有两种方式可以获得这些正确的数值:或者是用户显式地给出它们,或者是<BR>驱动程序自己探测。无疑,自动探测是最好的驱动程序配置方法,而用户配置则是最好<BR>实现的;作为驱动程序编写者的一种权衡,他应该尽可能地实现自动配置,但又允许用<BR>户配置作为一种可选的方式替代自动配置。这种配置方法的另一个好处就是,在开发期<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>户配置作为一种可选的方式替代自动配置。这种配置方法的另一个好处就是,在开发期<BR>间可以给定参数,从而不用自动探测,可以在以后实现它。<BR>&nbsp;<BR>insmod在加载时接受命令行中给定的整数和字串值,可以给参数赋值。这条命令可以修<BR>改在模块中定义的全局变量。例如,如果你的源码中包含了这些变量:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>那么你就可以使用如下命令加载模块:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>例子里使用了printk,它可以显式,当init_module被调用时,赋值已经发生了。注意,<BR>insmod可以给任何整型或字符指针变量赋值,不管它们是否是公共符号表中的一部分。<BR>但对于声明为数组的串是不能在加载时赋值的,因为它已经在编译时解析出来了,以后<BR>就不能修改了。<BR>&nbsp;<BR>自动配置可以设计为按如下方式工作:“如果配置变量是默认值,就执行自动探测;否<BR>则,保留当前值。”为了让这种方法可以工作,“默认”值应该不是任何用户可以在加<BR>载时设定的值。<BR>&nbsp;<BR>下面这段代码给出了skull是如何自动探测设备的端口地址的。在这个例子中,使用自动<BR>探测查找多个设备,而手动配置只限于一个设备。注意,函数skull_detect在上面已经<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>探测查找多个设备,而手动配置只限于一个设备。注意,函数skull_detect在上面已经<BR>给出了,而skull_init_board负责完成设备相关的初始化工作,这里没有给出。<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>为了方便用户在insmod命令行中给出相应的参数,而且如果这些符号不会放到主符号表<BR>中的话,实际使用的驱动程序可以去掉配置变量的前缀(在本例中就是skull_)。如果<BR>它们确实要放到主符号表中,好的办法就是声明两个符号:一个没有前缀,在加载时赋<BR>值,一个有前缀,用register_symtab放到符号表中。<BR>&nbsp;<BR>在用户空间编写驱动程序<BR>到现在为止,一个首次接触内核问题的Unix程序员困难会对编写模块非常紧<BR>--<BR>&nbsp;<BR><FONT 
      color=#00ff00>※&nbsp;来源:.华南网木棉站&nbsp;bbs.gznet.edu.cn.[FROM:&nbsp;202.38.196.234]</FONT><BR>--<BR><FONT 
      color=#00ffff>※&nbsp;转寄:.华南网木棉站&nbsp;bbs.gznet.edu.cn.[FROM:&nbsp;211.80.41.106]</FONT><BR>--<BR><FONT 
      color=#0000ff>※&nbsp;转寄:.华南网木棉站&nbsp;bbs.gznet.edu.cn.[FROM:&nbsp;211.80.41.106]</FONT><BR>--<BR><FONT 
      color=#ffff00>※&nbsp;转载:.南京大学小百合站&nbsp;bbs.nju.edu.cn.[FROM:&nbsp;211.80.41.106]</FONT><BR>&nbsp;<BR>--<BR><FONT 
      color=#ff0000>※&nbsp;转载:·饮水思源&nbsp;bbs.sjtu.edu.cn·[FROM:&nbsp;211.80.41.106]</FONT><BR></P></FONT>
      <P align=center><A href="http://joyfire.net/lsdp/index.htm"><FONT 
      color=#ffffff size=2>目录页</FONT></A> | <A 
      href="http://joyfire.net/lsdp/3.htm"><FONT color=#ffffff 
      size=2>上一页</FONT></A> | <A href="http://joyfire.net/lsdp/5.htm"><FONT 
      color=#ffffff size=2>下一页</FONT></A></P></SPAN></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>
  <TBODY>
  <TR>
    <TD colSpan=3 height=2>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -