Введение

Системный вызов для выполнения специфических операций ввода/вывода для устройств.

В информатике, ioctl (аббревиатура от input/output control – управление вводом/выводом) – это системный вызов для выполнения специфических операций ввода/вывода для устройств и других операций, которые нельзя выразить стандартной семантикой файлов. Он принимает параметр, определяющий код запроса; эффект вызова полностью зависит от этого кода. Коды запросов часто зависят от конкретного устройства. Например, драйвер CD-ROM устройства, который может дать команду физическому устройству извлечь диск, предоставит код запроса ioctl для выполнения этой операции. Независимые от устройства коды запросов иногда используются для предоставления пользовательскому пространству доступа к функциям ядра, которые используются только основными системными программами или все еще находятся в разработке. Системный вызов ioctl впервые появился в седьмой версии Unix под этим названием. Он поддерживается большинством Unix и Unix-подобных систем, включая Linux и macOS, хотя доступные коды запросов различаются в зависимости от системы. Microsoft Windows предоставляет аналогичную функцию под названием "DeviceIoControl" в своем API Win32.

Предыстория

Обычные операционные системы можно разделить на два уровня: пользовательское пространство и ядро. Код приложений, такой как текстовый редактор, располагается в пользовательском пространстве, а базовые средства операционной системы, например, сетевой стек, – в ядре. Код ядра управляет конфиденциальными ресурсами и реализует механизмы безопасности и надёжности между приложениями; по этой причине приложения, работающие в пользовательском режиме, не имеют прямого доступа к ресурсам ядра. Приложения пользовательского пространства обычно обращаются к ядру посредством системных вызовов, код которых находится в ядре. Системный вызов обычно представлен в виде "вектора системного вызова", в котором желаемый системный вызов указывается с помощью индексного номера. Например, выход из программы может быть системным вызовом номер 1, а запись – номер 4. Затем вектор системного вызова используется для поиска соответствующей функции ядра для обработки запроса. Таким образом, обычные операционные системы обычно предоставляют несколько сотен системных вызовов для пользовательского пространства. Хотя это удобная конструкция для доступа к стандартным функциям ядра, системные вызовы иногда не подходят для доступа к нестандартным аппаратным периферийным устройствам. По необходимости, большинство аппаратных периферийных устройств (также известных как устройства) адресуются напрямую только в пределах ядра. Однако пользовательскому коду может потребоваться прямое взаимодействие с устройствами; например, администратор может настроить тип среды на интерфейсе Ethernet. Современные операционные системы поддерживают разнообразные устройства, многие из которых предлагают широкий набор функций. Некоторые из этих функций могут быть не учтены разработчиком ядра, и, как следствие, ядру сложно предоставить системные вызовы для работы с этими устройствами. Для решения этой проблемы ядро разрабатывается с возможностью расширения и может принимать дополнительный модуль, называемый драйвером устройства, который работает в пространстве ядра и может напрямую обращаться к устройству. Интерфейс ioctl представляет собой единый системный вызов, посредством которого пользовательское пространство может взаимодействовать с драйверами устройств. Запросы к драйверу устройства векторизуются относительно этого системного вызова ioctl, как правило, с использованием дескриптора устройства и номера запроса. Таким образом, базовое ядро может предоставить пользовательскому пространству доступ к драйверу устройства, не зная ничего о поддерживаемых устройством функциях и не требуя чрезмерно большого количества системных вызовов.

Конфигурация аппаратного устройства

Обычно ioctl используется для управления аппаратными устройствами. Например, в системах Win32 вызовы ioctl могут взаимодействовать с USB-устройствами или получать информацию о геометрии дисков подключенных устройств хранения. На OpenBSD и NetBSD ioctl используется драйвером псевдоустройства и утилитой bioctl для реализации управления RAID-массивами в едином, независимом от производителя интерфейсе, аналогичном ifconfig. На NetBSD ioctl также используется в системе мониторинга sysmon.

Терминалы

Одно из применений ioctl в коде, доступном для пользовательских приложений, — это ввод-вывод терминала. Операционные системы Unix традиционно активно использовали интерфейсы командной строки, изначально с аппаратными текстовыми терминалами, такими как VT100, подключенными к последовательным портам, а затем с эмуляторами терминалов и серверами удаленного входа, использующими псевдотерминалы. Устройства последовательных портов и псевдотерминалы управляются и конфигурируются с помощью вызовов ioctl. Например, размер экрана устанавливается с помощью вызова TIOCSWINSZ. Функция ioctl TIOCSTI (управление вводом-выводом терминала, имитация ввода с терминала) может передать символ в поток устройства.

Расширения ядра

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

sysctl альтернативный

По словам разработчика OpenBSD, ioctl и sysctl — это два системных вызова для расширения ядра, причём sysctl, возможно, проще в использовании. В NetBSD фреймворк sysmon envsys для мониторинга оборудования использует ioctl через proplib, в то время как OpenBSD и DragonFly BSD вместо этого используют sysctl для своего аналогичного фреймворка hw. sensors. Изначальная версия envsys в NetBSD была реализована с использованием ioctl до появления proplib и содержала сообщение о том, что фреймворк является экспериментальным и должен быть заменён интерфейсом sysctl(8), если он будет разработан. Это может объяснить выбор sysctl в OpenBSD и последующее внедрение hw. sensors в 2003 году. Однако, когда в 2007 году фреймворк envsys был переработан с использованием proplib, системный вызов остался ioctl, а сообщение было удалено.

Другие векторные интерфейсы вызовов

Устройства и расширения ядра могут быть связаны с пользовательским пространством посредством дополнительных новых системных вызовов, хотя этот подход применяется редко, поскольку разработчики операционных систем стремятся поддерживать интерфейс системных вызовов лаконичным и эффективным. На операционных системах Unix популярны два других векторных интерфейса вызовов: системный вызов `fcntl` ("управление файлами") конфигурирует открытые файлы и используется, например, для включения неблокирующего ввода-вывода; и системный вызов `setsockopt` ("настройка опций сокета") конфигурирует открытые сетевые сокеты, средство, используемое для настройки пакетного фильтра ipfw в системах BSD Unix.

Картировка памяти

Интерфейсы устройств Unix и возможности ввода/вывода иногда реализуются с помощью файлов, отображаемых в память. Приложения, взаимодействующие с устройствами, открывают в файловой системе расположение, соответствующее устройству, как если бы они выполняли вызов ioctl, но затем используют системные вызовы отображения памяти, чтобы связать часть своего адресного пространства с адресным пространством ядра. Этот интерфейс представляет собой гораздо более эффективный способ организации передачи больших объемов данных между устройством и приложением в пользовательском пространстве; отдельные вызовы ioctl или системные вызовы чтения/записи создают накладные расходы из-за многократных переключений между пользовательским пространством и ядром, в то время как доступ к диапазону адресов, отображенному в память, не влечет за собой таких накладных расходов. Можно использовать методы буферизованного ввода/вывода Win32 или именованные объекты отображения файлов; однако для простых драйверов устройств достаточно стандартных обращений DeviceIoControl METHOD.

Сетевая связь

Netlink — это механизм, подобный сокету, для межпроцессного взаимодействия (IPC), разработанный как более гибкая замена ioctl.

Сложность

Вызовы ioctl минимизируют сложность системного интерфейса вызовов ядра. Однако, предоставляя разработчикам место для "сокрытия" фрагментов и деталей интерфейсов программирования ядра, вызовы ioctl усложняют общий API взаимодействия пользователя с ядром. Ядро, предоставляющее несколько сотен системных вызовов, может предоставлять несколько тысяч вызовов ioctl. Хотя интерфейс вызовов ioctl несколько отличается от традиционных системных вызовов, на практике разница между вызовом ioctl и системным вызовом невелика: вызов ioctl – это просто системный вызов с другим механизмом диспетчеризации. Многие аргументы против расширения интерфейса системных вызовов ядра применимы и к интерфейсам ioctl. Для разработчиков приложений системные вызовы неотличимы от подпрограмм приложений: это просто вызовы функций, принимающие аргументы и возвращающие значения. Библиотеки времени выполнения ОС скрывают сложность, связанную с вызовом системных вызовов. К сожалению, библиотеки времени выполнения не обеспечивают такую же прозрачность для вызовов ioctl. Простые операции, такие как получение IP-адресов машины, часто требуют сложной последовательности вызовов ioctl, каждый из которых требует использования "магических чисел" и структур аргументов. Libpcap и libdnet – два примера сторонних Unix-библиотек-обёрток, предназначенных для сокрытия сложности интерфейсов ioctl, для захвата и ввода-вывода пакетов соответственно.

Безопасность

Пользовательские интерфейсы основных операционных систем часто подвергаются тщательному аудиту на предмет дефектов кода и уязвимостей безопасности до выпуска. Эти аудиты обычно сосредоточены на хорошо документированных интерфейсах системных вызовов; например, аудиторы могут удостовериться, что критически важные вызовы безопасности, такие как изменение идентификаторов пользователей, доступны только административным пользователям. Интерфейсы ioctl более сложны, разнообразны и, следовательно, их труднее аудировать, чем системные вызовы. Более того, поскольку вызовы ioctl могут предоставляться сторонними разработчиками, зачастую после выпуска основной операционной системы, реализации вызовов ioctl могут подвергаться меньшему контролю и, таким образом, содержать больше уязвимостей. Наконец, многие вызовы ioctl, особенно для сторонних драйверов устройств, не документированы. Поскольку обработчик вызова ioctl находится непосредственно в режиме ядра, входные данные из пользовательского пространства должны быть тщательно проверены. Локальные пользователи могут эксплуатировать уязвимости в драйверах устройств, передавая недопустимые буферы в вызовы ioctl. Операционные системы Win32 и Unix могут защитить имя устройства в пользовательском пространстве от доступа приложений, применяя к устройству специальные средства контроля доступа. Проблемы безопасности могут возникать, если разработчики драйверов устройств не применяют соответствующие средства контроля доступа к объектам, доступным из пользовательского пространства. Некоторые современные операционные системы защищают ядро от вредоносного кода пользовательского пространства (например, приложений, зараженных эксплойтами переполнения буфера) с помощью оберток системных вызовов. Обертки системных вызовов реализуют ролевой контроль доступа, определяя, какие системные вызовы могут быть вызваны какими приложениями; обертки могут, например, использоваться для "отзыва" у почтовой программы права запускать другие программы. Интерфейсы ioctl усложняют использование оберток системных вызовов из-за их большого количества, каждого с различными аргументами, некоторые из которых могут потребоваться обычным программам.