предисловие
В последние годы в разработке серверных приложений широко упоминаются понятия «асинхронность» и «неблокировка». Часто всем нравится описывать их вместе, что может привести к путанице в понимании этих двух слов. В этой статье делается попытка начать с LinuxI/O
с точки зрения обид и обид между ними двумя.
В этой статье рассматриваются следующие вопросы:
-
Linux
изI/O
базовые знания; -
I/O
Значение модели и существующие категории: - блокировать
I/O
; - многопоточная блокировка
I/O
; - неблокирующий
I/O
; -
I/O
мультиплексирование:select
/poll
/epoll
; - асинхронный
I/O
-
libuv
как решитьI/O
Эта проблема.
Кроме того, примеры, рассмотренные в этой статье, размещены наGITHUB, добро пожаловать на загрузку для пробной эксплуатации.
Основы ввода-вывода в Linux
Начните с примера чтения файла
Теперь есть требование читать файлы через скрипты оболочки. Я считаю, что большинство людей смогут завершить реализацию сразу:
$ cat say-hello.txt
А теперь мы просимC
для достижения той же функции, чтобы раскрыть больше деталей.
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
char buffer[1024];
int fd = open("say-hello.txt", O_RDONLY, 0666);
int size;
if (fd < 0) {
perror("open");
return 1;
}
while ((size = read(fd, buffer, sizeof(buffer))) > 0)
{
printf("%s\n", buffer);
}
if (size < 0) {
perror("read");
}
close(fd);
return 0;
}
передачаopen
функция принимает число, используя его дляwrite
а такжеread
операция, последний звонокclose
, что является самым основнымLinux
I/O
Операционные процедуры.
Здесь часто пишут JavaScript, и у людей, не знакомых с c, могут возникнуть две проблемы.
- Какое число возвращает открытый метод?
- Операция чтения будет считывать ресурсы с жесткого диска, а код после чтения нужно ждать, как это сделать (вроде бы отличается от Node.js).
С сомнения начинаем следующее знание.
файловый оператор
мы знаемLinux
Есть предложениеslogan
Это называется "все есть файл". Очень важным моментом, отражающим эту особенность, является механизм дескриптора файла.
Подытожим общееI/O операций, в том числе:
-
TCP
/UDP
- стандартный ввод и вывод
- файл читать и писать
DNS
- Трубы (технологическая связь)
Linux
Механизм файловых операторов используется для обеспечения согласованности интерфейса, в том числе:
- представлятьфайловый оператор(
file descriptor
, именуемый в дальнейшемfd
); - единая пара
read
а такжеwrite
Метод операций чтения и записи.
Пример, упомянутый вышеopen
Функция возвращает число, являющееся дескриптором файла, который соответствует единственному файлу в текущем процессе.
Linux
В эксплуатации будетБлок управления технологическим процессом (PCB)Используйте массив фиксированного размера, каждый элемент массива указывает наядродля каждогообработатьВедется запись файлов, открытых этим процессом.
struct task_struct {
...
/* Open file information: */
struct files_struct *files;
...
}
/*
* Open file table structure
*/
struct files_struct {
spinlock_t file_lock ____cacheline_aligned_in_smp;
unsigned int next_fd;
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
unsigned long full_fds_bits_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
а такжеfd
Фактически это порядковый номер соответствующей файловой структуры в этом массиве, который такжеfd
Причина, по которой он начнется с 0.
так вread
илиwrite
прошёл во время операцииfd
,Linux
Естественно, вы можете найти нужный файл для работы.
как восприниматьfd
существует, вы можете использоватьlsof
например, печать с помощью следующей командыChrome
браузер в данный момент открытfd
ситуации (если вы использовалиChrome
браузер для доступа к текущей странице).
$ lsof -p$(ps -ef |grep Chrome|awk 'NR==1{print $2}')
Вторая проблема, представленная вышеприведенным примером:
Операция чтения будет считывать ресурсы с жесткого диска, а код после чтения нужно ждать, как это сделать (вроде бы отличается от Node.js).
В работающем процессе Linux основной частью выполняемой программы является процесс или поток (до версии 2.4 ядро Linux было процессом, а затем основной единицей планирования стал поток, а процесс стал контейнером потока). .
Процесс будет иметь базовое рабочее состояние во время выполнения процесса, как это
В сочетании с приведенным выше примером после выполнения функции чтения процесс переходит в состояние блокировки, иI/O
После завершения система прерывает процесс, чтобы снова разблокировать процесс, переходит в состояние готовности и переходит в состояние выполнения после ожидания выделения кванта времени.
То есть наш процесс будет вI/O
Блокировка во время операции, вот объяснение проблемы выше.
Модель ввода/вывода
этот описан вышеI/O 机制
это мыI/O 模型
середина阻塞 I/O
механизм.
блокировка ввода/вывода
блокироватьI/O
даread
/write
Механизм выполнения функции по умолчанию переводит процесс в состояние блокировки при выполнении операций чтения и записи.После завершения ввода-вывода системное прерывание переводит его в состояние готовности, ожидание выделения кванта времени, и выполнить его.
но блокируетI/O
Проблема с механизмом в том, что он не может выполняться одновременноI/O
операции или вI/O
Операция выполняется одновременно с вычислением ЦП. В сценарии веб-запрос/ответ, если один запрос блокирует чтение состояния, другие запросы не могут быть обработаны.
Нам нужно решить эту проблему.
Многопоточный блокирующий ввод-вывод
Первая идея заключается в использовании многопоточности.
Мы предварительно инициализируем пул потоков, используя семафорwait
Примитив переходит в состояние блокировки. подожди пока не будетI/O
Когда операция нужна, через семафорsignal
разбудить поток и выполнить соответствующийI/O
работать. Для подробной работы, пожалуйстасм. код.
Но многопоточный неблокирующийI/O
Недостаток заключается в том, что когда количество соединений достигает большого уровня, переключение потоков также требует больших накладных расходов.
Итак, ожидайте, что сможете разрешить в потокеI/O
Операция ожидания позволяет избежать накладных расходов на переключение контекста потока, вызванное открытием нескольких потоков. Есть ли такой способ, чтобы мы могли ввести неблокирующийI/O
режим.
Неблокирующий ввод-вывод
Неблокирующий ввод-вывод — это механизм, который позволяет пользователюI/O
После чтения и записи функции немедленно возвращайтесь, если буфер недоступен для чтения или записи, возвращайтесь напрямую-1
. Вот неблокирующийI/O
Пример построения веб-сервера вы можетесм. код.
Ключевая функция такова:
fcntl(fd, F_SETFL, O_NONBLOCK);
Вы можете увидеть процесс здесьSTATE
больше не блокируется,I/O
Во время операции другиеCPU
операция
неблокирующийI/O
Может помочь нам решить параллельное выполнение в одном потокеI/O
Операционные требования также могут вызвать проблемы:
- если
while
Циклический опрос операций, ожидающих выполнения, может вызвать ненужныеCPU
напрасная операция, потому что в это времяI/O
операция не завершена,read
Функция не может получить результат; - При использовании
sleep
/usleep
способ заставить процесс спать в течение определенного периода времени, а затем вызватьI/O
Возвращение операции было несвоевременным.
Итак, есть ли в системе механизм, позволяющий нам изначально ожидать несколькихI/O
Выполнение операции. Ответ - да, нам нужно представить нашуI/O
Мультиплексирование.
Мультиплексирование ввода/вывода
Мультиплексирование ввода-вывода, поэтому название означает, что несколько операций ввода-вывода выполняются одновременно в рамках процесса. Также есть сам по себе эволюционный процесс, это select, poll, epoll (замена на macOS — kqueue). Мы представим их по очереди.
select
select
Использование обычно состоит из трех функций (подробных,тыкать это):
void FD_SET(int fd, fd_set *fdset);
int select(int nfds, fd_set *restrict readfds,
fd_set *restrict writefds, fd_set *restrict errorfds,
struct timeval *restrict timeout);
int FD_ISSET(int fd, fd_set *fdset);
select
Функция заключается в мониторинге в пакетном режимеfd
, когда входящийfd_set
любой изfd
Когда буфер переходит в состояние, доступное для чтения/записи, разблокировать и передатьFD_ISSET
перейти к конкретномуfd
.
fd_set tmpset = *fds;
struct timeval tv;
tv.tv_sec = 5;
int r = select(fd_size, &tmpset, NULL, NULL, &tv);
if (r < 0) do {
perror("select()");
break;
} while(0);
else if (r) {
printf("fd_size %d\n", r);
QUEUE * q;
QUEUE_FOREACH(q, &loop->wq->queue) {
watcher_t * watcher = QUEUE_DATA(q, watcher_t, queue);
int fd = watcher->fd;
if (FD_ISSET(fd, &tmpset)) {
int n = watcher->work(watcher);
}
}
}
else {
printf("%d No data within five seconds.\n", r);
}
вот одинselect
Пример построения веб-сервера вы можетепосмотри.
select
функция может решитьI/O
Проблема мультиплексирования, но есть и недочеты:
- Максимальное количество fds, разрешенное структурой fd_set, равно 1024. Если оно превышает это число, для его решения все равно может потребоваться использование многопоточности;
- накладные расходы на производительность
-
select
Каждый раз, когда функция выполняется, она существуетfd_set
Копировать из состояния использования в состояние ядра; - Ядро должно опросить
fd_set
серединаfd
положение дел; - Возвращаемое значение также необходимо опросить в пользовательском режиме, чтобы определить, какие из них
fd
перешел в читаемое состояние;
poll
Первый вопрос задаетpollрешено,poll
Коллекция fd, полученная функцией, заменяется на массив, и ограничение размера в 1024 больше не существует.poll
Функция определяется следующим образом:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
конкретное использование иselect
очень похожи:
struct pollfd * fds = poll_init_fd_set(loop, count);
int r = poll(fds, count, 5000);
if (r < 0) do {
perror("select()");
break;
} while(0);
else if (r) {
QUEUE * q;
QUEUE_FOREACH(q, &loop->wq->queue) {
watcher_t * watcher = QUEUE_DATA(q, watcher_t, queue);
int fd = watcher->fd;
if (watcher->fd_idx == -1) {
continue;
}
if ((fds + watcher->fd_idx)->revents & POLLIN) {
watcher->work(watcher);
}
}
}
else {
printf("%d No data within five seconds.\n", r);
}
Вот пример создания опроса веб-сервера, который можетпосмотри.
но вышеselect
С упомянутыми накладными расходами проблема остается. пока вepoll
, проблема решена.
epoll
Подробности могут бытьСмотри сюда. просто говоря,epoll
Будуselect
, poll
Одноэтапная операция делится на три этапа:
-
epoll_create,Создавать
epoll_fd
, для 3-этапного мониторинга; -
epoll_ctl, привяжите fd, который хотите прослушать
epoll_fd
над; -
epoll_wait, входящий
epoll_fd
, процесс переходит в состояние блокировки, и любойfd
Процесс разблокируется после изменения.
Давайте взглянемepoll
Как решить упомянутые выше накладные расходы на производительность:
-
fd
Привязка находится вepoll_ctl
этап пройден,epoll_wait
Просто пройти вepoll_fd
, нет необходимости повторно передаватьfd
собирать; -
epoll_ctl
входящийfd
Красно-черное дерево будет поддерживаться в состоянии ядра, когдаI/O
Когда операция завершена, она находится в O(LogN) по красно-черному дереву.fd
, чтобы избежать опроса; - вернуться в пользовательский режим
fd
Массив фактически вводится в состояние, доступное для чтения и записи.fd
Коллекция, больше не требует, чтобы пользователь опрашивал все fds.
Похоже на то,epoll
Схема является лучшей схемой для схемы мультиплексирования. это имеетepoll
Пример построения веб-сервера вы можетепосмотри.
ноepoll
Нет ли недостатков? ответ отрицательный:
-
epoll
В настоящее время поддерживается толькоpipe
, созданный такими операциями, как сетьfd
, в настоящее время не поддерживает сгенерированную файловую системуfd
.
Асинхронный ввод-вывод
описано выше, блокирует лиI/O
или неблокирующийI/O
ещеI/O
Мультиплексирование, все синхронноI/O
. требовать от пользователя ожиданияI/O
Операция завершается, и возвращенное содержимое получено. Сама операционная система также обеспечивает асинхронныйI/O
Программы соответствуют разным операционным системам:
- Linux
- aio, который в настоящее время подвергается критике, самый большой недостаток в том, что он поддерживает только
Direct I/O
(файловая операция) - io_uring, новое дополнение к ядру Linux в версии 5.1, считается асинхронным в Linux.
I/O
новый дом - windows
- iocp, как решение для асинхронной обработки для libuv в Windows. (Автор
windows
Не так много исследований, не так много введения. )
Пока что некоторые общиеI/O
Модель.
и в настоящее время вLinux
Рекомендуемое решение вышеepoll
Механизмы. ноepoll
Файлы мониторинга не поддерживаютсяfd
проблема, нам еще нужно включить мозги, давайте посмотримlibuv
как это решить.
решение libuv
libuv
использоватьepoll
строитьevent-loop
, куда:
-
socket
,pipe
ждать, чтобы пройтиepoll
способ контролироватьfd
тип, черезepoll_wait
способ наблюдения; - Обработка файлов/разрешение DNS/распаковка, сжатие и другие операции обрабатываются с помощью рабочих потоков, а запрос и результат соединяются через две очереди, а
pipe
общаться с основным потоком,epoll
контролироватьfd
способ определить, когда читать очередь.
На этом статья закончится. Подведем краткий итог:
Сначала мы представили концепцию файловых дескрипторов, а затемLinux
Переключатель основного состояния процесса. затем ввести блокировкуI/O
концепция и недостатки, которые вводят многопоточную блокировкуI/O
, неблокирующийI/O
,так же какI/O
Мультиплексирование и асинхронностьI/O
Концепция чего-либо. Наконец, в сочетании с вышеизложенным знанием, краткое введение во внутреннюю либувуI/O
рабочий механизм.
Наконец, сделайте «небольшое объявление».ByteDance искренне приглашает выдающихся фронтенд-инженеров иNode.js
Инженеры могут присоединиться к нам, чтобы делать интересные вещи.
Ладно, увидимся в следующий раз.