文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>CH2:Building and Running Modules

CH2:Building and Running Modules

时间:2006-09-17  来源:aaronwong

2.5 Using Resources

一个模块要完成其特定功能,必须要使用相应的系统资源,例如内存、I/O端口、外部存储器、中断线(interrupt line)和DMA通道。

编写内核代码与编写应用程序一样需要管理内存的分配。使用kmalloc或kfree来获取或释放内存空间,并通常带优先级GFP_KERNEL或GFP_USER;GFP(get free page)。内存分配详见第7章。

通常各个设备需要分派特定的独占端口,因而必须确保各驱动程序要使用的端口或其他系统资源未被其他设备占用。

 

2.5.1 I/O Ports and I/O Memory

典型驱动程序的工作在大多数时候都是对I/O口和I/O memory(统称I/O区)进行读写,包括设备的初始化以及正常工作时。必须保证一个设备的驱动以独占的方式访问其I/O区域。

避免设备间I/O冲突的主要方法是使用请求/释放机制来管理I/O资源。注意这个机制是一个协助系统管理资源的软机制,而与硬件特性无关(硬件可以支持也可以不支持)。例如,对I/O口的非法访问并不产生象“段失效”之类的错误,因为硬件不支持端口注册。

文件/proc/ioports中保存了已注册的端口资源,每一条目指定了以十六进制形式给出的由某一驱动程序或硬件独占的端口空间。

 

Ports

当往系统添加新设备而该设备的I/O空间由跳线来设置时,可使用/proc/ioports文件来避免端口冲突:用户可检查已使用的端口并为新设备分配可用的I/O空间。

事实上,更重要的是ioports文件背后的数据结构,当驱动程序对设备进行初始化时,它就知道哪些端口已经被使用;驱动需要探测I/O端口来侦测新设备时,它不会去探测已被其他设备或驱动所使用的端口。

用于访问I/O寄存器的编程接口通过以下三个函数实现:

int check_region(unsigned long start, unsigned long len);

struct resource *request_region(unsigned long start, unsigned long len, char *name);

void release_region(unsigned long start, unsigned long len);

驱动程序可调用check_region函数来查看一个端口区域是否可用,若否则返回一个负的错误代码(如-EBUSY或-EINVAL)。request_region函数则用于实际分配端口空间,若申请并分配成功则返回一个非NULL指针值。驱动程序不必使用或保存返回的实际指针,而只需要检查是否返回NULL(仅当内核的资源管理子系统对该函数进行内部调用时才会用到这个返回的实际指针)。当驱动程序不再使用端口时则调用release_region释放端口。这三个函数实际上是声明于<linux/ioport.h>中的宏。

注册端口的典型顺序如下面的示例驱动程序skull所示(函数skull_probe_hw包含设备相关代码,因而这里没有给出具体代码):

# include <linux/ioport.h>

# include <linux/errno.h>

static int skull_detect(unsigned int port, unsigned int range)

{

       int err;

 

       if ((err=check_region(port,range))<0)    return err; /* busy */

       if (skull_probe_hw(port, range) !=0)          return –ENODEV; /* not found */

       request_region(port, range, “skull”);              /* Can’t fail */

       return 0;

}

程序首先查看所请求的端口区域是否可用,如果端口不可分配,则不会去搜索硬件。实际的端口分配要等到系统发现硬件设备之后才进行。request_region不会出现调用失败的情况,因为内核在同一时刻只能加载一个模块,因此在硬件检测阶段不会出现由于其他模块插入而抢走端口的情况。

任何由驱动指派的I/O口最后都要释放,可在cleanup_module中调用release_region来释放:

static void skull_release(unsigned int port, unsigned int range)

{

       release_region(port, range);

}

 

Memory

       与I/O端口类似,I/O memory信息在/proc/iomem文件中。

       关于驱动程序,对I/O memory的寄存器的访问方式与I/O端口是一样的,因为它们基于相同的内部机制。也有三个函数:

       int check_mem_region(unsigned long start, unsigned long len);

       int request_mem_region(unsigned long start, unsigned long len, char *name);

       int release_mem_region(unsigned long start, unsigned long len);

       典型的驱动程序通常已经知道其自己的I/O memory空间,因而相对于前面I/O端口的注册代码来说就缩减为如下形式:

       if (check_mem_region(mem_addr, mem_size)){

              printk(“drivername:memory already in use\n”);

              return –EBUSY;

       }

       request_mem_region(mem_addr, mem_size, “drivername”);

       即省去了对申请memory空间是否成功的判断,因为申请的总是预先特定为某一设备分配或说预留的I/O memory空间。

 

2.5.2 Resource Allocation in Linux 2.4

该部分描述资源分配机制。基本的资源分配函数(request_region等)仍通过宏来实现并且仍普遍使用,这是为了与以前的内核版本相兼容。

Linux资源管理可以控制任意资源,并以分层方式进行管理。已知的整体资源可以划分为小的子集——例如,对于一个特定的总线插槽相关的资源,如有必要,各个驱动程序可以进一步细分各自占用的空间。

资源空间通过一个resource结构体来描述,它在<linux/ioport.h>中声明:

struct resource{

       const char *name;

       unsigned long start, end;

       unsigned long flags;

       struct resource *parent, *sibling, *child;

}

顶层(root)资源在系统启动时创建。例如,描述I/O端口空间的资源结构体创建如下:

struct resource ioport_resource=

       {“PCI IO”, 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO};

因此,资源名称为PCI IO,空间范围从0到IO_SPACE_LIMIT,后者根据不同的硬件平台而定,可以是0xffff(16位地址空间,如x86,IA-64,Alpha,M68k和MIPS),0xffff ffff(32位:SPARC,PPC,SH)或0xffff ffff ffff ffff(64位:SPARC64)。

一个给定的资源的子区域可以用allocate_resource来创建。例如,PCI的初始化就创建了一项新资源,而实际上一个物理设备只用到其中的一个区域。当PCI代码读这些端口或存储区域时,就会为它们创建一项新资源,并在ioport_resource或iomem_resource下分派这些端口。

驱动程序可以请求某一特定资源的子集(实际上就是一个global resource的subrange)并通过调用__request_region将其标记为busy,这时就返回一个指针指向一个新的struct resource类型的数据结构,该数据结构描述了被请求的资源(若请求出错则返回NULL)。该结构体已经是global resource tree的一部分,驱动程序不可随意使用它。

感兴趣的话可以浏览一下kernel/resource.c中的资源以及相关细节。

 

分层机制有很多优点。一是使得系统的I/O结构在内核数据结构中非常明晰,资源分配结果保存在文件/proc/ioports,例如:

e800-e8ff :Adaptec AHA-2940u2/W / 7890

e800-e8be:aic7xxx

空间e800-e8ff分配给一个Adaptec卡,它告诉PCI总线驱动自己的身份。aic7xxx驱动程序则请求了该空间的大部分。

 

2.6 Automatic and Manual Configuration

驱动程序所需要的一些参数可能会依系统而不同。例如,driver必须知道硬件的实际I/O地址,或者存储器空间(对于设计优良的总线接口不会存在这样的问题,只用于ISA设备)。有时你不得不传递必要的参数给driver以帮助它找到对应的设备或使能/禁止某些特性。

除I/O地址外可能还有其他参数影响driver的行为,例如设备品牌和序列号。Driver必须知道这些信息才能正确工作。在设备初始化时需要用相关参数的正确值来设置driver。

有两种方法来得到正确值:要么用户指定,要么driver自动检测。前者易于实现,后者则无疑是更好的driver configuration的方法。应尽量用后者实现。当然开发的初始阶段可以手动配置,以专注于设备模块的驱动实现,其后再实现自动配置。

许多driver也保留一些配置选项给用户选择,例如IDE(Integrated Device Electronics)driver允许用户控制DMA操作选项。因此,即使在加载硬件时driver会对参数自动配置,可能仍需要保留一些配置选项留给用户。

参数值可在加载模块时通过insmod或modprobe来指定;后者还可从配置文件/etc/modules.conf中读取parameter assignment。命令可接受命令行中的整型和string类型值。因此,如果要给一个模块提供一个skull_ival整型参数和一个skull_sval字符串参数,则在模块加载时可在命令行下用insmod如下设置参数:

insmod skull skull_ival=666 skull_sval=”the beast”

注意,在使用insmod来改变模块参数值之前,模块必须先使参数有效,即事先在驱动程序中使用宏MODULE_PARM来声明这些参数,该宏定义在module.h中。MODULE_PARM使用两个参数:一是变量名,二是描述该变量类型的字符串。该宏应该放在函数外,并通常放在源程序的头部。上面提到的两个参数可以在源程序中如下声明:

int skull_ival=0;

char *skull_sval;

MODULE_PARM (skull_ival, “i” );

MODULE_PARM (skull_sval, “s”);

目前支持5种类型的模块参数:b,字节;h,短整型(两个字节);I,整型;l,长整型;以及s,字符串型。如果是字符串型,则需要声明一个指针变量;insmod将为用户提供的参数以及用户设置的相应的参数值分配存储空间。

也可以声明数组,例如:

int skull_array[4];

MODULE_PARM (skull_array, “2-4i”);

声明了长度为4的整型数组,其中“2-4i”表示在命令行下用insmod传递参数值时,最少要给出2个,最多不超过4个整型(i)参数值。更多信息可见<linux/module.h>。

 

还有一个宏是MODULE_PARM_DESC,它允许程序员对模块的参数进行描述。该描述信息保存在目标文件(object file)中;并可使用类似objdump工具来查看,还可以通过自动系统管理工具来显示。给出一个实例如下:

int base_port=0x300;

MODULE_PARM (base_port, “i”);

MODULE_PARM_DESC (base_port, “The base I/O port (default 0x300)”);

所有模块参数必须给一个缺省值;只有当用户显示地告知要改变参数值时insmod才改变它们的值。自动参数配置就可以如此设计:如果待配置变量有缺省值,则进自动检测(autodetection);否则就保持当前值。缺省值需要设置为在实际加载时绝不会需要用户来指定的值。

下面的代码显示了skull是如何自动检测一个设备的端口地址的。该例中,自动检测用于搜索多个设备,而手动配置(manual configuration)则限于一个设备。函数skull_detect已在前面“Ports”部分定义,skull_init_board负责设备相关的(device-specific)初始化,这里没有给出。

/ *

 * port ranges: the device can reside between

 * 0x280 and 0x300, in steps of 0x10. It uses 0x10 ports.

 */

# define SKULL_PORT_FLOOR 0x280

# define SKULL_PORT_CEIL   0x300

# define SKULL_PORT_RANGE 0x010

/ *

 * the following function performs autodetection, unless a specifie

 * value was assigned by insmod to ‘skull_port_base’

 */

 

static int skull_port_base=0;       /* 0 forces autodetection */

MODULE_PARM (skull_port_base, “i”);

MODULE_PARM_DESC (skull_port_base, “Base I/O port for skull”);

 

static int skull_find_hw(void) /* returns the # of devices */

{

       /* base is either the load-time value or the first trial */

       int base=skull_port_base ? skull_port_base

                     : SKULL_PORT_FLOOR;

       int result=0;

       /* loop one time if value assigned, try them all if autodetecting */

       do{

              if (skull_detect(base, SKULL_PORT_RANGE)==0){

                     skull_init_board(base);

                     result++;

              }

              base+=SKULL_PORT_RANGE;    /* prepare for next trial */

       }while (skull_port_base == 0 && base< SKULL_PORT_CEIL);

      

       return result;

}

如果配置变量(configuration variables)仅在驱动程序内部使用(而不发布内核符号表中),则为方便用户在insmod命令行中给出相应的参数,driver writer可以去掉配置变量的前缀(本例中就是skull_)。

 

另外,还有三个宏用于将documentation放入目标文件中:

MODULE_AUTHOR (name)

       将作者名字放入目标文件。

MODULE_DESCRIPTION (desc)

       将对模块的描述放入目标文件。

MODULE_SUPPORTED_DEVICE (dev)

       将一个说明模块所支持的设备的条目放入目标文件。内核源文件建议将这个参数最终用于协助自动模块加载,在这里没有这种作用。

 

2.7 Doing It in User Space

   

        写一个用户程序直接对设备端口进行读写对于初次涉足内核问题的程序员来说更为容易。

 

相关阅读 更多 +
排行榜 更多 +
拼优汇

拼优汇

购物比价 下载
4Read小说

4Read小说

浏览阅读 下载
摩托GP特技

摩托GP特技

体育竞技 下载