LINUX.ORG.RU

Ядро и epoll_wait(): почему его не тянет вернуться из ядра на первое же событие?

 


1

1

Щач будет наркоманский вопрос, конечно. Когда epoll_wait вернулся из ядра и принёс пачку событий, почему он НЕ вернулся сразу же, когда случилось самое первое событие с первым сокетом, а позволил себе постоять в ядре и подождать ещё? Точнее так: допустим я ядро и пришёл какой-то пакет, относящийся к сокету процесса N. Как мне понять - бежать сразу будить процесс N на тему одного сокета или подождать - вдруг в следующие микросекунды навалит ещё пакетов про другие сокеты ЭТОГО ЖЕ процесса и я смогу сходить в N 1 раз оптом?

Ясно, что вопрос в ядре так не ставится, а возврат epoll_wait сразу с пачкой сокетов скорее следствие того, что ядро видит события от сетевухи тоже сразу оптом, оптом рассовывает по сокетам и потом уже смотрит какие бы процессы разбудить. И вообще работать над пакетами событий выгоднее, а не контекстсвитчить туда-сюда - это ясно. Но это все равно лишь дебильные догадки, хотелось бы почитать про сабж детальнее. Ядро разгребает накопившиеся события, относящиеся к одному процессу, не чаще фиксированного интервала, типа 1 миллисек? Сетевуха сгружает тыщи пакетов за одно прерывание? Ядро пытается контекстсвитчить не чаще N микросекунд? В общем, что является причиной того, что epoll_wait() в принципе способен вернуться сразу с пачкой евентов и его не колбасит на каждое. В то же время, если сокетов будет 10К и событие произойдёт только на 1, он вернутся достаточно шустро. Хотя наверное не так шустро, как в DPDK мире и прочем подобном?



Последнее исправление: trisobakov (всего исправлений: 2)

С другой стороны, в ядре могла накопится целая пачка событий между вызовами epoll_wait. Тогда их точно нужно сразу пачкой и отдать.

C ситуациями, когда висящая (спящая) на epool_wait программа получает сразу пачку событий виесто одного, я не знаком.

trex6 ★★★★★
()
Последнее исправление: trex6 (всего исправлений: 2)
Ответ на: комментарий от exception13

но я сталкивался с тем что события еще и не по порядку приходили

Да, всякое бывает. Но моментальный выход из epoll_wait с пачкой событий я встречал намного чаще.

trex6 ★★★★★
()

В общем, что является причиной того, что epoll_wait() в принципе способен вернуться сразу с пачкой евентов и его не колбасит на каждое.

В тот что RUNNABLE процесс спящий в epoll_wait() получит CPU не сразу. Рассчитывайте придти на CPU с задержкой порядка 1ms. Ну и считайте сами сколько пакетов может набежать за это время при ваших workloads. И на скольки сокетах.

Это если совсем «на пальцах». А дальше начинаются interrupt coalescing, NAPI и тому подобные вещи.

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от exception13

но я сталкивался с тем что события еще и не по порядку приходили.

Вы бы не могли развернуть тему? Особенно в плане «не по порядку»? Мне правда интересно. Без сарказма.

bugfixer ★★★★★
()
Ответ на: комментарий от exception13

EPOLLHUP если память не изменяет.

Меня больше интересовал порядок активных sockets в массивчике что epoll_wait() возвращает относительно прибытия физических пакетов. Была надежда что in-order (сам не проверял). Но пока мы действуем по старинке и rotate processing как если бы select() использовался. Чтобы не дай бог у кого-то не появился competitive advantage.

То что у Вас EPOLLHUP вызывал out-of-order - скорее всего баг. С epoll надо быть вообще аккуратным - там очень интересные effects вылазят.

bugfixer ★★★★★
()

Причина в том, что на момент вызова epoll_wait() в очереди уже может быть пачка событий, и вернётся она тогда вся. А если её нет - будет ожидаться одно и вернётся одно (ну может и правда сетевуха сразу пачку отгрузит и они все придут, но это скорее всего намного реже чем первая причина).

Всякие регулировки типа «не свитчить зря» тут не нужны, они автоматом получаются: если ты тратишь много времени на свитчи, процесс перестаёт успевать всё разгребать по одному и к слеующему вызову epoll_wait() их там уже много. А если успевает - почему бы и не свитчить? Задержки будут меньше, проц нагружен да, но его ж хватает.

firkax ★★★★★
()
Ответ на: комментарий от bugfixer

То что у Вас EPOLLHUP вызывал out-of-order - скорее всего баг.

Да, и баг скорее всего в программе, а не в ядре.

С epoll надо быть вообще аккуратным - там очень интересные effects вылазят.

Хм, вот сколько с epoll работал - ни разу не видел. Но в жизни всякое бывает.

pathfinder ★★★★
()
Ответ на: комментарий от pathfinder

С epoll надо быть вообще аккуратным - там очень интересные effects вылазят.

Хм, вот сколько с epoll работал - ни разу не видел.

Это до тех пор пока у Вас сокеты «живут» в одном процессе. Как только Вы их начинаете «гонять» между разными (таки это возможно) и/или alias’ятся - запросто можно попасть в ситуацию когда events начинают валиться на unrelated fds, и если это случается то ничего уже с этим не сделать пока процесс не умрёт. Arguably kernel glitch. Но более-менее понятно почему сделали именно так - не хотели платить лишнего ради спасения «злобных буратин».

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Arguably kernel glitch

Эээээ.... получается в линуксе много лет глючный один из ключевых элементов и об этом ничего не сказано в man? И вообще об этом мало кто знает. Нелегко в это поверить.

pathfinder ★★★★
()
Ответ на: комментарий от pathfinder

И вообще об этом мало кто знает.

Ну, use-case довольно специфичный. Но об этом ниже.

Нелегко в это поверить.

Наслово мне, безусловно, верить не надо, но проверить можно. Glitch в следующем: подписки в epoll автоматически вычищаются только когда умирает underlying socket, а не конкретный дескриптор с ним ассоциированный. Чтобы воспроизвести - создайте сокет, подпишите его в epoll, dup’ните и закройте оригинальный дескриптор. Ну и посмотрите какие epoll events будут валится. Удачи от них отписаться ;)

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

в q1/a1 и q6/a6 man 7 epoll разве не это описано ?

1.  What happens if you register the same file descriptor on an
           epoll instance twice?

           You will probably get EEXIST.  However, it is possible to add
           a duplicate (dup(2), dup2(2), fcntl(2) F_DUPFD) file
           descriptor to the same epoll instance.  This can be a useful
           technique for filtering events, if the duplicate file
           descriptors are registered with different events masks.
6.  Will closing a file descriptor cause it to be removed from
           all epoll interest lists?

           Yes, but be aware of the following point.  A file descriptor
           is a reference to an open file description (see open(2)).
           Whenever a file descriptor is duplicated via dup(2), dup2(2),
           fcntl(2) F_DUPFD, or fork(2), a new file descriptor referring
           to the same open file description is created.  An open file
           description continues to exist until all file descriptors
           referring to it have been closed.

           A file descriptor is removed from an interest list only after
           all the file descriptors referring to the underlying open
           file description have been closed.  This means that even
           after a file descriptor that is part of an interest list has
           been closed, events may be reported for that file descriptor
           if other file descriptors referring to the same underlying
           file description remain open.  To prevent this happening, the
           file descriptor must be explicitly removed from the interest
           list (using epoll_ctl(2) EPOLL_CTL_DEL) before it is
           duplicated.  Alternatively, the application must ensure that
           all file descriptors are closed (which may be difficult if
           file descriptors were duplicated behind the scenes by library
           functions that used dup(2) or fork(2)).

добавлено впервые a6 https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/commit/?id=a4a120c7681a80594f6d9f58c83c0aec7525e261

поменяно a1 https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/commit/?id=2b22933411d2b2506ca981ff98ee068757a1f719

с этого комммита вроде никаких изменений нет https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/commit/?id=d1d87801792096cd68e73811ee0022c0524f6c69

dhampire ★★★
()
Последнее исправление: dhampire (всего исправлений: 1)
Ответ на: комментарий от dhampire

разве не это описано ?

По поводу №6 - всё правильно. И понятно почему так сделано. НО - it can bite you badly, если ты недостаточно аккуратен. И это именно то что я имел в виду. select() гораздо более всепрощающий - можно fd из fd_set убрать вне зависимости от. В этом смысле - epoll ни в одном глазу не drop-in replacement. А теперь представьте - Вы отдали сокет другому процессу и все дескрипторы на него в текущем закрыли. Как Вы в принципе можете после этого отписаться от epoll events с этого сокета? И это вовсе не гипотетическая фантазия - мы на это реально нарвались. Являлось ли это багом нашего user-space - безусловно. Можно ли было это сделать более user-friendly со стороны ядра - кмк - однозначно.

А вот по поводу пункта №1 - я на самом деле очень удивлён. В свете этого №6 does not make any sense. Если реально позволяются разные event masks на разных fds (означает per-fd indirection layer already exists), то невычищение подписок по закрытию fd - imho, plain bug. Но я не думаю что оно реально так работает (что per-fd masks are allowed). Будет время - обязательно проверю.

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 3)