为什么 ioctl(fd, EVIOCGRAB, 1) 有时会导致密钥垃圾邮件?

Why does `ioctl(fd, EVIOCGRAB, 1)` cause key spam sometimes?

我正在尝试编写自己的 "keyboard driver"(实际上并没有编写内核模块), 通过在我认为是用户空间中最低抽象级别的地方抓住键盘:/dev/input/event*.

如果您更改 ioctl(fd, EVIOCGRAB, UNGRAB) 的第一次出现,则以下代码会进行抓取 至 ioctl(fd, EVIOCGRAB, GRAB).

// gcc main.c -o main

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <linux/input.h>
#include <fcntl.h>
#include <errno.h>

#define EXIT_KEY  KEY_ESC
#define UNGRAB    0
#define GRAB      1

const char* kbd_device = "/dev/input/event4";

// ------------------------------------------------------------------------------------------------
int main(void){
  int fd = open(kbd_device, O_RDONLY);
  if(fd == -1){
    printf("Cannot open %s. %s.\n", kbd_device, strerror(errno));
    return -1;
  }

  if(ioctl(fd, EVIOCGRAB, UNGRAB))
    printf("Couldn't grab %s. %s.\n", kbd_device, strerror(errno));
  else
    printf("Grabbed %s!\n", kbd_device);

  while(1){
    struct input_event event;
    read(fd, &event, sizeof(event));
    if (event.type == EV_KEY && event.value >= 0 && event.value <= 2){
      printf("%d %3d\n", event.value, event.code);

      if(event.code == EXIT_KEY){
        ioctl(fd, EVIOCGRAB, UNGRAB);
        close(fd);
        return 0;
      }

    }
  }
}

问题

为什么会发生?

备注

动机

我正在尝试编写一个既适用于 X 又不适用于 X(例如 TTY)的键盘 "driver"。

我理解 X11 的键盘 library/extension 是 XKB。我认为 TTY 的键盘库是 linux/divers/tty/vt/keyboard.c (source), 它使用的初始键盘映射位于 linux/drivers/tty/vt/defkeymap.map (source), and it can be modified by using loadkeys (source here) 中。如果我错了请纠正我。

当您输入时

gcc main.c -o main && sudo ./main ↵

GCC 需要一些时间,所以 密钥在 ./main 运行时已经释放。

当您输入时

sudo ./main ↵

一按下,终端就向shell发送一个换行符,并开始执行./main。然后 released 事件会被你的程序看到,但不会被你的终端看到,因为你的程序已经抓住了输入设备。因此,在终端看来 被卡住了,所以它继续产生换行符。

这个问题已经回答过了,但是还是缺少优雅的解决方法

我前段时间实现的驱动程序也有同样的问题,它也需要捕获键盘。

我找不到一种方法来强制内核在捕获之前识别设备中的密钥释放,因此解决方案是在您检测到所有密钥实际上已被释放之前不要抓取设备。这可以通过在打开设备并抓住它之前使用 EVIOCGKEY ioctl 监视设备来实现。

OBS:请注意 while 循环中显然是虚拟的 read 函数是必要的,以避免忙等待,这样循环将在来自输入设备的每个事件后迭代.另请注意,文件描述符必须配置为阻塞 I/O(默认值)。

void waitReleaseAll(int fd) {

  struct input_event evt;
  unsigned char key_b[KEY_MAX/8 + 1];
  int i, nothing;

  while ( 1 ) {
    memset(key_b, 0, sizeof(key_b));
    ioctl(fd, EVIOCGKEY(sizeof(key_b)), key_b);
    for ( nothing = 1 , i = 0 ; i < KEY_MAX/8 + 1 ; i++ ) {
      if ( key_b[i] != 0 ) { nothing = 0; break; }
    }
    if ( nothing ) break;
    read(fd, &evt, sizeof(evt));
  }
  printf("All keys are now released\n");

}

要解决您的问题,您应该在代码中使用 SIGINT 来识别用户的 Ctrl-C 击键。

在您的代码中实现 SIGNAL

static volatile sig_atomic_t stop = 0;
    
static void interrupt_handler(int sig)
{
    stop = 1;
} // Outside of the main function.

int main(int argc, char *argv[])
{
    signal(SIGINT, interrupt_handler);
    while (!stop) {
    //your code    
    }
    exit(0);
}