输入子系统学习笔记之源码分析3_流程分析
看一个驱动程序的时候一般是从入口函数开始看,输入子系统的核心实现函数是 input.c,入口函数是input_init(),因此要分析输入子系统那么就从input_init()函数开始,input_init()代码如下:
static int __init input_init(void) { int err; //创建一个类input_class err = class_register(&input_class); if (err) { printk(KERN_ERR "input: unable to register input_dev class\n"); return err; } //在/proc下创建入口项 err = input_proc_init(); if (err) goto fail1; // 注册设备号INPUT_MAJJOR的设备,记住input子系统的设备的主设备号是13,即INPUT_MAJOR // 为13,并与input_fops相关联 err = register_chrdev(INPUT_MAJOR, "input", &input_fops); if (err) { printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); goto fail2; } return 0; fail2: input_proc_exit(); fail1: class_unregister(&input_class); return err; }
在入口函数里面创建了一个input_class类,主要作用就是在sys目录里面创建一些节点,比如cd到/sys/class下面可以看到这一类的设备,与这个相关的就是一些kobjects。当然对于一个新设备,可以注册进一个class也可以不注册进去,如果存在对应class的话注册进去更好。另外在/proc创建了入口项,这样就可以/proc目录看到input的信息,然后就是设备驱动函数里面都需要的注册设备,可以看出输入子系统的主设备号是13,在这里并没有生成设备文件,从前面的分析可知,在输入子系统中事件处理层负责生成与应用程序的设备文件接口,因此生成设备文件的工作应该是位于事件处理层上。这里还要注意一点是主设备号与input_fops关联。下面看看输入子系统的file_operation,如下:
static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, };
这里只有一个open函数input_open_file,当应用程序打开输入设备文件时将调用这个函数,但是这里没有实现读写函数,在input_open_file里面可能做了一些工作,那么可以继续跟踪input_open_file函数,代码如下:
static int input_open_file(struct inode *inode, struct file *file) { /* 依据次设备号找到事件处理程序 handler句柄*/ struct input_handler *handler = input_table[iminor(inode) >> 5]; const struct file_operations *old_fops, *new_fops = NULL; int err; /* No load-on-demand here? */ /* 得到handler 里面的file_operation替代当前的file_operation*/ if (!handler || !(new_fops = fops_get(handler->fops))) return -ENODEV; /* * That's _really_ odd. Usually NULL ->open means "nothing special", * not "no device". Oh, well... */ if (!new_fops->open) { fops_put(new_fops); return -ENODEV; } old_fops = file->f_op; file->f_op = new_fops; /* 调用handler里面的open函数*/ err = new_fops->open(inode, file); if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } fops_put(old_fops); return err; } static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, };
从input_open_file 函数里面就可知道,在里面做了一个很重要替换工作,那就是根据设备文件的次设备号找到相应的handler,然后将handler里面file_operation替换当前的file_operation,因此以后open,read,write等将调用handler里面的open,read,write。这是一个非常有用的编程思想,统一了入口,然后根据一些条件选择相应的分支。这里要知道的handler怎么来了,那么就得知道input_table是由谁构建的,搜索代码可知input_table是由input_register_handler函数调用,input_register_handler是一个向上注册函数,也就是向核心层注册handler,那么是由谁调用input_register_handler呢?搜索代码可知input_register_handler由evdev.c,mousedev.c等文件调用,而这些文件一个文件实现了一个handler,每一个文件都向核心层注册,这些handler就是事件处理层的一个驱动,因此这个层次关系就出来了。可以打开evdev.c看看这个向上注册函数,如下:
static int __init evdev_init(void) { return input_register_handler(&evdev_handler); }
在里面初始化了一个handler结构体,查看这个结构体可知,里面有个evdev_fops结构体,里面已经实现了read,write,poll,fasync等函数,以前这个结构体需要我们自己实现,现在内核已经帮我们做好了,其中evdev.c 中minor = EVDEV_MINOR_BASE(64),有input_table[minor >> 5可知,evdev这个事件处理驱动位于input_table的第二项。现在假设应用程序去读这个设备,那么过程如下:
input_open_file —> fops —> handler—>fops —>read
这边的handler是一个纯软件的概念,所谓的handler是处理者的意思,那么它处理谁呢? 那就是设备,设备代表硬件! handler 需要向上注册,dev同样也是向上注册,这样input核心层,才能管理设备和事件处理层,才能作为两者的桥梁!那么这里的hander 和 dev又是怎么建立连接的呢?也就是这个hander这个事件处理能不能支持这个设备呢? 在handler 结构体里面有一项是id_table,这个就是表示handler支持的设备,比如说evdev里面 id_table为evdev_ids,如下:
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
这个表示evdev 这个handler支持所有的设备,也就是所有的输入设备它都可以处理。在handler结构体里面有一项connect,如果支持这个设备,就会调用这个函数。这个分析input_register_dev可知,input_register_dev所做的事如下:
注册输入设备: input_register_device // 放入链表 list_add_tail(&dev->node, &input_dev_list); // 对于每一个input_handler,都调用input_attach_handler list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev
同时可以看看handler的注册过程:
注册input_handler: input_register_handler // 放入数组 input_table[handler->minor >> 5] = handler; // 放入链表 list_add_tail(&handler->node, &input_handler_list); // 对于每个input_dev,调用input_attach_handler list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev
input_attach_handler id = input_match_device(handler->id_table, dev); error = handler->connect(handler, dev, id);
由代码可知注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能支持,则调用input_handler的connect函数建立"连接"。那么怎么建立连接呢?不同的handler建立连接的过程可能不一样,那么以evdev.c里面的connect函数为例,说明下分析该函数,得知过程如下:
1. 分配一个input_handle结构体,需要注意的是input_handle没有r,然后对这个handle进行设置
2.
input_handle.dev = input_dev; // 指向左边的input_dev
input_handle.handler = input_handler; // 指向右边的input_handler
3. 注册:
input_handler->h_list = &input_handle;
inpu_dev->h_list = &input_handle;
evdev_connect
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个input_handle
// 设置
evdev->handle.dev = dev; // 指向左边的input_dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; // 指向右边的input_handler
evdev->handle.private = evdev;
// 注册,注册的过程是把handle 放在dev和handler里面的h_list链表里面,这样的结果是dev可以通过
// handle找到handler,handler可以通过handle找到dev
error = input_register_handle(&evdev->handle);
这个过程的结果就是建立连接的过程,要实现的目地如下图所示:
输入子系统核心实现的功能不仅如此,现在假设应用程序去读read按键,那么怎么去读呢?
怎么读按键?
app: read
--------------------------
....... // 这个过程省略,前面也分析了
evdev_read
// 无数据并且是非阻塞方式打开,则立刻返回
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
// 否则休眠
retval = wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist);
既然是读按键,那么必须要有硬件设备有按键按下才会返回按键值,这里还是处于事件处理层,应用程序在这里休眠,那么谁来唤醒呢? 很显然,有按键按下才去唤醒,因此这个工作就交给了设备驱动层,那么找到这个唤醒呢,直接去找不好找,那么可以直接搜索 evdev->wait,搜索结果可知evdev->wait在evdev_event()函数或者那个被唤醒。那么evdev_event()又是被谁调用呢?很显然是设备驱动层,现在看一个设备层例子,内核中有个按键的例子,gpiokey.c,这只是个例子不针对任何设备,在gpiokey.c 终端处理函数里面
gpio_keys_isr // 上报事件 input_event(input, type, button->code, !!state); input_sync(input);
然后搜索input_event(),
input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) struct input_handle *handle; list_for_each_entry(handle, &dev->h_list, d_node) if (handle->open) handle->handler->event(handle, type, code, value);
这里有dev –>h_list 找到handle,然后由handle找到handler,在调用handler里面的event,在event()里面唤醒evdev->wait,整个过程体现出来了。设备驱动层实际上通过上报事件与事件处理层交互信息,事件处理层提供统一的接口给应用层。因此屏蔽了底层设备的差异。
输入子系统的流程分析在到这了,关于输入子系统的笔记也到此了,后面会写一个简单输入子系统的驱动,这个驱动当然是设备驱动层的,作为事件处理层,是一个标准的,适用于任何输入设备,因此要实现的也就是设备驱动层。对于输入子系统的理解应该还只是入门的阶段,这里只是记录自己这个时间的理解,随着对linux内核的慢慢深入,相信不久会有新的认识,到时再来补充! ^_^