Linux 输入子系统
在Linux中,输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理,是底层在按键、触摸时,触发一个中断,或者驱动通过定时器定时查询,通过这两种方式通知CPU,CPU然后通过SPI、I2C或I/O接口读取键值、坐标等数据,放入缓冲区,字符设备驱动管理该缓冲区,向上提供read接口供应用程序使用。
在上述的工作流程中,只有终端、读取数值是根具体硬件设备相关,而输入事件的缓冲区管理以及字符设备驱动的接口函数,都是通用的,因此,有必要统一这些不同的输入设备,提炼出通用部分。
Linux的Input子系统整体框架如下:
先介绍核心数据结构体,再介绍一个简单的例子,然后引入基本功能函数。
核心数据结构体
Input子系统有三层,比较核心的结构体有四个,分别为 输入事件input_event,输入设备input_dev,核心处理input_handle,事件处理input_handler,分属于Input的不同层级,在上面这些结构体中,input_handle处于核心地位。如上图所示:
整个Input子系统有 两个全局链表,一个是input_dev_list 链表,里面有当前系统下,所有的底层输入设备,一个是input_handler_list链表,里面有当前系统下,所有的事件处理函数。
输入设备
struct input_dev {
…..
struct input_id id;//与input_handler匹配用的id,包括 总线类型、生产厂商、产品类型、版本
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //设备所支持的事件类型 ,如按键事件 EV_KEY
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //设备所支持的子事件类型,如 按键值
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反应设备当前的按键状态
struct input_handle *grab;//当前占有该设备的input_handle
struct list_head h_list;//该链表头用于链接此设备所关联的input_handle
struct list_head node; //用于将此设备链接到input_dev_list
}
事件处理器
struct input_handler{
…..
int minor; //表示设备的次设备号
/*event用于处理事件*/
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
/*connect用于建立handler和device的联系*/
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
const struct file_operations *fops;//handler的一些处理函数
const struct input_device_id *id_table;//用于和device匹配 ,这个是事件处理器所支持的input设备
const struct input_device_id *blacklist;//匹配黑名单,这个是事件处理器应该忽略的input设备
struct list_head h_list;//这个链表用来链接他所支持的input_handle结构,input_dev与input_handler配对之后就会生成一个input_handle结构
struct list_head node; //链接到input_handler_list,这个链表链接了所有注册到内核的事件处理器
}
连接结构体
每一个 input_handle 结构体代表一个成功配对的 input_dev 和 input_handler。
struct input_handle {
void *private; //每个配对的事件处理器都会分配一个对应的设备结构,如evdev事件处理器的evdev结构,注意这个结构与设备驱动层的input_dev不同,初始化handle时,保存到这里。
int open; //打开标志,每个input_handle 打开后才能操作,这个一般通过事件处理器的open方法间接设置
const char *name;
struct input_dev *dev; //关联的input_dev结构
struct input_handler *handler; //关联的input_handler结构
struct list_head d_node; //input_handle通过d_node连接到了input_dev上的h_list链表上
struct list_head h_node; //input_handle通过h_node连接到了input_handler的h_list链表上
};
数据结构之间的关系
struct input_dev物理输入设备的基本数据结构,包含设备相关的一些信息
struct input_handler 事件处理结构体,定义怎么处理事件的逻辑
struct input_handle用来创建 input_dev 和 input_handler 之间关系的结构体
input_dev 通过全局的input_dev_list链接在一起。设备注册的时候实现这个操作。
input_handler 通过全局的input_handler_list链接在一起。事件处理器注册的时候实现这个操作(事件处理器一般内核自带,一般不需要我们来写)
input_hande 没有一个全局的链表,它注册的时候将自己分别挂在了input_dev 和 input_handler 的h_list上了。通过input_dev 和input_handler就可以找到input_handle在设备注册和事件处理器,注册的时候都要进行配对工作,配对后就会实现链接。通过input_handle也可以找到input_dev和input_handler。
那么为什么一个input_device和input_handler中拥有的是h_list而不是一个handle呢?因为一个device可能对应多个handler,而一个handler也不能只处理一个device,比如说一个鼠标,它可以对应even handler,也可以对应mouse handler,因此当其注册时与系统中的handler进行匹配,就有可能产生两个实例,一个是evdev,另一个是mousedev,而任何一个实例中都只有一个handle。至于以何种方式来传递事件,就由用户程序打开哪个实例来决定。后面一个情况很容易理解,一个事件驱动不能只为一个甚至一种设备服务,系统中可能有多种设备都能使用这类handler,比如event handler就可以匹配所有的设备。在input子系统中,有8种事件驱动,每种事件驱动最多可以对应32个设备,因此dev实例总数最多可以达到256个。
Input Driver例子
下面以一个简单驱动为例子来介绍
#include <asm/irq.h>
#include <asm/io.h>
static struct input_dev *button_dev; /*输入设备结构体*/
static irqreturn_t button_interrupt(int irq, void *dummy) /*中断处理函数*/
{
input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1); /*向输入子系统报告产生按键事件*/
input_sync(button_dev); /*通知接收者,一个报告发送完毕*/
return IRQ_HANDLED;
}
static int __init button_init(void) /*加载函数*/
{
int error;
if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) /*申请中断,绑定中断处理函数*/
{
printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);
return -EBUSY;
}
button_dev = input_allocate_device(); /*分配一个设备结构体*/
//input_allocate_device()函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化.
if (!button_dev)
{
printk(KERN_ERR "button.c: Not enough memory\n");
error = -ENOMEM;
goto err_free_irq;
}
button_dev->evbit[0] = BIT_MASK(EV_KEY); /*设置按键信息*/
button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
//分别用来设置设备所产生的事件以及上报的按键值。Struct iput_dev中有两个成员,一个是evbit.一个是keybit.分别用
//表示设备所支持的动作和键值。
error = input_register_device(button_dev); /*注册一个输入设备*/
if (error)
{
printk(KERN_ERR "button.c: Failed to register device\n");
goto err_free_dev;
}
return 0;
err_free_dev:
input_free_device(button_dev);
err_free_irq:
free_irq(BUTTON_IRQ, button_interrupt);
return error;
}
static void __exit button_exit(void) /*卸载函数*/
{
input_unregister_device(button_dev); /*注销按键设备*/
free_irq(BUTTON_IRQ, button_interrupt); /*释放按键占用的中断线*/
}
module_init(button_init);
module_exit(button_exit);
这个demo代码,在button_init()中,首先注册了中断处理函数,然后调用input_allocate_device()函数分配一个input_dev结构体,并调用input_register_device函数对其进行注册。在中断处理函数中,demo将接受到的按键信息上报给Input子系统,Input子系统向用户态程序提供按键输入信息。
在上述这个简单的驱动里面,涉及到几个问题
1. 输入设备如何传递事件到核心层
2. 核心层如何找到对应事件的事件处理函数
3. 底层输入设备驱动是如何和事件处理层联系上的
基本功能函数
下面从使用流程入手,简要介绍以下input的整体流程,这里只会标注出主要代码流程。
1. 分配一个输入设备
从注释中可知,释放一个还未注册的输入设备,使用input_free_device,释放一个已经注册的设备,使用input_unregister_device。
由于没有输入参数,因此,可以猜测出这个分配出来的input_dev的一些配置都是默认配置。
这里面,比较重要的有两个链表h_list和node,分配后,我们需要在默认配置的基础上,添加自己的配置信息。
2. 注册一个输入设备
从注释上,可以知道,传入参数必须为input_allocate_device的返回值。
这个函数里面会设置input_dev所支持的基本事件类型,注意,一个设备可以支持一种或者多种事件类型。Input子系统需要在sysfs文件系统中体现出现,因此,input在sysfs中的device名称会在这里面设置。
然后将底层输入设备input_dev添加到全局设备链表input_dev_list中,对全局链表input_handler_list中的每一个handler函数,调用
input_attach_handler()。
每一次input_dev的注册,都会遍历事件处理链表input_handler_list,寻找输入设备对应的事件处理程序。
每一次input_hanlder的注册,都会遍历设备链表input_dev_list,寻找事件处理程序对应的输入设备。
上面这两个操作几乎是对称的,机制同platform中的device和device driver的相互寻找类型。
具体代码如下:
platform机制中的寻找,是根据设备名和设备驱动名称来匹配的,这里也不例外,匹配的过程,通过对比input_dev和input_handler的id成员,具体为id成员的总线类型、设备厂商、设备号、设备版本是否一致来判断是否匹配成功。
3. 输入设备找到事件处理程序
正常情况下,使用 事件处理程序中的 id_table和输入设备input_dev.id成员进行匹配。id_table指向该事件处理程序支持的设备列表。
匹配成功后,调用handler->connect,将handler和input_dev连接起来。
4. 向Input核心层报告输入事件
这里面的核心函数为 input_handle_event(dev,type,code,value),里面是一个大的switch,一级为事件类型type,二级switch为event code,这里只分析按键相关:
disposition 的取值有如下几种,它表示使用什么样的方式处理该输入事件。
#define INPUT_IGNORE_EVENT 0 // 表示忽略事件,不对其进行处理
#define INPUT_PASS_TO_HANDLERS 1 // 表示将事件交给 handler 处理
#define INPUT_PASS_TO_DEVICE 2 // 表示将事件交给 input_dev 处理
#define INPUT_PASS_TO_ALL (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)
如果该事件是传递给设备自身,则调用设备驱动自身的event函数来处理事件。
如果该事件时传递给上层事件处理函数,则调用input_pass_event来传递事件,将调用输入设备对于的handler的event()函数来处理输入事件。
注意:只有在handle被打开的情况下,才会接收到事件。
5. Input子系统输入事件处理层
输入事件处理层是在系统初始化时,注册进系统的。
输入子系统的事件处理层核心数据结构为 input_handler,所有输入子系统的事件处理程序都挂在input_handler_list中。在
6. 输入事件处理层注册
系统定义了8个输入事件处理层, 这些事件处理层通过handler->h_list连接起来,同时,也存储在全局input_table数组(他们在数组中的索引为设备号右移5位的值)和全局input_handler_list链表中。
同输入设备注册一样,输入事件处理注册时,需要寻找对应的输入设备。代码如下:
在input_attach_handler中,最后会调用error = handler->connect(handler, dev, id);也就是evdev_handler->connect,也就是
evdev_connect函数,在这里面初始化input handle,并且注册到系统。
在这里面,会将handle挂到所对应input device的h_list链表上.还将handle挂到对应的handler的hlist链表上,因此,可以把handle看成是 handler和 input device的信息集合体 .在这个结构里集合了匹配成功的 handler和 input device。就这样,handler和input dev匹配到一起。
7. 事件层处理来自核心层的事件
这里,会调用事件处理层的event函数,也就是evdev_event。每当input dev上报一个事件时,会将其交给和它匹配的handler的event函数来处理,在这里,又会通过遍历链表来调用evdev_pass_event来处理。
这里的操作,就是将event上传的数据保存到client->buffer中。client->head是当前的数据位置,这里是一个环形缓冲区,
写数据是从client->head写.而读数据则是从client->tail中读.
写完之后,通过向上层发起SIGIO信号来通知有事件发生,可以从缓冲区中读取数据了。
7. 输入事件处理函数的文件访问接口
输入设备在上层表现为主设备号为INPUT_MAJOR的设备文件,对他的读写会通过VFS,最后传递到evdev_fops的文件操作结构体中去。
1: static ssize_t evdev_read(struct file *file, char __user *buffer,
2: size_t count, loff_t *ppos)
3: {
4: struct evdev_client *client = file->private_data;
5: struct evdev *evdev = client->evdev;
6: struct input_event event;
7: int retval;
8:
9: if (count < input_event_size())
10: return -EINVAL;
11:
12: if (client->head == client->tail && evdev->exist &&
13: (file->f_flags & O_NONBLOCK))
14: return -EAGAIN;
15:
16: retval = wait_event_interruptible(evdev->wait,
17: client->head != client->tail || !evdev->exist);
18: if (retval)
19: return retval;
20:
21: if (!evdev->exist)
22: return -ENODEV;
23:
24: while (retval + input_event_size() <= count &&
25: evdev_fetch_next_event(client, &event)) {
26:
27: if (input_event_to_user(buffer + retval, &event))
28: return -EFAULT;
29:
30: retval += input_event_size();
31: }
32:
33: return retval;
34: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
首先,它判断缓存区大小是否足够.在读取数据的情况下,可能当前缓存区内没有数据可读.在这里先睡眠等待缓存
区中有数据.如果在睡眠的时候,.条件满足.是不会进行睡眠状态而直接返回的. 然后根据read()提够的缓存区大小.将
client中的数据写入到用户空间的缓存区中.
参考文献:
http://blog.csdn.net/lbmygf/article/details/7360084
http://blog.chinaunix.net/uid-27717694-id-3758334.html
Linux 输入子系统的更多相关文章
- linux输入子系统(input subsystem)之evdev.c事件处理过程
1.代码 input_subsys.drv.c 在linux输入子系统(input subsystem)之按键输入和LED控制的基础上有小改动,input_subsys_test.c不变. input ...
- Linux输入子系统(转)
Linux输入子系统(Input Subsystem) 1.1.input子系统概述 输入设备(如按键,键盘,触摸屏,鼠标等)是典型的字符设备,其一般的工作机制是低层在按键,触摸等动作发生时产生一个中 ...
- Linux输入子系统(Input Subsystem)
Linux输入子系统(Input Subsystem) http://blog.csdn.net/lbmygf/article/details/7360084 input子系统分析 http://b ...
- Linux输入子系统框架分析(1)
在Linux下的输入设备键盘.触摸屏.鼠标等都能够用输入子系统来实现驱动.输入子系统分为三层,核心层和设备驱动层.事件层.核心层和事件层由Linux输入子系统本身实现,设备驱动层由我们实现.我们在设备 ...
- Linux输入子系统详解
input输入子系统框架 linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(Input ...
- linux输入子系统
linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(InputCore)和输入子系统设备驱 ...
- linux输入子系统概念介绍
在此文章之前,我们讲解的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥.非阻塞.定时器去抖动. 上一节文章链接:http://blo ...
- linux输入子系统简述【转】
本文转载自:http://blog.csdn.net/xubin341719/article/details/7678035 1,linux输入子系统简述 其实驱动这部分大多还是转载别人的,linux ...
- 7.Linux 输入子系统分析
为什么要引入输入子系统? 在前面我们写了一些简单的字符设备的驱动程序,我们是怎么样打开一个设备并操作的呢? 一般都是在执行应用程序时,open一个特定的设备文件,如:/dev/buttons .... ...
- Android底层开发之Linux输入子系统要不要推断系统休眠状态上报键值
Android底层开发之Linux输入子系统要不要推断系统休眠状态上报键值 题外话:一个问题研究到最后,那边记录文档的前半部分基本上都是没用的,甚至是错误的. 重点在最后,前边不过一些假想猜測. ht ...
随机推荐
- sql server常用知识点
--删除表 use [20130823_Recource] go drop table my_table1,my_table2,My_table3 --创建表 use [20130823_Recour ...
- centos 服务器配置(三) 之定时任务
有些liunx系统已经自带定时任务crontab,但是有的新装系统还未安装定时任务,这个时候就需要我们手动对其进行安装. 安装crontab: yum install crontabs 说明: /sb ...
- Sharepoint 列表ItemAdding事件判断文件类型、获取当前上传的文件
using System; using System.Security.Permissions; using Microsoft.SharePoint; using Microsoft.SharePo ...
- 数据连接命令join
join主要用来将两个相关联的文件连接起来.两个文件相关联的意思是指这两个文件中有一些字段是关联的,例如两个文件的第1个字段都是学号,且每个学生的学号是唯一的.像这种具有唯一性关联的文件,就可以使用j ...
- Apache Shiro 使用手册---转载
原文地址:http://www.360doc.com/content/12/0104/13/834950_177177202.shtml (一)Shiro架构介绍 一.什么是Shiro Apache ...
- 10 ways to be a faster code reviewer--reference
reference:http://blog.codacy.com/top-10-faster-code-reviews/ This is a blog post of our Code Reading ...
- 在window平台搭建Qt开发环境(使用VS2008 IDE)
一直用QT Creator(mingw)开发Qt应用程序,每次如果需要修改编译链接参数选项时,都要修改pro文件,而这个文件是基于文本的,每次都要记住这些选项参数名,如果在知道原理的情况下还记住这些字 ...
- keytool 错误 java.io.IOException: incorrect AVA format
给一个APK做签名,选择新建一个key并填写相关信息,但在Finish时,keytool报出了一个错误:keytool error: java.io.IOException: Incorrect AV ...
- NET开发必备工具之-LINQPad
第一步,下载:http://www.linqpad.net/ 第二步,安装 第三步,打开LINQPad 第四步,添加链接 第五步,输入SQL Server,用户名,密码 第六步,点击OK,成功链接 第 ...
- mysql表的创建和删除
在创建数据库表时,最好是在编辑器中写好创建表的代码,然后粘贴到命令行中,这样如果有错修改起来方便. 现在来创建一个user表: -- 打开数据库, --后面必须要有空格, 表示注释 USE mydb3 ...