linux内核输入子系统分析
1、为何引入input system?
以前我们写一些输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的。问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,有木有可以实现一种机制,可以对分散的、不同类别的输入设备进行统一的驱动,所以才出现了输入子系统。
输入子系统引入的好处:
(1)统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。
(2)提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。X windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。
(3)抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入的访问。
2、输入子系统架构
上图展示了输入子系统的操作。此子系统包括一前一后运行的两类驱动:输入事件(event)驱动和输入设备(device)驱动。
输入事件驱动负责和应用程序的接口;
而输入设备驱动负责和底层输入设备的通信。
输入事件驱动和输入设备驱动都可以利用输入子系统的高效、可重用的核心提供的服务。
Now,我们看到输入子系统中有两个类型的驱动,当我们要为一个输入设备(如触摸屏)的编写驱动的时候,我们是要编写两个驱动:输入设备驱动和输入事件驱动??
答案是否定的。在子系统中,事件驱动是标准的,对所有的输入类都是可以用的,所以你更可能的是实现输入设备驱动而不是输入事件驱动。你的设备可以利用一个已经存在的,合适的输入事件驱动通过输入核心和用户应用程序接口。
总结如下:
输入子系统由输入子系统核心层( Input Core ),驱动层和事件处理层(Event Handler)三部份组成。一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。
设备驱动层:将底层的硬件输入转化为统一事件型式,向输入核心(InputCore)汇报。
输入核心层:为设备驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;
事件驱动层:主要作用是和用户空间交互,如提供read,open等设备方法,创建设备文件等。
内核代码分析:以键盘输入设备为例
输入设备注册分析:
通过input_register_device函数注册一个输入设备,在这个函数中,首先通过设备ID去匹配handler的ID,以此找到对应的handler(事件处理者)。以按键输入设备为例:若匹配成功,则调用handler(evdev_handler是一个常用的handler,能够匹配任意设备)结构的connect函数创建一个设备文件,通过设备文件里的主设备号和次设备号将设备文件与字符设备驱动关联起来。(此处不用注册字符设备驱动,因为字符设备驱动已经在整个输入子系统初始化(input_init)的时候创建好<register_chrdev>)
事件上报分析:
以按键输入设备为例:通过调用input_report_key---input_event---input_handle_event---input_pass_event函数,调用handler(evdev_handler)结构中的event函数将事件的信息(事件类型:按键;键值;按键状态etc,)打包成一个input_event结构,保存在handler的buffer中。当有app访问时,会通过访问字符设备文件(例如dev/event1),间接访问输入子系统初始化时创建的字符设备驱动,此处字符设备驱动的fops中只有open的实现函数,并没有其他实现函数,当进入fops中的open函数时,此处巧妙地将handler(evdev_handler)中的fops赋给字符设备驱动当中的fops,当app进行read时,此时调用的即为handler结构中的fops中的read函数(evdev_read---evdev_fetch_next_event)从buffer当中取出事件信息,交给用户空间(input_event_to_user---copy_to_user)。
Input driver编写要点
1、分配、注册、注销input设备
struct input_dev *input_allocate_device(void)
int input_register_device(struct input_dev *dev)
void input_unregister_device(struct input_dev *dev)
2、设置input设备支持的事件类型、事件码、事件值的范围、input_id等信息
include/linux/input.h中定义了支持的类型(下面列出的是2.6.22内核的情况)
#define EV_SYN 0x00
#define EV_KEY 0x01
#define EV_REL 0x02
#define EV_ABS 0x03
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件码。比如:EV_KEY事件,需要定义其支持哪些按键事件码。
3、如果需要,设置input设备的打开、关闭、写入数据时的处理方法
参见usb键盘驱动:usbkbd.c
input_dev->open = usb_kbd_open;
input_dev->close = usb_kbd_close;
input_dev->event = usb_kbd_event;
4、在发生输入事件时,向子系统报告事件
用于报告EV_KEY、EV_REL、EV_ABS等事件的函数有:
void input_report_key(struct input_dev *dev, unsigned int code, int value)
void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
如果你觉得麻烦,你也可以只记住1个函数(因为上述函数都是通过它实现的)
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
三、Event Handler层解析
1、Input输入子系统数据结构关系图
2、input_handler结构体
以evdev.c中的evdev_handler为例:
static struct input_handler evdev_handler = {
.event = evdev_event, //向系统报告input事件,系统通过read方法读取
.connect = evdev_connect, //和input_dev匹配后调用connect构建
.disconnect = evdev_disconnect,
.fops = &evdev_fops, //event设备文件的操作方法
.minor = EVDEV_MINOR_BASE, //次设备号基准值
.name = "evdev",
.id_table = evdev_ids, //匹配规则
};
3、input字符设备注册过程
drivers/input/input.c中:
static int __init input_init(void)
{
int err;
err = class_register(&input_class);
……
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
……
}
input_fops定义:
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
Input_dev和input_handler匹配后调用input_handler的connect。以evdev_handler为例:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
{
struct evdev *evdev;
struct class_device *cdev;
dev_t devt;
int minor;
int error;
for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
if (minor == EVDEV_MINORS) {
printk(KERN_ERR "evdev: no more free evdev devices\n");
return -ENFILE;
}
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//为每个匹配evdev_handler的设备创建一个evdev。
if (!evdev)
return -ENOMEM;
INIT_LIST_HEAD(&evdev->client_list);
init_waitqueue_head(&evdev->wait);
evdev->exist = 1;
evdev->minor = minor;
evdev->handle.dev = dev;
evdev->handle.name = evdev->name;
evdev->handle.handler = handler;
evdev->handle.private = evdev;
sprintf(evdev->name, "event%d", minor);
evdev_table[minor] = evdev;//记录evdev的位置,字符设备/dev/input/evnetx访问时根据次设备号及EVDEV_MINOR_BASE最终在evdev_open中找到对应的evdev
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name);//创建了event字符设备节点
……
}
4、input字符设备的打开过程
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5];
//得到对应的input_handler
const struct file_operations *old_fops, *new_fops = NULL;
int err;
if (!handler || !(new_fops = fops_get(handler->fops)))
//取出对应input_handler的file_operations
return -ENODEV;
if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}
old_fops = file->f_op;
file->f_op = new_fops;//重定位打开的设备文件的操作方法
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;
}
5、input字符设备的其它操作
由于在open阶段已经把设备文件的操作操作方法重定位了到了具体的input_handler,所以其它接口操作(read、write、ioctl等),由各个input_handler的fops方法决定。如evdev.c中的:evdev_fops。
linux内核输入子系统分析的更多相关文章
- 12.Linux之输入子系统分析(详解)
版权声明:本文为博主原创文章,转载请标注出处: 在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥/非阻塞.定时 ...
- linux input输入子系统分析《四》:input子系统整体流程全面分析
1 input输入子系统整体流程 本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层.由于上节代码讲解了设备 ...
- 7.Linux 输入子系统分析
为什么要引入输入子系统? 在前面我们写了一些简单的字符设备的驱动程序,我们是怎么样打开一个设备并操作的呢? 一般都是在执行应用程序时,open一个特定的设备文件,如:/dev/buttons .... ...
- 20169210《Linux内核原理与分析》第二周作业
<Linux内核原理与分析>第二周作业 本周作业分为两部分:第一部分为观看学习视频并完成实验楼实验一:第二部分为看<Linux内核设计与实现>1.2.18章并安装配置内核. 第 ...
- Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)
http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...
- Linux内核源码分析 day01——内存寻址
前言 Linux内核源码分析 Antz系统编写已经开始了内核部分了,在编写时同时也参考学习一点Linux内核知识. 自制Antz操作系统 一个自制的操作系统,Antz .半图形化半命令式系统,同时嵌入 ...
- 2018-2019-1 20189221 《Linux内核原理与分析》第六周作业
2018-2019-1 20189221 <Linux内核原理与分析>第六周作业 实验五 实验过程 将Fork函数移植到Linux的MenuOS fork()函数通过系统调用创建一个与原来 ...
- Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】
原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://bl ...
- 2017-2018-1 20179215《Linux内核原理与分析》第二周作业
20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...
随机推荐
- 解决Oracle在win10 64使用plsql 无法显示表
将当前用户切换至所有用户,然后再切换至当前用户,问题解决 图中sql也可查询出当前库中存在的表: select object_name from user_objects where lower(ob ...
- map 地图组件
地图选择器网址 http://datav.aliyun.com/tools/atlas/#&lat=31.769817845138945&lng=104.29901249999999& ...
- 13_Python的面向对象编程-类class,对象object,实例instance
1.面向对象概述 1.类是用来描述对象的工具,把拥有相同属性和行为的对象分为一组 2.对象是由类实例化出来的一个具体的对象 属性: 对象拥有的名词,用变量表示 ...
- postgres 无法删除表
起因 在postgress下删除表的时候报错 解决 简单的百度了一下,有些人说是用户权限的问题,需要切换到库的拥有者下删除,但是切换后还是没有解决··· 最后换了一种方式搜索,不直接搜索报错命令,直接 ...
- [PyTorch 学习笔记] 6.2 Normalization
本章代码: https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson6/bn_and_initialize.py https: ...
- 一篇文章教你快速上手接口管理工具swagger
一.关于swagger 1.什么是swagger? swagger是spring fox的一套产品,可以作为后端开发者测试接口的工具,也可以作为前端取数据的接口文档. 2.为什么使用? 相比于传统的接 ...
- oracle之网络
Oracle 网络 sqlplus sys/123123@192.168.143.90:1521/urpdb as sysdba 15.1 Oracle Net 基本要素: 15.1.1 服务器端的l ...
- Git进阶之路:配置git同时推送github和gitee仓库
1. 进入工程根目录打开.git文件夹(需要显示隐藏文件夹) 2. 打开.git文件夹下的config文件进行编辑添加github和gitee仓库地址 3. 推送验证 github提交记录 gitee ...
- Dubbo必须会的知识点
前言 应用架构演变: 单一架构ORM:单机构建网站,是一个高内聚版本,所有功能部署在一起.通过一个容器和JSP/Servlet技术或通过一些开源的框架如SSM以及SSH,通过数据库管理系统来存储数据. ...
- express 4.0 connect-mongo 运行时报错的解决方法
如果使用的是express 4.0,且入口文件app.js的模板引入和中间件配置如下 如果你的package.json文件中有以下版本信息 报出的错误如下所示 或者 那么只需修改app.js中 ...