linux下鼠标事件丢失与evdev缓冲区溢出问题

31 篇文章 1 订阅
28 篇文章 4 订阅

之前遇到这样一个bug:在一个性能较差的linux平台上的一个Qt程序,当UI线程在执行耗时操作时,界面会卡顿,而这时频繁点击滑动鼠标,会出现鼠标事件丢失的问题。举个例子:某个控件收到一个鼠标按下的事件,但再也没有收到鼠标弹起事件,而此时鼠标按键实际上已经弹起了,这就导致程序进入了一种异常状态,除非再次点击鼠标,否则无法恢复。上面这个问题是在QApplication的事件过滤器中确定的,全局的QApplication事件过滤器能记录下所有的Qt事件。

那么如何确定事件是在哪里丢失的呢?首先要大概的了解一下Qt鼠标事件的生成与分发过程,由于上述平台没有窗口管理系统(一般是Xorg),所以大致流程是这样的:

  1. 鼠标产生动作
  2. 内核响应中断,将鼠标事件放入evdev缓冲区
  3. Qt从evdev缓冲区中读取鼠标事件
  4. Qt分发鼠标事件
  5. Qt处理鼠标事件

那么事件是在哪一步丢失的呢?经过分析,第3步是由QEvdevMouseHandler::readMouseData函数完成的,通过记录日志发现,鼠标弹起事件在这一步就已经不见了。而这一步呢,是Qt能接触到鼠标事件的第一现场,也就是说问题的直接原因并不在于Qt,而在于内核。

回过头来看一下,上面的1-5步看起来像是串行的,实际上并不是:内核的中断响应与Qt的事件读取是两个独立的流程,1-2是一个内核的循环,3-5是一个Qt的循环,而这两个循环共享了同一个evdev缓冲区。如果内核的循环跑的很快,而Qt的循环由于什么原因被卡住而跑的很慢时会发生什么呢?自然evdev缓冲区会发生溢出。以我手上的4.16.0版本的内核源码为例,其中evdev.c中的__pass_event函数用来向evdev缓冲区中压入事件:

static void __pass_event(struct evdev_client *client,
			 const struct input_event *event)
{
	client->buffer[client->head++] = *event;
	client->head &= client->bufsize - 1;

	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].input_event_sec =
						event->input_event_sec;
		client->buffer[client->tail].input_event_usec =
						event->input_event_usec;
		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;
	}

	if (event->type == EV_SYN && event->code == SYN_REPORT) {
		client->packet_head = client->head;
		kill_fasync(&client->fasync, SIGIO, POLL_IN);
	}
}

可以看出来evdev缓冲区是一个环状缓冲区,如果插入事件导致head与tail相等,即可判定缓冲区发生了溢出,会丢弃缓冲区中的所有事件,同时内核会向缓冲区放入一个SYN_DROPPED事件,通知用户程序事件有丢失。不巧的是,Qt的QEvdevMouseHandler::readMouseData对这个事件没有做任何处理,也就无法自动从异常状态恢复了。如果希望解决这个问题,可以考虑从这个特殊的SYN_DROPPED事件下手。

PS:虽然事件是被内核丢掉的,但Qt和用户程序对此也要负一半责任:为什么Qt要把事件读取和事件响应放在一个UI线程里面去跑呢?用户代码为什么要把耗时操作直接在UI线程中跑呢?

PPS:如果事件源源不断产生,而响应事件平摊耗时总是大于生成事件耗时,不管缓冲区有多大,总有一天会溢出的,不过这种极端情况似乎并不容易出现。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值