基于Linux平台PCI设备驱动程序设计(3)

发表于:2007-07-04来源:作者:点击数: 标签:
2.4 访问I/O和内存空间 配置项PCI_BASE_ADDRESS_0 到PCI_BASE_ADDRESS_5表示PCI外设的六个地址区段(这里的“区段”指一个PCI地址范围),每个区段可以由内存或I/O位置组成,或者根本不存在。由于PC上的I/O空间已经相当拥挤,且有的处理器(如Alpha)自身没

  2.4 访问I/O和内存空间
  配置项PCI_BASE_ADDRESS_0 到PCI_BASE_ADDRESS_5表示PCI外设的六个地址区段(这里的“区段”指一个PCI地址范围),每个区段可以由内存或I/O位置组成,或者根本不存在。由于PC上的I/O空间已经相当拥挤,且有的处理器(如Alpha)自身没有I/O空间,因此大多数设备用一个内存区段代替它们的I/O 端口。
  PCI定义的I/O空间是一个32位的地址空间,如果设备使用64位的地址总线,那么它可以为每个区段用两个连续的PCI_BASE_ADDRESS寄存器在64位的内存空间来声明区段。
  然后我们就可以用前面讲到的函数来读写区段地址了, 例如:
  pcibios_read_config_dword(bus,fun, PCI_BASE_ADDRESS_0,&port);
  将PCI_BASE_ADDRESS_0代表的地址读到port中。
  pcibios_write_config_dword(bus,fun, PCI_BASE_ADDRESS_3,val);
  将val的值写到PCI_BASE_ADDRESS_3中。
  如何访问I/O端口呢?
  在已得到了I/O端口地址的前提下,可以使用Linux内核头文件中定义的函数访问I/O端口:
  unsigned char inb(unsigned short port);
  void outb(unsigned char byte,unsigned short port);
  按字节读写8位端口。
  unsigned short inw(unsigned short port);
  void outw(unsigned short word,unsigned short port);
  按字宽度读写16位端口。
  unsigned long inl(unsigned short port);
  void outl(unsigned long word,unsigned short port);
  按双字读写32位端口。
  注意,port参数在x86平台上定义为unsigned short,但在Alpha平台上定义为unsigned long。
  第三章 可加载的内核模块:
  3.1 Linux内核与驱动程序
  在Linux系统中,若干并发进程执行着不同的任务。每个进程都可能有获得系统资源的要求。内核是一整块可执行代码,它负责处理所有这样的请求。内核可以被划分为以下这些部分:进程管理、内存管理、文件系统、设备控制、网络
  Linux有一个很好的特性,即通过加载模块可以扩展内核代码,也就是说可以随时增加系统的功能。而本文所要讨论的PCI设备驱动程序其实也是加载到内核中的一个模块。
  世界各地钻研Linux内核的人群当中,大多是在写设备驱动程序。尽管每个驱动程序都不一样,而且还需知道自己设备的特殊细节,但是这些设备驱动程序的许多原则和基本技术技巧都是一样的。
  在编写驱动程序时,程序员应该注意以下问题:不同用户有不同的需求,程序员编写内核代码访问硬件时,不能强迫用户采用某种特定的策略。设备驱动程序应该仅仅处理硬件,将如何使用硬件的问题留给应用程序。我们可以这样来看待我们所编写的驱动程序:它是位于应用层与实际设备之间的软件。程序员可以使不同的驱动程序提供不同的能力,甚至相同的设备也可以提供不同的能力,只要使用不同的驱动程序即可。
  3.2 模块与应用程序
  3.2.1 内核模块与应用程序之间的区别
  一个应用程序从头到尾完成一个任务;而模块是可以在系统启动之后任何时刻动态连接到核心的代码块,它们可以在系统不再需要它们时从核心删除并卸载。init_module( )(模块的入口点)在加载模块时被调用,其任务就是为以后调用模块的函数做准备;模块的第二个入口点,cleanup_module,仅当模块被下载前才被调用。能够卸载是模块化的优良特性之一,这可以使程序开发者减少开发时间:无需每次都花很长的时间开关机就可以测试所编写的驱动程序。
  内核编程和应用程序编程还有一个区别,就是它们出错后所造成的后果不同:在应用程序开发期间,段违例是无害的,利用调试器可以轻松地跟踪到引起问题的错误之处;然而内核失效却是致命的,即使不至于使整个系统崩溃,那至少会使当前进程无法继续运行。
  在涉及到内核模块与应用程序之间的区别时,还得注意一下“名字空间污染”问题:即存在很多函数和全局变量时,它们的名字已不再富有足够的意义来很容易地区分彼此的问题。在编写应用程序时,程序员就必须花大量的精力来记住某些“保留”名,并为新符号寻找新的唯一的名字。而在编写内核代码时如果出现“名字空间污染”问题,那对程序员来说简直是无法容忍的,因为即便是最小的模块也要连接到整个内核中。防止此类问题出现的方法是把所有自己定义的符号都声明为static。此外,也可以通过声明一个符号表来避免对所有符号都使用static声明。
  3.2.2 用户空间和内核空间
  操作系统要为程序提供一个计算机硬件一致的视图;同时,操作系统有处理程序的独立操作,并防止对资源的未经授权的访问。这就要求CPU具有可以防止系统软件免受应用软件干扰的保护机制,而每种现代处理器都能实现这种功能。实现的方案就是在CPU内部实现不同的操作模式(或级),不同的级有不同的权限,而且某些操作不允许在最低级使用,程序代码只能从一个级切换到另一个级。在Linux系统中,执行态分最高级(也称为“管理员态”)和最低级(也称为“用户态”),它们分别对应“内核空间”和“用户空间”。模块就是在“内核空间”运行的,而应用程序则是在“用户空间”中运行的。
  3.3 模块的基本结构
  内核模块至少必须包含两个函数:init_module和cleanup_module。第一个函数是在把模块加载入内核时调用的;第二个函数则是在删除该模块时调用。一般说来,init_module向内核注册模块所能提供的所有新功能,即可以由应用程序使用的新功能。函数cleanup_module的任务是清除掉init_module所做的一切,这样,这个模块就被安全地卸载了。
  
  

原文转自:http://www.ltesting.net