С боссом Цинхуа, посмотрите на IO с точки зрения ядра!

Netty

Во-первых, видео открытого урока

b23.tv/4XHhPT

подготовить знания

Посмотрите, как я снял NIO с алтаря

Как создавать масштабируемые высокопроизводительные сетевые сервисы (подробное объяснение шаблона программирования Reactor)

Hermes

Пользовательский режим и режим ядра

Прежде чем говорить о IO, нам нужно понять системную архитектуру Linux. Одна из философий дизайна Linux заключается в предоставлении разных разрешений на выполнение разных операций, то есть в привилегированном режиме. То есть некоторые ключевые операции, связанные с системой, должны выполняться программой с наивысшим уровнем привилегий. Например: ресурсы ЦП, ресурсы хранения, ресурсы ввода-вывода и т. д.

Процессор Intel с архитектурой X86 обеспечивает четыре уровня привилегий от 0 до 3, чем меньше число, тем выше привилегия. Операционная система Linux в основном использует два уровня привилегий, 0 и 3, которые соответствуют режиму ядра и режиму пользователя соответственно.

计算机内核

пользовательский режим: Только ограниченный доступ к памяти, без доступа к периферийным устройствам (диск, сетевая карта), переключение процессов не допускается.

Состояние ядра: ЦП может получить доступ ко всем данным в памяти, включая периферийные устройства (жесткие диски, сетевые карты), что позволяет переключать процессы.

Когда создается любой пользовательский процесс в Linux, он содержит два стека: стек ядра и пользовательский стек, которые являются частными для процесса, и процесс начинает работать из пользовательского режима. Когда процесс выполняет собственный код пользователя, говорят, что он находится в пользовательском рабочем состоянии (пользовательском состоянии). То есть в этот момент процессор работает в наименее привилегированном (уровень 3) пользовательском коде. Когда задача (процесс) выполняет системный вызов и обнаруживается при выполнении кода ядра, мы говорим, что процесс находится в состоянии выполнения ядра (или просто называется состоянием ядра). Теперь процессор выполняет код ядра с самым привилегированным доступом (уровень 0). Когда процесс находится в состоянии ядра, исполняемый код ядра использует стек ядра текущего процесса.

Переключение из пользовательского режима в режим ядра

  • Системный вызов (мягкое прерывание)

    Все пользовательские программы запускаются в пользовательском режиме, но иногда программа должна выполнять некоторые вещи режима ядра, такие как данные чтения с жесткого диска и т. Д. Единственное, что может сделать эти вещи, это операционная система, поэтому программа должна сначала запросить операционную систему для выполнения этой операции во имя программы. В это время необходимо механизм: программа пользовательского режима переключается в режим ядра, но не может управлять инструкциями, выполненными в режиме ядра. Этот механизм называется системным вызовом, а его реализация в процессоре называется инструкцией ловушки.

  • Периферийные прерывания (жесткие прерывания)

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

  • ненормальное событие

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

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

Аппаратное обеспечение инициирует сигнал, в результате чего ядро ​​вызывает обработчик прерывания и входит в пространство ядра. В этом процессе ядру также передаются некоторые переменные и параметры оборудования, и ядро ​​выполняет обработку прерывания через эти параметры. Так называемый «контекст прерывания» на самом деле можно рассматривать как эти параметры, передаваемые аппаратным обеспечением и некоторыми другими средами, которые необходимо сохранить ядру (в основном, среда процесса прерванного в данный момент выполнения).

Можно увидеть пользовательский режим к переключению режима ядра является значительным накладным расходом.

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

ввод-вывод из ядра

step1

использоватьstrace -ff -o ./log java BioServerКоманда запускает программу Java, а команда strace может помочь нам отследить все системные вызовы во время работы программы.

public class BioServer {


    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8091);
        System.out.println("step1: bind 8091");
        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println("step2: accept " + socket.getPort());
            new Thread(() -> {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                     PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        System.out.println(line);
                        out.println("Server recv:" + line);
                    }
                } catch (Exception e) {

                }
            }).start();
        }

    }
}

Глядя на журнал, напечатанный strace, сокет 5 вызывает функции ядра bind и listen, затем выполняет стандартный вывод 1 и, наконец, выполняет функцию опроса для блокировки.

step2

  1. jpsПосмотреть pid BioServer

  1. /proc/pid/fdВсе fds (файловые дескрипторы) процесса хранятся в каталоге

  • 0: стандартный ввод
  • 1: Стандартный выход
  • 2: вывод ошибки
  • 3: библиотека jdk
  • 4, 5: сокет ipv4, ipv6
  1. /proc/pid/taskВ каталоге хранятся все потоки процесса

  1. Установите два соединения, используя порт nc ip

Снова взглянув на файловый дескриптор процесса с pid=27290, сокетов 6 и 7 действительно больше.

  1. использоватьnetstat -natpКоманда для просмотра порта 8091 также устанавливает доступное TCP-соединение.

  1. Просмотр журналов трассировки

Сначала вызовите функцию accept, чтобы создать новый сокет fd=7, а затем вызовите функцию clone, чтобы создать новый поток с pid=7616.

  1. Просмотр журнала pid=7616

Поток 7616 заблокирован в функции recvfrom(fd=7).

Эволюция ИО

Итак, суть BIO заключается в том, что поскольку функция recvfrom(fd) системного вызова заблокирована, для подключения и чтения сокетов необходимо использовать многопоточность.

В чем проблема с этой моделью? Создается слишком много потоков, суть в том, что①Функция recvfrom ядра блокируется.

Если функция recvfrom не блокируется, то проблема обработки N клиентов одним потоком может быть решена.

Но если только функция recvfrom неблокирующая, если клиентов более миллиона, то каждый soekct будет вызывать функцию recvfrom один раз в каждом цикле,② Независимо от того, есть ли в сокете данные для чтения, произойдет переключение из пользовательского режима в режим ядра (системный вызов), а временная сложность будет равна O(n)..

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

Так ядро ​​обеспечивает системный вызов select (мультиплексирование), который может вернуть дескриптор файла доступного для чтения сокета, а затем программа вызывает функцию recvfrom, что значительно сокращает количество системных вызовов.

Что не так с этой моделью? То есть③ Каждый раз, когда выбор цикла будет передавать файловые дескрипторы всех сокетов ядру, а затем ядро ​​будет проходить по всем файловым дескрипторам..

Как решить эту проблему — epoll.

epoll模型

Приложение проходит сначалаepoll_createСистемный вызов создает область памяти, а затем вызывает过epoll_ctlДобавляет дескриптор файла в открытую область и является событием accpet. Когда клиент подключен, он отправляет жесткое прерывание ядру через сетевую карту. Доступный для чтения сокет будет обрабатываться приложением. Таким образом, ядро ​​​​epollуправляемый событиями. В java соответствует selectKey.

Два режима запуска epoll

  • Горизонтальный триггер (LT)

  • Срабатывание по фронту (ET)

Обсуждение ET и LT

Ууху. Call.com/question/20…

Технология нулевого копирования Linux

blog.CSDN.net/Лин Сонбин 1…

Woohoo. IBM.com/developer Я…

Ссылаться на:

сегмент fault.com/ah/119000001…

b23.tv/4XHhPT