Какой вариант использования EPOLLET?

epoll в режиме триггера это странный зверь. Требуется, чтобы процесс отслеживал, каков последний ответ для каждого отслеживаемого FD. Он обязывает процесс обрабатывать, в обязательном порядке, каждое событие, о котором сообщалось (иначе мы могли бы подумать, что FD ничего не сообщает, хотя на самом деле он отключен из-за поведения триггера фронта).

Когда имеет смысл использовать epoll сюда?

1 ответ

Решение

Основной вариант использования для EPOLLET что я знаю, это с микропотоками.

Напомним, что пользовательское пространство выполняет переключение контекста между микропотоками (которое я собираюсь называть "волокнами", потому что оно короче) в зависимости от наличия возможности для работы. Это также называется "совместная многозадачность".

Основная обработка файловых дескрипторов заключается в обертывании соответствующих функций ввода-вывода следующим образом:

ssize_t read(int fd, void *buffer, size_t length) {
  // fd should already be in O_NONBLOCK mode
  while(true) {
    ssize_t result = ::read(fd, buffer, length); // The real read
    if( result!=-1 || (errno!=EAGAIN && errno!=EWOULDBLOCK) )
      return result;

    start_monitoring(fd, READ);
    wait_event();
  }
}

start_monitoring это функция, которая гарантирует, что fd контролируется на доступность для чтения. wait_event выполняет переключение контекста, пока планировщик не разбудит это волокно, потому что fd теперь есть данные, готовые для чтения.

Обычный способ реализовать это с epoll это позвонить EPOLL_CTL_MOD на fd в start_monitoring добавить прослушивание для EPOLLINи снова после того, как эполл сообщил о событии, чтобы прекратить слушать EPOLLIN,

Это означает, что read у которого есть доступные данные, закончится в течение 1 системного вызова, но чтение, которое возвращает EAGAIN займет не менее 4 системных вызовов (оригинал read, два EPOLL_CTL_MODи финал read это удается).

Обратите внимание, что выше не учитывается epoll_wait это также должно иметь место. Я не считаю это, потому что я принимаю щедрое предположение, что другие волокна также будут разбужены этим же системным вызовом, поэтому несправедливо полностью приписывать его стоимость нашему волокну. В общем, этот механизм требует 4+ х системных вызовов, где х между 0 и один.

Одним из способов снижения стоимости является использование EPOLLONESHOT, Это удаляет fd от автоматического контроля, снижая наши расходы до 3+ х. Лучше, но мы можем сделать еще лучше.

Войти EPOLLET, Предыдущий fd состояние может быть либо вооруженным, либо безоружным (т. е. будет ли следующее событие вызывать epoll). Кроме того, ФД может или не может в настоящее время (в точке входа в read) иметь данные готовы. Четыре штата. Давайте распространять их.

Готов (вооружен или нет): первый звонок read возвращает данные. 1 системный вызов. Этот путь не меняет вооруженное государство, и готовность государства зависит от того, все ли мы прочитаем.

Не готов (вооружен или нет): первый звонок read возвращается EAGAINТаким образом, вооружение FD. Мы идем спать в wait_event без необходимости выполнять другой системный вызов. Как только мы просыпаемся, мы находимся в безоружном режиме (как мы только что проснулись). Нам при этом не нужно звонить epoll_ctl отключить прослушивание на фд. Мы называем read который возвращает данные. Мы оставляем функцию готовой или нет, но без оружия.

Общая стоимость: 2+ х.

Нам придется столкнуться с одним ложным fdкак fd начинается вооруженным. Наш код должен обрабатывать случай, когда epoll сообщает о том, что ни одно волокно не слушает. Обработка, в этом случае, просто означает игнорировать и двигаться дальше. FD больше не будет сообщаться.

Другие вопросы по тегам