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

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

📁 html格式
💻 HTM
📖 第 1 页 / 共 4 页
字号:
            <DIV align=center><FONT color=#ffffff>|</FONT></DIV></TD>
          <TD width="8%" height=4>
            <DIV align=center><A href="mailto:joyfire@sina.com"><FONT 
            color=#ffffff>联系</FONT></A></DIV></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE borderColor=#666666 cellPadding=2 width="90%" align=center border=2>
  <TBODY>
  <TR>
    <TD bgColor=#000000>
      <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>
      <P align=center><FONT face=黑体 color=#ffffff size=6>(LDD) Ch02-编写和运行模块(转载) 
      </FONT></P><SPAN style="LINE-HEIGHT: 1; LETTER-SPACING: 0pt"><FONT 
      color=#ffffff size=3>
      <P>发信人:&nbsp;Altmayer&nbsp;(alt),&nbsp;信区:&nbsp;GNULinux<BR>标&nbsp;&nbsp;题:&nbsp;(LDD)&nbsp;Ch02-编写和运行模块(转载)<BR>发信站:&nbsp;饮水思源&nbsp;(2001年12月13日08:57:09&nbsp;星期四),&nbsp;站内信件<BR>&nbsp;<BR>【&nbsp;以下文字转载自&nbsp;<FONT 
      color=#00ff00>UNIXpost&nbsp;</FONT>讨论区&nbsp;】<BR>【&nbsp;原文由<FONT 
      color=#00ff00>&nbsp;altmayer.bbs@bbs.nju.edu.cn,</FONT>&nbsp;所发表&nbsp;】<BR>&nbsp;<BR>【&nbsp;以下文字转载自&nbsp;<FONT 
      color=#00ff00>altmayer&nbsp;</FONT>的信箱&nbsp;】<BR>第2章&nbsp;编写和运行模块<BR>&nbsp;<BR>&nbsp;<BR>非常高兴现在终于可以开始编程了。本章将介绍模块编程和内核编程所需的所有必要的<BR>概念。我们将要不多的篇幅来编写和运行一个完整的模块。这种专业技术(expertise)<BR>是编写如何模块化设备驱动程序的基础。为了避免一下子给你很多概念,本章仅介绍模<BR>块,不介绍任何类别的设备。<BR>&nbsp;<BR>这里介绍的所有内核内容(函数,变量,头文件和宏)也将在本章最后的参考部分再次<BR>介绍。<BR>&nbsp;<BR>如果你已经座不住了,下面的代码是一个完整的“Hello,&nbsp;World”模块(这个模块事实<BR>上并没什么功能)。它可以在Linux&nbsp;2.0或以上版本上编译通过,但不能低于或等于1.2<BR>,关于这一点本章将在稍后的部分解释*。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>(代码)<BR>&nbsp;<BR>函数printk是由Linux内核定义的,功能与printf相似;模块可以调用printk,这是因为<BR>在insmod加载了模块后,模块就被连编到内核中了,也就可以调用内核的符号了。字符<BR>串&lt;1&gt;是消息的优先级。我之所以在模块中使用了高优先级是因为,如果你使用的是内核<BR>2.0.x和旧的klogd守护进程,默认优先级的消息可能不能显示在控制台上(关于这个问<BR>题,你可以暂且忽略,我们将在第4章,“调试技术”,的“Printk”小节中详细解释)<BR>。<BR>&nbsp;<BR>通过执行insmod和rmmod命令,你可以试试这个模块,其过程如下面的屏幕输出所示。注<BR>意,只有超级用户才能加载&nbsp;托对啬&nbsp;块。<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>正如你所见,编写一个模块很容易。通过本章我们将深入探讨这个内容。<BR>&nbsp;<BR>模块与应用程序<BR>在深入探讨模块之前,很有必要先看一看内核模块与应用程序之间的区别。<BR>&nbsp;<BR>一个应用从头到尾完成一个任务,而模块则是为以后处理某些请求而注册自己,完成这<BR>个任务后它的“主”函数就立即中止了。换句话说就是,init_module()(模块的入口点<BR>)的任务就是为以后调用模块的函数做准备;这就好比模块在说,“我在这,这是我能<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>)的任务就是为以后调用模块的函数做准备;这就好比模块在说,“我在这,这是我能<BR>做的。”模块的第二个入口点,cleanup_module,仅当模块被下载前才被调用。它应该<BR>跟内核说,“我不在这了,别再让我做任何事了。”能够卸载也许是你最喜爱的模块化<BR>的特性之一,它可以让你减少开发时间;你无需每次都花很长的时间开机关机就可以测<BR>试你的设备驱动程序。<BR>&nbsp;<BR>作为一个程序员,你一定知道一个应用程序可以调用应用程序本身没有定义的函数:前<BR>后的连编过程可以用相应的函数库解析那些外部引用。printf就是这样一个函数,它定<BR>义在libc中。然而,内核要仅能连编到内核中,它能调用的仅是由内核开放出来的那些<BR>函数。例如,上面的helllo.c中的printk函数就是内核版的printf,并由内核开放给模<BR>块给使用;除了没有浮点支持外,它和原函数几乎一模一样。<BR>&nbsp;<BR>如图2-1所示,它勾画了为了在运行的内核中加入新函数,是如何调用函数以及如何使用<BR>函数指针的。<BR>&nbsp;<BR>由于没有库连接到模块中,源码文件不应该模块任何常规头文件。与内核有关的所有内<BR>容都定义在目录/usr/include/linux和/usr/include/asm下的头文件中。在编译应用程<BR>序也会间接使用这些头文件;其中的内核代码通过#ifdef&nbsp;__KERNEL__保护起来。这两个<BR>内核头文件目录通常都是到内核源码所在位置的符号连接。如果你根本就想要整个内核<BR>源码,你至少还要这两个目录的头文件。在比较新的内核中,你还可以在内核源码中发<BR>现net和scsi头文件目录,但很少有模块会需要这两个目录。<BR>&nbsp;<BR>内核头文件的作用将稍后需要它们的地方再做介绍,<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>内核头文件的作用将稍后需要它们的地方再做介绍,<BR>&nbsp;<BR>内核模块与应用程序的另一个区别是,你得小心“名字空间污染”问题。程序员在写小<BR>程序时,往往不注意程序的名字空间,但当这些小程序成为大程序的一部分时就会造成<BR>许多问题了。名字空间污染是指当存在很多函数和全局变量时,它们的名字已不再富有<BR>足够的意义来很容易的区分彼此的问题。不得不处理这种应用程序的程序员必须花很大<BR>的精力来单单记住这些“保留”名,并为新符号寻找新的唯一的名字。如果在写内核代<BR>码时出现这样的错误,这对我们来说是无法忍受的,因为即便最小的模块也要连编到整<BR>个内核中。防止名字空间污染的最佳方法是把所有你自己的符号都声明为static的,而<BR>且给所有的全局量加一个well-defined前缀。此外,你还可以通过声明一个符号表来避<BR>免使用static声明,这些内容将在本章的“注册符号表”小节中介绍。即便是模块内的<BR>私有符号也最好使用选定的前缀,这样有时会减轻调试的工作。通常,内核中使用的前<BR>缀都是小写的,今后我们将贯彻这一约定。<BR>&nbsp;<BR>内核编程和应用程序编程的最后一个区别是如何处理失效:在应用程序开发期间,段违<BR>例是无害的,利用调试器可以轻松地跟踪到引起问题的错误之处,然而内核失效却是致<BR>命的,如果不是整个系统,至少对于当前进程是这样的。我们将在第4章“调试系统失效<BR>”小节中介绍如何跟踪内核错误。<BR>&nbsp;<BR>用户空间和内核空间<BR>本节的讨论概而言之就是,模块是在所谓的“内核空间”中运行的,而应用程序则是在<BR>“用户空间”中运行的。这些都是操作系统理论的最基本概念。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>事实上,操作系统的作用就是给程序提供一个计算机硬件的一致的视图。此外,操作系<BR>统处理程序的独立操作,并防止对资源的未经授权的访问。当且仅当CPU可以实现防止系<BR>统软件免受应用软件干扰的保护机制,这些不同寻常的工作才有可能实现。<BR>&nbsp;<BR>每种现代处理器都能实现这种功能。人们选择的方案是在CPU内部实现不同的操作模式(<BR>或级)。不同的级有不同的作用,而且某些操作不允许在最低级使用;程序代码仅能通<BR>过有限数目的“门”从一个级切换到另一个级。Unix系统就是充分利用这一硬件特性设<BR>计而成的,但它只使用了两级(与此不同,例如,Intel处理器就有四级)。在Unix系统<BR>中,内核在最高级执行(也称为“管理员态”),在这一级任何操作就可以,而应用程<BR>序则执行在最低级(所谓的“用户态”),在这一级处理器禁止对硬件的直接访问和对<BR>内存的未授权访问。<BR>&nbsp;<BR>正如前面所述,在谈到软件时,我们通常称执行态为“内核空间”和“用户空间”,它<BR>们分别引用不同的内存映射,也就是程序代码使用不同的“地址空间”。<BR>&nbsp;<BR>Unix通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。执行系统调用的<BR>内核代码在进程的上下文上执行――它代表调用进程操作而且可以访问进程地址空间的<BR>数据。但与此不同,处理中断的代码相对进程而言是异步的,而且与任何一个进程都无<BR>关。<BR>&nbsp;<BR>模块的功能就是扩展内核的功能;运行在内核中的模块化的代码。通常,一个设备驱动<BR>程序完成上面概括的两个任务:模块的某些函数做为系统调用执行,而某些函数则负责<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>事实上,操作系统的作用就是给程序提供一个计算机硬件的一致的视图。此外,操作系<BR>统处理程序的独立操作,并防止对资源的未经授权的访问。当且仅当CPU可以实现防止系<BR>统软件免受应用软件干扰的保护机制,这些不同寻常的工作才有可能实现。<BR>&nbsp;<BR>每种现代处理器都能实现这种功能。人们选择的方案是在CPU内部实现不同的操作模式(<BR>或级)。不同的级有不同的作用,而且某些操作不允许在最低级使用;程序代码仅能通<BR>过有限数目的“门”从一个级切换到另一个级。Unix系统就是充分利用这一硬件特性设<BR>计而成的,但它只使用了两级(与此不同,例如,Intel处理器就有四级)。在Unix系统<BR>中,内核在最高级执行(也称为“管理员态”),在这一级任何操作就可以,而应用程<BR>序则执行在最低级(所谓的“用户态”),在这一级处理器禁止对硬件的直接访问和对<BR>内存的未授权访问。<BR>&nbsp;<BR>正如前面所述,在谈到软件时,我们通常称执行态为“内核空间”和“用户空间”,它<BR>们分别引用不同的内存映射,也就是程序代码使用不同的“地址空间”。<BR>&nbsp;<BR>Unix通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。执行系统调用的<BR>内核代码在进程的上下文上执行――它代表调用进程操作而且可以访问进程地址空间的<BR>数据。但与此不同,处理中断的代码相对进程而言是异步的,而且与任何一个进程都无<BR>关。<BR>&nbsp;<BR>模块的功能就是扩展内核的功能;运行在内核中的模块化的代码。通常,一个设备驱动<BR>程序完成上面概括的两个任务:模块的某些函数做为系统调用执行,而某些函数则负责<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>程序完成上面概括的两个任务:模块的某些函数做为系统调用执行,而某些函数则负责<BR>处理中断。<BR>&nbsp;<BR>内核中的并发<BR>内核编程新手首先要问的问题之一就是多任务是如何管理的。事实上,除了调度器之外<BR>,关于多任务并没有什么可以多说的,而且调度器也超出了程序员的一般活动范围。你<BR>可能会遇到这些任务,除了掌握如下这些原则外,模块编写者无需了解多任务。<BR>&nbsp;<BR>与串行的应用程序不同,内核是异步工作的,代表进程执行系统调用。内核负责输入/输<BR>出以及系统内对每一个进程的资源管理。<BR>&nbsp;<BR>内核(和模块)函数完全在一个线程中执行,除非它们要“睡眠”,否则通常都是在单<BR>个进程的上下文中执行――设备驱动程序应该能够通过交织不同任务的执行来支持并发<BR>。例如,设备可能由两个不同的进程同时读取。设备驱动程序串行地响应若干read调用<BR>,每一个都属于不同的进程。由于代码需要区别不同的数据流,内核(以及设备驱动程<BR>序)必须维护内部数据结构以区分不同的操作。这与一个学生学习交织在一起的若干门<BR>课程并非不无相似之处:每门课都有一个不同的笔记本。解决多个访问问题的另一个方<BR>法就是避免它,禁止对设备的并发访问,但这种怠惰的技术根本不值的讨论。<BR>&nbsp;<BR>当内核代码运行时,上下文切换不可能无意间发生,所以设备驱动程序无需是可重入的<BR>,除非它自己会调用schedule。必须等待数据的函数可以调用sleep_on,这个函数接着<BR>又调用schedule。不过你必须要小心,存在某些函数会无意导致睡眠,特别是任何对用<BR>户空间的访问。利用“天然非抢占”特性不是什么好的方法。我将在第5章,“字符设备<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>户空间的访问。利用“天然非抢占”特性不是什么好的方法。我将在第5章,“字符设备<BR>驱动程序的扩展操作”的“编写可重入代码”小节中讲解可重入函数。<BR>&nbsp;<BR>就对设备驱动程序的多个访问而言,有许多不同的途径来分离这些不同的访问,但都是<BR>依赖于任务相关的数据。这种数据可以是全局内核变量或是传给设备驱动程序函数的进<BR>程相关参数。最重要的用来跟踪进程的全局变量是current:一个指向struct<BR>task_struct结构的指针,在&lt;linux/sched.h&gt;中定义。current指针指向当前正在运行的<BR>用户进程。在系统调用执行期间,如open或read,当前进程就是调用这个调用的进程*。<BR>如果需要的话,内核代码就可以利用current使用进程相关信息。第5章“设备文件的访<BR>问控制”小节中就有使用这种技术的例子。<BR>&nbsp;<BR>编译器就象外部引用printk一样处理current。模块可以在任何需要的地方引用current<BR>,insmod会在加载时解析出所有对它的引用。例如,如下语句通过访问struct<BR>task_struct中的某些域打印当前进程的进程ID和命令名:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>存储在current-&gt;comm中的命令名是当前进程最后执行的可执行文件的基名。<BR>&nbsp;<BR>编译和加载<BR>本章的剩下部分将介绍编写虽然是无类别但很完整的模块。就是说,模块不属于任何第1<BR>章“设备和模块的类别”中罗列的类别中的任何一个。本章中出现的设备驱动程序称为s<BR>kull,是“Simple&nbsp;Kernel&nbsp;Utility&nbsp;for&nbsp;Loading&nbsp;Localities”的缩写。去掉这个模块<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>kull,是“Simple&nbsp;Kernel&nbsp;Utility&nbsp;for&nbsp;Loading&nbsp;Localities”的缩写。去掉这个模块<BR>提供的范例函数,你可以重用这个模块,向内核加载你自己的本地代码。*<BR>&nbsp;<BR>在我们介绍init_module和cleanup_module的作用之前,首先让我们写一个Makefile来编<BR>译内核可以加载的目标代码。<BR>&nbsp;<BR>首先,在包含任何头文件前,我们需要在预处理器中定义符号__KERNEL__。这个符号用<BR>于选择使用头文件的哪一部分。由于libc包含了这些头文件*,应用程序最终也会包含内<BR>核头文件,但应用程序不需要内核原型。于是就用__KERNEL__符号和#ifdef将那些额外<BR>的去掉。将内核符号和宏开放给用户空间的程序会造成那个程序的名字空间污染。如果<BR>你正在为一台SMP(对称多处理器)机器编译,你还需要在包含内核头文件前定义__SMP_<BR>_。这一要求似乎有点不那么方便,但一旦开放人员找到达成SMP透明的正确方法,它就<BR>会逐渐消失的。<BR>&nbsp;<BR>另一个很重要的符号就是MODULE,必须在包含&lt;linux/module.h&gt;前定义这个符号。除非<BR>要把设备驱动程序编译到内核映象中去,MODULE应该总是定义了的。由于本书所涉及的<BR>驱动程序都不是直接连编到内核中去的,它们都定义了这个符号。<BR>&nbsp;<BR>由于头文件中的函数都是声明为inline的,模块编写者还必须给编译器指定-O选项。gcc<BR>只有打开优化选项后才能扩展内嵌函数,不过它能同时接受-g和-O选项,这样你就可以<BR>调试那些内嵌函数的代码了*。<BR>&nbsp;<BR>最后,为了防止发生令人不愉快的错误,我建议你使用-Wall(全面报警)编译选项,并<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>最后,为了防止发生令人不愉快的错误,我建议你使用-Wall(全面报警)编译选项,并<BR>且还要修改源码去除所有编译器给出的警告,即便这样做会改变你已有的编程风格,你<BR>也要这么做。<BR>&nbsp;<BR>所有我目前介绍的定义和选项都在make使用的CFLAGS变量中。<BR>&nbsp;<BR>除了一个合适的CFLAGS变量外,将要编写的Makefile还需要一个将不同目标文件连接在<BR>一起的规则。这条规则仅当一个模块被分成若干个不同的源文件时才需要,这种并非很<BR>不常见。通过命令ld&nbsp;-r将模块连接在一起,这条命令虽然调用了连接器,但并没有连编<BR>操作。这是因为输出还是一个目标文件,它是输入文件的混合。-r选项的意思是“可重<BR>定位”;&nbsp;输出文件是可重定位的,这是因为它尚未嵌入绝对地址。<BR>&nbsp;<BR>下面的Makefile实现了上述的所有功能,它能建立由两个源文件组成的模块。如果你的<BR>模块是由一个源文件组成的,只要跳过包含ld&nbsp;-r的那项就可以了。<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>上面文件中那个复杂的install规则将模块安装到一个版本相关的目录中,稍后将做解释<BR>。Makefile中的变量VER是从&lt;linux/version.h&gt;中截取的版本号。<BR>&nbsp;<BR>模块编好了,接下来必须把它加载到内核中。正如我前面所说,insmod就是完成这个工<BR>作的。这个程序有点象ld,它要将模块中未解析的符号连编到正在运行的内核的符号表<BR>中。但与连接器不同,它并不修改磁盘文件,而是修改内存映象。insmod有很多命令选<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>中。但与连接器不同,它并不修改磁盘文件,而是修改内存映象。insmod有很多命令选<BR>项(如果想知道细节,可以看man),可以在模块连编到内核前修改模块中的整数值和字<BR>符串值。因此,如果一个模块设计得体,可以在加载时对其进行配置;加载时配置要比<BR>编译时配置更灵活,但不幸的是,有时候仍然有人使用后者。加载时配置将在本章的后<BR>面“自动和手动配置”小节中讲解。<BR>&nbsp;<BR>感兴趣的读者可能想知道内核是怎样支持insmod的:它依赖于kernel/modulec.c中定义<BR>的几个系统调用。sys_create_module为装载模块分配内存(这些内存是由vmalloc分配<BR>的,见第7章“获取内存”中的“vmalloc及其同胞”一节),为了连编模块,系统调用g<BR>et_kernel_syms返回内核符号表,sys_init_module将可重定位目标码复制到内核空间并<BR>调用模块的初始化函数。<BR>&nbsp;<BR>如果你看过了内核源码,你就会发现系统调用的名字都有sys_前缀。所有系统调用都是<BR>这样,其他函数并没有这个约定;当你在源码中查找系统调用时,知道这一点会对你有<BR>所帮助。<BR>&nbsp;<BR>版本相关性<BR>要时刻牢记,对于你想连编的每一个不同版本的内核,你的模块都要相应地编译一次。<BR>每个模块都定义了一个称为kernel_version的符号,insmod检查这个符号是否与当前内<BR>核版本号匹配。较新的内核已在&lt;linux/module.h&gt;中替你定义了这个符号(这也就是为<BR>什么hello.c中没有对它的声明)。这也意味着,如果你的模块是由多个源文件组成的,<BR>你只能有一个源文件包含了&lt;linux/module.h&gt;。与此相反,当你在Linux&nbsp;1.2下编译时,<BR>必须在你的源码中定义kernel_version。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>必须在你的源码中定义kernel_version。<BR>&nbsp;<BR>如果版本不匹配,而你仍然想在不同版本的内核里加载你的模块,可以在insmod命令中<BR>指定-f(“强制”)选项完成,但这个操作不安全,可能会失败。而且很难事先说明要<BR>发生那种情况。由于符号不匹配,加载就会失败,此时你会得到一个错误信息。内核内<BR>部的变化也会造成加载失败。如何这种情况发生了,你可能会在系统运行时得到一个非<BR>常严重的错误,很可能造成系统panic――出于这个缘由,注意版本失配。事实上,通过<BR>内核里的“版本机制”更完美地解决版本失配问题(稍后,第11章“Kerneld和高级模块<BR>化”的“模块内版本控制”小节将介绍这一更先进的内容)。<BR>&nbsp;<BR>如果你需要为某个特定的内核编译模块,你必须在上面的Makefile中包含相应内核的头<BR>文件(例如,通过声明不同的INCLUDEPATH)。<BR>&nbsp;<BR>为了处理加载时的版本相关性,insmod安装特定的路径查询:如果不能在当前目录找到<BR>模块,就在版本相关的目录中查找,如果还失败就在/lib/modules/misc中查找。上面那<BR>个Makefile中的install规则就遵循了这一约定。<BR>&nbsp;<BR>写一个可以在从1.2.13到2.0.x的任一版本的内核上编译的内核是件复杂的任务。模块化<BR>接口已经做了修改,配置越来越容易。你可以看到上面的那个hello.c中,只要你只处理<BR>较新的内核就什么都不用声明。与此不同,可移植的接口如下所示:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>在2.0或更新的内核中,module.h包含了version.h,而且,如果没有定义__NO_VERSION_<BR>_,module.h还定义了kernel_version。<BR>&nbsp;<BR>如果你需要将多个源文件连接在一起组成一个模块,而又有多个文件都需要包含&lt;linux/<BR>module.h&gt;――比如你需要module.h里声明的宏,就可以使用符号__NO_VERSION__。在包<BR>含module.h前定义__NO_VERSION__就可以在你不想要自动声明字符串kernerl_version的<BR>源文件里防止它的发生(ld&nbsp;-r会对一个符号的多处定义报警)。本书中的模块就使用__<BR>NO_VERSION__达成这一目的。<BR>&nbsp;<BR>其他基于内核版本的相关性可以通过预处理的条件编译解决――version.h定义了整数宏<BR>LINUX_VERSION_CODE。这个宏展开后是内核版本的二进制表示,一个字节代表版本发行<BR>号的一部分。例如,1.3.5的编码是66309(即,0x10305)。*利用这个信息,你可以轻<BR>松地判断你正处理的是哪个版本的内核。<BR>&nbsp;<BR>当你检查某个版本时,使用十进制表示是不方便的。为了在一个源文件里支持多个内核<BR>版本,我将用下面的宏通过版本号的3个部分构建版本编码:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>内核符号表<BR>我们已经知道insmod是如何利用公开内核符号来解析未定义符号的了。这张表包含了实<BR>现模块化设备驱动程序所需的全局内核&nbsp;瞑D―函数和变量。可以从文件/proc/ksyms中以<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>现模块化设备驱动程序所需的全局内核&nbsp;瞑D―函数和变量。可以从文件/proc/ksyms中以<BR>文本的方式读取这个公开符号表<BR>&nbsp;<BR>当你的模块被加载时,你声明的任何全局符号都成为内核符号表的一部分,你可以从文<BR>件/proc/ksyms或命令ksyms的结果了解这一点。<BR>&nbsp;<BR>新模块可以使用你开放出来的符号,而且你在其他模块之上堆叠新模块。在主流的内核<BR>源码中也使用了这种模块堆叠的方法:msdos文件系统依赖于fat模块开放出来的符号,<BR>而ppp驱动程序则堆叠在报头压缩模块上。<BR>&nbsp;<BR>在处理复杂对象时,模块堆叠非常有用。如果以设备驱动程序的形式实现一个新的抽象<BR>,它可以提供一个设备相关的插接口。比如,帧缓冲视频驱动程序可以将符号开放给下<BR>层VGA驱动程序使用。每个用户都加载帧缓冲视频驱动程序,然后在根据自己安装的设备<BR>加载相应的VGA模块。<BR>&nbsp;<BR>分层次的模块化简化了每一层的任务,大大缩减了开发时间。这同我们第1章中讨论的机<BR>制与策略分离很相似。<BR>&nbsp;<BR>注册符号表<BR>另一种开放你的模块中的全局符号的方法是使用函数register_symtab,这个函数是符号<BR>表管理的正式接口。这里所涉及的编程接口适用于内核1.2.13和2.0。如果想详细了解2.<BR>1开发用内核所做的变动,请参见第17章“最新发展”。<BR>&nbsp;<BR></P></FONT><FONT 

⌨️ 快捷键说明

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