linux下鼠標事件丟失與evdev緩衝區溢出問題

之前遇到這樣一個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:如果事件源源不斷產生,而響應事件平攤耗時總是大於生成事件耗時,不管緩衝區有多大,總有一天會溢出的,不過這種極端情況似乎並不容易出現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章