Расширения ядра и драйверы

В OS X v10.8 и позже, ядро является 64-разрядным (и некоторые аппаратные средства еще использовали 64-разрядное ядро v10.6). В этой главе описываются объяснение для этого изменения и объясняет, как это влияет на Вас как на разработчика драйверов устройств или других расширений ядра.

Эта глава разделена на четыре раздела:

Почему 64-разрядное ядро?

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

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

В дополнение к этим записям таблицы страниц операционная система поддерживает дополнительные структуры данных, отслеживающие, которых связаны физические страницы, с которыми процессами, который страницы свободны и т.д. Из них наиболее распространенное (по объему) vm_page структура (обычно замечаемый в форме vm_page_t указатели на него), который описывает различные свойства свободных страниц и резидентного объекта (в физической памяти) логические страницы, такие как их физический адрес, их состояние разбивки на страницы, число соединенных проводом карт распределения памяти, ссылающихся на страницу и т.д. OS поддерживает a vm_page структура для каждой физической страницы RAM.

Для компьютера с 64 ГБ RAM, учитывая размер страницы на 4 КБ, OS должен управлять почти 17 миллионами страниц физического RAM, каждый из которого имеет запись таблицы страниц и a vm_page структура. Всего, эти структуры данных потенциально использовали бы хорошо более чем гигабайт памяти ядра собой. В 32-разрядном адресном пространстве (на 4 ГБ) это значительно ограничило бы адресное пространство ядра, доступное для других целей.

Пространственные ограничения составлены другими структурами данных (mbuf хранение, например), которому нужно идеально позволить масштабироваться с размером доступной памяти. Путем перемещения в 64-разрядное адресное пространство (когда работавший поддерживаемое оборудование), ядро OS X может разместить эти структуры данных в конфигурациях памяти большой емкости.

В OS X v10.8 и позже, ядро работает исключительно в 64-разрядном режиме. В более ранних операционных системах ядро работало в различных режимах в зависимости от аппаратных средств. (См. http://support.apple.com/kb/HT3770 для подробных данных.)

Что необходимо сделать

Как разработчик драйвера, Вы must обновите свои драйверы с 64-разрядными двоичными файлами. 64-разрядное ядро не может загрузить 32-разрядные расширения ядра. К счастью, потому что Набор I/O является относительно современной средой с немногими устаревшими конструктивными ограничениями, большинство расширений ядра может быть адаптировано довольно легко к 64-разрядному. Много драйверов “просто работают” после изменения настроек компиляции. Однако существует несколько шагов, которые необходимо предпринять по пути.

Перекомпилируйте свой код

В 64-разрядной среде ядра драйверы устройств и расширения ядра должны быть сделаны 64-разрядными чистый и скомпилированный как 64-разрядные исполнимые программы. Этот процесс является по существу тем же что касается любого другого 64-разрядного кода. В частности необходимо знать об изменениях, описанных в Главных 64-разрядных Изменениях, Делая Код 64-разрядным Чистый, и Компилируя 64-разрядный Код. Существует одна небольшая разница, однако: необходимо использовать GCC 4.2 или позже при компиляции 64-разрядных расширений ядра. (64-разрядные приложения могут быть скомпилированы с GCC 4.0.)

Обновите информацию о зависимостях

В 64-разрядном ядре ядро экспортирует только зависимости KPI, не общие зависимости от ядра или неподдерживаемые зависимости. Например, com.apple.kpi.iokit поддерживается, но com.apple.kernel.iokit не.

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

Прекратите использовать неподдерживаемые символы

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

Обновите код пользователя-клиента

Драйверы устройств, говорящие непосредственно с приложением пространства пользователя, не используя семьи I/O Kit (такие как пользовательские клиенты и сами семьи I/O Kit), возможно, должны быть изменены для корректной передачи с приложениями. 32-разрядному расширению ядра, вероятно, придется связаться с 64-разрядным приложением и наоборот, который может вызвать проблемы с размером структуры данных, выравниванием, и т.д.

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

Для больше о пользовательских клиентах и интерфейсах устройства в целом, считайте Основные принципы IOKit.

Завершите перемещение к IODMACommand

На основанных на Intel компьютерах Macintosh с 64-разрядными процессорами Intel драйверы устройств, поддерживающие прямой доступ к памяти (DMA), должны быть обновлены для использования IODMACommand класс.

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

Проверьте на специфичные для ядра изменения типа данных

Два ключевых типа данных, используемые в ядре, имеют различные базовые базовые типы в 64-разрядной среде ядра. Это иногда может вызывать проблемы при печати некоторых числовых значений и при разделении на подклассы других классов. Эти изменения описаны в 64-разрядных Изменениях Типа данных Ядра.

Исправьте ошибки

После внесения этих всеобъемлющих изменений, остающихся исправлений, необходимо искать проблему, описанную в Дополнительных Подсказках Для 64-разрядного KEXTs, и исправить их при необходимости.

64-разрядные изменения типа данных ядра

В дополнение к общим изменениям типа данных C, описанным в Изменениях Типа данных, изменился базовый тип позади двух специфичных для ядра типов данных.

Имя типа

32-разрядный тип

64-разрядный тип

SInt32

long

int

UInt32

unsigned long

unsigned int

Эти изменения излагают две потенциальных проблемы: строки формата и переопределение метода C++.

Во-первых, эти изменения влияют на строки формата для printf и IOLog вызовы. При печати этих значений можно или изменить код для использования %ld при компиляции 32-разрядный и %d при компиляции 64-разрядный или бросок оба значения к int и используйте %d явно избегать предупреждения.

Во-вторых, эти изменения могут влиять на переопределенные методы, как описано в Дополнительных Подсказках Для 64-разрядного KEXTs.

Дополнительные подсказки для 64-разрядного KEXTs

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

Используйте корректную версию SDK

Несмотря на то, что код пространства пользователя может быть скомпилирован против 10,5 SDK, необходимо скомпилировать код драйвера пространства ядра против 10,6 SDK или позже при компиляции 64-разрядной части. Для создания KEXT, поддерживающего существующую 32-разрядную архитектуру необходимо использовать настройки сборки на архитектуру, как описано в Использовании Архитектурно-зависимых Флагов.

Проверьте подписи переопределенных методов

Как упомянуто в 64-разрядных Изменениях Типа данных Ядра, типах данных UInt32 и SInt32 имеют тип long в 32-разрядной среде ядра, но имеют тип int в 64-разрядной среде ядра. Эти изменения представляют потенциальную проблему для переопределенных методов C++.

Если метод C++ имеет параметр типа UInt32 (например), и подкласс переопределяет тот метод, но определяет параметр как то, чтобы иметь тип long, в 32-разрядной среде версия подкласса переопределяет метод в суперклассе правильно, потому что типы эквивалентны.

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

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

Избегите усекать виртуальные адреса

В ядре ссылки на адреса виртуальной памяти часто обрабатываются с помощью неуказательных типов. Наиболее популярный способ использования является значением, возвращенным getVirtualAddress метод IOMemoryMap. Старайтесь присвоить эти адреса только переменным с 64-разрядными целыми типами такой как mach_vm_address_t и к никогда переменным с 32-разрядными целыми типами такой как UInt32.

Используйте большой нулевой флаг страницы

Чтобы помочь отладить проблемы усечения указателя, передайте -no_shared_cr3 отметьте как часть Ваших загрузочных аргументов. (См. Создание и Отладку Ядер для получения информации об установке загрузочных аргументов.) Этот флаг заставляет ядро осуществлять 128 нулей страницы TB в ядре и предоставляет подобные преимущества нулю страницы на 4 ГБ в приложениях пространства пользователя.

В то время как в настоящее время активное приложение пространства пользователя занимает нижнюю часть 128 TB (или 4 ГБ для 32-разрядного приложения), с 64-разрядным ядром само ядро занимает главный 128 TB виртуального адресного пространства. Поскольку пользовательские отображения не накладываются с отображениями ядра, они не должны быть сброшены при переключении в пространство ядра и назад. (Полномочия страницы используются, чтобы гарантировать, что к адресному пространству ядра не может фактически получить доступ приложение пространства пользователя даже при том, что существуют отображения.)

Как побочный эффект, однако, потому что эта объединенная таблица страниц используется, страницы в в настоящее время выполняющемся приложении пространства пользователя остаются доступными после перехода в ядро. Если 32-разрядное приложение (или 64-разрядное приложение без нуля страницы 4GB) работают, какой-либо указатель, используемый ядром, становящимся усеченным к 32 битам, может закончить тем, что указал в допустимый диапазон адресов, содержащий код или данные приложения. В результате небезопасно предположить, что усеченный указатель в ядре приведет к панике несанкционированного доступа. (Далее, такой случайный указатель может заставить приложения отказывать твердо диагностируемыми способами.)

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

Примечание: -no_shared_cr3 флаг ведет себя несколько по-другому с 32-разрядным ядром. Для большинства 64-разрядных приложений, потому что нижняя область 4 ГБ обычно не отображается, ядро может быть отображено в эту область. Таким образом, при переключении в ядро, таблица страниц остается тем же, и никакой сброс TLB не необходим. -no_shared_cr3 отметьте вызывает перезагрузку таблицы страниц и сброс TLB во время этого перехода.

Используйте IODMACommand для устройств с ограниченной поддержкой обращения

Некоторые устройства могут только обработать физические адреса, вписывающиеся в 32 бита. До такой степени, что возможно использовать 64-разрядные адреса, необходимо сделать так, но для этих устройств, можно или использовать IODMACommand или initWithPhysicalMask метод IOBufferMemoryDescriptor для выделения возврата буферизуют в нижней части 4 ГБ физической памяти.

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

Коммуникация между приложением пространства пользователя и кодом ядра является в основном тем же, являетесь ли Вы в 32-разрядном ядре или 64-разрядном ядре. Однако если Ваша платформа пространства пользователя только в настоящее время поддерживает 32-разрядные приложения, переход к 64-разрядному ядру (и другие изменения в OS X v10.6) может потребовать, чтобы Вы обновили этот код для поддержки 64-разрядных приложений.

Ваш пользовательский клиент должен быть в состоянии обработать коммуникацию от любого типа процесса, поддерживаемого любой версией ОС, которую Вы поддерживаете. Для приложений, работающих в OS X v10.7 и позже, это означает 32-разрядный и 64-разрядный Intel. Для приложений, работающих в v10.6, это включает 32-разрядный PowerPC (Розетта). Для приложений, работающих в более старых версиях OS X, это может даже включать 64-разрядный PowerPC.

Вот некоторые подсказки для коммуникации перекрестной архитектуры:

  • Поддержите непротиворечивые размеры структуры — Где возможно, создайте свои структуры данных таким способом, которым они не изменяются в размере между архитектурой. Если Ваши структуры содержат указатели, поддержание непротиворечивых размеров структуры является более трудным, но не невозможным. Один способ сделать загруженные указателем структуры непротиворечивыми состоит в том, чтобы использовать объединение между указателем и большим типом данных. Например:

    struct my_struct {
        int a;
        char b;
        union {
            void *c;
            uint64_t pad_01;
        };
    };
  • Использовать в своих интересах IOUserClient::initWithTask— Если Вы пишете пользовательскому клиенту, IOUserClient::initWithTask метод имеет две формы. Одна форма берет дополнительное OSDictionary параметр, предоставляющий информацию о клиенте. Чтобы определить, является ли удаленный процесс 32-разрядным клиентом PowerPC, работающим в Розетте, включайте этот код в свой пользовательский клиент:

    if (properties && properties->getObject(kIOUserClientCrossEndianKey)) {
        // Connecting application is a 32-bit PowerPC
        // application running in Rosetta.  Byte
        // swap as needed.
    }

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

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

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

    Например, рассмотрите следующую структуру и операторы присваивания:

    struct mystruct {
        uint32_t magic;
        ....
    };
    struct mystruct mystruct_instance;
    mystruct_instance.magic=0x32160804;
    mystruct_instance.pad = 0xffffffff;

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

    void *blob = ...
    uint32_t *magic = blob;
    if (*magic == 0x32160804) {
        // no swap needed
    } else {
        // byte swap needed
    }

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

    Как альтернатива, если можно гарантировать, что четыре байта после магического числа никогда не будут нулем, можно проверить на 64-разрядные приложения как это:

    void *blob = ...
    uint32_t *magic = blob;
    if (*magic == 0x04081632) {
        // Application is 32-bits, byte swap needed.
    } else if (*magic == 0x00000000) {
        // Application is 64-bit PowerPC.
    } else if (*magic == 0x32160804) {
        // Application is built for the same
        // architecture as this code, but may
        // be either 32-bit or 64-bit on Intel.
        magic++
        if (*magic == 0x00000000) {
            // remote app is 64-bit Intel.
        } else {
            // remote app is 32-bit, built for the
            // same architecture as this code.
        }
    }

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

  • Объявите размер порядка байтов и слова явно — Это подобно понятию магических чисел за исключением того, что удаленный конец коммуникации идентифицирует свою архитектуру явно. Например, Вы могли бы записать код как это:

    #if defined(__LP64__)
        #ifdef __LITTLE_ENDIAN__
            #define HOST_ORDER=1
        #else
            #define HOST_ORDER=2
        #endif
    #elif defined(__LITTLE_ENDIAN__)
            #define HOST_ORDER=3
    #else
            #define HOST_ORDER=4
    #endif
     
    struct mystruct {
        int order;
    };
    struct mystruct mystruct_instance;
    mystruct_instance.order = HOST_ORDER;

    Это все еще помещает нагрузку чтения поля прямо на коде, получающем структуру, но делает его намного проще.

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

  • Устаревшая замена IOConnectMethod* вызовы — следующие функции не поддерживаются в 64-разрядной среде:

    • IOConnectMethodScalarIScalarO

    • IOConnectMethodScalarIStructureO

    • IOConnectMethodScalarIStructureI

    • IOConnectMethodStructureIStructureO

    • IOConnectMethodScalarIScalarO

    • IOConnectMethodScalarIStructureO

    • IOConnectMethodScalarIStructureI

    • IOConnectMethodStructureIStructureO

    Необходимо вместо этого использовать IOConnectCall* функции:

    • IOConnectCallMethod

    • IOConnectCallAsyncMethod

    • IOConnectCallStructMethod

    • IOConnectCallAsyncStructMethod

    • IOConnectCallScalarMethod

    • IOConnectCallAsyncScalarMethod