11.4.2 字符设备驱动程序的注册

具有相同主设备号和类型的每类设备文件都是由device_struct数据结构来描述的,该结构定义于fs/devices.c

struct device_struct {

        const char * name;

        struct file_operations * fops;

};

其中,name是某类设备的名字,fops是指向文件操作表的一个指针。所有字符设备文件的device_struct描述符都包含在chrdevs表中:

 

static struct device_struct chrdevs[MAX_CHRDEV];

 

该表包含有255个元素,每个元素对应一个可能的主设备号,其中主设备号255为将来的扩展而保留的。表的第一项为空,因为没有一个设备文件的主设备号是0

 

chrdevs表最初为空。register_chrdev( )函数用来向其中的一个表中插入一个新项,而unregister_chrdev( )函数用来从表中删除一个项。我们来看一下register_chrdev()的具体实现:

int register_chrdev(unsigned int major, const char * name, struct file_operations *fops)

 {

         if (major == 0) {

                 write_lock(&chrdevs_lock);

                 for (major = MAX_CHRDEV-1; major > 0; major--) {

                         if (chrdevs[major].fops == NULL) {

                                 chrdevs[major].name = name;

                                 chrdevs[major].fops = fops;

                                 write_unlock(&chrdevs_lock);

                                 return major;

                         }

                 }

                 write_unlock(&chrdevs_lock);

                return -EBUSY;

         }

         if (major >= MAX_CHRDEV)

                return -EINVAL;

         write_lock(&chrdevs_lock);

         if (chrdevs[major].fops && chrdevs[major].fops != fops) {

                 write_unlock(&chrdevs_lock);

                return -EBUSY;

         }

         chrdevs[major].name = name;

         chrdevs[major].fops = fops;

         write_unlock(&chrdevs_lock);

         return 0;

}

从代码可以看出,如果参数major0,则由系统自动分配第一个空闲的主设备号,并把设备名和文件操作表的指针置于chrdevs表的相应位置。

例如,可以按如下方式把并口打印机驱动程序的相应结构插入到chrdevs表中:

 

register_chrdev(6, "lp", &lp_fops);

该函数的第一个参数表示主设备号,第二个参数表示设备类名,最后一个参数是指向文件操作表的一个指针。

如果设备驱动程序被静态地加入内核,那么,在系统初始化期间就注册相应的设备文件类。但是,如果设备驱动程序作为模块被动态装入内核,那么,对应的设备文件在装载模块时被注册,在卸载模块时被注销。

字符设备被注册以后,它所提供的接口,即file_operations结构在fs/devices.c中定义如下:

/*

  * Dummy default file-operations: the only thing this does

  * is contain the open that then fills in the correct operations

  * depending on the special file...

  */

 static struct file_operations def_chr_fops = {

         open:           chrdev_open,

};

由于字符设备的多样性,因此,这个缺省的file_operations仅仅提供了打开操作,具体字符设备文件的file_operationschrdev_open()函数决定:

 

  /*

  * Called every time a character special file is opened

  */

int chrdev_open(struct inode * inode, struct file * filp)

{

         int ret = -ENODEV;

 

         filp->f_op=get_chrfops(MAJOR(inode->i_rdev), MINOR(inode->i_rdev));

         if (filp->f_op) {

                 ret = 0;

                 if (filp->f_op->open != NULL) {

                        lock_kernel();

                         ret = filp->f_op->open(inode,filp);

                        unlock_kernel();

                 }

         }

         return ret;

 }

 

    首先调用MAJOR()和MINOR()宏从索引节点对象的i_rdev域中取得设备驱动程序的主设备号和次设备号,然后调用get_chrfops()函数为具体设备文件安装合适的文件操作。如果文件操作表中定义了open方法,就调用它。

注意,最后一次调用的open( )方法就是对实际设备操作,这个函数的工作是设置设备。通常,open(  )函数执行如下操作:

·      如果设备驱动程序被包含在一个内核模块中,那么把引用计数器的值加1,以便只有把设备文件关闭之后才能卸载这个模块。

·      如果设备驱动程序要处理多个同类型的设备,那么,就使用次设备号来选择合适的驱动程序,如果需要,还要使用专门的文件操作表选择驱动程序。

·      检查该设备是否真正存在,现在是否正在工作。

·      如果必要,向硬件设备发送一个初始化命令序列。

·      初始化设备驱动程序的数据结构。