Linux输入子系统框架分析(1)

在Linux下的输入设备键盘、触摸屏、鼠标等都可以用输入子系统来实现驱动。输入子系统分为三层,核心层和设备驱动层,事件层。核心层和事件层由Linux输入子系统本身实现,设备驱动层由我们实现。我们在设备驱动层将输入事件上报给核心层input.c,核心层找到匹配的事件层,将事件交给事件层处理,事件层处理完后传递到用户空间。

我们最终要搞清楚的是在用户空间调用open和read最终在内核中是怎样处理的,向内核上报的事件又是谁处理的,处理完后是怎样传递到用户空间的?


上面两个图是输入子系统的框架。

下面以按键驱动为例分析输入子系统的工作流程。

设备驱动层:

在设备驱动层的init入口函数中调用input_allocate_device(),分配返回一个input_dev结构体,填充该结构体,调用input_register_device(button_dev),注册设备。跟踪input_register_device如下:

list_add_tail(&dev->node, &input_dev_list);将input_dev结构体放进input_dev_list链表中
list_for_each_entry(handler, &input_handler_list, node
)遍历input_handler_list链表中的每一个handler

input_attach_handler(dev, handler);将input_dev和handler比较

input_match_device(handler->id_table, dev);<*input.c*>
handler->connect(handler, dev, id);<*input.c*>
比较的方式是通过对比handler的id_table和input_dev,若找  到匹配的handler,则调用handler的connect方法。

在evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)中调用了 input_register_handle,调用handle之前填充handle中成员dev和handler结构体。

evdev->handle.dev = input_get_device(dev);
evdev->handle.handler = handler;

在input_register_handle中list_add_tail_rcu(&handle->d_node, &dev->h_list)list_add_tail(&handle->h_node, &h andler->h_list)两个函数将handle加入进handler和dev的h_list链表中。通过dev->h_list可以找到handle,通过 handle找到handle.handler,同理,通过handler->h_list可以找到handle,再找到handle.dev。

关键的问题来了?handler到底是什么?由谁注册的?handler是事件层调用核心层的函数注册的。

事件层:(evdev.c,keyboard.c,ts.c)

 static struct input_handler evdev_handler = {//handler结构体
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};

在evdev_init(void)入口函数中调用input_register_handler(&evdev_handler)来注册handler,注册的handler会放入input_table[ ]数组中。

核心层:(input.c)

核心层的入口函数input_init(void)注册设备input。

当在应用层调用open时会在内核中调用input_open_file(struct inode *inode, struct file *file),根据handler = input_table[iminor(inode) >> 5];根据打开的文件的次设备号从数组input_table中得到已注册的对应的handler。

new_fops = fops_get(handler->fops)从handler中得到新的fop。

file->f_op = new_fops;
err = new_fops->open(inode, file);open最终调用的是handler->fops->open。
原来的设备驱动的open等方法是自 己写的,现在输入子系统中的事件层帮我们写好了open等设备方法。同理在应用层调用read会调用事件层中的 read,即handler->fops->read,即evdev_fops.read,在read中会阻塞。直到在设备驱动层中过input_report_key 上报事件:

input_event(dev, EV_KEY, code, !!value);

input_handle_event(dev, type, code, value); 

input_pass_event(dev, type, code, value);

handle->handler->event(handle,type, code, value);调用handler中的event事件方法,处理完上报的事 件后,唤醒休眠的read,再read出事件的处理结果。

总结:

在用户空间调用open将最终调用事件层中handler的fops的open设备方法,具体是匹配到evdev.c还是keyboard.c的handler要根据设备驱动init入口函数中填充的input_dev结构体的id_table。用户空间调用read将调用handler的read。在设备驱动中通过input_event上报事件到核心层,最终调用对应的handler的event方法来处理事件,处理完后通过read传递到用户空间。这样就搞清楚了open是谁调用的?read是谁调用的?



Linux输入子系统框架分析(1),古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。