input子系统事件处理层(evdev)的环形缓冲区【转】
在事件处理层(evdev.c)中结构体evdev_client定义了一个环形缓冲区(circular buffer),其原理是用数组的方式实现了一个先进先出的循环队列(circular queue),用以缓存内核驱动上报给用户层的input_event事件。
struct evdev_client {
unsigned int head; // 头指针
unsigned int tail; // 尾指针
unsigned int packet_head; // 包指针
spinlock_t buffer_lock;
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
unsigned int clk_type;
bool revoked;
unsigned long *evmasks[EV_CNT];
unsigned int bufsize; // 循环队列大小
struct input_event buffer[]; // 循环队列数组
};
evdev_client对象维护了三个偏移量:head、tail以及packet_head。head、tail作为循环队列的头尾指针记录入口与出口偏移,那么包指针packet_head有什么作用呢?
packet_head
内核驱动处理一次输入,可能上报一到多个input_event事件,为表示处理完成,会在上报这些input_event事件后再上报一次同步事件。头指针head以input_event事件为单位,记录缓冲区的入口偏移量,而包指针packet_head则以“数据包”(一到多个input_event事件)为单位,记录缓冲区的入口偏移量。
环形缓冲区的工作机制
- 循环队列入队算法:
head++;
head &= bufsize - 1;
- 循环队列出队算法:
tail++;
tail &= bufsize - 1;
- 循环队列已满条件:
head == tail
- 循环队列为空条件:
packet_head == tail
“求余”和“求与”
为解决头尾指针的上溢和下溢现象,使队列的元素空间可重复使用,一般循环队列的出入队算法都采用“求余”操作:
head = (head + 1) % bufsize; // 入队
tail = (tail + 1) % bufsize; // 出队
为避免计算代价高昂的“求余”操作,使内核运作更高效,input子系统的环形缓冲区采用了“求与”算法,这要求bufsize必须为2的幂,在后文中可以看到bufsize的值实际上是为64或者8的n倍,符合“求与”运算的要求。
环形缓冲区的构造以及初始化
用户层通过open()函数打开input设备节点时,调用过程如下:
open() -> sys_open() -> evdev_open()
在evdev_open()函数中完成了对evdev_client对象的构造以及初始化,每一个打开input设备节点的用户都在内核中维护了一个evdev_client对象,这些evdev_client对象通过evdev_attach_client()函数注册在evdev1对象的内核链表上。
接下来我们具体分析evdev_open()函数:
static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
// 1.计算环形缓冲区大小bufsize以及evdev_client对象大小size
unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
unsigned int size = sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event);
struct evdev_client *client;
int error;
// 2. 分配内核空间
client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
if (!client)
client = vzalloc(size);
if (!client)
return -ENOMEM;
client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
client->evdev = evdev;
// 3. 注册到内核链表
evdev_attach_client(evdev, client);
error = evdev_open_device(evdev);
if (error)
goto err_free_client;
file->private_data = client;
nonseekable_open(inode, file);
return 0;
err_free_client:
evdev_detach_client(evdev, client);
kvfree(client);
return error;
}
在evdev_open()函数中,我们看到了evdev_client对象从构造到注册到内核链表的过程,然而它是在哪里初始化的呢?其实kzalloc()函数在分配空间的同时就通过__GFP_ZERO标志做了初始化:
static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}
生产者/消费者模型
内核驱动与用户程序就是典型的生产者/消费者模型,内核驱动产生input_event事件,然后通过input_event()函数写入环形缓冲区,用户程序通过read()函数从环形缓冲区中获取input_event事件。
环形缓冲区的生产者
内核驱动作为生产者,通过input_event()上报input_event事件时,最终调用___pass_event()函数将事件写入环形缓冲区:
static void __pass_event(struct evdev_client *client,
const struct input_event *event)
{
// 将input_event事件存入缓冲区,队头head自增指向下一个元素空间
client->buffer[client->head++] = *event;
client->head &= client->bufsize - 1;
// 当队头head与队尾tail相等时,说明缓冲区空间已满
if (unlikely(client->head == client->tail)) {
/*
* This effectively "drops" all unconsumed events, leaving
* EV_SYN/SYN_DROPPED plus the newest event in the queue.
*/
client->tail = (client->head - 2) & (client->bufsize - 1);
client->buffer[client->tail].time = event->time;
client->buffer[client->tail].type = EV_SYN;
client->buffer[client->tail].code = SYN_DROPPED;
client->buffer[client->tail].value = 0;
client->packet_head = client->tail;
}
// 当遇到EV_SYN/SYN_REPORT同步事件时,packet_head移动到队头head位置
if (event->type == EV_SYN && event->code == SYN_REPORT) {
client->packet_head = client->head;
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
}
环形缓冲区的消费者
用户程序作为消费者,通过read()函数读取input设备节点时,最终在内核调用evdev_fetch_next_event()函数从环形缓冲区中读取input_event事件:
static int evdev_fetch_next_event(struct evdev_client *client,
struct input_event *event)
{
int have_event;
spin_lock_irq(&client->buffer_lock);
// 判缓冲区中是否有input_event事件
have_event = client->packet_head != client->tail;
if (have_event) {
// 从缓冲区中读取一次input_event事件,队尾tail自增指向下一个元素空间
*event = client->buffer[client->tail++];
client->tail &= client->bufsize - 1;
if (client->use_wake_lock &&
client->packet_head == client->tail)
wake_unlock(&client->wake_lock);
}
spin_unlock_irq(&client->buffer_lock);
return have_event;
}
input子系统事件处理层(evdev)的环形缓冲区【转】的更多相关文章
- 【Linux高级驱动】input子系统框架
[1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架? 1) 通过网络搜索 2) 自己想办法跟内核代码! 2.1 定位此驱动是属于哪种类 ...
- 【Linux高级驱动】input子系统框架【转】
转自:http://www.cnblogs.com/lcw/p/3802617.html [1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架? 1) 通过网 ...
- 【驱动】input子系统全面分析
初识linux输入子系统 linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(InputC ...
- linux输入子系统(6)-input子系统介绍及结构图
注:本系列转自: http://www.ourunix.org/post/290.html input子系统介绍 输入设备(如按键,键盘,触摸屏,鼠标,蜂鸣器等)是典型的字符设备,其一 ...
- input子系统详解
一.初识linux输入子系统 linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(Inpu ...
- driver: Linux设备模型之input子系统详解
本节从整体上讲解了输入子系统的框架结构.有助于读者从整体上认识linux的输入子系统.在陷入代码分析的过程中,通过本节的知识能够找准方向,明白原理. 本节重点: 输入子系统的框架结构 各层对应内核中的 ...
- driver: Linux设备模型之input子系统具体解释
本节从总体上解说了输入子系统的框架结构.有助于读者从总体上认识linux的输入子系统.在陷入代码分析的过程中,通过本节的知识可以找准方向,明确原理. 本节重点: 输入子系统的框架结构 各层相应内核中的 ...
- linux kernel input 子系统分析
Linux 内核为了处理各种不同类型的的输入设备 , 比如说鼠标 , 键盘 , 操纵杆 , 触摸屏 , 设计并实现了一个对上层应用统一的试图的抽象层 , 即是Linux 输入子系统 . 输入子系统的层 ...
- Linux Input子系统
先贴代码: //input.c int input_register_handler(struct input_handler *handler) { //此处省略很多代码 list_for_each ...
随机推荐
- 写在19年初的后端社招面试经历(两年经验): 蚂蚁 头条 PingCAP
去年(18年)年底想出来看看机会,最后很幸运地拿到了 PingCAP,今日头条的 offer 以及蚂蚁金服的口头 offer.想着可以总结一下经验,分享一下自己这一段"骑驴找马"过 ...
- HDU 6044--Limited Permutation(搜索+组合数+逆元)
题目链接 Problem Description As to a permutation p1,p2,⋯,pn from 1 to n, it is uncomplicated for each 1≤ ...
- EF code first,set composite primary key 复合key问题
环境: EF core 2.0 Net core 2.0 错误: 因实体定义了多个key,打开数据库时程序报以下错误 An unhandled exception occurred while pro ...
- PHP之高性能I/O框架:Libevent(三)
Swoole Swoole里也提供了一些直接操作底层epoll/kqueue事件循环的接口,可将其他扩展创建的socket.PHP代码中stream/socket扩展创建的socket等加入到Swoo ...
- SQL 必知必会·笔记<19>使用游标
游标(cursor)是一个存储在DBMS服务器上的数据库查询,它不是一条SELECT语句,而是被该语句检索出来的结果集.在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据. 使用游标 使用游标 ...
- rabbitMQ和对应的erlang版本匹配
来源自https://www.rabbitmq.com/which-erlang.html erlang安装包下载地址 https://packages.erlang-solutions.com/er ...
- 从逻辑思维中学习CSS,从宽高说起
从宽高说起 从宽高说起,我们知道一个物体的大小是由长.宽.高三个方向的尺寸决定的,但是你想啊电脑显示器是一个平面的,而不是3维,另因网页大部分情况下只需要使用到2维,所以为了简单在CSS中只有宽和高的 ...
- BizTalk 新增/修改/删除 XmlDocument 名字空间的高效方法
新增一个名字空间 public class AddXmlNamespaceStream : XmlTranslatorStream { private String namespace_; priva ...
- 使用WPF教你一步一步实现连连看(二)
连连看算法 第一步:我们考虑在同行或者同列的情况: 同行或者同列又分三种情况: 第一:边线,RowNum或者ColNum等于0或者9 第二:两个相邻 第三:同行不相邻,这种事有条件的,所在行(或列)的 ...
- Centos7下开启80端口
firewall-cmd --zone=/tcp --permanent 命令含义: --zone #作用域 --add-port=80/tcp #添加端口,格式为:端口/通讯协议 -- ...