我们来看一个最简单的字符设备,即“空设备”/dev/null。大家知道,应用程序在运行的过程中,一般都要通过其预先打开的标准输出通道或标准出错通道在终端显示屏上输出一些信息,但是有时候(特别是在批处理中)不宜在显示屏上显示信息,又不宜将这些信息重定向到一个磁盘文件中,而要求直接使这些信息流入“下水道”而消失,这时候就可以用/dev/null来起这个“下水道”的作用,这个设备的主设备号为1。
如前所述,主设备号为1的设备其实不是“设备”,而都是与内存有关,或是在内存中(不必通过外设)就可以提供的功能,所以其主设备号标识符为MEM_MAJOR,其定义于include/linux/major.h中:
#define MEM_MAJOR 1
其file_operatins结构为memory_fops,定义于dreivers/char/mem.c中:
static
struct file_operations memory_fops = {
open:
memory_open, /*
just a selector for the real open */
};
因为主设备号为1的字符设备并不能唯一的确定具体的设备驱动程序,因此需要根据次设备号来进行进一步的区分,所以memory_fops还不是最终的file_operations结构,还需要由memory_open()进一步加以确定和设置,其代码在同一文件中:
static int
memory_open(struct inode * inode, struct file * filp)
{
switch (MINOR(inode->i_rdev)) {
case 1:
filp->f_op = &mem_fops;
break;
case 2:
filp->f_op = &kmem_fops;
break;
case 3:
filp->f_op = &null_fops;
break;
…
}
if (filp->f_op && filp->f_op->open)
return
filp->f_op->open(inode,filp);
return 0;
}
因为/dev/null的次设备号为3,所以其file_operations结构为null_fops:
static struct file_operations null_fops = {
llseek:
null_lseek,
read:
read_null,
write:
write_null,
};
由于这个结构中函数指针open为NULL,因此在打开这个文件时没有任何附加操作。当通过write()系统调用写这个文件时,相应的驱动函数为write_null(),其代码为:
static
ssize_t write_null(struct file * file, const char * buf,
size_t count, loff_t *ppos)
{
return count;
}
从中可以看出,这个函数什么也没做,仅仅返回count,假装要求写入的字节已经写好了,而实际把写的内容丢弃了。
再来看一下读操作又做了些什么,read_null()的代码为:
static
ssize_t read_null(struct file * file, char * buf,
size_t count, loff_t *ppos)
{
return 0;
}
返回0表示从这个文件读了0个字节,但是并没有到达(永远也不会到达)文件的末尾。
当然,字符设备的驱动程序不会都这么简单,但是总的框架是一样的。