Стиль программирования ядра

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

Однако даже ограничение себя к задокументированным интерфейсам не защищает Вас от ряда ловушек. Самой большой потенциальной проблемой, к которой Вы обращенным, является конфликт пространства имен, происходящий, когда Ваша функция, переменная или имя класса совпадают с чьим-либо. Так как это делает одно расширение ядра или другой сбой для загрузки правильно (недетерминированным способом), Apple установил функциональные соглашения о присвоении имен для C и кода C++ в ядре. Они описаны в Стандарте C Соглашения о присвоении имен и Соглашения о присвоении имен C++, соответственно.

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

Соглашения о присвоении имен C++

Основные соглашения о присвоении имен C++ Набора I/O определяются в документе Руководство по проектированию Драйвера устройства IOKit. Этот раздел совершенствовал те соглашения способами, которые должны сделать их более полезными для Вас как программист.

Основные соглашения

Основные соглашения следующие:

  • Используйте соглашение о присвоении имен DNS реверса стиля Java, заменяя подчеркиваниями в течение многих периодов. Например, com_apple_foo.

  • Избегите следующих зарезервированных префиксов:

    • ОС

    • OS

    • IO

    • io

    • Apple

    • яблоко

    • AAPL

    • aapl

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

Дополнительные инструкции

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

  • Для предотвращения конфликтов пространства имен необходимо снабдить префиксом имена классов и семей с названиями проекта или другими довольно уникальными префиксными кодами.

    Например, если Вы работаете над драйвером видеосъемки, и один из его классов вызывают capture, Вы, вероятно, встретитесь с коллизией имени в конечном счете. Вместо этого необходимо назвать класс чем-то как com_mycompany_driver_myproduct_capture. Точно так же имена как

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

    #define captureClass com_mycompany_driver_myproduct_capture
    #define captureFamily com_mycompany_iokit_myproduct_capture
  • Используйте префиксы на имена функций и имена методов, чтобы упростить видеть отношения между ними. Например, Apple использует NS, CF, IO и другие префиксы, чтобы указать, что функции принадлежат определенным платформам. Это могло бы быть так же просто как добавление префикса функции с именем включения или связало класс, или это могла бы быть некоторая другая схема, которая целесообразна для Вашего проекта.

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

Стандарт C соглашения о присвоении имен

Соглашения о присвоении имен для C++ были определены в течение некоторого времени в документе Руководство по проектированию Драйвера устройства IOKit. Однако никакие соглашения не были даны для стандарта C код. Поскольку стандарт C имеет еще большую возможность конфликта пространства имен, чем C++, важно, что Вы следуете этим инструкциям при записи C кода для использования в ядре.

Поскольку C не обладает преимуществом классов, намного проще столкнуться с конфликтом имен между двумя функциями. Поэтому следующие соглашения предлагаются:

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

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

Обычно используемые функции

Когда программирование в ядре является использованием «стандартных» функций — вещи как, одна из наиболее распространенных проблем обратилась printf или bcopy. Много обычно используемых функций стандартной библиотеки для C реализованы в ядре. Для использования их, однако, необходимо включать надлежащие прототипы, которые могут отличаться от прототипов пространства пользователя для тех функций, и обычно имеющие различные имена, когда включено от кода ядра.

В целом любой заголовок Кита non–I/O, который можно безопасно включать в ядро, расположен в xnu/bsd/sys или xnu/osfmk/mach, несмотря на то, что существует несколько специализированных заголовков в других местах как libkern и libsa. Нормальные заголовки (те в /usr/include) не может использоваться в ядре.

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

Таблица 7-1  Обычно использовала функции C

Имя функции

Путь заголовка

printf

<sys/systm.h>

Буферные функции кэша (bread, bwrite, и brelse)

<sys/buf.h>

Записи каталога

<sys/dirent.h>

Коды ошибки

<sys/errno.h>

Специальные переменные ядра

<sys/kernel.h>

Спин-блокировки

<sys/lock.h>

malloc

<sys/malloc.h>

Очереди

<sys/queue.h>

Генератор случайных чисел

<sys/rand.h>

bzero, bcopy, copyin, и copyout

<sys/systm.h>

timeout и untimeout

<sys/system.h>

Различные функции времени

<sys/time.h>

Стандартные описания типа

<sys/types.h>

<mach/mach_types.h>

Удостоверения пользователя

<sys/ucred.h>

OS и информация о системе

<sys/utsname.h>

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

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

Где возможно, необходимо указать зависимость от версии KPI этих символов. Однако эти символы только доступны в v10.4 и позже. Для Набора I/O и libkern, это должно иметь мало значения. Для других областей, таких как сетевые расширения ядра или файловая система KEXTs, необходимо использовать версии KPI, если Вы хотите, чтобы Ваше расширение загрузилось в OS X v10.4 и позже.

Для полного списка символов в любой из этих зависимостей, выполненных nm на двоичных файлах в /System/Library/Extensions/System.kext/PlugIns.

Производительность и подсказки по устойчивости

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

Производительность и подсказки по устойчивости

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

  • Рекурсия должна быть ограничена (к не больше, чем нескольким уровням).

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

  • Большие переменные штабеля (функциональная локальная переменная) опасны. Не используйте их. Это также применяется к большим локальным массивам.

  • Динамично выделенные переменные предпочтены (использование malloc или эквивалентный) по локальным переменным для объектов больше, чем несколько байтов в размере.

  • Функции должны иметь как можно меньше параметров.

    • Указатели передачи на структуры, не вспыхнувшие элементы.

    • Не используйте параметры для избегания использования глобальных переменных или переменных класса.

    • Действительно назовите глобальные переменные в пути, защищающем Вас от коллизии.

  • Функции C++ должны быть объявлены статичные.

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

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

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

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

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

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

Во-вторых, AltiVec не поддерживался в ядре до OS X v10.3. Не было возможно обнаружить эту поддержку из ядра до более поздних 10,3 обновлений программного обеспечения. Если необходимо развернуть KEXT на более ранних версиях OS X, необходимо или обеспечить версию non-AltiVec кода или выполнить инструкции AltiVec в пространстве пользователя.

Наконец, инструкции потока данных AltiVec (dst, dstt, dstst, dss, и dssall) не поддерживаются в ядре, даже для процессоров, поддерживающих их в пространстве пользователя. Не пытайтесь использовать их.

Если Вы решаете использовать AltiVec в ядре, Ваш код может определить, поддерживает ли CPU AltiVec с помощью sysctlbyname вызовите для получения hw.optional.altivec свойство. Для получения дополнительной информации посмотрите sysctlbyname Системный вызов.

Подсказки по устойчивости

  • Не спите при содержании ресурсов (блокировки, например). В то время как это не запрещается, этому строго обескураживают для предотвращения мертвой блокировки.

  • Старайтесь выделить и свободная память с соответствием вызовов. Например, не используйте подпрограммы распределения от Набора I/O и подпрограммы освобождения от BSD. Аналогично, не использовать IOMallocContiguous с IOFreePageable.

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

  • Объекты блокирования прежде, чем воздействовать на них, даже изменить подсчеты ссылок.

  • Никогда не разыменовывайте указатели, не проверяя, что они не NULL. В частности никогда не делайте это:

    int foo = *argptr;

    если Вы уже не проверили это argptr не может возможно быть NULL.

  • Тестовый код в разделах и попытке продумать вероятные граничные случаи для вычислений.

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

  • Никогда не предполагайте, что никогда не будет изменяться размер экземпляра типа. Всегда используйте sizeof если Вам нужна эта информация.

  • Никогда не предполагайте, что указатель всегда будет тем же размером как int или long.

Сводка стиля

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