我們有時候需要獲取/dev/input目錄下的eventX設備支持哪些事件(EV_KEY、EV_REL和EV_ABS等),可以通過ioctl調用指定EVIOCGBIT(ev, len)選項來獲取,例如:
ioctl(fd, EVIOCGBIT(0, EV_MAX), buf);
來獲取fd設備支持的事件。這涉及到一個問題:buf需要指定多大的長度?
EVIOCGBIT宏的第二個參數是事件標誌位最高位可能有多高,例如當前4.16內核版本中該值爲0x1f,說明緩衝區最高第0x1f位可能會被置位。因此之前緩衝區是這樣指定的:
uint8_t buf[EV_MAX / 8 + 1];
這樣的話理論上可以容納下所有標誌位。但是實際執行時(64位機器上)會有棧溢出問題:
$ sudo ./eviocgbit /dev/input/event4
Supported event types:
Event type 0x00 (Synch Events)
Event type 0x01 (Keys or Buttons)
*** stack smashing detected ***: <unknown> terminated
Aborted
爲什麼會這樣呢,跟了一下內核代碼,發現在計算需要往用戶空間拷貝多少字節的數據是在這個函數中計算的:
static int bits_to_user(unsigned long *bits, unsigned int maxbit,
unsigned int maxlen, void __user *p, int compat)
{
int len = BITS_TO_LONGS(maxbit) * sizeof(long);
if (len > maxlen)
len = maxlen;
return copy_to_user(p, bits, len) ? -EFAULT : len;
}
其中的maxbit在本例中即爲我們指定的EV_MAX,這個函數首先使用BITS_TO_LONGS宏計算出需要幾個long型數據能夠放下這麼多位的數據,然後乘以long的大小,得到緩衝區的大小。雖然下面有使用maxlen限制緩衝區大小,但是maxlen也被指定爲了EV_MAX,所以並沒有效果。
如上所述,我們在申請緩衝區時也要像內核代碼一樣以long型數據大小爲最小單位,申請n個long型數據大小的緩衝區就沒有問題了:
uint8_t evtype_b[(EV_MAX / (sizeof(long) * 8) + 1) * sizeof(long)];