Пожалуйста, указывайте первоисточник при перепечатке, спасибо!
скажи это прямо
Часть 1Основы, связанные с NIO 2, в основном вводит блокировку файлов, и более важныеSelector, эта статья продолжает темы, связанные с NIO, в основном речь идет о некоторых моделях сетевого ввода-вывода Linux, нулевом копировании и другом контенте. Это все, что я могу понять на данный момент. В будущем будет одна или две статьи, связанные с контентом NIO. Предполагается, что после того, как будут завершены последующие исследования netty и другие исследования, я вернусь к серии NIO и добавлю больше.
Концепции пользовательского пространства и пространства ядра
Мы знаем, что операционные системы теперь используют виртуальную память, поэтому для 32-битной операционной системы ее адресное пространство (виртуальное пространство для хранения) равно 4G (2 в 32-й степени). Ядром системы беспокойства является ядро, которое не зависит от обычных приложений, может обращаться к защищенному пространству памяти и имеет все разрешения для доступа к базовым аппаратным устройствам. Чтобы гарантировать, что пользовательский процесс не может напрямую управлять ядром и обеспечить безопасность ядра, система беспокойства делит виртуальное пространство на две части: одна — пространство ядра, а другая — пространство пользователя. Для операционной системы Linux самые старшие байты 1G (от виртуального адреса 0xC0000000 до 0xFFFFFFFF) используются ядром, которое называется пространством ядра, а младшие байты 3G (от виртуального адреса 0x00000000 до 0xBFFFFFFF) используются для каждого использования Процесса, называется пространством пользователя. Каждый процесс может войти в ядро через системные вызовы, поэтому ядро Linux является общим для всех процессов в системе. Таким образом, с точки зрения конкретного процесса, каждый процесс может иметь виртуальное пространство размером 4 ГБ.
Распределение пространства показано на следующем рисунке:
С пространством пользователя и пространством ядра всю внутреннюю структуру Linux можно разделить на три части, снизу вверх: аппаратное обеспечение --> пространство ядра --> пространство пользователя.Как показано ниже:
Детали, на которые необходимо обратить внимание, состав ядра можно увидеть на приведенном выше рисунке:
- Код ядра и данные хранятся в пространстве ядра, а код и данные пользовательской программы хранятся в пространстве пользователя процесса. Будь то пространство ядра или пространство пользователя, все они находятся в виртуальном пространстве.
- В Linux используется двухуровневый механизм защиты: уровень 0 используется ядром, а уровень 3 — пользовательскими программами.
Модель сетевого ввода-вывода Linux
Мы все знаем, что ради безопасности ОС и других соображенийПроцесс не может напрямую управлять устройством ввода-вывода, он должен запросить у ядра помощь в завершении действия ввода-вывода через системный вызов, а ядро будет поддерживать буфер для каждого устройства ввода-вывода.. Как показано ниже: Весь процесс запроса выглядит следующим образом: пользовательский процесс инициирует запрос.После получения запроса ядро получает данные от устройства ввода-вывода в буфер, а затем копирует данные из буфера в адресное пространство пользователя. После того, как пользовательский процесс получит данные, ответьте клиенту.В течение всего процесса запроса требуется время на ввод данных в буфер, а также требуется время на копирование данных из буфера в процесс. Следовательно, в соответствии с различными методами ожидания в течение этих двух периодов действия ввода-вывода можно разделить на следующие:пять режимов:
- Блокировка ввода/вывода
- Неблокирующий ввод-вывод
- Мультиплексирование ввода/вывода
- Управляемый сигналом ввод/вывод
- Асинхронный ввод-вывод (асинхронный ввод-вывод)**Примечание: **Если вам нужно больше знаний о linux/unix, вы можете самостоятельно изучить некоторые принципы сетевого программирования, там должно быть подробное описание.Однако для большинства программистов Java вам не нужно знать основные детали, достаточно знать концепцию и знать, что нижний уровень поддерживается системой..
Наиболее важной ссылкой для этой статьи является «Сетевое программирование UNIX®, том 1, третье издание: сеть сокетов» Ричарда Стивенса, раздел 6.2 «Модели ввода-вывода»,Публичный аккаунт [изобретательность ноль] Ответ: linux, для получения информации рекомендуется скачать ее на компьютер (большего размера и в формате chm),Блок-схема в этой статье также взята из.
Важно помнить об этих двух моментах1 Ожидание готовности данных 2 Копирование данных из ядра в процесс
Блокировка ввода/вывода
В Linux все сокеты по умолчанию заблокированы.Типичный поток операции чтения выглядит следующим образом:
Когда пользовательский процесс вызывает системный вызов recvfrom, ядро запускает первую стадию ввода-вывода: ожидание подготовки данных. Для сетевого ввода-вывода много раз данные не поступали в начале (например, не был получен полный UDP-пакет), в это время ядру приходится ждать поступления достаточного количества данных. На стороне пользовательского процесса весь процесс будет заблокирован. Когда ядро ждет, пока данные будут готовы, оно скопирует данные из ядра в пользовательскую память, затем ядро возвращает результат, а пользовательский процесс освобождает состояние блока и снова начинает работать. Таким образом, характеристики блокирующего ввода-вывода:Заблокировано на обеих фазах выполнения ввода-вывода.
Неблокирующий ввод-вывод
В Linux вы можете сделать его неблокирующим, установив socket. При выполнении операции чтения на неблокирующем сокете поток выглядит следующим образом:
Когда пользовательский процесс вызывает recvfrom, система не будет блокировать пользовательский процесс, а сразу вернет ошибку eWouldblock.С точки зрения пользовательского процесса ждать не нужно, а результат получается сразу. Когда пользовательский процесс определяет, что установлен флаг ewouldblock, он знает, что данные не готовы, поэтому он может делать другие вещи, поэтому он может снова отправить recvfrom, как только данные в ядре будут готовы. И снова получает системный вызов пользовательского процесса, затем сразу копирует данные в пользовательскую память, а потом возвращается. Когда приложение вызывает recvfrom в неблокирующем цикле в цикле, мы называем это опросом. Приложение постоянно опрашивает ядро, чтобы узнать, готово ли оно к какой-либо операции. Обычно этотрата процессорного времени, но эта закономерность иногда встречается.
Мультиплексирование ввода/вывода
Термин «мультиплексирование ввода-вывода» может быть немного незнакомым, но если я скажу select, epoll, я, вероятно, пойму. В некоторых местах этот метод ввода-вывода также называется вводом-выводом, управляемым событиями. Все мы знаем, что преимущество select/epoll заключается в том, что один процесс может обрабатывать ввод-вывод нескольких сетевых подключений одновременно. Его основной принцип заключается в том, что функция select/epoll будет постоянно опрашивать все сокеты, за которые она отвечает, и когда в определенный сокет поступают данные, она уведомляет пользовательский процесс. Его процесс выглядит следующим образом:
Когда пользовательский процесс вызывает select, весь процесс будет заблокирован, и в то же время ядро будет «мониторить» все сокеты, за которые отвечает select.Когда данные в каком-либо сокете будут готовы, select вернется. В это время пользовательский процесс снова вызывает операцию чтения, чтобы скопировать данные из ядра в пользовательский процесс. Этот график мало чем отличается от графика блокирующего ввода-вывода, а даже хуже. Потому что здесь нужно использовать два системных вызова (select и recvfrom), а блокирующий ввод-вывод вызывает только один системный вызов (recvfrom). Однако преимущество использования select заключается в том, что он может обрабатывать несколько подключений одновременно. (Еще одно слово. Следовательно, если количество обрабатываемых соединений не очень велико, веб-сервер, использующий select/epoll, не обязательно лучше, чем веб-сервер, использующий многопоточность + блокирующий ввод-вывод, и задержка может быть больше. select/ Преимущество epoll не в том, что он может быстрее обрабатывать одно соединение, а в том, что он может обрабатывать больше соединений.) На практике в модели мультиплексирования ввода-вывода каждый сокет обычно настроен как неблокирующий, однако, как показано на рисунке выше, весь пользовательский процесс на самом деле все время заблокирован. Просто процесс блокируется функцией select, а не сокетом IO.
файловый дескриптор fd
Ядро Linux рассматривает все внешние устройства как файл для работы. Тогда наши операции с внешними устройствами можно рассматривать как операции с файлами. Мы читаем и записываем файл, вызывая системный вызов, предоставленный ядром; ядро возвращает нам скриптор файла (fd, файловый дескриптор). Чтение и запись сокета также будут иметь соответствующий дескриптор, называемый socketfd (дескриптор сокета). Дескриптор — это число, указывающее на структуру (путь к файлу, область данных и т. д.) в ядре. Затем наше приложение читает и записывает в файл, читая и записывая дескриптор.
select
**Основной принцип: **Файловые дескрипторы, отслеживаемые функцией select, делятся на три категории, а именно writefds, readfds и excludefds. После вызова функция select будет блокироваться до тех пор, пока дескриптор не будет готов (данные доступны для чтения, записи или исключения) или пока не истечет время ожидания (время ожидания указывает время ожидания, если оно возвращается немедленно, для него может быть установлено значение null), и функция возвращается. Когда функция select возвращает значение, готовый дескриптор можно найти, перейдя по файлу fdset.
недостаток: 1. Самый большой недостаток select заключается в том, что FD, открываемый одним процессом, имеет определенное ограничение, которое задается FD_SETSIZE, по умолчанию для 32-битных машин 1024, а для 64-битных машин 2048. Вообще говоря, этот номер во многом связан с системной памятью, "конкретный номер можно посмотреть с помощью cat /proc/sys/fs/file-max". 32-битная машина по умолчанию 1024. 64-битная машина по умолчанию имеет значение 2048. 2. При сканировании сокета это линейное сканирование, то есть используется метод опроса, и эффективность низкая. Когда сокетов много, каждый select() должен завершить планирование, пройдя сокеты FD_SETSIZE.Независимо от того, какой сокет активен, он будет пройден снова. Это тратит много процессорного времени. «Если вы можете зарегистрировать функцию обратного вызова для сокета, когда они активны, соответствующая операция будет автоматически завершена, тогда опрос будет предотвращен», что и делают epoll и kqueue. 3. Необходимо поддерживать структуру данных, используемую для хранения большого количества fds, что заставит пространство пользователя и пространство ядра копировать структуру при ее передаче.
poll
**Основной принцип: **опрос по существу ничем не отличается от выбора.Он копирует переданный пользователем массив в пространство ядра, а затем запрашивает статус устройства, соответствующий каждому fd.Если устройство готово, он добавляет элемент в очередь ожидания устройства и продолжить обход. Если после обхода всех fd не будет найдено готовое устройство, текущий процесс будет приостановлен до тех пор, пока устройство не будет готово или не истечет активное время ожидания. После пробуждения он снова пройдет fd. Этот процесс прошел через множество ненужных обходов.
У него нет ограничения на максимальное количество подключений, так как он хранится на основе связанного списка, но у него есть и недостаток:1. Большое количество массивов fd копируется целиком между пользовательским режимом и адресным пространством ядра, вне зависимости от того, имеет смысл такое копирование или нет. 2. Еще одной особенностью опроса является «горизонтальный запуск».
**Примечание.** Из вышеизложенного и select, и poll должны пройти через файловый дескриптор, чтобы после возврата получить готовый сокет. На самом деле большое количество одновременно подключенных клиентов может иметь несколько состояний готовности одновременно, поэтому эффективность снижается линейно по мере роста числа отслеживаемых дескрипторов.
epoll
epoll был предложен в ядре 2.6 и представляет собой расширенную версию предыдущих методов select и poll. По сравнению с select и poll, epoll является более гибким и не имеет ограничений по дескрипторам. epoll использует один дескриптор файла для управления несколькими дескрипторами и сохраняет события файловых дескрипторов, связанных с пользователем, в таблице событий ядра, так что копирование в пространство пользователя и пространство ядра необходимо выполнить только один раз.
**Основной принцип: **epoll поддерживает запуск по горизонтали и запуск по краю.Самая большая функция — запуск по краю.Он только сообщает процессу, какие fds только что стали готовыми, и он будет уведомлен только один раз. Еще одна особенность заключается в том, что epoll использует метод уведомления о готовности «событие» для регистрации fd через epoll_ctl.После того, как fd будет готов, ядро использует механизм обратного вызова, подобный обратному вызову, для активации fd, и epoll_wait может получить уведомление.
Преимущества эполла:1. Максимальное количество одновременных подключений не ограничено, а верхний предел открываемых ФД намного больше 1024 (на 1Г памяти можно мониторить около 100 000 портов). 2. Улучшается эффективность, а не метод опроса, и эффективность не будет снижаться с увеличением количества ФД. Только активные и доступные FD будут вызывать функцию обратного вызова, то есть самым большим преимуществом Epoll является то, что он заботится только о ваших «активных» соединениях, независимо от общего количества соединений, поэтому в реальном сетевом окружении эффективность Epoll будет намного выше, чем выбор и опрос. 3. Копирование памяти, используйте файл mmap() для сопоставления памяти, чтобы ускорить передачу сообщений с пространством ядра, т. е. epoll использует mmap для уменьшения накладных расходов на копирование.
Версия JDK1.5_update10 использует epoll для замены традиционного выбора/опроса, что значительно повышает производительность связи NIO.
**Примечание.** Ошибки JDK NIO, такие как печально известная ошибка epoll, которая приводит к пустому опросу Selector, в конечном итоге приводят к 100% загрузке ЦП. Официальный сайт утверждает, что проблема была исправлена в обновлении 18 версии JDK1.6, но проблема все еще существует до версии JDK1.7, но вероятность появления BUG немного снижена, и принципиально она не решена. .Это можно объяснить в последующих сериях netty.
Управляемый сигналом ввод/вывод
Поскольку ввод-вывод, управляемый сигналом, на практике обычно не используется, он кратко упоминается.
Очевидно, что пользовательский процесс не блокируется. Сначала пользовательский процесс устанавливает обработчик сигнала SIGIO и выполняет функцию обработки сигнала с помощью системного вызова sigaction. В это время пользовательский процесс может заниматься другими делами. Как только данные будут готовы, система сгенерирует сигнал SIGIO для процесса. чтобы уведомить его. Данные готовы, поэтому пользовательский процесс вызывает recvfrom для копирования данных из ядра и возвращает результат.
Асинхронный ввод-вывод
В общем, эти функции работают, сообщая ядру о начале операции и уведомляя нас, когда вся операция (включая копирование данных ядром в буфер) завершена. Основное различие между этой моделью и предыдущей моделью ввода-вывода, управляемого сигналами, заключается в том, что в случае ввода-вывода, управляемого сигналами, ядро сообщает нам, когда можно инициировать операцию ввода-вывода, а в случае асинхронного ввода-вывода ядро сообщает нам, когда можно инициировать операцию ввода-вывода. нам, когда операция ввода-вывода может быть инициирована Операция /O завершена.
Когда пользовательский процесс инициирует операцию ядру, она будет немедленно возвращена, и все задачи будут переданы ядру для завершения (включая копирование данных из ядра в собственный буфер пользователя). нужно только вернуть сигнал Сигнал сообщает пользователю, что процесс на нем завершен.
Сравнение моделей ввода-вывода в 5
** Результаты показывают, что: ** Основное различие между первыми четырьмя моделями заключается в первом этапе, второй этап из четырех моделей одинаков: процесс блокируется в вызове recvfrom при копировании данных из ядра в пользовательский буфер. Однако асинхронный ввод-вывод обрабатывается в два этапа, в отличие от первых четырех.
С точки зрения синхронизации, асинхронного и блокирующего, неблокирующего двух измерений:
нулевая копия
ЦП не выполняет задачу копирования данных из одной области хранения в другую, что часто используется для экономии циклов ЦП и пропускной способности памяти при передаче файлов по сети.
Кэш ввода-вывода
Кэшированный ввод-вывод также известен как стандартный ввод-вывод, и операция ввода-вывода по умолчанию в большинстве файловых систем — это кэшированный ввод-вывод. В механизме кэширования ввода-вывода в Linux операционная система будет кэшировать данные ввода-вывода в кэше страниц (кеше страниц) файловой системы, то есть данные будут сначала скопированы в буфер ядра операционной системы, а затем затем загружается из кеша Буферы ядра операционной системы копируются в адресное пространство приложения.
Недостатки кэшированного ввода-вывода: во время передачи данных требуется несколько операций копирования данных в адресном пространстве приложения и в ядре.Накладные расходы ЦП и памяти, вызванные этими операциями копирования данных, очень велики.
Классификация технологий нулевого копирования
Развитие технологии нулевого копирования очень разнообразно, и существует много типов существующих технологий нулевого копирования, но в настоящее время не существует технологии нулевого копирования, подходящей для всех сценариев. Для Linux существует множество технологий с нулевым копированием.Большинство из этих технологий с нулевым копированием существуют в разных версиях ядра Linux.Некоторые старые технологии были значительно усовершенствованы между разными версиями ядра Linux или были постепенно заменены новыми технологиями. В этой статье эти технологии нулевого копирования разделены на разные сценарии, для которых они применимы. Подводя итог, можно сказать, что технологии нулевого копирования в Linux в основном включают следующее:
- Прямой ввод-вывод: для этого метода передачи данных прикладная программа может напрямую обращаться к аппаратному хранилищу, а ядро операционной системы только помогает в передаче данных: этот тип технологии нулевого копирования нацелен на ядро операционной системы, которому не требуется для непосредственной обработки данных.В этом случае данные могут передаваться напрямую между буфером в адресном пространстве приложения и диском, без поддержки кэша страниц, предоставляемого ядром операционной системы Linux.
- В процессе передачи данных следует избегать копирования данных между буфером в адресном пространстве ядра операционной системы и буфером в адресном пространстве пользовательского приложения. Иногда приложению не требуется доступ к данным в процессе передачи данных, тогда можно полностью избежать копирования данных из кеша страниц Linux в буфер пользовательского процесса.Переданные данные сохраняются в кеше страниц. обработанный. В некоторых особых случаях эта технология нулевого копирования может обеспечить более высокую производительность. Аналогичные системные вызовы в Linux в основном включают mmap(), sendfile() и splice().
- Оптимизируйте передачу данных между кэшем страниц Linux и буферами пользовательских процессов. Эта технология нулевого копирования ориентирована на гибкую обработку операций копирования данных между буфером пользовательского процесса и кэшем страниц операционной системы. Этот метод продолжает традиционный способ общения, но является более гибким. В Linux этот метод в основном использует метод копирования при записи.
Цель первых двух типов методов в основном состоит в том, чтобы избежать операций буферного копирования между адресным пространством приложения и адресным пространством ядра операционной системы. Эти два типа технологий нулевого копирования обычно применимы в некоторых особых случаях, например, передаваемые данные не нуждаются в обработке ядром операционной системы или прикладной программой. Третий тип метода наследует традиционную концепцию передачи данных между адресным пространством приложения и адресным пространством ядра операционной системы, а затем оптимизирует саму передачу данных. Мы знаем, что передача данных между аппаратным и программным обеспечением может осуществляться с использованием прямого доступа к памяти.В процессе передачи данных с помощью прямого доступа к памяти почти нет необходимости в участии ЦП, так что ЦП может быть освобожден для выполнения других задач, но когда данные нужны При передаче между буфером адресного пространства пользователя и кэшем страниц ядра операционной системы Linux нет такого инструмента, как DMA, который можно использовать.ЦП должен участвовать в этой операции копирования данных на протяжении всего процесса, Таким образом, этот третий тип метода предназначен для эффективного повышения эффективности передачи данных между адресным пространством пользователя и адресным пространством ядра операционной системы.
Когда приложение обращается к определенному фрагменту данных, операционная система сначала проверяет, был ли недавно доступ к файлу и кэшируется ли содержимое файла в буфере ядра.Содержимое буфера копируется в буфер пользовательского пространства, указанный buf . Если нет, операционная система сначала копирует данные с диска в буфер ядра, который в настоящее время использует DMA для передачи, а затем копирует содержимое буфера ядра в пользовательский буфер. Затем системный вызов записи копирует содержимое пользовательского буфера в буфер ядра, относящийся к сетевому стеку, и, наконец, сокет отправляет содержимое буфера ядра на сетевую карту.Обратите внимание, что возможность реализации различных механизмов нулевого копирования зависит от того, обеспечивает ли базовая операционная система соответствующую поддержку.
Как видно из приведенного выше рисунка, всего создается четыре копии данных.Даже если для связи с оборудованием используется DMA, центральному процессору все равно необходимо обрабатывать две копии данных.В то же время это также происходит в пользовательский режим и режим ядра Множественные переключения контекста, несомненно, увеличат нагрузку на ЦП. Во время этого процесса мы не вносили никаких изменений в содержимое файла, поэтому копирование данных туда и обратно между пространством ядра и пространством пользователя, несомненно, является пустой тратой времени, и нулевое копирование в основном предназначено для решения этой неэффективности.
Чтобы разрешить передачу данных без прохождения через пространство пользователя, используйте mmap
Одним из способов уменьшить количество копий является вызов mmap() вместо вызова чтения:
buf = mmap(diskfd, len);
write(sockfd, buf, len);
вызов приложенияmmap()
, данные на диске проходят черезDMA
Скопированный буфер ядра, затем операционная система будет совместно использовать этот буфер ядра с приложением, поэтому нет необходимости копировать содержимое буфера ядра в пространство пользователя. Отзыв приложенийwrite()
, операционная система напрямую копирует содержимое буфера ядра вsocket
буфер, все это происходит в режиме ядра, и, наконец,socket
Затем буфер отправляет данные на сетевую карту.
Опять же, глядя на картинку очень просто:
Использование mmap вместо read заведомо уменьшает одну копию, а при большом количестве копируемых данных несомненно повышает эффективность. но использоватьmmap
Есть цена. когда вы используетеmmap
Когда вы это сделаете, вы можете столкнуться с некоторыми скрытыми ловушками. Например, когда ваша программаmap
Файл создается, но когда файл усекается другим процессом, будет вызван системный вызов записи для доступа к недопустимому адресу.SIGBUS
сигнал прекращен.SIGBUS
Сигнал по умолчанию уничтожит ваш процесс и создастcoredump
, если ваш сервер будет приостановлен таким образом, он понесет убытки.
Обычно мы используем следующие решения, чтобы избежать такого рода проблем:
-
Установите обработчик сигнала для сигнала SIGBUS.при встрече
SIGBUS
сигнал, обработчик сигнала просто возвращает значение,write
Системный вызов возвращает количество байтов, записанных до прерывания, иerrno
будет установлен на успех, но это плохой способ справиться с этим, потому что вы не решаете настоящую суть проблемы. -
Использовать блокировки аренды файловОбычно мы используем этот метод, используя блокировку аренды дескриптора файла, мы применяем блокировку аренды к ядру для файла, когда другие процессы хотят обрезать файл, ядро отправит нам в режиме реального времени
RT_SIGNAL_LEASE
Сигнал, сообщающий нам, что ядро снимает блокировку чтения-записи, которую вы установили для файла. Таким образом, программа получает доступ к недопустимой памяти иSIGBUS
прежде чем убитьwrite
Системные вызовы прерываются.write
возвращает количество записанных байтов и устанавливаетerrno
для успеха. мы должны бытьmmap
Файл заблокирован до и разблокирован после манипулирования файлом:
if(fcntl(diskfd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
perror("kernel lease set signal");
return -1;
}
/* l_type can be F_RDLCK F_WRLCK 加锁*/
/* l_type can be F_UNLCK 解锁*/
if(fcntl(diskfd, F_SETLEASE, l_type)){
perror("kernel lease set type");
return -1;
}
Ссылаться на: https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy1/ https://www.jianshu.com/p/fad3339e3448
**Примечание: **Текущий уровень нулевого копирования ограничен. Вероятно, я написал так много первым. Нулевое копирование все еще учится. Когда придет время, серия netty увидит, есть ли еще одна статья.
заключительные замечания
Мой уровень ограничен, и неизбежны недопонимания, если найдёте, пожалуйста, укажите на это, спасибо! ! !
Если вы чувствуете себя вознагражденным после прочтения, пожалуйста, поставьте лайк, подпишитесь и добавьте официальную учетную запись [Ingenuity Zero], чтобы узнать больше захватывающей истории! ! !