ݺߣ

ݺߣShare a Scribd company logo
Недостатки Linux API
с точки зрения разработчика веб-сервера.
Валентин Бартенев
NGINX, Inc.
Стачка 2017
(Сверх)краткое введение
● Ядро:
○ взаимодействие с оборудованием
○ управление памятью
○ управление процессами
○ работа с файловыми системами
○ сетевые протоколы
● Linux API: набор системных вызовов и библиотек
● POSIX (portable operating system interface)
2 / 20
Мультиплексирование ввода-вывода
● POSIX: select(), poll()
○ Неэффективны при большом количестве
соединений
Эффективные механизмы:
● Linux: epoll
● FreeBSD: kqueue
● Solaris: /dev/poll, event ports
3 / 20
Базовый интерфейс epoll
Создание экземпляра epoll:
int epoll_create();
Регистрация дескрипторов:
int epoll_ctl(int epfd, int op, int fd,
struct epoll_event *event);
Ожидание событий:
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
4 / 20
Параметры epoll_ctl()
int epoll_ctl(int epfd, int op, int fd,
struct epoll_event *event);
int epfd - экземпляр epoll
int op - тип операции
int fd - отслеживаемый дескриптор
struct epoll_event {
uint32_t events; - битовая маска типов событий
epoll_data_t data; - указатель на пользовательские данные
};
5 / 20
События
struct epoll_event {
uint32_t events; - битовая маска типов событий
epoll_data_t data; - указатель на пользовательские данные
};
Основные типы событий:
EPOLLIN - доступен на чтение
EPOLLOUT - доступен на запись
6 / 20
Прочие флаги событий
EPOLLERR - ошибка
EPOLLHUP - “зависание”
EPOLLRDHUP - закрытие соединения (Linux 2.6.17+)
EPOLLET - переключает режим работы
EPOLLONESHOT - однократное событие
EPOLLWAKEUP - “не спать”
EPOLLPRI - Out-of-band данные
EPOLLEXCLUSIVE - “эксклюзивный” режим работы
7 / 20
Особенности флагов
EPOLLERR, EPOLLHUP - заданы всегда
EPOLLRDHUP - тихо игнорируется старыми ядрами
EPOLLWAKEUP - работает без EPOLLONESHOT и EPOLLET
EPOLLEXCLUSIVE - ограничения на другие флаги
8 / 20
epoll_ctl() и типы операций
int epoll_ctl(int epfd, int op, int fd,
struct epoll_event *event);
Типы операций (int op):
EPOLL_CTL_ADD - добавить
EPOLL_CTL_MOD - изменить
EPOLL_CTL_DEL - удалить
Можно обойтись двумя? А одной?
int epoll_ctl(epfd, fd, *event);
9 / 20
epoll_ctl() в работе
10 / 20
epoll и close()
● После close(fd) вызов epoll_ctl(fd, EPOLL_CTL_DEL)
не нужен, удаление происходит автоматически
● Однако на ядрах до версии Linux 3.3 и после 3.13 вызов
epoll_ctl(fd, EPOLL_CTL_DEL) перед вызовом close(fd)
убирает тормоза!
11 / 20
epoll vs. kqueue
epoll_ctl()
epoll_wait()
epoll_pwait()
epoll_create()
epoll_create1()
eventfd()
eventfd2()
timerfd_create()
timerfd_settime()
timerfd_gettime()
signalfd()
signalfd4()
inotify_init()
inotify_init1()
inotify_add_watch()
inotify_rm_watch()
12 / 20
kqueue()
kevent()~=
Линус о kqueue
« I've actually read the BSD kevent stuff,
and I think it's classic over-design. It's not
easy to see what it's all about, and the
whole <kq, ident, filter> tuple crap is just
silly. Looks much too complicated. »
13 / 20
http://yarchive.net/comp/linux/event_queues.html
man epoll и связанные:
12 517 слов
man kqueue:
2 479 слов
Асинхронная работа с файлами
● POSIX AIO (aio_read(), aio_write() и т. д.) в
Linux:
○ Обертка от glibc в пользовательском пространстве
○ Плохо масштабируется
○ Высокие накладные расходы
● Kernel AIO (io_submit(), io_setup() и т. д.)
○ Требования к выравниванию
○ Работает только в режиме O_DIRECT
14 / 20
Асинхронная работа с файлами через
собственный пул потоков
● «Пулы потоков: ускоряем NGINX в 9 и более раз»
https://habrahabr.ru/post/260669/
● Накладные расходы и проблема очереди
● Предложенные решения (так и не включены в ядро):
○ pwritev2()/preadv2() с флагом RWF_NONBLOCK
○ fincore()
15 / 20
Интерфейс sendfile()
Linux:
ssize_t sendfile(int out_fd, int in_fd,
off_t *offset, size_t count);
FreeBSD (ещё поддерживает асинхронное чтение):
int sendfile(int fd, int s, off_t offset,
size_t nbytes, struct sf_hdtr *hdtr,
off_t *sbytes, int flags);
Solaris:
ssize_t sendfilev(int fildes, struct sendfilevec *vec,
int sfvcnt, size_t *xferred);
16 / 20
Особенности sendfile() в Linux 4.3+
ret = sendfile(out_fd, in_fd, offset, count);
Linux до 4.3:
● EINTR только в самом начале
● ret < count эквивалентно EAGAIN
Linux после 4.3:
● EINTR в произвольный момент
● Не отличить EAGAIN от EINTR при ret < count
● Требуется на один вызов больше
17 / 20
Linux и SO_REUSEPORT
● В Linux и DragonFly BSD работает иначе, чем в
большинстве других unix-подобных систем
● Помогает:
○ Избежать борьбы за лок на listen сокете
○ Равномерно распределять соединения
○ Складывать пакеты от одного отправителя в один
и тот же процесс
● Теряет соединения при закрытии
○ В DragonFly BSD при реализации SO_REUSEPORT
консультировались с разработчиками nginx и
проблему решили
18 / 20
Недостатки Linux API с точки зрения разработчика веб-сервера.
Спасибо за внимание.
vbart@nginx.com
http://vbart.ru

More Related Content

Недостатки Linux API с точки зрения разработчика веб-сервера.

  • 1. Недостатки Linux API с точки зрения разработчика веб-сервера. Валентин Бартенев NGINX, Inc. Стачка 2017
  • 2. (Сверх)краткое введение ● Ядро: ○ взаимодействие с оборудованием ○ управление памятью ○ управление процессами ○ работа с файловыми системами ○ сетевые протоколы ● Linux API: набор системных вызовов и библиотек ● POSIX (portable operating system interface) 2 / 20
  • 3. Мультиплексирование ввода-вывода ● POSIX: select(), poll() ○ Неэффективны при большом количестве соединений Эффективные механизмы: ● Linux: epoll ● FreeBSD: kqueue ● Solaris: /dev/poll, event ports 3 / 20
  • 4. Базовый интерфейс epoll Создание экземпляра epoll: int epoll_create(); Регистрация дескрипторов: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); Ожидание событий: int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 4 / 20
  • 5. Параметры epoll_ctl() int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epfd - экземпляр epoll int op - тип операции int fd - отслеживаемый дескриптор struct epoll_event { uint32_t events; - битовая маска типов событий epoll_data_t data; - указатель на пользовательские данные }; 5 / 20
  • 6. События struct epoll_event { uint32_t events; - битовая маска типов событий epoll_data_t data; - указатель на пользовательские данные }; Основные типы событий: EPOLLIN - доступен на чтение EPOLLOUT - доступен на запись 6 / 20
  • 7. Прочие флаги событий EPOLLERR - ошибка EPOLLHUP - “зависание” EPOLLRDHUP - закрытие соединения (Linux 2.6.17+) EPOLLET - переключает режим работы EPOLLONESHOT - однократное событие EPOLLWAKEUP - “не спать” EPOLLPRI - Out-of-band данные EPOLLEXCLUSIVE - “эксклюзивный” режим работы 7 / 20
  • 8. Особенности флагов EPOLLERR, EPOLLHUP - заданы всегда EPOLLRDHUP - тихо игнорируется старыми ядрами EPOLLWAKEUP - работает без EPOLLONESHOT и EPOLLET EPOLLEXCLUSIVE - ограничения на другие флаги 8 / 20
  • 9. epoll_ctl() и типы операций int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); Типы операций (int op): EPOLL_CTL_ADD - добавить EPOLL_CTL_MOD - изменить EPOLL_CTL_DEL - удалить Можно обойтись двумя? А одной? int epoll_ctl(epfd, fd, *event); 9 / 20
  • 11. epoll и close() ● После close(fd) вызов epoll_ctl(fd, EPOLL_CTL_DEL) не нужен, удаление происходит автоматически ● Однако на ядрах до версии Linux 3.3 и после 3.13 вызов epoll_ctl(fd, EPOLL_CTL_DEL) перед вызовом close(fd) убирает тормоза! 11 / 20
  • 13. Линус о kqueue « I've actually read the BSD kevent stuff, and I think it's classic over-design. It's not easy to see what it's all about, and the whole <kq, ident, filter> tuple crap is just silly. Looks much too complicated. » 13 / 20 http://yarchive.net/comp/linux/event_queues.html man epoll и связанные: 12 517 слов man kqueue: 2 479 слов
  • 14. Асинхронная работа с файлами ● POSIX AIO (aio_read(), aio_write() и т. д.) в Linux: ○ Обертка от glibc в пользовательском пространстве ○ Плохо масштабируется ○ Высокие накладные расходы ● Kernel AIO (io_submit(), io_setup() и т. д.) ○ Требования к выравниванию ○ Работает только в режиме O_DIRECT 14 / 20
  • 15. Асинхронная работа с файлами через собственный пул потоков ● «Пулы потоков: ускоряем NGINX в 9 и более раз» https://habrahabr.ru/post/260669/ ● Накладные расходы и проблема очереди ● Предложенные решения (так и не включены в ядро): ○ pwritev2()/preadv2() с флагом RWF_NONBLOCK ○ fincore() 15 / 20
  • 16. Интерфейс sendfile() Linux: ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); FreeBSD (ещё поддерживает асинхронное чтение): int sendfile(int fd, int s, off_t offset, size_t nbytes, struct sf_hdtr *hdtr, off_t *sbytes, int flags); Solaris: ssize_t sendfilev(int fildes, struct sendfilevec *vec, int sfvcnt, size_t *xferred); 16 / 20
  • 17. Особенности sendfile() в Linux 4.3+ ret = sendfile(out_fd, in_fd, offset, count); Linux до 4.3: ● EINTR только в самом начале ● ret < count эквивалентно EAGAIN Linux после 4.3: ● EINTR в произвольный момент ● Не отличить EAGAIN от EINTR при ret < count ● Требуется на один вызов больше 17 / 20
  • 18. Linux и SO_REUSEPORT ● В Linux и DragonFly BSD работает иначе, чем в большинстве других unix-подобных систем ● Помогает: ○ Избежать борьбы за лок на listen сокете ○ Равномерно распределять соединения ○ Складывать пакеты от одного отправителя в один и тот же процесс ● Теряет соединения при закрытии ○ В DragonFly BSD при реализации SO_REUSEPORT консультировались с разработчиками nginx и проблему решили 18 / 20