具有相同主设备号和类型的每类设备文件都是由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;
}
从代码可以看出,如果参数major为0,则由系统自动分配第一个空闲的主设备号,并把设备名和文件操作表的指针置于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_operations由chrdev_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,以便只有把设备文件关闭之后才能卸载这个模块。
·
如果设备驱动程序要处理多个同类型的设备,那么,就使用次设备号来选择合适的驱动程序,如果需要,还要使用专门的文件操作表选择驱动程序。
·
检查该设备是否真正存在,现在是否正在工作。
·
如果必要,向硬件设备发送一个初始化命令序列。
·
初始化设备驱动程序的数据结构。