Разработка под Apple iOS

индекс
167,97

Основы работы с IOKit. Тонкости программирования драйверов


В рамках поддержки блога разработки под Mac OS X, я представляю свою статью о низкоуровневой разработке под Mac OS X. Обычно тема разработки драйверов не столь популярна, однако и здесь Mac OS X выгодно выделяется из ряда прочих операционных систем. Да, писать драйвера для Mac O S X – просто! Проще чем когда-либо ранее!


Экскурс в глубины теории

Очень часто я слышу фразы, что Mac OS X – это Linux. Или же что OS X основано на ядре FreeBSD и посему драйвера легко портировать из одной системы в другую. А вот и нет! Очень часто концепция разработки под Mac OS X противоречит классическим принципам, к примеру вместо традиционных спин локов (spin lock) Apple предлагает использовать примитив IOCommandGate для синхронизации доступа. Также достаточно жестко регламентируется использование ядерной памяти. Примерный совет от Apple выглядит так: «Выделяйте столько памяти в ядре, сколько вам необходимо использовать и не байтом больше. Предпочитайте основную часть работы с большими буферами памяти переносить в пользовательский режим (user space)».

Сердцем, т.е. ядром, Mac OS X является XNU. XNU – это гибридное ядро, которое сочетает в себе множество преимуществ, а также недостатков, монолитных и микроядерных ОС. Собственно, XNU состоит из микроядра mach, разработанного институтом Карнеги Мелоуна, подсистемы Posix из BSD 4.3 (позже эта часть синхронизировалась с последней веткой FreeBSD, дописывалась самой Apple, а также 3-ми лицами), и объектно-ориентированного фреймворка IOKit, на который положена ответственность взаимодействовать с аппаратной частью Mac’ов. Стоит упомянуть, что ядро mach предоставляет операционной системе следующие сервисы: планировщик потоков и процессов, вытесняющую многозадачность (pre-emptive), механизм виртуальной памяти, защиту памяти, mach-IPC (через передачу сообщений), отладчик ядра.


Кое-что об IOKit

И так, что же из себя представляет IOKit? Как и многие другие наработки, IOKit – это наследие NeXT Computer (гениальные люди видимо работали в NeXT). IOKit – это объектно-ориентированный фреймворк реализующий драйверную модель Mac OS X. Написан IOKit на С++, вернее на его урезанной версии – Embedded C++ [1]. Проще говоря, это старый добрый С++ без множественного наследования, RTTI, шаблонов и исключений. Также в IOKit запрещается определять конструкторы и деструкторы классов. Впрочем, если очень хочется, то использовать можно все, кроме RTTI и исключений, но Apple за это вас по голове не погладит! Ранее IOKit был написан на Objective C, как и фреймворк Cocoa, и назывался Driver Kit. Однако, для упрощения разработки драйверов 3-ми лицами, Driver Kit переписали на C++. Впрочем, по словам одного из основных разработчиков IOKit, Годфри ван дер Линдена (Godfrey van der Linden), это решение было ошибочным. Тем не менее, в IOKit можно невооруженным взглядом увидеть наследие Objective C, к примеру механизм подсчета ссылок и retain/release, именование классов (приставки OS и IO), именование методов классов и многое другое. Еще один интересный факт: Godfrey van der Linden — студент университета Nicta, того самого австралийского НИИ, который открыл исследовательский проект Darbat [2].

Для того, чтобы приступить в делу вам достаточно установленного XCode SDK, собственно самого IDE XCode, а также Terminal.app для загрузки драйверов. При создании нового проекта, выберете Generic IOKit Driver и XCode создаст для вас пустой проект. Вам остается только добавить код драйвера, откомпилировать его, и загрузить с помощью следующих строк (предполагается, что драйвер называется SampleDriver.kext):

sudo chmod –R 0755 ./SampleDriver.kext
sudo chown –R root: wheel ./SampleDriver.kext
sudo kextload –t ./SampleDriver.kext


Как видно из вышеприведенных строк: загрузка драйвера выполняется с правами root с помощью сторонней программы kextload (из набора kext utils, который в свою очередь входит в xnu utils). Драйвер, как и любое приложение Mac OS X (за исключением простых консольных приложений), представляет из себя bundle, т.е. директорию в которой хранятся данные, которые непосредственно относятся к драйверу, а именно: Info.plist файл, файл с локализированными строками и конечно же сам бинарный файл драйвера (SampleDriver.kext/Contents/MacOS/SampleDriver). Также драйвер может содержать дополнительные ресурсы (SampleDriver.kext/Contents/Resources), либо другие драйверы (SampleDriver.kext/Contents/PlugIns).

IOKit в своей концепции активно эксплуатирует две парадигмы ООП: наследование и полиморфизм. Наследование позволяет уменьшить количество использованной ядерной памяти: любой IOKit драйвер, наследует определенный базовый класс, который специфичен для каждого стека устройств в системе. Например для драйверов LAN устройств – это IOEthernetController, WAN устройств – IO80211Controller, звуковых карт – IOAudioDevice и т.д. Механизм виртуальных функций, позволяет драйверу легко переопределить определенные методы базово класса, таким образом реализуя необходимую функциональность.


Проба пера

Любой класс в IOKit драйвере должен быть прямо или косвенно унаследован от класса OSObject. Этот класс обеспечивает подсчет ссылок, поддержку псевдо-RTTI за счет макросов и дополнительной метаинформации, примитивы для создания инстанса класса (перегруженный оператор new) в среде IOKit и многое другое. Самый простой IOKit класс выглядит следующим образом:

*.h файл:
class MyIOKitClass: public OSObject
{
OSDeclareDefaultStructors(MyIOKitClass)

public:
virtual bool init();

protected:
virtual void free()

private:
void *fSimpleMember;

};


*.cpp файл:
bool MyIOKitClass:: init()
{
if (! OSObject:: init())
return false;

// TO-DO: Add basic initialization here
fSimpleMember = NULL;

return true;
}
void MyIOKitClass:: free()
{
// TO-DO: Add deinitialization code here
if (fSimpleMember)
{
// ...
}

OSObject:: free();
}

Наш класс MyIOKitClass унаследован от класса OSObject, в нем переопределены два метода:
  1. bool init() – данный метод будет вызван тогда, когда будет создан новый инстанс класса. В методе рекомендуется проводить начальную инициализацию полей класса, в общем все то, что вы бы сделали в конструкторе.
  2. void free() – данный метод будет вызван во время того, когда будет вызван последний release для экземпляра класса и его счетчик ссылок будет равен 0. Т.е. роль метода – деструктор класса.


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

От OSObject могут быть унаследованы лишь вспомогательные классы, которые необходимы при реализации более крупной системы. Главный класс драйвера (да и любой другой класс, который претендует на то, чтобы предоставлять операционной системе определенные сервисы) прямо или косвенно наследуется от класса IOService.

IOService помогает реализовать в вашем драйвере поддержку Plug’n’Play, Power Management (организовывает взаимодействие с Power Domain операционной системы), взаимодействие с IORegistry и другими драйверами системы. Здесь следует также упомянуть про IORegistry – это не что иное, как динамическое дерево устройств Mac OS X. Строить это дерево начинает загрузчик ОС (boot loader), ядро ОС, а также вспомогательные драйвера. К примеру драйвер IOACPIPlatformExpert строит в IORegistry дерево всех PCI устройств в системе, основываясь на информации из ACPI таблиц, а также APIC контроллера прерываний.

IOService имеет два достаточно полезных метода, которые вам наверняка следует реализовывать в большинстве ваших драйверов:
  1. bool start( IOService *provider ) – данный метод будет вызван во время старта вашего драйвера. Здесь вы можете поместить код, который будет заниматься выделением ресурсов, необходимых драйверу.
  2. void stop( IOService *provider ) – этот метод будет вызван во время останова вашего драйвера.


В сигнатуре вышеприведенных методов, вы могли заметить параметр provider. И так, что же он значит, и для чего нужен? Все очень просто, как было сказано ранее, в Mac OS X существует динамический список устройств IORegistry. В нем существует связка наб-драйвер. Любой драйвер, может регистрировать в системе свои сервисы и делать их набами (nubs) для любых других драйверов. Итак, наб – это класс, который другой драйвер системы зарегистрировал в IORegistry, и теперь этот наб может быть передан как провайдер в ваш драйвер. К примеру, для драйверов PCI устройств набом является инстанс класса IOPCIDevice. Информация о набе, а также идентификаторы устройства задаются в Info.plist файле драйвера. Следующая секция Info.plist файла иллюстрирует этот прием (данный код был взят из моего сетевого драйвера):

   <dict>
      <key>Realtek RTL8111B/RTL8168 NIC</key>
      <dict>
        <key>CFBundleIdentifier</key>
         <string>rtl.r1000.nic.ext</string>
         <key>IOClass</key>
         <string> RealtekR1000</string>
         <key>IOPCIMatch</key>
         <string>0x816910ec 0x816710ec 0x816810ec 0x813610ec</string>
         <key>IOProbeScore</key>
         <integer>500</integer>
         <key>IOProviderClass</key>
         <string>IOPCIDevice</string>
      </dict>
   </dict>


Опишем наиболее значимые параметры:
  • IOProviderClass – указывает тип наба, который IOKit передаст в метод start нашего драйвера.
  • IOClass – это имя класса нашего драйвера. Если обратится к нашему предыдущему примеру — это MyIOKitClass.
  • CFBundleIdentifier – bundle идентификатор нашего драйвера, проще говоря этот параметр однозначно идентифицирует наш драйвер в системе.
  • IOPCIMatch – список DeviceID-VendorID PCI устройств, которые будет обслуживать наш драйвер. К примеру возьмем 0x816810ec, 0x8168 – это DeviceID нашего сетевого контроллера, 0x10ec – это VendorID, в данном случае Realtek.


Остальные параметры – тема для еще одной статьи ;) Примерный же код нашего метода start будет выглядеть так:bool RealtekR1000:: start(IOService *provider)
{
   if (! IOEthernetController:: start(pciDev))
      return false;

   IOPCIDevice *pciDev = NULL;

   pciDev = OSDynamicCast(IOPCIDevice, provider);
   if (! pciDev)
      return false;

   pciDev->retain();   
   pciDev->open(this);

   // Add initialization of device here

   return true;
}


Я надеюсь, что данный код более-менее понятен для вас, добавлю лишь, что OSDynamicCast – это макрос, который заменяет отсутствие RTTI в IOKit за счет использования дополнительной метаинформации.


Сделаем что-нибудь полезное?

Все что вам необходимо сделать, чтобы создать свой сетевой драйвер:
  • Создать скелет для драйвера, как было описано выше.
  • Унаследовать класс драйвера от класса IOEthernetController.
  • Переопределить необходимые методы в своем классе (всего около 10-12).
  • Задать необходимые параметры в Info.plist файле.


И драйвер готов! Как видим, Mac OS X в очередной раз поражает нас изяществом. Здесь нет никаких огромных *.inf файлов, никакой системы минипортов, и прочих неочевидных вещей. Портирование драйвера для сетевого контроллера с Linux в Mac OS X заняло у меня около 7 дней, при том, что я должен был также освоить основы IOKit с нуля.


На этом этапе я намеряно делаю паузу до следующей статьи, если конечно же кому-то будет интересна данная тематика на хабре. Я опустил такие интересные вещи, как: Power Management, работа с IO портами и DMA, взаимодействие с user space и т.д. Желающим освоить IOKit могу порекомендовать начать с туторила IOKit Fundamentals [3], а заинтересованным – список тем о низкоуровневой разработке, которые им были бы наиболее интересны. Прошу оставлять свои отклики и пожелания ;)


Полезные ссылки


[1] Embedded C++
[2] Darbat project
[3] IOKit Fundamentals
+32
13 августа 2008, 17:43
32

комментарии (21)

+2
ur001 #
Списибо!
0
DaemonI #
Всегда пожалуйста ;)
0
kremlin #
отличная запись, спасибо.
0
emendz #
А есть ли в русскоязычном интернете форумы хорошего уровня посвященые разработке под mac?
0
DaemonI #
Если честно — не встречал. Мне хватает информации из туториалов, гайдов и SDK. Apple очень хорошо документирует и сопровождает свои средства разработки и фреймворки. Если возникают затруднения, обращаюсь в мейлин листы от Apple lists.apple.com/

Вот здесь, есть неплохие статьи по Cocoa www.steps3d.narod.ru
+2
lifeforweb #
Супер, спасибо.

Хотелось бы почитать про работу с внешними устройствами. В частности — с USB.
+1
yeleleo #
добавлю в мемориз — почитаю.
Хочу научить expresscard34-tvtuner работать нормально — а то бук его даже не видит.
+1
DaemonI #
Видимо тюнер основан на последнем PCI-E чипе от NXP/Phillips. Тогда советую обратить внимание на библиотеку MmInputFamily, потому как IOKit в сожалению не имеет стека для работы с мультимедийными устройствами подобного рода :(
+1
yeleleo #
блин, куда не сунься — везде загвоздка((
0
baal #
Супер! Прекрасная статья. Отправляю в избранное для дальнейшего изучения, с попутным занесением плюсов в карму и другие уязвимые места. Побольше бы таких познавательных статей.
–3
kronos #
кж/пе
+2
kronos #
А что такого в фразе «креатив жжот, пиши еще»?
0
sigizmund #
в том, что креатив — это на Удаве. Хабрахабр, слава Б-гу, не Удав, а статья технической направленности — все что угодно, но не «креатив».
0
kronos #
Ничего плохого в креативе не вижу, а чо такое удав я не знаю
0
firstbyte #
Отлично. А возможно ли написать драйвер для USB-модема? :) А то у меня замечательный хакинтош, но без интернета. Роутер конечно прикуплю, но просто интересует.
0
DaemonI #
Можно, более того существуют стандартные драйвера для этого — AppleUSBCDC, тебя интересует драйвер AppleUSBCDCACM. Возможно можно его подредактировать, или же добавить какой-нибудь дополнительные скрипт к скриптам модемов.
0
PSHKGRZN #
Огромное спасибо за инфо!
Надеюсь что сторонние разработки (имею в виду поддержку железа написанную руками рядовых маководов) вырастут в объеме и в качестве!
0
Saenco #
Инфа интересная, за неё спасибо. НО вот на счёт «писать драйвера для Mac OS X – просто» — нельзя забывать, что вечно могут вылезти разные «но».
Для примера:
Я являюсь довольным владельцем ноута toshiba a100. Решил себе поставить Хакентош. Поставил нормально, поработал (работаю до сих пор), но Wi-Fi не поднялся. Начал искать дрова(писать самому с нуля — тяжковато).
Набрёл на форум forum.insanelymac.com/index.php? showtopic=84072 и прозрел — разработка драйвера под Intel 3945 ведётся уже с января месяца несколькими прогерами и толпой бета-тестеров.
НО это так, для примера, не сомневаюсь, что бывает всё намного проще
За статью ещё раз спасибо. Хотелось бы ещё почитать.
0
DaemonI #
Бывает все наааамного проще. Дело в том, что IO80211Controller не документирован. И весь 80211 стек тоже, посему возникает масса трудностней с реализацией WiFi драйверов. Текущая реализация линух дров основывается на SoftMAC, т.е. весь MAC уровень реализован софтварно в стеке, 80211 стек от Apple пользует хардварный MAC на WiFi картах. А документации по хардверному MAC уровню на интеловских картах тоже очень мало. Меня просили глянуть на этот драйвер, но из-за отсутствия железа и нужного стимула, я пока туда не смотрел :)
0
Saenco #
Понятно, спасибо за разъяснения.
0
performer #
Кстати, Годфри ван дер Линден гнал, ибо со времен NextStep до 10.2 IOKit был как раз на ObjC написан. У меня товарисч работал в конторе, которая под Нексты разрабатывала дрова для контроллеров NCR.
Необходимость перехода была очевидна — отсутствие явного связывания во время компиляции часто приводит к проблемам во время исполнения, да и влияет на само время исполнения.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.