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

📄 (ldd) ch17-最新进展(转载).txt

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 3 页
字号:
       
      copy_from_user_ret(to, from, size);
       
      当在2.0上编译时,这些扩展为memcpy_fromfs;而在2.1上,则使用本身的函数。_ret一
      类在2.0上从不会返回,因为复制函数不会失败。
       
       
       
      我个人比较喜欢这种实现兼容性的方法,但这并不是唯一的方法。在我的示例代码中,
      任何用户空间的访问(除了用来read或write的缓冲区,它们已经事先检查过了)之前,
      verify_area_20必须被调用。另一种方法更加忠实于2.1的语义,即当用2.0时,在每个g
      et_user 和copy_from_user之前自动生成一个verify_area。这个选择在源码级要更清晰
      一些,但在版本2.0上编译时效率相当低,包括代码大小和执行时间。
       
      可以同时在2.0和2.1上编译的示例代码,如scull模块,可以在目录v2.1/scull中找到。
      我不觉得这个代码足够有趣,因此不在这里给出。
       
       
       
      任务队列

      任务队列
       
      从2.1.30开始的Linux版本不再定义函数queue_task_irq和queue_task_irq_off,因为在
      queue_task上的实际加速不值得花精力维护两个独立的函数。当新机制被加到核心时,
      这就变得明显了。
       
      在源码级,这是2.0和2.1之间唯一的区别;头文件定义了消失的函数简化了从2.0移植驱
      动程序。感兴趣的读者可以查看<asm/spinlock.h>以获得更多的细节。
       
       
       
      中断管理
       
      在2.1的开发中,有些Linux内部被修改了。新核心提供了对内部锁的很好的管理;通过
      使用几个细粒度的锁,而不是全局的锁,竞争条件被避免了,这样也获得了更好的性能-
      ---特别是SMP配置下。
       
      更细的锁机制的一个结果是intr_count不再存在了。2.1.34抛弃了这个全局变量,而布
      尔函数in_interrupt可以取而代之(这个函数从2.1.30开始存在)。目前,in_interrup
      t是在头文件<asm/hardirq.h>中声明的宏,这个头文件又包含在<linux/interrupt.h>中
      。头文件sysdep-2.1.h用intr_count的名义定义了in_interrupt以获得对2.0的向后兼容
      性。
       

       
      注意虽然in_interrupt是个整数,intr_count却是个unsigned long,因此,如果你想打
      印这个值,并在2.0和2.1间可移植,你必须强制将这个值转换为一个显式的类型,并在
      调用printk时指定一个合适的格式。
       
      在2.1.37中中断管理又引入了一个不同:快和慢中断处理程序不再存在了。SA_INTERRUP
      T不被新版本的request_irq使用,但它在处理程序执行以前仍然控制着中断是否被打开
      。如果几个处理程序共享一个中断线,每个可以是个不同的“类型”。中断打开与否依
      赖于第一个被调用的处理程序。当中断处理程序存在时,下半部总是执行。
       
       
       
      位操作
       
      2.1.37稍微改变了在<asm/bitops.h>中定义的位操作的作用。现在函数set_bit及其相关
      者返回void,而新的类似test_and_set_bit的函数已被引入。新的函数集有如下原型:
       
             void set_bit(int nr, volatile void * addr);
       
      void clear_bit(int nr, volatile void * addr);
       
      void change_bit(int nr, volatile void * addr);
       

       
      int test_and_set_bit(int nr, volatile void * addr);
       
      int test_and_clear_bit(int nr, volatile void * addr);
       
      int test_and_change_bit(int nr, volatile void * addr);
       
      int test_bit(nr, addr);
       
      如果你想获得与2.0的后向兼容性,你可以在你的模块中包含sysdep-2.1.h,并使用新的
      原型。
       
       
       
      转换函数
       
      版本2.1.10引入了一个新的转换函数,在<asm/byteorder.h>中声明。这些函数可以用来
      访问多字节值,只要这个值已知是小印地安字节序或大印地安字节序。因为这些函数为
      写驱动程序代码提供了很好的快捷方式,头文件sysdep-2.1.h在较早的版本就已经定义
      了它们。
       
      2.1核心源码提供的本身实现比sysdep-2.1.h提供的可移植的实现要快,因为它可以利用
      体系相关的功能。

      体系相关的功能。
       
      新函数对应下面的原型,其中le表示小印地安字节序,be表示大印地安字节序。注意编
      译器并不强制严格的数据类型化,因为大多数函数都是预处理宏;下面给出的类型仅供
      参考。
       
             __u16 cpu_to_le16(__u16 cpu_val);
       
             __u32 cpu_to_le32(__u32 cpu_val);
       
             __u16 cpu_to_be16(__u16 cpu_val);
       
             __u32 cpu_to_be32(__u32 cpu_val);
       
             __u16 le16_to_cpu(__u16 le_val);
       
             __u32 le32_to_cpu(__u32 le_val);
       
             __u16 be16_to_cpu(__u16 be_val);
       
             __u32 be32_to_cpu(__u32 be_val);
       
      这些函数在处理二进制数据流时很有用(例如文件系统数据或存在接口板中的信息),

      这些函数在处理二进制数据流时很有用(例如文件系统数据或存在接口板中的信息),
      版本2.1.43又增加了两个新的转换函数集。这些集允许你用指针获取一个值,或是对参
      数指定的一个值进行就地转换。对应与16位小印地安字节序的函数又如下的原型;类似
      的函数对其它类型的整数也存在,导致一共16个函数。
       
             __u16 cpu_to_le16p(__u16 *addr)
       
             __u16 le16_to_cpup(__u16 *addr)
       
             void cpu_to_le16s(__u16 *addr)
       
             void le16_to_cpus(__u16 *addr)
       
      “p”函数类似与指针的复引用,但在需要时转换这个值;“s”函数可以在原地转换一
      个值的印地安字节序(例如,cpu_to_le16s(addr) 和addr=cpu_to_le16(*addr)完成的
      工作是一样的)。
       
      这些函数也在sysdep-2.1.h中定义了。为了避免双重解释的副作用,这个头文件用线入
      函数,而不是预处理宏。
       
       
       
      vremap

      vremap
       
      在第七章“把握内存”中“vmalloc和朋友们”一节描述的vremap函数在版本2.1中得到
      一个新名字。新函数ioremap只是名字变了,它与旧的remap取同样的参数。响应的释放
      函数是iounmap,它代替vfree来释放被重映射的地址。
       
      这个改变是为了明确这个函数的实际作用:将I/O空间重映射到核心空间的一个虚地址。
      头文件sysdep-2.1.h强化了这种新规则,当在2.0版本编译时,它#define了ioremap和io
      unmap到它们2.0的对应者。
       
       
       
      虚拟内存
       
      在核心的版本2.1,Linux的Intel移植对虚拟内存有了一个成熟的视图。早些的版本的内
      存管理一直使用“分段”的方法,这是从核心生命期的开始时期继承下来的。这个改变
      并不影响驱动程序代码,但不管怎样,还是值得一说的。
       
      新的规则与Linux的其它移植匹配的起来。虚拟地址空间被构造成核心居于非常高的地址
      (从3GB往上),而用户地址空间在0-3GB范围。当一个进程运行在“管态”时,它可以
      访问两个空间。另一方面,当它运行在“用户态”时,它不能访问核心空间,因为属于
      核心的页被标记为“管理员”页,处理器阻止了对它们的访问。
       

       
      这种内存布局有助于取消旧的memcpy_to_fs一类的函数,因为已经没有FS段了。核心空
      间和用户空间使用同一个“段”,其区别在于CPU所在的优先级。
       
       
       
      处理核心空间错误
       
      Linux核心的2.1版本对从核心空间处理段错误的能力有一个极大的增强。本章里,我准
      备对其原则给一个快速的概述。新机制对源码的影响在“访问用户空间”中已经描述过
       
      如前面所提到过的,核心的最近版本充分利用了ELF二进制格式,特别是考虑到它的在编
      译的文件中定义用户定义的节的能力。编译器和链接器保证属于同一节的代码段在可执
      行文件中一定是连续的,因此当文件被装载时,在内存中也是连续的。
       
      例外处理是通过在核心可执行映象(vmlinux)中定义两个新节实现的。每次当源码通过co
      py_to_user, put_user, 或其读取的对应者访问用户空间时,一些代码被加到这两个节
      中。尽管这看起来是不可忽略的开销,这个新机制的一个结果是不再需要使用一个昂贵
      的verify_area。而且,如果使用的用户地址是正确的,计算流将不会有一个跳转。
       
      当被访问的用户地址是无效的时,硬件发出一个页面错。错误处理程序(在体系结构特
      定的源码树中的do_page_fault)确认这个错误是一个“不正确的地址”错(与“页不存

      定的源码树中的do_page_fault)确认这个错误是一个“不正确的地址”错(与“页不存
      在”相对),并使用下面的ELF节进行适当的动作:
       
      __ex_table
       
      这节是个指针对的表。每对的第一个指针指向一个可能因错误的用户空间地址而失败的
      指令,第二个值指向一个地址,处理器将在那里找到几条的指令来处理这个错误。
       
      ..fixup
       
      这节包含指令,处理在__ex_table中描述的所有可能的错误。这个表中每对的第二个指
      针指向居于.fixup中的代码。
       
       
       
      头文件<asm/uaccess.h>负责构造所需的ELF节。访问用户空间的每个函数(如put_user
      )扩展为汇编指令,它将指针加到__ex_table并处理.fixup中的错。
       
      当代码运行时,实际的执行路径有一下步骤组成:用于函数“返回值”的处理器寄存器
      被初始化为0(也就是没有错误),数据被传送,返回知被传回调用者。一般的操作的确
      非常快。如果一个异常发生,do_page_fault打印一条消息,查看__ex_table,跳转到.f
      ixup,这里设置返回值为-EFAULT,然后跳转到访问用户空间的指令后位置。
       

       
      新的行为可以用faulty(在v2.1/misc-modules目录)模块来检验。faulty在第四章“调
      试技巧”中“调试系统错误”一节描述。faulty的设备结点通过读取一个短缓冲区界外
      来传送数据到用户空间,这样当读取一个在模块页以上的地址时,会导致一个页面错。
      有趣的是注意到这个错误依赖于使用核心空间中的一个不正确地址,而大多数情况下异
      常是有出错的用户空间地址造成的。
       
      当在PC上用cat命令读faulty时,下面的消息被打印在控制台上:
       
             read: inode c1188348, file c16decf0, buf 0804cbd0, count 4096
       
             cat: Exception at [<c2807b7>](c2807115)
       
      前一行是由faulty的read方法打印的,而后者是由错误处理程序打印的。第一个数字是
      错误指令的地址,而第二个是修正代码(在.fixup节中)的地址。
       
       
       
      其它改变
       
      在2.0和2.1.43之间还有其它一些不同。以我的观点,它们不需要给予特别的关注,因此
      我将迅速地概述一下。
       

       
      proc_register_dynamic在2.1.29中消失了。最近的核心对每个/proc文件使用proc_regi
      ster接口;如果结构proc_dir_entry的low_ino域是0,那么会被分配一个动态的inode号
      。当为2.1.29或更新的核心编译时,头文件sysdep-2.1.h象proc_register一样定义proc
      _register_dynamic;这个在注册的proc_dir_entry结构以0为inode号时是可行的。
       
      在网络接口驱动程序领域,rebuild_header设备方法从2.1.15起有一个新的原型。如果
      你只开发以太网驱动程序,你不会关心这个不同,因为以太网驱动程序不实现它们自己
      的方法;它们依赖于通用的以太网实现。当旧的实现需要时,头文件sysdep-2.1定义了
      宏__USE_OLD_REBUILD_HEADER__。示例模块snull显示了如何使用这个宏,但每必要在这
      里给出。
       
      网络代码的另一个改变影响了结构enet_statistics,它从2.1.25起不再存在。代替它的
      是一个新结构net_device_stats,它在<linux/netdevice.h>中定义,而不是<linux/if_
      ether.h>。新结构与旧结构类似,但是多了两个域存储字节计数器:unsigned long
      rx_bytes, tx_bytes;一个全特征的网络接口驱动程序应该与rx_packets和tx_packets
      一道增加这些计数器,尽管一个快速的计划可能要抛弃这些计数器。核心头文件将enet_
      statistics(老结构的名字)定义为net_device_stats(新结构的名字)以方便已有驱
      动程序的可移植性。
       
      最后,我需要指出current不再是个全局变量x86, Alpha,以及Sparc的核心移植使用了聪
      明的技巧将current存在处理器中。这样核心的开发者努力又挤出了几个CPU周期。这个
      技巧避免了大量的内存访问,有时还能释放一个通用目的寄存器;编译器经常分配处理

      技巧避免了大量的内存访问,有时还能释放一个通用目的寄存器;编译器经常分配处理
      器寄存器来高速缓存几个经常访问的内存位置,而current是经常访问的。在不同的移植
      中使用了不同的技巧以优化访问。Alpha和Sparc版本使用一个处理器寄存器(编译器优
      化不使用的一个)来存储current。而Intel处理器有有限数目的寄存器,编译器可以使
      用它们所有;在这种情况下技巧包括将结构task_struct和核心栈页存储在连续的虚存页
      内。这允许current指针被“编码”在栈指针中。对每个Linux支持的平台,头文件<asm/
      current.h>给出了实际选择的实现。
       
      象所有重要的软件一样,Linux一直在改变着。如果你想为这个最新的、最伟大的核心写
      驱动程序,你需要保持跟上核心的发展。尽管处理不兼容性看起来可能很困难,我们发
      现两点特性:首先,主要的程序设计技巧一直在那里,不太可能改变(至少不常);第
      二,每次改变都变得更好了,经常使你在将来的开发中需要的工作越来越少。
       
       
       
      -----------------------------------------------------------------------------
       
      * 一个lvalue是一个可以作为赋值的左操作数的表达式。例如,count,
      v[34+check()], 和*((prt+offset)->field)是lvalue;i++,32, 和cli()都不是。
       
      --
      --
       

      现两点特性:首先,主要的程序设计技巧一直在那里,不太可能改变(至少不常);第
      二,每次改变都变得更好了,经常使你在将来的开发中需要的工作越来越少。
       
       
       
      -----------------------------------------------------------------------------
       
      * 一个lvalue是一个可以作为赋值的左操作数的表达式。例如,count,
      v[34+check()], 和*((prt+offset)->field)是lvalue;i++,32, 和cli()都不是。
       
      --
     

⌨️ 快捷键说明

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