【问题标题】:Linux Evdev Poll LagLinux Evdev 轮询滞后
【发布时间】:2021-11-24 00:23:00
【问题描述】:

我正在使用带有 2 个连接键盘(内置和 USB)的笔记本电脑。我正在使用libudev 获取这些连接的键盘,并使用epoll 通过evdev 接口轮询它们以获取输入:

// Compile with $(gcc udev.c -ludev)

#include <stdbool.h>
#include <stdio.h>
#include <string.h>

#include <sys/epoll.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#include <time.h>
#include <libudev.h>

#define BILLION 1000000000L

long timespec_diff(struct timespec *start, struct timespec *end)
{
  return (BILLION * (end->tv_sec - start->tv_sec)) +
         (end->tv_nsec - start->tv_nsec);
}

bool want_to_run = true;

int
main(int argc, char *argv[])
{
  int epoll_fd = epoll_create1(0);

  struct udev *udev_obj = udev_new();
  struct udev_enumerate *udev_enum = udev_enumerate_new(udev_obj);
  udev_enumerate_add_match_subsystem(udev_enum, "input");
  udev_enumerate_scan_devices(udev_enum);

  struct udev_list_entry *udev_entries = udev_enumerate_get_list_entry(udev_enum);
  struct udev_list_entry *udev_entry = NULL;
  udev_list_entry_foreach(udev_entry, udev_entries)
  {
    char const *udev_entry_syspath = udev_list_entry_get_name(udev_entry);
    struct udev_device *device = udev_device_new_from_syspath(udev_obj, 
                                                              udev_entry_syspath);

    char const *dev_prop = \
      udev_device_get_property_value(device, "ID_INPUT_KEYBOARD");
    if (dev_prop != NULL && strcmp(dev_prop, "1") == 0) 
    {
      const char *dev_path = udev_device_get_devnode(device);
      if (dev_path != NULL)
      {
        int dev_fd = open(dev_path, O_RDWR | O_NONBLOCK);

        struct epoll_event event = {};
        event.events = EPOLLIN;
        event.data.fd = dev_fd;
        epoll_ctl(epoll_fd, EPOLL_CTL_ADD, dev_fd, &event);
      }
    }
    udev_device_unref(device);
  }
  udev_enumerate_unref(udev_enum);

  struct timespec prev_timespec = {};
  clock_gettime(CLOCK_MONOTONIC_RAW, &prev_timespec);
  while (want_to_run)
  {
    struct epoll_event epoll_events[5] = {0};
    int num_epoll_events = epoll_wait(epoll_fd, epoll_events, 5, 0);
    for (int epoll_event_i = 0; epoll_event_i < num_epoll_events; ++epoll_event_i)
    {
      int dev_fd = epoll_events[epoll_event_i].data.fd;

      struct input_event dev_events[4] = {0};
      int dev_event_bytes_read = read(dev_fd, dev_events, sizeof(dev_events));

      int num_dev_events = dev_event_bytes_read / sizeof(dev_events[0]); 
      for (int dev_event_i = 0; dev_event_i < num_dev_events; ++dev_event_i)
      {
        int dev_event_type = dev_events[dev_event_i].type;
        int dev_event_code = dev_events[dev_event_i].code;
        int dev_event_value = dev_events[dev_event_i].value;

        bool is_released = (dev_event_type == EV_KEY ? dev_event_value == 0 : false);
        bool is_down = (dev_event_type == EV_KEY ? dev_event_value == 1 : false);
        bool was_down = (dev_event_type == EV_KEY ? dev_event_value == 2 : false);

        bool w = (dev_event_code == KEY_W);
        bool a = (dev_event_code == KEY_A);
        bool s = (dev_event_code == KEY_S);
        bool d = (dev_event_code == KEY_D);
        bool q = (dev_event_code == KEY_Q);
        bool e = (dev_event_code == KEY_E);
        bool up = (dev_event_code == KEY_UP);
        bool down = (dev_event_code == KEY_DOWN);
        bool left = (dev_event_code == KEY_LEFT);
        bool right = (dev_event_code == KEY_RIGHT);
        bool escape = (dev_event_code == KEY_ESC);
        bool space = (dev_event_code == KEY_SPACE);
        bool enter = (dev_event_code == KEY_ENTER);
        bool ctrl = (dev_event_code == KEY_LEFTCTRL);
        if (q) want_to_run = false;
      }
    }

    struct timespec end_timespec = {};
    clock_gettime(CLOCK_MONOTONIC_RAW, &end_timespec);
    printf("ns per frame: %lu\n", timespec_diff(&prev_timespec, &end_timespec)); 
    prev_timespec = end_timespec;
  }

  return 0;
}

通过在每个键盘上输入按键进行实验,我在以下情况下遇到了严重的延迟/失速(我鼓励您自己编译并尝试):

  1. 如果我开始在一个键盘上输入键,然后切换到另一个,程序会短暂停止。
  2. 如果我同时在每个键盘上输入键,程序将无限期停止,直到我停止输入键。

我用不同的键盘进行了测试,结果是一样的。这是怎么回事?

更新

我认为这可能特定于我的主机环境(Ubuntu 20.04),因为当我在另一个程序中的每个键盘上发送垃圾邮件时,例如gnome 终端、firefox 等也会发生相同的停顿和延迟。如果我在按键上嚎啕大哭,那就是严重的失速(cpu 风扇熄灭,一切都消失了)它只发生在键盘上,似乎将外接鼠标和触控板一起移动不会造成任何问题。

在英特尔 VTune 热点下运行分析表明,epoll_wait() 是卡顿的根源(这并不奇怪)

【问题讨论】:

  • @DavidC.Rankin 我正在使用epoll_create1
  • 对不起,你是,我晚年失明了。您是否能够确定导致延迟的呼叫?如果这是系统端 udev 问题,我不会感到惊讶。
  • @DavidC.Rankin Intel VTune 'hotspot' 分析显示 epoll_wait 是延迟的来源。我同意这似乎是一个系统范围的问题,因为我遇到了其他程序(如 Firefox、gnome 终端等)的问题。虽然不知道如何修复它......
  • 这似乎是一个问题,因为每次从不同的键盘进行新的按键操作时,udev 都不会在epoll_wait 监控的内存区域中触发文件描述符事件。老实说,我没有玩过决斗键盘游戏,甚至不知道是否设置了默认的 udev 规则来以这种方式处理键盘切换。既然您知道它是 epoll_wait 挂起等待文件描述符的更新,它是否正在监视 - 值得搜索 udev 帖子或 evdev 帖子以查找 epoll_wait 问题。

标签: c linux epoll udev evdev


【解决方案1】:

我已经在我的 ubuntu 桌面 (20.04) 中测试了你的程序,同样的问题发生了。但是当我进入CLI模式(CTRL + ALT + F3),再次运行程序,就没有问题了。

然后我回到 GUI 模式,将当前时间戳附加到 printf("ns per frame...),重建并运行,同时在两个键盘上输入键,程序停止输出,但如果我停止输入,经过一小段延迟后,记录滞后时间的时间戳会喷涌而出。所以看起来程序没有问题,可能是Xorg的一个BUG影响了所有桌面软件。

我发现了这个帖子:https://askubuntu.com/questions/1044985/using-2-keyboards-at-the-same-time-create-annoying-input-lag

【讨论】:

    【解决方案2】:

    谢谢@emptyhua。您的回答引导我进行特定的调查。

    虽然我不能肯定地说,但我相信问题的根源在于 GNOME 而不是 Xorg。 见GNOME issue 1GNOME issue 2

    安装替代 X 环境,例如 xfce4,sudo apt install xfce4 可以消除这种停滞。

    【讨论】:

    • 不客气,很高兴提供一些有用的信息
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-02
    • 1970-01-01
    相关资源
    最近更新 更多