Соображения Набора I/O
Набор I/O является мощной и относительно прямой средой драйвера. Однако как с большинством вещей, которые мощны, существует несколько вещей, которые не будут вести себя способ, которым Вы могли бы ожидать, особенно если Вы привыкли писать драйверы для других платформ.
В этой главе описываются некоторые вещи, которые необходимо рассмотреть при портировании драйвера от другой операционной системы, включая способы избежать потенциальной паники ядра, отказов загрузки драйвера, и т.д. в будущем
IOLog по сравнению с многоуровневым журналированием
Некоторые модели драйвера UNIX предоставляют дополнительную поддержку для журналирования, такого как использование многократных уровней многословия. Набор I/O не предоставляет такую поддержку, полагаясь на разработчика для реализации такой схемы, если это желаемо.
Удобный способ моделировать это состоит в том, чтобы заменить вызовы к printf
с вызовами к макрос как этот:
#define dIOLog(level, a, b...) {if (org_mklinux_iokit_swim3_debug & \ |
level) IOLog(a, ## b); } |
и затем определите различные макросы уровня как полномочия два. Это обеспечивает еще большее управление отладкой, чем основанная на уровне схема, позволяя Вам иметь до 32 отличных опций журнала (или 64, если Вы объявляете переменную отладки как 64-разрядный тип), который можно включить или выключить индивидуально путем изменения значения глобальной переменной (в этом случае, вызвать org_mklinux_iokit_swim3_debug
).
Обратите внимание на то, что, если Вы собираетесь использовать этот вид отладки в ядре драйвера C, необходимо или использовать глобальную переменную, передать значение переменной как параметр или передать указатель на переменную как параметр, так как код C не может получить доступ к переменным класса непосредственно.
Asynchronicity и Synchronous Returns
Во многих частях Набора I/O шаблонный класс включает и синхронные и асинхронные методы. В этих случаях абсолютно необходимо реализовать и асинхронные и синхронные вызовы. Асинхронные вызовы обычно включают обратный вызов для подпрограммы завершения. Если Вы вызываете complete
метод на этой подпрограмме завершения от того же потока, вызвавшего Вашу функцию, можно получить случайную панику ядра или сложить повреждение.
Проблема состоит в том, что самая традиционная архитектура драйвера не разработана с асинхронным I/O в памяти. Существует много способов переоснастить асинхронную поддержку в драйвер. Каждый испытывает его собственные затруднения.
Самое очевидное решение состоит в том, чтобы породить временный поток помощника для выполнения работы. Однако это сталкивается с проблемами с параллелизмом. В частности поток помощника должен быть в состоянии получить данные из основного потока, что означает, что буфер должен быть в общем ресурсе (например, как часть экземпляра класса). Однако некоторый другой поток мог быть в том же коде одновременно, приведя к повреждению запроса.
Очевидные (но неправильно) фиксируют для этого, должен использовать блокировку. Блокировкам Маха, однако, не нравится он, когда Вы блокируете их в одном потоке и выпускаете их в другом. Вместо этого необходимо использовать IOCommandGate
.
В Вашем объявлении класса необходимо включать место для хранения логического элемента команды:
IOCommandGate *myGate; |
В Вашей подпрограмме запуска необходимо инициализировать логический элемент команды. Чтобы сделать это, Вы могли бы добавить код, который похож на это:
myGate = new IOCommandGate(); |
Вам нужна функция обертки для передачи параметров функции doSyncReadWrite. Это могло бы быть похожим на это:
struct asyncAction { |
IOMemoryDescriptor *buffer; |
UInt32 block; |
UInt32 nblks; |
}; |
extern “C” static void org_mklinux_iokit_swim3_driver_doAsyncWrapper( |
void *selfref, |
void *actref, |
void *completionptr, |
void *) |
{ |
int retval; |
org_mklinux_iokit_swim3_driver *driver = selfref; |
retval = driver->doSyncReadWrite(myaction->buffer, |
myaction->block, |
myaction->nblks); |
IOStorage::complete(completion, retval, (retval ? 0 : |
(args.nblks * 512))); |
} |
В doAsyncReadWrite
, Вы сделали бы что-то как:
org_mklinux_iokit_swim3_driver::doAsyncReadWrite(IOMemoryDescriptor *buffer, |
UInt32 block,UInt32 nblks, |
IOStorageCompletion completion) |
{ |
struct asyncAction myaction; |
myaction.buffer = buffer; |
myaction.block = block; |
myaction.nblks = nblks; |
return (myGate->runAction( |
(Action)&org_mklinux_iokit_swim3_driver_doAsyncWrapper |
(void *)self, |
(void *)&myaction, |
(void *)&completion, |
NULL)); |
} |
тайм-аут, сон и нетайм-аут
Много традиционных драйверов стиля BSD используют функции sleep
, timeout
, и untimeout
как примитивы синхронизации, сродни условным переменным. У Вас мог бы быть блок кода, выглядящий примерно так:
timeout((timeout_fcn_t) wakeup, event, (2 * timeOut * HZ + 999) / 1000); |
sleep((char *)event, PZERO); |
где event
указатель на целое число, где результат ожидания будет сохранен, wakeup
(поддельный) указатель функции, вызванный, когда событие имеет место, тайм-аут обычно измеряется в некоторой части секунды, и PZERO
приоритетный уровень текущей работы. Это гарантирует, чтобы прерывания более низкого приоритета не будили этот код до тайм-аута. В такой системе прерывания ниже, чем PZERO, таким образом, они могли все еще привести к пробуждению.
Такой код также обычно содержал бы что-то как следующее:
untimeout((timeout_fcn_t) wakeup, (void *) event); |
в другой части кода (обычно в обработчике прерываний). Эта часть кода будит часть кода, ожидающего события.
OS X использует подобные конструкции в части BSD ядра. Однако они не представлены Набору I/O. Вместо этого необходимо использовать альтернативный механизм.
Если Вы не используете тайм-ауты, Вы могли бы просто использовать IOCommandGate
экземпляр. Однако это редко имеет место в коде, портированном от BSD.
Существует два рекомендуемых решения этого: IOLock
блокировки и IOTimerEventSource
/IOCommandGate
комбинация
IOLock
блокировкиЭто - самый простой способ заменить
timeout
/sleep
/untimeout
комбинация. Вместо того, чтобы установить тайм-аут и сон, Вы вычисляете метку времени, когда Вы хотите проснуться, возьмите блокировку и сон на блокировке.IOCommandGate
сIOTimerEventSource
Если Вы повторно проектируете свой код, это обеспечивает некоторую дополнительную гибкость за счет сложности. Это будет описано в предстоящем примере кода.
Этот пример кода предполагает, что использование приоритета должно строго предотвратить случайные пробуждения. Если Ваш код делает что-то более сложное с приоритетными уровнями, необходимо будет существенно улучшить этот код.
Этот код также предполагает, что существует только один запрос в рейсе в любой момент времени, и один поток, делающий сон, ожидает в любой момент времени. Иначе, необходимо будет добавить своего рода очередь вместо простого наличия единственной переменной для содержания статуса возврата ожидания.
С теми протестами следует альтернатива сну и пробуждению:
Использование перечисления 2-1 IOLock для сна/тайм-аута/нетайм-аута
#include <kern/kern_types.h> /* for THREAD_UNINT */ |
#include <IOKit/IOLocks.h> /* for IOLock */ |
#include <osfmk/kern/clock.h> /* for time manipulation functions */ |
/* instance-specific driver state structure */ |
struct driverstate_t { |
IOLock *lock; |
int wait_return; |
. |
. |
. |
} |
/* This function waits */ |
dosomething( ... , driverstate_t mydriverstate) |
{ |
IOLock *lock = mydriverstate->unitlock; |
int nanoseconds_to_delay = 1000000; # 1 msec for an example |
AbsoluteTime absinterval, deadline; |
. |
. |
. |
// timeout((timeout_fcn_t) wakeup, event, (2*timeOut * HZ + 999) / 1000); |
// sleep((char *)event, PZERO); |
IOLockLock(lock); |
nanoseconds_to_absolutetime(nanoseconds_to_delay, (uint64_t *)&absinterval); |
clock_absolutetime_interval_to_deadline(absinterval, (uint64_t *)&deadline); |
IOLockSleepDeadline(lock, event, deadline, THREAD_UNINT); |
wakeup = mydriverstate->wait_return; |
IOLockUnlock(lock); |
} |
/* This function wakes up the waiting thread */ |
interrupt_handler( ... , driverstate_t mydriverstate) |
{ |
// untimeout((timeout_fcn_t) wakeup, (void *) event); |
IOLockLock(lock); |
mydriverstate->wait_return = |
IOLockUnlock(lock); |
} |
Загрязнение пространства имен
Существует две главных фундаментальных проблемы пространства имен с портированием драйверов от UNIX до OS X: совместное использование между драйверами и совместное использование между экземплярами драйвера.
Загрязнение пространства имен является проблемой для любого разработчика драйвера, использующего код C. Проблема состоит в том, что Ваше пространство имен функций потенциально совместно используется с другими драйверами. При именовании функции C или глобальной переменной с тем же именем как та из функциональной или глобальной переменной в чьем-либо драйвере один из этих двух драйверов не загрузится.
Загрязнение пространства имен также является проблемой для драйверов, зависящих от глобальных переменных, если больше чем для одного экземпляра драйвера возможно быть загруженным за один раз. Поскольку глобальные переменные C совместно используются через экземпляры драйвера, эти два экземпляра закончат тем, что ударили данные друг друга. (Так как драйвер уже загружается, не будет никаких проблем соединения, в отличие от случая, где два различных драйвера имеют переменные или функции с тем же именем.)
В этом разделе описываются решения обеих из этих проблем подробно.
Используя структуры данных для предотвращения загрязнения пространства имен перекрестного экземпляра
Самой большой проблемой с использованием ядра C для драйвера Набора I/O является проблема совместно используемого пространства имен. В то время как это не будет препятствовать тому, чтобы многократные экземпляры были инстанцированы, это будет означать, что они совместно используют любые глобальные переменные через те многократные экземпляры. Поэтому специальный уход необходим при работе с глобальными переменными.
Большая часть основанной на UNIX архитектуры драйвера решает это путем обеспечения своего рода параметра модуля их базовым функциям. Как быстрое исправление, добавление «используемого» флага в структуре данных может использоваться, чтобы позволить ядру C++ обеспечивать эту ту же функциональность с некоторым фиксированным пределом числу параллельных экземпляров. Это не идеальное долгосрочное решение, однако, и должно только использоваться во время ранней разработки.
Чтобы сделать это, однако, многократные экземпляры драйвера должны сотрудничать. Это требует коллективной блокировки между ними. Совместное использование блокировки просто. Инициализация блокировки без условий состязания, однако, требует немного большего усилия, в частности статический инициализатор. Примером этой методологии является шоу в Перечислении 2-2.
Использование перечисления 2-2 таблицы экземпляра для глобальных переменных
/**** in the header ****/ |
/* Added “used” since there are no fixed interface numbers */ |
struct scsipi { |
int used = 0; |
. |
. |
. |
}; |
struct scsipi interface[NSCSI]; |
kern_return_t read(int unit, ...) |
{ |
. |
. |
. |
} |
class org_mklinux_driver_IOLockClass : public OSObject |
{ |
org_mklinux_driver_IOLockClass(); |
~org_mklinux_driver_IOLockClass(); |
public: |
IOLock *lock; |
}; |
/**** in the C++ wrapper ****/ |
extern “C” { |
IOLock *org_mklinux_driver_swim3_lock = NULL; |
int org_mklinux_driver_swim3_lock_refcount = 0; |
extern scsipi *interface; |
} |
/* declare a statically-initialized instance of the class so that |
its constructor will be called on driver load and its destructor |
will be called on unload. */ |
class org_mklinux_driver_IOLockClass org_mklinux_driver_swim3_lock; |
class org_mklinux_driver_IOLockClass : public OSObject |
{ |
org_mklinux_driver_IOLockClass() |
{ |
lock = IOLockAlloc(); |
} |
~org_mklinux_driver_IOLockClass(); |
{ |
IOLockFree(lock); |
} |
public: |
IOLock *lock; |
}; |
int org_mklinux_driver_swim3::new_unit() { |
int i; |
IOLockLock(org_mklinux_driver_swim3_lock->lock); |
for (i=0; i<NSCSI; i++) { |
if (!interface[i]->used) { |
interface[i]->used = 1; |
IOLockUnlock(org_mklinux_driver_swim3_lock->lock); |
return i; |
} |
} |
IOLockUnlock(org_mklinux_driver_swim3_lock->lock); |
return -1; |
} |
void org_mklinux_driver_swim3::free_unit(int unit) { |
IOLockLock(org_mklinux_driver_swim3_lock->lock); |
interface[unit]->used = 0; |
IOLockUnlock(org_mklinux_driver_swim3_lock->lock); |
} |
org_mklinux_driver_swim3::init(){ |
. |
. |
. |
/* unit should be an instance variable in your c++ wrapper class */ |
unit = new_unit() |
if (unit == -1) { |
/* Maybe print a warning here */ |
return false; |
} |
} |
org_mklinux_driver_swim3::free(){ |
unit_free(unit) |
} |
Лучшее долгосрочное решение, однако, состоит в том, чтобы объявить структуру данных как элемент класса обертки C++, затем переписать функции для передачи указателя вместо целочисленного аргумента и разыменования указателя использования (mystructptr->field
) вместо разыменования структуры (mystruct.field
).
Используя макросы для предотвращения загрязнения пространства имен перекрестного драйвера
Для предотвращения беспорядка необходимо всегда называть функции способами, предотвращающими загрязнение пространства имен. Это не всегда практично для именования функций с именами как org_mklinux_iokit_swim3_foo
, Тем не менее, поскольку это быстро становится нечитабельным. Вместо этого более нормальное решение состоит в том, чтобы использовать макросы для переименования функций во время компиляции.
Например, скажите, что у Вас есть вызванные функции bingo
, nameo
, и dog
. Вы хотите переименовать тогда с префиксом farmer_had_a_
. Вы могли бы включать макросы в заголовочный файл всего проекта, которые похожи на это:
#define dog(a, b, c) farmer_had_a_dog(a, b, c) |
#define bingo(a, b, c) farmer_had_a_bingo(a, b, c) |
#define nameo(a, b, c) farmer_had_a_nameo(a, b, c) |
Теперь существует некоторый риск, что Вы забудете включать, они определяют для функции. Существует простая фиксация для той проблемы, все же. Во-первых, вместо только включая макросы, вкрапите прототипы для функций в том же файле. Например:
void farmer_had_a_dog(int and, int bingo, void *was_his, char *nameo); |
#define dog(a, b, c) farmer_had_a_dog(a, b, c) |
void farmer_had_a_bingo(int clap_i_n_g_o); |
#define bingo(a, b, c) farmer_had_a_bingo(a, b, c) |
void farmer_had_a_nameo(int dog); |
#define nameo(a, b, c) farmer_had_a_nameo(a, b, c) |
Теперь это - только частичное решение. Для создания его полным решением добавьте флаг компилятора -Wmissing-prototypes
к gcc
параметры компилятора (CFLAGS
) в Вашем водительском Makefile
или в его Разработчике Проекта plist. Если Вы будете видеть уведомления о без вести пропавших прототипов, то Вы будете знать об упущении одного (или больше).
Для получения дополнительной информации о загрязнении пространства имен, см. Руководство по программированию Ядра в Документации Разработчика Apple.