Тщательно понять механизм реализации мультиплексирования ввода-вывода

Netty
Тщательно понять механизм реализации мультиплексирования ввода-вывода

Ставьте лайк и смотрите снова, формируйте привычку и ищите в WeChat [дайм тек] Обратите внимание на более оригинальные технические статьи.

эта статьяGitHub org_hejianhui/JavaStudyБыл включен, есть моя серия статей.

предисловие

Чтобы углубитьМеханизм мультиплексирования ввода/выводаВ духе разбивания запеканки до конца мы рассказали об основных понятиях BIO, NIO и AIO и некоторых общих проблемах, а также рассмотрели пять аспектов сетевого программирования Unix — разновидности модели IO. Эта статья посвящена изучению и пониманию основного механизма реализации мультиплексирования ввода-вывода.

Концептуальная записка

Существует три реализации мультиплексирования ввода-вывода.Прежде чем представить select, poll и epoll, давайте сначала представим операционную систему Linux.Базовые концепты:

  • пространство пользователя и пространство ядра
  • переключатель процесса
  • блокировка процесса
  • файловый дескриптор
  • Кэшированный ввод-вывод

пространство пользователя/пространство ядра

Сейчас операционные системы используют виртуальную память, поэтому для 32-битной операционной системы ее адресное пространство (виртуальное пространство для хранения) равно 4G (2 в 32-й степени). Ядром операционной системы является ядро, которое не зависит от обычных приложений и может обращаться к защищенному пространству памяти, а также имеет все разрешения для доступа к нижележащим аппаратным устройствам. Чтобы гарантировать, что пользовательские процессы не могут напрямую управлять ядром, и обеспечить безопасность ядра, операционная система делит виртуальное пространство на две части: одна — пространство ядра, а другая — пространство пользователя.

Для операционной системы Linux самые старшие байты 1G (от виртуального адреса 0xC0000000 до 0xFFFFFFFF) используются ядром, которое называется пространством ядра, а младшие байты 3G (от виртуального адреса 0x00000000 до 0xBFFFFFFF) используются для каждого использования Процесса, называется пространством пользователя.

переключатель процесса

Чтобы управлять выполнением процесса, ядро ​​должно иметь возможность приостановить процесс, работающий на ЦП, и возобновить выполнение ранее приостановленного процесса. Такое поведение называется переключением процессов. Поэтому можно сказать, что любой процесс выполняется при поддержке ядра операционной системы и тесно связан с ядром, а переключение процессов очень ресурсоемко.

При переходе от запуска одного процесса к запуску другого процесса в этот процесс вносятся следующие изменения:

  1. Сохраняет контекст процессора, включая программный счетчик и другие регистры.
  2. Обновите информацию о печатной плате.
  3. Переместите PCB процесса в соответствующую очередь, например, готово, заблокировано в очереди событий и т. д.
  4. Выберите другой процесс для выполнения и обновите его плату.
  5. Обновите структуры данных управления памятью.
  6. Восстановите контекст обработчика.

блокировка процесса

Выполняемый процесс, так как некоторые ожидаемые события не произошли, такие как отказ от запроса системных ресурсов, ожидание завершения операции, новые данные еще не поступили или нет новой работы и т. д., система автоматически выполняет блокирующий примитив (Block), переходит из рабочего состояния в заблокированное. Видно, что блокировка процесса является активным поведением самого процесса, поэтому только процесс в состоянии выполнения (получение ресурсов ЦП) может перевести его в состояние блокировки. Когда процесс переходит в состояние блокировки, он не занимает ресурсы процессора.

файловый дескриптор

Дескриптор файла — это термин в информатике, абстракция, используемая для обозначения ссылки на файл. Дескриптор файла формально представляет собой неотрицательное целое число. На самом деле это значение индекса, указывающее на таблицу записей открытых файлов процесса, поддерживаемую ядром для каждого процесса. Когда программа открывает существующий файл или создает новый файл, ядро ​​возвращает процессу дескриптор файла. В программировании некоторые низкоуровневые программы часто вращаются вокруг файловых дескрипторов. Однако концепция файловых дескрипторов часто применима только к таким операционным системам, как UNIX и Linux.

Кэшированный ввод-вывод

Кэшированный ввод-вывод также известен как стандартный ввод-вывод.Операция ввода-вывода по умолчанию для большинства файловых систем — это кэшированный ввод-вывод. В кэшированном механизме ввода-вывода Linux операционная система будет кэшировать данные ввода-вывода в страничном кэше файловой системы, то есть сначала данные будут скопированы в буфер ядра операционной системы, а затем данные будут скопированы из ядра операционной системы, а буфер скопирован в адресное пространство приложения.

Недостатки кэшированного ввода-вывода:

В процессе передачи данных требуется несколько операций копирования данных в адресном пространстве приложения и ядре, накладные расходы ЦП и памяти, вызванные этими операциями копирования данных, очень велики.

Что такое мультиплексирование ввода-вывода?

  • Мультиплексирование ввода-вывода — это синхронная модель ввода-вывода, которая реализует поток, который может отслеживать несколько дескрипторов файлов;
  • Когда дескриптор файла готов, он может уведомить приложение о выполнении соответствующих операций чтения и записи;
  • Никакие файловые дескрипторы не готовы заблокировать приложение, передав центральный процессор.

Мультиплексирование относится к сетевым соединениям, а мультиплексирование относится к одному и тому же потоку.

Почему существует механизм мультиплексирования ввода-вывода?

Когда нет механизма мультиплексирования ввода-вывода, есть две реализации BIO и NIO, но все они имеют некоторые проблемы.

Синхронная блокировка (БИО)

  • Сервер использует один поток, когдаacceptПосле запроса вrecvилиsendКогда вызов заблокирован, вы не сможетеacceptДругие запросы (необходимо дождаться обработки предыдущего запроса)recvилиsendend ) (не может обрабатывать параллелизм)
// 伪代码描述
while (true) {
	// accept阻塞
    client_fd = accept(listen_fd);
    fds.append(client_fd);
    for (fd in fds) {
    	// recv阻塞(会影响上面的accept)
        if (recv(fd)) {
        	// logic
        }
    }
}
  • На сервере реализована многопоточность.После принятия запроса открывается поток для recv для завершения параллельной обработки.Однако по мере увеличения количества запросов необходимо добавлять системные потоки.Большое количество потоков занимает большой объем памяти , а переключение потоков приведет к большим накладным расходам, 10 000 потоков фактически читают и пишут, фактическое количество потоков не будет превышать 20%, каждый прием открытия потока также является пустой тратой ресурсов.
// 伪代码描述
while(true) {
  // accept阻塞
  client_fd = accept(listen_fd)
  // 开启线程read数据(fd增多导致线程数增多)
  new Thread func() {
    // recv阻塞(多线程不影响上面的accept)
    if (recv(fd)) {
      // logic
    }
  }  
}

Синхронный неблокирующий (NIO)

  • серверная частьacceptПосле заявки присоединяйтесьfdsКоллекция, опрашиваемая один раз за опросfdsсобиратьrecv(неблокирующие) данные, немедленно возвращает ошибку, если данных нет, каждый раз опрашивая все fds (включая фактические fds, где не происходит чтения или записи), будет пустой тратой ресурсов процессора.
// 伪代码描述
while(true) {
  // accept非阻塞(cpu一直忙轮询)
  client_fd = accept(listen_fd)
  if (client_fd != null) {
    // 有人连接
    fds.append(client_fd)
  } else {
    // 无人连接
  }  
  for (fd in fds) {
    // recv非阻塞
    setNonblocking(client_fd)
    // recv 为非阻塞命令
    if (len = recv(fd) && len > 0) {
      // 有读写数据
      // logic
    } else {
       无读写数据
    }
  }  
}

мультиплексирование ввода-вывода

Серверная сторона принимает один поток черезselect/poll/epollПодождите, пока системный вызов получит список fd, пройдите через fd с событиямиaccept/recv/send, чтобы он мог поддерживать больше одновременных запросов на подключение.

// 伪代码描述
while(true) {
  // 通过内核获取有读写事件发生的fd,只要有一个则返回,无则阻塞
  // 整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,accept/recv是不会阻塞
  for (fd in select(fds)) {
    if (fd == listen_fd) {
        client_fd = accept(listen_fd)
        fds.append(client_fd)
    } elseif (len = recv(fd) && len != -1) { 
      // logic
    }
  }  
}

Три реализации мультиплексирования ввода-вывода

  • select
  • poll
  • epoll

select

Он только знает, что произошло событие ввода-вывода, но не знает, какие потоки (их может быть один, несколько или даже все), мы можем только опрашивать все потоки без разбора, чтобы узнать, какие потоки можно прочитать. данные или потоки, записанные в данные, работают с ними. такselect имеет O (n) неизбирательную сложность опроса, чем больше потоков обрабатывается одновременно, тем больше время неизбирательного опроса.

выберите процесс вызова

image.png(1) Используйте copy_from_user для копирования fd_set из пространства пользователя в пространство ядра.

(2) Зарегистрируйте функцию обратного вызова __pollwait

(3) Пройти все fds и вызвать соответствующий метод опроса (для сокета этот метод опроса — sock_poll, а sock_poll будет вызывать tcp_poll, udp_poll или datagram_poll в зависимости от ситуации)

(4) Взяв в качестве примера tcp_poll, его основной реализацией является __pollwait, которая является функцией обратного вызова, зарегистрированной выше.

(5) Основная работа __pollwait - повесить текущий (текущий процесс) в очередь ожидания устройства.Разные устройства имеют разные очереди ожидания.Для tcp_poll очередь ожидания sk->sk_sleep (обратите внимание, процесс завис Ожидание в очереди не означает, что процесс спит). После того, как устройство получит сообщение (сетевое устройство) или заполнит данные файла (дисковое устройство), оно разбудит устройство, ожидающее спящего процесса в очереди, а затем активируется текущий.

(6) Когда метод poll вернется, он вернет маску, описывающую, готовы ли операции чтения и записи, и присвоит значения fd_set в соответствии с этой маской.

(7) Если все fds были пройдены, а маска для чтения и записи не была возвращена, будет вызываться schedule_timeout, чтобы вызвать процесс выбора (то есть текущий) для перехода в спящий режим. Когда драйвер устройства сможет читать и записывать свои собственные ресурсы, он разбудит процесс, ожидающий перехода в режим ожидания в очереди. Если никто не просыпается по истечении определенного тайм-аута (указанного параметром schedule_timeout), процесс, вызывающий select, будет снова разбужен для получения ЦП, а затем повторно пройдет fd, чтобы определить, есть ли готовый fd.

(8) Скопируйте fd_set из пространства ядра в пространство пользователя.

выберите функциональный интерфейс

#include <sys/select.h>
#include <sys/time.h>

#define FD_SETSIZE 1024
#define NFDBITS (8 * sizeof(unsigned long))
#define __FDSET_LONGS (FD_SETSIZE/NFDBITS)

// 数据结构 (bitmap)
typedef struct {
    unsigned long fds_bits[__FDSET_LONGS];
} fd_set;

// API
int select(
    int max_fd, 
    fd_set *readset, 
    fd_set *writeset, 
    fd_set *exceptset, 
    struct timeval *timeout
)                              // 返回值就绪描述符的数目

FD_ZERO(int fd, fd_set* fds)   // 清空集合
FD_SET(int fd, fd_set* fds)    // 将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds)  // 判断指定描述符是否在集合中 
FD_CLR(int fd, fd_set* fds)    // 将给定的描述符从文件中删除  

выберите пример использования

int main() {
  /*
   * 这里进行一些初始化的设置,
   * 包括socket建立,地址的设置等,
   */

  fd_set read_fs, write_fs;
  struct timeval timeout;
  int max = 0;  // 用于记录最大的fd,在轮询中时刻更新即可

  // 初始化比特位
  FD_ZERO(&read_fs);
  FD_ZERO(&write_fs);

  int nfds = 0; // 记录就绪的事件,可以减少遍历的次数
  while (1) {
    // 阻塞获取
    // 每次需要把fd从用户态拷贝到内核态
    nfds = select(max + 1, &read_fd, &write_fd, NULL, &timeout);
    // 每次需要遍历所有fd,判断有无读写事件发生
    for (int i = 0; i <= max && nfds; ++i) {
      if (i == listenfd) {
         --nfds;
         // 这里处理accept事件
         FD_SET(i, &read_fd);//将客户端socket加入到集合中
      }
      if (FD_ISSET(i, &read_fd)) {
        --nfds;
        // 这里处理read事件
      }
      if (FD_ISSET(i, &write_fd)) {
         --nfds;
        // 这里处理write事件
      }
    }
  }

selectНедостатки

Select по сути выполняет следующий шаг, устанавливая или проверяя структуру данных, в которой хранится флаг fd. Недостатками этого являются:

  • ФД, открытые одним процессом, ограничены, т.к.FD_SETSIZEустановлено, по умолчанию 1024;

  • Каждый раз, когда вызывается select, набор fd необходимо копировать из пользовательского режима в режим ядра, и эти накладные расходы будут очень большими при наличии большого количества fd;

Необходимо поддерживать структуру данных для хранения большого количества fds, что заставит пространство пользователя и пространство ядра копировать структуру при передаче структуры.

  • При сканировании сокета происходит линейное сканирование, и используется метод опроса, который менее эффективен (высокий параллелизм).

Когда сокетов много, каждый select() должен завершить планирование, пройдя сокеты FD_SETSIZE.Независимо от того, какой сокет активен, он будет пройден снова. Это тратит много процессорного времени. Если вы можете зарегистрировать callback-функцию для сокетов, когда они активны, связанные операции будут автоматически завершены, тогда опрос будет исключен, что и делают epoll и kqueue.

poll

Poll по сути ничем не отличается от select: он копирует переданный пользователем массив в пространство ядра, а затем запрашивает статус устройства, соответствующий каждому fd.Но у него нет ограничений на максимальное количество подключений, причина в том, что он хранится на основе связанного списка.

интерфейс функции опроса

#include <poll.h>
// 数据结构
struct pollfd {
    int fd;                         // 需要监视的文件描述符
    short events;                   // 需要内核监视的事件
    short revents;                  // 实际发生的事件
};

// API
int poll(struct pollfd fds[], nfds_t nfds, int timeout);

пример использования опроса

// 先宏定义长度
#define MAX_POLLFD_LEN 4096  

int main() {
  /*
   * 在这里进行一些初始化的操作,
   * 比如初始化数据和socket等。
   */

  int nfds = 0;
  pollfd fds[MAX_POLLFD_LEN];
  memset(fds, 0, sizeof(fds));
  fds[0].fd = listenfd;
  fds[0].events = POLLRDNORM;
  int max  = 0;  // 队列的实际长度,是一个随时更新的,也可以自定义其他的
  int timeout = 0;

  int current_size = max;
  while (1) {
    // 阻塞获取
    // 每次需要把fd从用户态拷贝到内核态
    nfds = poll(fds, max+1, timeout);
    if (fds[0].revents & POLLRDNORM) {
        // 这里处理accept事件
        connfd = accept(listenfd);
        //将新的描述符添加到读描述符集合中
    }
    // 每次需要遍历所有fd,判断有无读写事件发生
    for (int i = 1; i < max; ++i) {     
      if (fds[i].revents & POLLRDNORM) { 
         sockfd = fds[i].fd
         if ((n = read(sockfd, buf, MAXLINE)) <= 0) {
            // 这里处理read事件
            if (n == 0) {
                close(sockfd);
                fds[i].fd = -1;
            }
         } else {
             // 这里处理write事件     
         }
         if (--nfds <= 0) {
            break;       
         }   
      }
    }
  }

минусы опроса

Не имеет ограничений на максимальное количество подключений, причина в том, что он хранится на основе связанного списка, но и у него есть недостатки:

  • Каждый раз, когда вызывается опрос, набор fd необходимо копировать из пользовательского режима в режим ядра, и эти накладные расходы будут очень большими при наличии большого количества fd;
  • Сканирование сокетов является линейным сканированием, и используется метод опроса, который менее эффективен (при высоком параллелизме).

epoll

epoll можно понимать как опрос событий, в отличие от опроса занятости и неразборчивого опроса, epoll будет уведомлять нас о том, какие события ввода-вывода произошли с каким потоком. Итак, мы говорим, что epoll на самом деле **управляется событиями (fd связан с каждым событием)**, и наши операции с этими потоками в настоящее время имеют смысл. (Сложность снижена до O(1))

интерфейс функции epoll

Когда процесс вызывает метод epoll_create, ядро ​​Linux создает структуру eventpoll.В этой структуре есть два члена, которые тесно связаны со способом использования epoll. Структура опроса событий выглядит следующим образом:

#include <sys/epoll.h>

// 数据结构
// 每一个epoll对象都有一个独立的eventpoll结构体
// 用于存放通过epoll_ctl方法向epoll对象中添加进来的事件
// epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可
struct eventpoll {
    /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
    struct rb_root  rbr;
    /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
    struct list_head rdlist;
};

// API
int epoll_create(int size); // 内核中间加一个 ep 对象,把所有需要监听的 socket 都放到 ep 对象中
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // epoll_ctl 负责把 socket 增加、删除到内核红黑树
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);// epoll_wait 负责检测可读队列,没有可读 socket 则阻塞进程

Каждый объект epoll имеет независимую структуру eventpoll, которая используется для хранения событий, добавленных в объект epoll с помощью метода epoll_ctl. Эти события будут смонтированы в красно-черном дереве, чтобы повторно добавляемые события могли быть эффективно идентифицированы красно-черным деревом (эффективность времени вставки красно-черного дерева равна lgn, где n — количество элементов дерева). красно-черное дерево).

А все добавленные в epoll события будут устанавливать callback-отношение с драйвером устройства (сетевой карты), то есть при возникновении соответствующего события будет вызываться callback-метод. Этот метод обратного вызова в ядре называется ep_poll_callback, и он добавит событие в двусвязный список rdlist.

В epoll для каждого события создается структура epitem следующим образом:

struct epitem{
    struct rb_node  rbn;//红黑树节点
    struct list_head    rdllink;//双向链表节点
    struct epoll_filefd  ffd;  //事件句柄信息
    struct eventpoll *ep;    //指向其所属的eventpoll对象
    struct epoll_event event; //期待发生的事件类型
}

При вызове epoll_wait для проверки наличия события необходимо только проверить наличие элемента epitem в двусвязном списке rdlist в объекте eventpoll. Если rdlist не пуст, скопируйте произошедшие события в пользовательский режим и одновременно верните количество событий пользователю.

image.png Из приведенного выше объяснения мы можем видеть, что благодаря красно-черному дереву и структуре данных двусвязного списка в сочетании с механизмом обратного вызова создается эффективность epoll.После объяснения механизма Epoll мы можем легко понять, как использовать epoll. Описание одного предложения: три шага.

  • Первый шаг: системный вызов epoll_create(). Этот вызов возвращает дескриптор, по которому идентифицируются все последующие использования.
  • Шаг 2: системный вызов epoll_ctl(). Добавляйте, удаляйте и изменяйте интересующие события для объекта epoll с помощью этого вызова, возвращайте 0, чтобы указать на успех, и верните -1, чтобы указать на неудачу.
  • Третья часть: системный вызов epoll_wait(). Соберите события, которые произошли в мониторинге epoll, посредством этого вызова.

пример использования epoll

int main(int argc, char* argv[])
{
   /*
   * 在这里进行一些初始化的操作,
   * 比如初始化数据和socket等。
   */

    // 内核中创建ep对象
    epfd=epoll_create(256);
    // 需要监听的socket放到ep中
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
 
    while(1) {
      // 阻塞获取
      nfds = epoll_wait(epfd,events,20,0);
      for(i=0;i<nfds;++i) {
          if(events[i].data.fd==listenfd) {
              // 这里处理accept事件
              connfd = accept(listenfd);
              // 接收新连接写到内核对象中
              epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
          } else if (events[i].events&EPOLLIN) {
              // 这里处理read事件
              read(sockfd, BUF, MAXLINE);
              //读完后准备写
              epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
          } else if(events[i].events&EPOLLOUT) {
              // 这里处理write事件
              write(sockfd, BUF, n);
              //写完后准备读
              epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
          }
      }
    }
    return 0;
}

Преимущества эполла

  • Максимальное количество одновременных подключений не ограничено, а верхний предел открываемых FD намного превышает 1024 (на 1G памяти можно отслеживать около 100 000 портов);
  • Повышение эффективности не является методом опроса, и эффективность не будет снижаться по мере увеличения количества FD. Только активные и доступные FD будут вызывать функцию обратного вызова, то есть самым большим преимуществом Epoll является то, что он заботится только о ваших «активных» соединениях, независимо от общего количества соединений, поэтому в реальном сетевом окружении эффективность Epoll будет намного выше, чем select и poll;
  • Копирование памяти, используйте файл mmap() для сопоставления памяти, чтобы ускорить передачу сообщений с пространством ядра; то есть epoll использует mmap для уменьшения накладных расходов на копирование.

Недостатки эполла

  • epoll может работать только под linux

Разница между режимами epoll LT и ET

epoll имеет два режима запуска, EPOLLLT и EPOLLET, LT — режим по умолчанию, а ET — «высокоскоростной» режим.

  • В режиме LT, пока у fd есть данные для чтения, каждый epoll_wait будет возвращать свое событие, напоминая программе пользователя о необходимости работы;
  • В режиме ET он будет запрашивать только один раз и не будет запрашивать снова до следующего поступления данных, независимо от того, есть ли еще данные, доступные для чтения в fd. Таким образом, в режиме ET при чтении fd обязательно читайте его буфер, иначе возникнет ошибка EAGIN.

epoll использует метод уведомления о готовности «событие» для регистрации fd через epoll_ctl.После того, как fd будет готов, ядро ​​​​использует механизм обратного вызова, подобный обратному вызову, для активации fd, и epoll_wait может получить уведомление.

Разница между select/poll/epoll

select, poll и epoll — все это механизмы мультиплексирования ввода-вывода. Мультиплексирование ввода/вывода использует механизм для мониторинга нескольких дескрипторов.Когда дескриптор готов (обычно готов к чтению или записи), он может уведомить программу о выполнении соответствующих операций чтения и записи.Но select, poll и epoll по сути являются синхронным вводом-выводом, потому что все они должны отвечать за чтение и запись после того, как событие чтения-записи будет готово, что означает, что процесс чтения-записи заблокирован., а асинхронный ввод-вывод не должен сам отвечать за чтение и запись.Реализация асинхронного ввода-вывода будет отвечать за копирование данных из ядра в пространство пользователя. 

И epoll, и select могут предоставить решения для мультиплексирования ввода-вывода. Он может поддерживаться в текущем ядре Linux, среди которых epoll уникален для Linux, а выбор должен быть предусмотрен POSIX, реализованным в общих операционных системах.

select poll epoll
Метод работы траверс траверс Перезвони
структура данных bitmap множество красно-черное дерево
Максимальное количество подключений 1024 (x86) или 2048 (x64) неограниченный неограниченный
Максимальное количество поддерживаемых файловых дескрипторов Обычно существует максимальный предел 65535 65535
копия Каждый раз, когда вы вызываете select, вам нужно скопировать набор fd из пользовательского режима в режим ядра. Каждый раз, когда вызывается опрос, набор fd необходимо копировать из пользовательского режима в режим ядра. fd вызывает epoll_ctl copy в первый раз и не копирует каждый раз, когда вызывается epoll_wait
Рабочий режим LT LT Поддержка эффективного режима ET
Эффективность работы Каждый вызов проходится линейно, а временная сложность составляет O (n) Каждый вызов проходится линейно, а временная сложность составляет O (n) В методе уведомления о событии всякий раз, когда fd будет готов, будет вызываться функция обратного вызова, зарегистрированная системой, и готовый fd будет помещен в readyList.Временная сложность O(1)

В настоящее время epoll является предпочтительной моделью для крупномасштабной параллельной разработки сетевых программ в Linux. В большинстве случаев производительность намного превышает возможности выбора и опроса. Популярный в настоящее время высокопроизводительный веб-сервер Nginx официально использует эффективную службу опроса сетевых сокетов, предоставляемую epoll. Однако в случае небольшого количества одновременных подключений многопоточность + блокировка ввода-вывода могут работать лучше.

Поддерживает максимальное количество подключений, которые может открыть процесс

  • select: максимальное количество подключений, которые может открыть один процесс, определяется макросом FD_SETSIZE, а его размер равен 32 целым числам (на 32-битной машине размер равен 32_32, а FD_SETSIZE на 64-битной машине). is 32_64), конечно, мы можем внести изменения в ядро, а затем перекомпилировать его, но производительность может пострадать, что требует дальнейшего тестирования.
  • Опрос: опрос по сути такой же, как выбор, но не имеет ограничения на максимальное количество подключений, поскольку хранится на основе связанного списка.
  • epoll: Хотя существует верхний предел количества подключений, он очень велик: на машине с памятью 1G можно открыть около 100 000 подключений, а на машине с памятью 2G — около 200 000.

Проблемы с эффективностью ввода-вывода, вызванные резким увеличением FD

  • выберите: поскольку соединение проходит линейно каждый раз, когда оно вызывается, по мере увеличения FD это вызовет «проблему линейной деградации производительности» медленной скорости обхода.
  • опрос: то же, что и выше
  • epoll: поскольку реализация в ядре epoll основана на функции обратного вызова для каждого fd, только активный сокет будет активно вызывать обратный вызов, поэтому в случае меньшего количества активных сокетов использование epoll не имеет линейного снижения, как в предыдущих двух , Проблемы с производительностью, но могут быть проблемы с производительностью, когда все сокеты активны.

Обмен сообщениями

  • select: ядру необходимо передать сообщение в пользовательское пространство, и требуется действие копирования ядра
  • опрос: то же, что и выше
  • epoll: epoll реализуется путем разделения части памяти между ядром и пользовательским пространством.

Суммировать

Реализации выбора и опроса должны непрерывно опрашивать все наборы fd сами по себе, пока устройство не будет готово, в течение которого оно может чередоваться между спящим режимом и пробуждением несколько раз. На самом деле, epoll также должен вызывать epoll_wait для непрерывного опроса списка готовности, в течение периода сон и пробуждение могут много раз чередоваться, но когда устройство готово, оно вызывает callback-функцию, кладет готовый фд в готовый list и просыпается, чтобы заснуть в процессе epoll_wait. Несмотря на то, что как sleep, так и alter, select и poll должны пройти через всю коллекцию fd в состоянии «бодрствования», а epoll нужно только оценить, пуст ли список готовых файлов в состоянии «бодрствования», что экономит много времени процессора. Это улучшение производительности, вызванное механизмом обратного вызова.

При каждом вызове select и poll набор fd нужно копировать из пользовательского режима в режим ядра, и текущий будет висеть в очереди ожидания устройства один раз, в то время как epoll нужно скопировать только один раз, а текущий будет только быть зависшим в очереди ожидания один раз (в начале epoll_wait обратите внимание, что очередь ожидания здесь — это не очередь ожидания устройства, а очередь ожидания, определенная внутри epoll). Это также может сэкономить много затрат. 

часто задаваемые вопросы на собеседовании

Что такое мультиплексирование ввода-вывода?

Прочитав вышеприведенную статью, я думаю, вы сможете ответить на нее.

Какая модель ввода-вывода используется nginx/redis?

Модель ввода-вывода Nginx

Nginx поддерживает различные модели параллелизма, и конкретная реализация модели параллелизма зависит от системной платформы.

На платформах, поддерживающих несколько моделей параллелизма, nginx автоматически выбирает наиболее эффективную модель. Но мы также можем явно определить модель параллелизма в файле конфигурации, используя директиву use.

Модели параллелизма, поддерживаемые в NGINX:

1. выберите

Мультиплексирование ввода-вывода, стандартная модель параллелизма. При компиляции nginx модуль select будет скомпилирован автоматически, если используемая системная платформа не имеет более эффективной модели параллелизма. настроить параметры скрипта: --with-select_module и --without-select_module можно использовать для принудительного включения или отключения компиляции выбранного модуля.

2. Опрос

Мультиплексирование ввода-вывода, стандартная модель параллелизма. Как и в случае с Select, при компиляции NGINX, если используемая системная платформа не имеет более эффективных параллельных моделей, модуль Poll будет скомпилирован автоматически. Параметры настройки сценариев: -with-poll_module и --without-poll_module могут использоваться для включения компиляции модуля опроса для обязательного

3. Эполл

Мультиплексирование ввода-вывода, эффективная модель параллелизма, доступная в ядрах Linux 2.6+ и выше.

4. очередь

Мультиплексирование ввода-вывода, эффективная модель параллелизма, доступная на платформах FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 и Mac OS X.

5. /dev/опрос

Эффективная модель параллелизма, доступная на платформах Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+ и Tru64 UNIX 5.1A+

6. порт событий

Эффективная модель параллелизма, доступная на платформе Solaris 10. PS: из-за некоторых известных проблем вместо этого рекомендуется использовать /dev/poll.

Технология мультиплексирования ввода-вывода Redis

Redis — это однопоточная, но очень высокопроизводительная база данных в памяти, в основном используемая в качестве системы кэширования. Redis использует технологию сетевого мультиплексирования ввода-вывода, чтобы обеспечить высокую пропускную способность системы при наличии нескольких подключений.

Почему в Redis используется мультиплексирование ввода-вывода?

Во-первых, Redis работает в одном потоке, и все операции выполняются линейно по порядку, но поскольку операции чтения и записи заблокированы в ожидании ввода или вывода пользователя, операции ввода-вывода часто не могут вернуться напрямую в обычных условиях. привести к тому, что блокировка ввода-вывода файла приведет к тому, что весь процесс не сможет предоставлять услуги другим клиентам, иМультиплексирование ввода/выводаОн создан для решения этой проблемы.

Модель ввода-вывода Redis в основном реализована на основе epoll, но также обеспечивает реализацию select и kqueue, причем epoll используется по умолчанию.

Разница между выбором, опросом, epoll

Прочитав вышеприведенную статью, я думаю, вы сможете ответить на нее.

В чем разница между epoll Level Trigger (LT) и Edge Trigger (ET)?

Существует две модели событий EPOLL:

  • Запуск по фронту (ET) Триггер по фронту запускается только при поступлении данных, независимо от того, есть ли данные в буфере.
  • Запуск по уровню (LT) Запуск по уровню срабатывает всякий раз, когда есть данные.

Прочитав вышеприведенную статью, я думаю, вы сможете ответить на нее.

Статья постоянно обновляется, вы можете искать в WeChat "дайм тек«Читал в первый раз, Эта статьяGitHuborg_hejianhui/JavaStudyОн был записан, добро пожаловать в Star.

Категории