Недостатки 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