Граничные пересечения

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

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

В защищенной среде памяти каждому процессу дают его собственное адресное пространство. Это означает, что никакая программа не может изменить данные другой программы, если те данные также не находятся в его собственном пространстве памяти (общая память). То же применяется к ядру. Это находится в своем собственном адресном пространстве. Когда программа связывается с ядром, данные не могут просто быть переданы от одного адресного пространства до другого, как Вы могли бы между потоками (или между программами в средах как Mac OS 9 и большинство операционных систем реального времени, не имеющих защищенной памяти).

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

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

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

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

Кроме того, Набор I/O использует user-client/device-interface API для большей части коммуникации. Поскольку, что API является определенным для Набора I/O, он не охвачен в этой главе. Пользовательский клиент API покрыт Основными принципами IOKit, Получив доступ к Аппаратным средствам Из Приложений и Руководства по проектированию Драйвера устройства IOKit.

ioctl API является также определенным для конструкции драйверов устройств и в основном выходит за рамки этого документа. Однако с тех пор ioctl BSD API, это покрыто сразу для Вашего удобства.

В этой главе рассматривается одного подмножества Маха IPC — вызов удаленной процедуры (RPC) Маха API. Это также покрывает syscall, sysctl, отображение памяти и блок, копирующий APIs.

Соображения безопасности

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

Выбор граничного метода пересечения

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

Мах, обменивающийся сообщениями и межпроцессное взаимодействие Маха (IPC), являются относительно низкоуровневыми способами связаться между двумя задачами Маха (процессы), а также между задачей Маха и ядром. Они формируют основание для большей части коммуникации за пределами BSD и Набора I/O. Вызов удаленной процедуры (RPC) Маха API является процедурной абстракцией высокого уровня, созданной поверх Маха IPC. Мах RPC является наиболее популярным способом использования IPC.

BSD syscall API является API для того, чтобы вызвать функции ядра от пространства пользователя. Это используется экстенсивно при записи файловых систем и сетевых протоколов способами, которые очень зависимы от подсистемы. Разработчики строго отговорены использовать syscall API за пределами файловой системы и расширений сети, поскольку никакой сменный API не существует для регистрации нового системного вызова с syscall механизм.

BSD sysctl API (в его пересмотренной форме) заменяет syscall API и также обеспечивает относительно безболезненный способ заменить отдельные переменные ядра от пространства пользователя. Это имеет прямую сменную архитектуру, делая его хорошим выбором, если это возможно.

Отображение памяти и блок, копирующий, используются в сочетании с одним из другого APIs, упомянутого, и обеспечивают способы переместить большие объемы данных (больше, чем несколько байтов) или непостоянно измеренные данные к и от пространства ядра.

Подсистемы ядра

Выбор граничных методов пересечения зависит в основном со стороны ядра, в которое Вы добавляете код. В частности граничный метод пересечения, предпочтенный для Набора I/O, отличается от предпочтенного для BSD, отличающегося от предпочтенного для Маха.

Если Вы пишете драйвер устройства или другой связанный код, Вы, вероятно, имеете дело с Набором I/O. В этом случае необходимо вместо этого считать надлежащие разделы в Основных принципах IOKit, Получив доступ к Аппаратным средствам Из Приложений и Руководства по проектированию Драйвера устройства IOKit.

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

Если Вы пишете код, находящийся где-либо еще, необходимо будет, вероятно, использовать Маха, обменивающегося сообщениями.

Пропускная способность и задержка

Инструкции в предыдущем разделе применяются к большей части коммуникации между кодом ядра и приложениями. Упомянутым методам, однако, несколько недостает, где высокая пропускная способность или низкая задержка являются проблемами.

Если Вы требуете высокой пропускной способности, но задержка не является проблемой, необходимо, вероятно, рассмотреть выполнение коммуникации с отображенной памятью. Для больших сообщений это обрабатывается несколько прозрачно Махом RPC, делая его разумным выбором. Для частей BSD ядра, однако, необходимо явно передать указатели и использование copyin и copyout перемещать большие количества данных. Это обсуждено более подробно в Копировании Отображения и Блока Памяти.

Если Вы требуете низкой задержки, но пропускная способность не является проблемой, sysctl и syscall не хороший выбор. Мах RPC, однако, может быть приемлемым решением. Другая возможность состоит в том, чтобы фактически соединить страницу проводом памяти (см., что Память Отображается и Блочное Копирование для подробных данных), запустите асинхронного Маха RPC simpleroutine (чтобы обработать данные) и использовать или блокировки или высокие/низкие водяные знаки (буферное обилие) для определения когда, считать и записать данные. Это может работать на коммуникацию высокой пропускной способности также.

При требовании и высокой пропускной способности и низкой задержки необходимо также смотреть на пользовательскую модель интерфейса клиента/устройства, используемую в Наборе I/O, так как та модель имеет подобные требования.

Мах, обменивающийся сообщениями и межпроцессное взаимодействие Маха (IPC)

Мах IPC и Мах, обменивающийся сообщениями, является основанием для большой части коммуникации в OS X. Во многих случаях, однако, эти средства используются косвенно службами, реализованными поверх одного из них. Мах, обменивающийся сообщениями и IPC, существенно подобны за исключением того, что Мах, обменивающийся сообщениями, является не сохраняющим состояние, который предотвращает определенные типы восстановления после ошибки, как объяснено позже. Кроме, где явно утверждено, этот раздел обрабатывает два как эквивалентные.

Основная единица Маха IPC является портом. Понятие портов Маха может быть трудно объяснить в изоляции, таким образом, вместо этого этот раздел принимает передающее знание подобного понятия, тот из портов в TCP/IP.

В TCP/IP сервер прислушивается к входящим соединениям по сети на определенном порту. Многократные клиенты могут соединиться с портом и отправить и получить данные в измеренных блоках или многократного слова размера слова. Однако только один серверный процесс может быть связан с портом за один раз.

В Махе IPC понятие является тем же, но проигрыватели отличаются. Вместо многократных узлов, соединяющихся с портом TCP/IP, у Вас есть многократные задачи Маха на том же компьютере, соединяющемся с портом Маха. Вместо правил брандмауэра о порте, Вы имеете права порта, указывающие то, что задачи могут отправить данным в определенный порт Маха.

Кроме того, порты TCP/IP двунаправлены, в то время как порты Маха однонаправлены, во многом как каналы UNIX. Это означает, что, когда задача Маха соединяется с портом, она обычно выделяет порт ответа и отправляет, сообщение, содержащее, отправляют права на тот порт ответа так, чтобы задача получения могла передать сообщения обратно передающей задаче.

Как с TCP/IP, многократные клиентские задачи могут открыть соединения с портом Маха, но только одна задача может слушать на том порту за один раз. В отличие от TCP/IP, однако, сам механизм IPC обеспечивает простые средние значения для одной задачи вручить от права слушать произвольную задачу. Срок получает права, относится к возможности задачи послушать на данном порту. Получите права, может быть отправлен от задачи до задачи в сообщении Маха. В случае Маха IPC (но не Маха, обменивающегося сообщениями), получите права, может даже быть сконфигурирован для автоматического возврата к исходной задаче, если новая задача отказывает или становится недостижимой (например, если новая задача работает на другом компьютере и сбои маршрутизатора).

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

Используя четко определенные порты

Прежде чем можно будет использовать Маха IPC для коммуникации задачи, передающая задача должна быть в состоянии получить, отправляют права на порту задачи задачи получения. Исторически, существует несколько способов сделать это, не, все из которых поддерживаются OS X. Например, в OS X, в отличие от большинства других деривативов Маха, нет никакой службы сервер-серверного или сервера имен. Вместо этого задача начальной загрузки и mach_init включают в категорию эту функциональность.

Когда задача создается, она дана, отправляют права на порт начальной загрузки для отправки сообщений к задаче начальной загрузки. Обычно задача использовала бы этот порт для отправки сообщения, дающего задачу начальной загрузки, отправляют права на другом порту так, чтобы задача начальной загрузки могла тогда возвратить данные задаче вызова. Различные подпрограммы существуют в bootstrap.h тот краткий обзор этот процесс. Действительно, большинство пользователей Маха, IPC или Мах, обменивающийся сообщениями фактически, используют вызовы удаленной процедуры (RPC) Маха, реализованные поверх Маха IPC.

Так как прямое использование IPC редко желательно (потому что не просто сделать правильно), и потому что базовая реализация IPC исторически изменилась регулярно, подробные данные не покрыты здесь. Можно найти больше информации об использовании Маха IPC непосредственно в Махе 3 Руководства Писателя Сервера от Silicomp (раньше Open Group, раньше Научно-исследовательский институт Фонда открытого программного обеспечения), который может быть получен из раздела разработчика веб-сайта Apple. В то время как большая часть информации, содержавшейся в той книге, не полностью актуальна относительно OS X, это должен все еще быть относительно хороший ресурс при использовании Маха IPC.

Вызовы удаленной процедуры (RPC)

Мах RPC является наиболее популярным способом использования Маха IPC. Это часто используется для коммуникации пользовательского ядра, но может также использоваться для задачи определить задачу или даже коммуникация от компьютера к компьютеру. Программисты часто используют Маха RPC для установки определенных параметров ядра, таких как политика планирования данного потока.

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

В каталоге osfmk/mach (относительно Вашего контроля xnu модуля от CVS), существует много файлов, заканчивающихся в .defs; эти файлы содержат определения RPC. Когда ядро (или модуль ядра) компилируется, Mach Interface Generator (MIG) использует эти определения для создания кода IPC для поддержки функций, экспортируемых через RPC. Обычно, если Вы хотите добавить новый вызов удаленной процедуры, необходимо сделать так путем добавления определения одному из этих существующих файлов. (См. Создание и Отладку Ядер для получения дополнительной информации о получении источников ядра.)

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

routine thread_policy_get(
                    thread      : thread_act_t;
                    flavor      : thread_policy_flavor_t;
            out     policy_info : thread_policy_t, CountInOut;
            inout   get_default : boolean_t);

Заметьте подобный C синтаксис определения. Каждый параметр в подпрограмме примерно отображается на параметр в функции C. Прототип C для этой функции следует.

kern_return_t thread_policy_get(
    thread_act_t            act,
    thread_policy_flavor_t  flavor,
    thread_policy_t         policy_info,
    mach_msg_type_number_t  *count,
    boolean_t               get_default);

Первые два параметра являются целыми числами и передаются как вызов по значению. Третьим является a struct содержа целые числа. Это - исходящий параметр, что означает, что значения, сохраненные в той переменной, не будут получены функцией, но будут перезаписаны по возврату.

Оттуда это становится более интересным. Четвертый параметр в прототипе C является представлением размера третьего. В файле определения это представлено добавленной опцией, CountInOut.

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

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

В дополнение к routine, У Маха RPC также есть simpleroutine. A simpleroutine подпрограмма т.е. по определению, асинхронный. Это может иметь нет out или inout параметры и никакое возвращаемое значение. Вызывающая сторона не ожидает функции для возврата. Одно возможное применение для этого могло бы быть должно сказать устройству ввода-вывода отправлять данные, как только это готово. В том использовании, simpleroutine мог бы просто ожидать данных, затем отправить сообщение в задачу вызова указать доступность данных.

Другая важная функция MIG является функцией подсистемы. В MIG подсистема является группой routines и simpleroutines это связано в некотором роде. Например, семафорная подсистема содержит связанные подпрограммы, воздействующие на семафоры. Существуют также подсистемы для различных таймеров, частей системы виртуальной памяти (VM) и десятков других в различных местах всюду по ядру.

Большую часть времени, если необходимо использовать RPC, Вы будете делать его в существующей подсистеме. Подробные данные создания новой подсистемы выходят за рамки этого документа. Разработчики, бывшие должные добавить новую подсистему Маха, должны консультироваться с Махом 3 Руководства Писателя Сервера от The Open Group (TOG), которая может быть получена из различных расположений в Интернете.

Другая функция MIG является типом. Тип в MIG является точно той же вещью, как это находится в языках программирования. Однако конструкция составных типов отличается несколько.

type clock_flavor_t     = int;
type clock_attr_t       = array[*:1] of int;
type mach_timespec_t    = struct[2] of int;

Данные типа array передается как адрес пространства пользователя того, что, как предполагается, является непрерывным массивом базового типа, в то время как структура передается путем копирования всех отдельных значений массива базового типа. Иначе, они обрабатываются так же. «Структура» не походит на структуру C, поскольку элементы структуры MIG должны все иметь тот же базовый тип.

Синтаксис объявления подобен Паскалю, где *:1 и 2 представляйте размеры для массива или структуры, соответственно. *:1 конструкция указывает массив переменного размера, где размер может быть до 1, включительно, но не больше.

Вызов RPC от пользовательских приложений

RPC, как упомянуто ранее, фактически очевиден для клиента. Вызов процедуры похож на любой другой вызов функции C, и никакая дополнительная связь библиотеки не необходима. Необходимо только ввести надлежащие заголовки с a #include директива. Компилятор автоматически распознает вызов как вызов удаленной процедуры и обрабатывает базовые аспекты MIG для Вас.

BSD syscall API

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

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

BSD ioctl API

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

Использование ioctl интерфейс является по существу тем же под OS X, как это находится в других полученных из BSD операционных системах, кроме способа, которым драйверы устройств регистрируются себя в системе. В OS X, в отличие от большей части BSDs, содержания /dev каталог создается динамично ядром. Эта файловая система смонтировалась на /dev упоминается как devfs. Можно, конечно, все еще вручную создать узлы устройства с mknod, потому что devfs является объединением, смонтированным по корневой файловой системе.

Набор I/O автоматически регистрирует некоторые типы устройств с devfs, создавая узел в /dev. Если Ваше семейство устройства не делает этого, можно вручную зарегистрировать себя в использовании devfs cdevsw_add или bdevsw_add (для устройств посимвольного ввода-вывода и блочных устройств, соответственно).

При регистрации устройства вручную в devfs, Вы создаете a struct cdevsw или struct bdevsw самостоятельно. В той структуре устройства один из указателей функции к ioctl функция. Необходимо определить определенные значения, переданные ioctl функция в заголовочном файле, доступном для лица, компилирующего приложение.

Пользовательское приложение может также искать устройство с помощью вызова функции Набора I/O getMatchingServices и затем используйте различные вызовы Набора I/O для настройки параметра вместо этого. Для получения дополнительной информации о поиске драйвера устройства из приложения см., что документ Получает доступ к Аппаратным средствам Из Приложений.

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

BSD sysctl API

Системное управление (sysctl) API специально предназначен для настройки параметра ядра. Эта функциональность заменяет syscall API, и также обеспечивает простой способ настроить простые параметры ядра, фактически не будучи должен записать подпрограмму обработчика в ядре. sysctl пространство имен разделено на несколько широких категорий, соответствующих цели параметров в нем. Некоторые из этих областей включают

Большую часть времени программы используют sysctl вызовите для получения текущей стоимости параметра ядра. Например, в OS X, hw sysctl группа включает опцию ncpu, который возвращает число процессоров в данном компьютере (или максимальное количество процессоров, поддерживаемых ядром на том определенном компьютере, какой бы ни меньше).

sysctl API может также использоваться для изменения параметров (хотя большинство параметров может только быть изменено полностью). Например, в сетевой иерархии, net.inet.ip.forwarding может быть установлен в 1 или 0, чтобы указать, должен ли компьютер передачи пакетов между многократными интерфейсами (базовая маршрутизация).

Общая информация о добавлении a sysctl

При добавлении sysctl необходимо сделать все следующие сначала:

  • добавьте, что следующее включает:

    #include <mach/mach_types.h>

    #include <sys/systm.h>

    #include <sys/types.h>

    #include <sys/sysctl.h>

  • добавить -no-cpp-precomp к Вашим параметрам компилятора в Разработчике Проекта (или к CFLAGS в Вашем make-файле при создании вручную).

Добавление a sysctl Вызов процедуры

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

Предпочтительный способ добавить a sysctl взгляды что-то как следующее:

SYSCTL_PROC(_hw, OID_AUTO, l2cr, CTLTYPE_INT|CTLFLAG_RW,
    &L2CR, 0, &sysctl_l2cr, "I", "L2 Cache Register");

_PROC часть указывает регистрацию процедуры для обеспечения значения (в противоположность простому чтению от статического адреса в памяти ядра). _hw высокоуровневая категория (в этом случае, аппаратные средства), и OID_AUTO указывает, что Вы должны быть присвоены следующий доступный элемент управления ID в той категории (в противоположность фиксированным средствам управления ID старого стиля). l2cr имя Вашего управления, которое будет использоваться приложениями для поиска числа использования управления sysctlbyname.

CTLTYPE_INT указывает, что изменяемое значение является целым числом. Другие юридические значения CTLTYPE_NODE, CTLTYPE_STRING, CTLTYPE_QUAD, и CTLTYPE_OPAQUE (также известный как CTLTYPE_STRUCT). CTLTYPE_NODE единственный, который не несколько очевиден. Это относится к узлу в sysctl иерархия, которая не непосредственно применима, но вместо этого является родителем к другим записям. Два примера узлов hw и kern.

CTLFLAG_RW указывает, что значение может быть считано и записано. Другие юридические значения CTLFLAG_RD, CTLFLAG_WR, CTLFLAG_ANYBODY, и CTLFLAG_SECURE. CTLFLAG_ANYBODY средние значения, что значение должно быть модифицируемым кем-либо. (Значение по умолчанию для переменных, чтобы быть изменяемым только корнем.) CTLFLAG_SECURE средние значения, в которых переменная может быть заменена только при выполнении securelevel <= 0 (эффективно, в однопользовательском режиме).

L2CR расположение, где sysctl будет хранить свои данные. Так как адрес установлен во время компиляции, однако, это должно быть глобальной переменной или статической локальной переменной. В этом случае, L2CR глобальная переменная типа unsigned int.

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

sysctl_l2cr функция-обработчик для этого sysctl. Прототип для этих функций имеет форму

static int sysctl_l2cr SYSCTL_HANDLER_ARGS;

Если sysctl перезаписываемо, функция может или использовать sysctl_handle_int получить значение передало в от пространства пользователя, и сохраните его в расположении по умолчанию или используйте SYSCTL_IN макрос для хранения его в альтернативный буфер. Эта функция должна также использовать SYSCTL_OUT макрос для возврата значения пространству пользователя.

“I” указывает, что параметр должен относиться к переменной типа integer (или константа, указатель или другая часть данных эквивалентной ширины), в противоположность “L” для a long, “A” для a string, “N” для a node (a sysctl это - родитель a sysctl категория или подкатегория), или “S” для a struct. “L2 Cache Register” человекочитаемое описание Вашего sysctl.

Для управления, чтобы быть доступным из приложения, это должно быть зарегистрировано. Чтобы сделать это, Вы делаете следующее:

sysctl_register_oid(&sysctl__hw_l2cr);

Необходимо обычно делать это в init подпрограмме для загружаемого модуля. Если Ваш код не является частью загружаемого модуля, необходимо добавить Ваш sysctl к списку встроенного OIDs в файле kern/sysctl_init.c.

Если Вы учитесь SYSCTL_PROC макрос конструктора, Вы заметите это sysctl__hw_l2cr имя переменной, создаваемой тем макросом. Это означает что SYSCTL_PROC строка должна быть прежде sysctl_register_oid в файле, и должен быть в том же (или более широк) объем. Это имя находится в форме sysctl_ сопровождаемый именем он - родительский узел, сопровождаемый другим подчеркиванием ( _ ) сопровождаемый именем Вашего sysctl.

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

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

static int myhandler SYSCTL_HANDLER_ARGS
{
 
    int error, retval;
 
    error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2,  req);
    if (!error && req->newptr) {
        /* We have a new value stored in the standard location.*/
        /* Do with it as you see fit here. */
        printf("sysctl_test: stored %d\n", SCTEST);
    } else if (req->newptr) {
        /* Something was wrong with the write request */
        /* Do something here if you feel like it.... */
    } else {
        /* Read request. Always return 763, just for grins. */
        printf("sysctl_test: read %d\n", SCTEST);
        retval=763;
        error=SYSCTL_OUT(req, &retval, sizeof retval);
    }
    /* In any case, return success or return the reason for failure  */
    return error;
}

Это демонстрирует использование SYSCTL_OUT отсылать произвольное значение в пространство пользователя от sysctl обработчик. «Фантом» req параметром является часть прототипа функции когда SYSCTL_HANDLER_ARGS макрос расширен, как oidp переменная, используемая в другом месте. Остающимися параметрами является указатель (введите равнодушный), и длина данных для копирования (в байтах).

Этот пример кода также представляет новую функцию, sysctl_handle_int, то, которое берет параметры, передало sysctl, и пишет целое число в обычную область хранения (L2CR в более раннем примере, SCTEST в этом). Если Вы хотите видеть новое значение, не храня его (чтобы сделать проверку работоспособности, например), необходимо вместо этого использовать SYSCTL_IN макрос, параметры которого совпадают с SYSCTL_OUT.

Регистрация нового верхнего уровня sysctl

В дополнение к добавлению нового sysctl опции, можно также добавить новую категорию или подкатегорию. Макрос SYSCTL_DECL может использоваться для объявления узла, который может иметь дочерние элементы. Это требует, чтобы изменение одного дополнительного файла создало дочерний список. Например, если Ваш основной файл C делает это:

SYSCTL_DECL(_net_newcat);
SYSCTL_NODE(_net, OID_AUTO, newcat, CTLFLAG_RW, handler, "new  category");

тогда это - в основном та же вещь как объявление extern sysctl_oid_list sysctl__net_newcat_children в Вашей программе. Для ядра для компиляции, или модуль для соединения необходимо тогда добавить эту строку:

struct sysctl_oid_list sysctl__net_newcat_children;

Если Вы не пишете модуль, это должно войти в файл kern/kern_newsysctl.c. Иначе, это должно войти в один из файлов Вашего модуля. Как только Вы создали эту переменную, можно использовать _net_newcat как родитель при создании нового управления. Как с любым sysctl, узел (sysctl__net_newcat) должен быть зарегистрирован в sysctl_register_oid и может быть не зарегистрировано с sysctl_unregister_oid.

Добавление простого sysctl

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

  • SYSCTL_INT(parent, nbr, name, access, ptr, val, descr)

  • SYSCTL_LONG(parent, nbr, name, access, ptr, descr)

  • SYSCTL_STRING(parent, nbr, name, access, arg, len, descr)

  • SYSCTL_OPAQUE(parent, nbr, name, access, ptr, len, descr)

  • SYSCTL_STRUCT(parent, nbr, name, access, arg, type, descr)

Первые четыре параметра для каждого макроса совпадают с для SYSCTL_PROC (описанный в предыдущем разделе), как последний параметр. len параметр (где применимо) дает длину строки или непрозрачного объекта в байтах.

arg параметры являются указателями точно так же, как ptr параметры. Однако параметры называют ptr явно описаны как указатели, потому что необходимо явно использовать “адрес” (&) оператор, если Вы уже не работаете с указателем. Параметры вызвали, аргумент или воздействует на базовые типы, которые являются неявно указателями или добавляют и оператор в надлежащем месте во время макрорасширения. В обоих случаях параметр должен относиться к целому числу, символу или другому объекту что sysctl будет использовать для хранения текущей стоимости.

type параметр является именем типа минус “struct”. Например, если у Вас есть объект типа struct scsipi, тогда Вы использовали бы scsipi как тот параметр. SYSCTL_STRUCT макрос функционально эквивалентен SYSCTL_OPAQUE, за исключением того, что это скрывает использование sizeof.

Наконец, val параметр для SYSCTL_INT значение по умолчанию. Если значение передало в ptr NULL, это значение возвращается когда sysctl используется. Можно использовать это, например, при добавлении a sysctl это является определенным для определенных аппаратных средств или определенных опций компиляции. Одним возможным примером этого могло бы быть специальное значение для feature.version это означает “не существующий”. Если бы та функция стала доступной (например, если бы модуль был загружен некоторым пользовательским действием), то это могло бы тогда обновить тот указатель. Если бы тот модуль был впоследствии разгружен, то он мог бы задержать указатель на NULL.

Вызов a sysctl От пространства пользователя

В отличие от RPC, sysctl требует явного вмешательства со стороны программиста. Для усложнения вещей далее существует два различных способа вызвать sysctl функции и никакая работают на каждое управление. Старого стиля sysctl вызов может только вызвать управление, если он перечислен в статической таблице OID в ядре. Новый стиль sysctlbyname вызов будет работать на любого добавленного пользователями sysctl, но не для перечисленных в статической таблице. Иногда, Вы даже найдете управление, регистрирующееся обоими способами, и таким образом доступное обоим вызовам. Для понимания различия необходимо сначала считать функции используемыми.

sysctlbyname Системный вызов

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

sysctlbyname(char *name, void *oldp, size_t *oldlenp,
        void *newp, u_int newlen)

Параметр name имя sysctl, закодированный как стандартная струна до.

Параметр oldp указатель на буфер, где будет сохранено старое значение. oldlenp параметр является указателем на буфер целочисленного размера, содержащий текущий размер oldp буфер. Если oldp буфер не является достаточно большим для содержания возвращенных данных, вызов перестанет работать с набором errno к ENOMEM, и значение, которым указывают oldlenp будет изменен для указания размера буфера, необходимого для будущего вызова для следования.

Вот пример для чтения целого числа, в этом случае размер буфера.

int get_debug_bufsize()
{
char *name="debug.bpf_bufsize";
int bufsize, retval;
size_t len;
len=4;
retval=sysctlbyname(name, &bufsize, &len, NULL, 0);
/* Check retval here */
return bufsize;
}

sysctl Системный вызов

sysctlbyname системный вызов является рекомендуемым способом вызвать системные вызовы. Однако не каждое управление встроенной системы регистрируется в ядре таким способом, с которым это можно вызвать sysctlbyname. Поэтому необходимо также знать sysctl системный вызов.

sysctl системный вызов является частью исходной исторической реализации BSD системных средств управления. Вы не должны зависеть от его использования ни для какого управления, которое Вы могли бы добавить к системе. Классическое использование sysctl похож на следующий

sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp,
        void *newp, u_int newlen)

Системные средства управления, в этой форме, основываются на MIB или архитектуре Базы информации управления. MIB является списком объектов и идентификаторов для тех объектов. Каждый идентификатор объекта или OID, является списком целых чисел, представляющих токенизацию пути через sysctl дерево. Например, если hw класс sysctl номер 3, первое целое число в OID было бы номером 3. Если l2cr опция встроена в систему и присвоила номер 75, тогда второе целое число в OID будет 75. Для помещения его иначе каждое число в OID является индексом в список узла дочерних элементов.

Вот короткий пример вызова для получения скорости шины данного компьютера:

int get_bus_speed()
{
int mib[2], busspeed, retval;
unsigned int miblen;
size_t len;
mib[0]=CTL_HW;
mib[1]=HW_BUS_FREQ;
miblen=2;
len=4;
retval=sysctl(mib, miblen, &busspeed, &len, NULL, 0);
/* Check retval here */
return busspeed;
}

Для получения дополнительной информации о sysctl системный вызов, см. страницу руководства sysctl.

Отображение памяти и блочное копирование

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

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

То же сохраняется с памятью, отображающейся между приложением и ядром. BSD sysctl и syscall интерфейсы (и до степени, Мах IPC) были разработаны для передачи маленьких единиц информации известного размера, таких как массив четырех целых чисел. В этом отношении они во многом как традиционный вызов функции C. Если необходимо передать большой объем данных функции в C, необходимо передать указатель. Это - также истина когда передающие данные между приложением и ядром с добавлением отображения памяти или копирования, чтобы позволить тому указателю быть разыменованным в ядре.

Существует много ограничений к способу, которым отображение памяти может использоваться для обмена данными между приложением и ядром. Для одного память, выделенная в ядре, не может быть записана в приложениями, включая тех, которые работают как корень (если ядро не работает в небезопасном режиме, таком как однопользовательский режим). Поэтому, если буфер должен быть изменен приложением, буфер должен быть выделен той программой, не ядром.

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

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

В ядре OS X данные наиболее легко копируются в пространство ядра с BSD copyin функция, и отступает к пространству пользователя с copyout функция. Для больших блоков данных все страницы будут копией на записи использования с отображенной памятью. Поэтому обычно не полезно сделать память, отображающуюся вручную.

Получение данных от ядра до приложения может быть сделано многими способами. Наиболее распространенный метод является реверсом вышеупомянутого, в котором приложение передает в буферном указателе, каракулях ядра на блоке данных, использования copyout скопировать буферные данные в адресное пространство приложения и возвраты KERN_SUCCESS. Обратите внимание на то, что это действительно использует буфер, выделенный в приложении, даже при том, что физическая память, возможно, была фактически выделена ядром. Принятие ядра освобождает свою ссылку на буфер, никакая память не потрачена впустую.

Особый случай отображения памяти происходит при выполнении I/O к устройству от пространства пользователя. Так как операции I/O могут, в некоторых случаях, быть выполнены аппаратными средствами DMA, работающими на основе физической адресации, жизненно важно, чтобы память связалась с буферами I/O не быть разбитой на страницы, в то время как аппаратные средства копируют данные в или от буфера.

Поэтому, когда драйверу или другому объекту ядра нужен буфер для I/O, это должно предпринять шаги для маркировки его как не листаемый. Этот шаг упоминается как проводное соединение страниц в памяти.

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

Страницы могут быть соединены проводом двумя способами. Когда область памяти выделяется, она может быть выделена нелистаемым способом. Подробные данные выделения памяти для I/O отличаются, в зависимости от того, какую часть ядра Вы изменяете. Это описано более подробно в надлежащих разделах этого документа, или в случае Набора I/O, в справочной документации API (доступный от раздела разработчика веб-сайта Apple). Поочередно, отдельные страницы могут быть соединены проводом после выделения.

Рекомендуемый способ сделать это через вызов к vm_wire в частях BSD ядра, с mlock из приложений (но только процессами, работающими как корень), или с IOMemoryDescriptor::prepare в Наборе I/O. Поскольку это может перестать работать по ряду причин, особенно крайне важно проверить возвращаемые значения при проводном соединении памяти. vm_wire вызовите и другие темы виртуальной памяти обсуждены более подробно в Памяти и Виртуальной памяти. IOMemoryDescriptor класс описан более подробно в Наборе I/O ссылка API, доступная от раздела разработчика веб-сайта Apple.

Сводка

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