话说马上要吃饭了,不过能写多少就写多少吧,写不完,下午接着写嘛,哈哈,你不会也饿了吧?不开玩笑了。上一篇讨论简单的按键应用层测试程序,还记得咱们详细看event0的熟悉的结果吧,如果忘了,可以再看看,通过查看,我们知道event0是一个字符设备,并且主设备号为13、次设备号为64。那问题来了,这个字符设备是在什么地方注册的,因为我前面还特意强调,在设备驱动程序里没有做相关的工作,其实是在linux-3.0.8/drivers/input/input.c文件中注册的,下面把代码贴出来,做进一步的分析:
static int __init input_init(void)
{
int err;
err = class_register(&input_class); //注册input设备类
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}
err = input_proc_init();//初始化input在proc的节点
if (err)
goto fail1;
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);//这里注意一下,INPUT_MAJOR等于13,也就是说注册了一个主设备号为13,名字为“input”的字符设备
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
从上面的代码来看,咱们没有发现event*的字样,但主设备号的确是13,而且从打开文件的路径“/dev/input/event*”来看,input包含了event*,没错,input表示的是一类设备,而event*只是里面具体对于的设备。既然input是包含event*的一类设备,那怎么就沿着这条线继续看吧,这样看是对的,不管操作什么设备,首先应该打开嘛,而打开具体设备时,总是要通过包含它的设备类的。好吧,那咱们看“input_fops”这个文件操作结构,看看里面到底做了什么,下面是源码。
static const struct file_operations input_fops = { //这个结构体就是实际操作input设备的结构体,不过发现里面仅仅包含open和llseek函数了,因为操作设备首先要打开设备,那open函数就是关键了,下面源码中好好研究研究吧
.owner = THIS_MODULE,
.open = input_open_file,
.llseek = noop_llseek,
};
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler; //这是事件操作的接口
const struct file_operations *old_fops, *new_fops = NULL;
int err;
err = mutex_lock_interruptible(&input_mutex);
if (err)
return err;
/* No load-on-demand here? */
handler = input_table[iminor(inode) >> 5]; //这里首先通过此设备号寻找handler,handler就是事件操作接口嘛,这个会在下面详细解释
if (handler)
new_fops = fops_get(handler->fops); //得到handler里面包含的文件接口
mutex_unlock(&input_mutex);
/*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops || !new_fops->open) { //如果得到的文件操作接口是空的,那么就返回了
fops_put(new_fops);
err = -ENODEV;
goto out;
}
old_fops = file->f_op;
file->f_op = new_fops; //看到这里吧,这是重点,把新得到的文件操作结构体赋给file的f_op字段
err = new_fops->open(inode, file);//打开新的文件操作接口,在这里才是真真的文件打开
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops); //减少旧的文件操作接口计数
out:
return err;
}
先简单的梳理一下open这个函数的作用吧。a.open函数通过所要打开的具体文件的次设备号,得到input的事件操作接口handler;b.通过handler得到具体设备文件的fops;c.然后把通过把file的fops字段换成用次设备得到的新的fops;d.把原来的fops字段的计数减一。现在解释一下iminor(inode)>>5,也就是把次设备号向右移5位,因为在事件处理层中是一类设备对应的处理只有一种,也就是说不是每一个设备对应一个实际存在的处理文件,而是一类对应一个,这也就是抽象的好处,要不然那得写多少个具体事件处理文件啊。handler *input_table[8],这是input_table的定义,它是一个全局的指针数组,你看它只定义了8个,它代表8个类了,比如evdev的次设备号是从64到95的;mousedev的次设备号是从32到63的,这里evdev和mousedev是实际处理一类input设备的事件输入的驱动,向右移位5正好把他们能区分开来,比如说从32到63中的任何数向右移5位,得到的都是1,而从64到95,就是2了。
现在咱们得做一个假设,假设咱们要打开的设备是“/dev/input/event0”,而且event0对应的次设备号就是64,因为咱们得根据具体的一个做进一步的深入,这里不可能把所有的都讲了。次设备号是64,向右移5位得到2,也就是说input_table[2]对应的事件操作接口就是咱们要找的,这一部分你可以参考linux-3.0.8/Documentation/input/input.txt文件知道它对应的是evdev.c注册的一类设备中的一个。那咱们就打开在linux-3.0.8/drivers/input/evdev.c这个文件,在这个文件中的函数evdev_connect中看到如下的一行函数:
dev_set_name(&evdev->dev, "event%d", minor);
这行函数用次设备号生产设备的名字,这也的确证明了次设备号为64的设备在对应的事件处理层驱动是evdev.c,因为咱们的设备名字是“event0”嘛。在前面的open函数中已经把对应handler的fops赋给了file的f_op字段,而且打开了handler的fops字段中的open函数。再看看evdev.c中的handler对应的fops字段吧,如下:
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};
看到了吧,这里涵盖处理evdev类的字符设备的函数,其实通过上面的open打开的真真的open函数就是这里的evdev_open 函数了,具体我就不贴出来了。再做一个简单的总结吧,当咱们用“/dev/input/event0”打开一个设备文件时,首先调用input_open_file()这个函数,然后在这个函数中,通过次设备号得到真真的文件操作接口,最后打开得到的文件操作接口的open函数,同时以后的什么read啊、write啊都会调用evdev.c中定义read和write方法了。通过这些方法就可以操作具体的设备文件了。
通过在应用层往下分析,咱们了解到具体设备是怎么被打开的,但是同时又有个问题,我们知道在写设备驱动函数时(比如前面的按键驱动),我们并没有定义什么次设备号啊,或者定义需要让evdev处理,它们是怎么样做匹配的?这可能需要从驱动层往上分析可能更清晰一点,好吧,咱们下一篇就继续讨论吧,下篇见喽!