Несколько месяцев назад я написал пост о введении I/O Ring в Windows. После его публикации несколько человек попросили сравнить кольцо ввода-вывода Windows и io_uring Linux, поэтому я решил сделать именно это:
Информация о реализации io_uring была собрана в основном отсюда(https://kernel.dk/io_uring.pdf) — документ, описывающий внутреннюю реализацию и использование io_uring в Linux и объясняющий некоторые причины его существования и способ его создания.
Как я уже сказал, базовая реализация обоих механизмов очень похожа — оба построены вокруг очереди отправки и очереди завершения, которые имеют общие представления как в адресном пространстве пользователя, так и в адресном пространстве ядра. Приложение записывает запрошенные данные операции в очередь отправки и передает их ядру, которое обрабатывает запрошенное количество записей и записывает результаты в очередь завершения. В обоих случаях существует максимальное количество разрешенных записей на кольцо, и очередь завершения может иметь до. Однако существуют некоторые различия во внутренней структуре, а также в том, как приложение должно взаимодействовать с кольцом ввода-вывода.
Инициализация и отображение памяти
Одним из таких отличий является этап инициализации и сопоставление очередей с пользовательским пространством: в Windows ядро полностью инициализирует новое кольцо, включая создание обеих очередей и создание общего представления в адресном пространстве пользовательского режима приложения с использованием MDL. Однако в реализации Linux io_uring система создает запрошенное кольцо и очереди, но не отображает их в пространстве пользователя. Ожидается, что приложение вызовет mmap(2), используя соответствующие файловые дескрипторы, чтобы отобразить обе очереди в свое адресное пространство, а также массив SQE, который отделен от основной очереди.
Это еще одно отличие, на которое стоит обратить внимание: в Linux кольцо завершения (или очередь) непосредственно содержит массив CQE, а кольцо отправки — нет. Вместо этого поле sqes в кольце отправки является указателем на другую область памяти, содержащую массив SQE, который должен отображаться отдельно. Для индексации этого массива у sqring есть дополнительное поле массива, которое содержит индекс в массиве SQE. Не будучи специалистом по Linux, я не буду пытаться объяснить причину этого дизайна, а просто приведу аргументацию, приведенную в упомянутой выше статье:
В Windows есть только два важных региона, поскольку SQE являются частью кольца отправки. На самом деле оба кольца выделяются системой в одной и той же области памяти, поэтому существует только одно общее представление между пользователем и пространством ядра, содержащее два отдельных кольца.
Еще одно отличие существует при создании нового кольца ввода-вывода: в Linux количество записей в кольце отправки может быть от 1 до 0x1000 (4096), тогда как в Windows оно может быть от 1 до 0x10000, но всегда будет не менее 8 записей. выделяться. В обоих случаях очередь завершения будет содержать в два раза больше записей, чем очередь отправки. Есть одно небольшое отличие в отношении точного количества записей, запрошенных для кольца: по техническим причинам количество записей в обоих кольцах должно быть степенью двойки. В Windows система берет запрошенный размер кольца и выравнивает его с ближайшей степенью двойки, чтобы получить фактический размер, который будет использоваться для выделения кольцевой памяти. В Linux система этого не делает, и ожидается, что приложение запросит размер, равный степени двойки.
Версии
Windows уделяет гораздо больше внимания совместимости, чем Linux, прилагая много усилий, чтобы гарантировать, что когда появится новая функция, приложения, использующие ее, смогут правильно работать в разных сборках Windows даже при изменении функции. По этой причине Windows реализует управление версиями для своих структур и функций, а Linux — нет. Windows также реализует кольца ввода-вывода поэтапно, отмеченные теми версиями, где первые версии реализовывали только операции чтения, следующая версия будет реализовывать операции записи и сброса и так далее. При создании кольца ввода-вывода вызывающая сторона должна передать версию, чтобы указать, какую версию кольца ввода-вывода он хочет использовать.
Однако в Linux эта функция была полностью реализована с самого начала и не требует управления версиями. Кроме того, Linux не уделяет столько внимания совместимости, и ожидается, что пользователи io_uring будут использовать и поддерживать новейшие функции.
Ожидание завершения операции
Как в Windows, так и в Linux вызывающий абонент может не ждать завершения событий в кольце ввода-вывода, а просто получать уведомления о завершении всех операций, что делает эту функцию полностью асинхронной. В обеих системах вызывающая сторона также может выбрать ожидание всех событий полностью синхронно, указав тайм-аут на случай, если обработка событий займет слишком много времени. Все, что находится между ними, является областью, в которой системы различаются.
В Linux вызывающий абонент может запросить ожидание завершения определенного количества операций в кольце, что не позволяет Windows. Эта возможность позволяет приложениям начать обработку результатов после завершения определенного количества операций, а не ждать их всех. В более новых сборках Windows добавила аналогичную, но немного более ограниченную опцию — регистрацию события уведомления, которое будет установлено, когда первая запись в кольце будет завершена, чтобы сигнализировать ожидающему приложению, что можно безопасно начать обработку результатов сейчас.
Вспомогательные библиотеки
В обеих системах приложение может само управлять своими кольцами с помощью системных вызовов. Это вариант, который допускается в Linux и крайне не рекомендуется в Windows, где NT API не документирован и официально не должен использоваться в коде, отличном от Microsoft. Однако в обеих системах большинству приложений не нужно управлять самими кольцами, и большая часть общего кода управления кольцами может быть абстрагирована и управляться отдельным компонентом.
Это делается с помощью вспомогательных библиотек — KernelBase.dll в Windows и liburing в Linux.
Обе библиотеки экспортируют общие функции, такие как создание, инициализация и удаление кольца ввода-вывода, создание записей очереди отправки, отправка кольца и получение результата из очереди завершения.
Обе библиотеки используют очень похожие функции и структуры данных, что значительно упрощает задачу переноса кода с одной платформы на другую.
Заключение
Реализация колец ввода/вывода в Windows настолько похожа на io_uring в Linux, что кажется, что некоторые заголовки были почти скопированы из реализации io_uring. Между этими двумя функциями есть некоторые различия, в основном из-за философских различий между двумя системами, а также роли и обязанностей, которые они возлагают на пользователя. Linux io_uring был добавлен пару лет назад, что сделало его более зрелой функцией, чем новая реализация Windows, хотя все еще относительно молодой и не без проблем. Будет интересно посмотреть, куда эти две функции пойдут в будущем и какой паритет будет в них существовать через несколько лет.
Ссылки
Эта статья является переводом, оригинал доступен тут
Перевод:
Azrv3l cпециально для xss.pro
- Короткий ответ: реализация Windows почти идентична реализации Linux, особенно при использовании функции-оболочки, предоставляемой вспомогательными библиотеками.
- Длинный ответ: это то, о чем я расскажу в оставшейся части этого поста.
Информация о реализации io_uring была собрана в основном отсюда(https://kernel.dk/io_uring.pdf) — документ, описывающий внутреннюю реализацию и использование io_uring в Linux и объясняющий некоторые причины его существования и способ его создания.
Как я уже сказал, базовая реализация обоих механизмов очень похожа — оба построены вокруг очереди отправки и очереди завершения, которые имеют общие представления как в адресном пространстве пользователя, так и в адресном пространстве ядра. Приложение записывает запрошенные данные операции в очередь отправки и передает их ядру, которое обрабатывает запрошенное количество записей и записывает результаты в очередь завершения. В обоих случаях существует максимальное количество разрешенных записей на кольцо, и очередь завершения может иметь до. Однако существуют некоторые различия во внутренней структуре, а также в том, как приложение должно взаимодействовать с кольцом ввода-вывода.
Инициализация и отображение памяти
Одним из таких отличий является этап инициализации и сопоставление очередей с пользовательским пространством: в Windows ядро полностью инициализирует новое кольцо, включая создание обеих очередей и создание общего представления в адресном пространстве пользовательского режима приложения с использованием MDL. Однако в реализации Linux io_uring система создает запрошенное кольцо и очереди, но не отображает их в пространстве пользователя. Ожидается, что приложение вызовет mmap(2), используя соответствующие файловые дескрипторы, чтобы отобразить обе очереди в свое адресное пространство, а также массив SQE, который отделен от основной очереди.
Это еще одно отличие, на которое стоит обратить внимание: в Linux кольцо завершения (или очередь) непосредственно содержит массив CQE, а кольцо отправки — нет. Вместо этого поле sqes в кольце отправки является указателем на другую область памяти, содержащую массив SQE, который должен отображаться отдельно. Для индексации этого массива у sqring есть дополнительное поле массива, которое содержит индекс в массиве SQE. Не будучи специалистом по Linux, я не буду пытаться объяснить причину этого дизайна, а просто приведу аргументацию, приведенную в упомянутой выше статье:
Поначалу это может показаться странным и запутанным, но за этим есть некоторые причины. Некоторые приложения могут встраивать блоки запросов во внутренние структуры данных, и это позволяет им делать это гибко, сохраняя при этом возможность отправки нескольких запросов за одну операцию. Это, в свою очередь, упрощает преобразование указанных приложений в интерфейс io_uring.
В Windows есть только два важных региона, поскольку SQE являются частью кольца отправки. На самом деле оба кольца выделяются системой в одной и той же области памяти, поэтому существует только одно общее представление между пользователем и пространством ядра, содержащее два отдельных кольца.
Еще одно отличие существует при создании нового кольца ввода-вывода: в Linux количество записей в кольце отправки может быть от 1 до 0x1000 (4096), тогда как в Windows оно может быть от 1 до 0x10000, но всегда будет не менее 8 записей. выделяться. В обоих случаях очередь завершения будет содержать в два раза больше записей, чем очередь отправки. Есть одно небольшое отличие в отношении точного количества записей, запрошенных для кольца: по техническим причинам количество записей в обоих кольцах должно быть степенью двойки. В Windows система берет запрошенный размер кольца и выравнивает его с ближайшей степенью двойки, чтобы получить фактический размер, который будет использоваться для выделения кольцевой памяти. В Linux система этого не делает, и ожидается, что приложение запросит размер, равный степени двойки.
Версии
Windows уделяет гораздо больше внимания совместимости, чем Linux, прилагая много усилий, чтобы гарантировать, что когда появится новая функция, приложения, использующие ее, смогут правильно работать в разных сборках Windows даже при изменении функции. По этой причине Windows реализует управление версиями для своих структур и функций, а Linux — нет. Windows также реализует кольца ввода-вывода поэтапно, отмеченные теми версиями, где первые версии реализовывали только операции чтения, следующая версия будет реализовывать операции записи и сброса и так далее. При создании кольца ввода-вывода вызывающая сторона должна передать версию, чтобы указать, какую версию кольца ввода-вывода он хочет использовать.
Однако в Linux эта функция была полностью реализована с самого начала и не требует управления версиями. Кроме того, Linux не уделяет столько внимания совместимости, и ожидается, что пользователи io_uring будут использовать и поддерживать новейшие функции.
Ожидание завершения операции
Как в Windows, так и в Linux вызывающий абонент может не ждать завершения событий в кольце ввода-вывода, а просто получать уведомления о завершении всех операций, что делает эту функцию полностью асинхронной. В обеих системах вызывающая сторона также может выбрать ожидание всех событий полностью синхронно, указав тайм-аут на случай, если обработка событий займет слишком много времени. Все, что находится между ними, является областью, в которой системы различаются.
В Linux вызывающий абонент может запросить ожидание завершения определенного количества операций в кольце, что не позволяет Windows. Эта возможность позволяет приложениям начать обработку результатов после завершения определенного количества операций, а не ждать их всех. В более новых сборках Windows добавила аналогичную, но немного более ограниченную опцию — регистрацию события уведомления, которое будет установлено, когда первая запись в кольце будет завершена, чтобы сигнализировать ожидающему приложению, что можно безопасно начать обработку результатов сейчас.
Вспомогательные библиотеки
В обеих системах приложение может само управлять своими кольцами с помощью системных вызовов. Это вариант, который допускается в Linux и крайне не рекомендуется в Windows, где NT API не документирован и официально не должен использоваться в коде, отличном от Microsoft. Однако в обеих системах большинству приложений не нужно управлять самими кольцами, и большая часть общего кода управления кольцами может быть абстрагирована и управляться отдельным компонентом.
Это делается с помощью вспомогательных библиотек — KernelBase.dll в Windows и liburing в Linux.
Обе библиотеки экспортируют общие функции, такие как создание, инициализация и удаление кольца ввода-вывода, создание записей очереди отправки, отправка кольца и получение результата из очереди завершения.
Обе библиотеки используют очень похожие функции и структуры данных, что значительно упрощает задачу переноса кода с одной платформы на другую.
Заключение
Реализация колец ввода/вывода в Windows настолько похожа на io_uring в Linux, что кажется, что некоторые заголовки были почти скопированы из реализации io_uring. Между этими двумя функциями есть некоторые различия, в основном из-за философских различий между двумя системами, а также роли и обязанностей, которые они возлагают на пользователя. Linux io_uring был добавлен пару лет назад, что сделало его более зрелой функцией, чем новая реализация Windows, хотя все еще относительно молодой и не без проблем. Будет интересно посмотреть, куда эти две функции пойдут в будущем и какой паритет будет в них существовать через несколько лет.
Ссылки
- Investigating Filter Communication Ports
- An End to KASLR Bypasses?
- Understanding a New Mitigation: Module Tampering Protection
- One I/O Ring to Rule Them All: A Full Read/Write Exploit Primitive on Windows 11
- One Year to I/O Ring: What Changed?
- HyperGuard Part 3 – More SKPG Extents
- An Exercise in Dynamic Analysis
- HyperGuard – Secure Kernel Patch Guard: Part 2 – SKPG Extents
- HyperGuard – Secure Kernel Patch Guard: Part 1 – SKPG Initialization
- IoRing vs. io_uring: a comparison of Windows and Linux implementations
Эта статья является переводом, оригинал доступен тут
Перевод:
Azrv3l cпециально для xss.pro