Linux下对input设备调用ioctl时指定EVIOCGBIT选项的缓冲区该多大

wenfanwu 2018-09-13

我们有时候需要获取/dev/input目录下的eventX设备支持哪些事件(EV_KEY、EV_REL和EV_ABS等),可以通过ioctl调用指定EVIOCGBIT(ev, len)选项来获取,例如:

ioctl(fd, EVIOCGBIT(0, EV_MAX), buf);
1

来获取fd设备支持的事件。这涉及到一个问题:buf需要指定多大的长度?

EVIOCGBIT宏的第二个参数是事件标志位最高位可能有多高,例如当前4.16内核版本中该值为0x1f,说明缓冲区最高第0x1f位可能会被置位。因此之前缓冲区是这样指定的:

uint8_t buf[EV_MAX / 8 + 1];
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
1
2
3
4
5
6

为什么会这样呢,跟了一下内核代码,发现在计算需要往用户空间拷贝多少字节的数据是在这个函数中计算的:

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;
}
1
2
3
4
5
6
7
8
9
10

其中的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)];

Linux下对input设备调用ioctl时指定EVIOCGBIT选项的缓冲区该多大

相关推荐