Какой вариант использования 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 больше не будет сообщаться.