Средства управления KEXT и уведомления
В этой главе описываются два механизма для взаимодействия с сетевым расширением ядра: управление ядром и событие APIs ядра. Этот основанный на сокете APIs позволяет Вам связываться с KEXT и получать широковещательные уведомления от KEXT, соответственно.
Для поддержки этой коммуникации OS X определяет новый домен сокета — PF_SYSTEM
домен — для обеспечения пути к приложениям, чтобы сконфигурировать и управлять KEXTs. PF_SYSTEM
домен, в свою очередь, поддерживает два протокола, SYSPROTO_CONTROL
и SYSPROTO_EVENT
.
Управление ядром (kern_control
) API, использующий SYSPROTO_CONTROL
протокол, позволяет приложениям конфигурировать и управлять KEXT.
Событие ядра (kern_event
) API, использующий SYSPROTO_EVENT
когда определенные события ядра имеют место, протокол, позволяет приложениям и другому KEXTs быть уведомленными. Когда многократные клиенты должны знать о данном событии и не предназначаются как механизм связи точка-точка, это должно использоваться. В целом, управление ядром, API предпочтен, поскольку оно обеспечивает двунаправленную связь.
Для подробной справочной документации на этом APIs посмотрите Ссылку Платформы Ядра.
Используя управление ядром API для управления KEXT
Управление ядром API является механизмом двунаправленной связи между приложением пространства пользователя и KEXT. В этом разделе описываются этот API на уровне ядра и уровне пространства пользователя.
Поддержка средств управления ядром в Вашем KEXT
Поддержка средств управления ядром в KEXT является относительно прямой.
В функции запуска KEXT необходимо зарегистрировать управляющую структуру ядра с помощью ctl_register
функция. ctl_register
функция определяется в <sys/kern_control.h>
следующим образом:
int ctl_register(struct kern_ctl_reg *userctl, |
kern_ctl_ref *ctlref); |
kern_ctl_reg
структура содержит три поля, использующиеся для идентификации управления. Поля ctl_id
и ctl_name
может быть совместно использован через многократные средства управления.
Заключительное поле, ctl_unit
, содержит значение, которое является определенным для данного контроля. Управление может быть зарегистрировано многократно с тем же ctl_id
, но для каждого экземпляра должно использоваться различное число модуля. Для динамично выделенного управления IDs это значение заполнено в автоматически.
Другие поля kern_ctl_reg
структура содержит функции-обработчики, которые необходимо создать для обрабатывания различных запросов управления.
Поля структуры определяются следующим образом:
ctl_name
Строка идентификатора пакета для Вашего управления до
MAX_KCTL_NAME
байты (включая завершающийся нуль). Это может использоваться для генерацииctl_id
.ctl_id
уникальный 4-байтовый ID для управления. (См. примечание ниже.)
ctl_unit
число модуля для управления. Значение автоматически присваивается для динамично выделенного
ctl_id
значения.ctl_flags
флаги, влияющие на поведение управления. Можно установить
CTL_FLAG_PRIVILEGED
флаг, чтобы потребовать, чтобы у пользователя были административные привилегии связаться с управлением.Для большего подобного TCP поведения, флага
CTL_FLAG_REG_SOCK_STREAM
может быть указан, чтобы указать, что управление должно быть зарегистрировано для потоковых соединений, а не дейтаграмм. Отметьте, однако, это, если Вы устанавливаетеCTL_FLAG_REG_SOCK_STREAM
, необходимо соединиться с использованием управленияSOCK_STREAM
вместоSOCK_DGRAM
.ctl_sendsize
размер буфера зарезервирован для отправки сообщений. Значение 0 указывает, что должен использоваться размер по умолчанию.
ctl_recvsize
размер буфера зарезервирован для получения сообщений. Значение 0 указывает, что должен использоваться размер по умолчанию.
ctl_connect
вызванный, когда клиентский процесс вызывает подключение на сокете с числом ID/модуля зарегистрированного управления.
ctl_disconnect
вызванный, когда пользовательский клиентский процесс закрывает сокет управления.
ctl_send
вызванный, когда пользовательский клиентский процесс пишет данные в сокет.
ctl_setopt
вызванный, когда пользовательский клиентский процесс вызывает
setsockopt
установить конфигурацию управления.ctl_getopt
вызванный, когда пользовательский клиентский процесс вызывает
getsockopt
на сокете.
По успешному возврату, второму параметру, ctlref
, будет содержать ссылку на зарегистрированное управление ядром. Эта ссылка должна использоваться, чтобы не зарегистрировать управление и также передается как параметр любым обратным вызовам, когда их вызывают.
Возможно использовать в своих интересах именование управления ядром, чтобы позволить процессам взаимодействовать с KEXT по-разному. KEXT может, например, зарегистрировать управление только для корня для конфигурирования KEXT. Это могло бы зарегистрировать второе управление, доступное любому процессу, для сбора статистики. Каждый экземпляр управления будет иметь различное ctlref
, и это значение может тогда использоваться для определения который поведение использовать.
Когда управление ядром получает соединение от процесса пространства пользователя, управление ctl_connect_func
обратный вызов вызывают. В этой функции необходимо определить число модуля, связанное с соединением так, чтобы можно было позже передать данные обратно соединительному процессу. Вы должны тогда создать структуру данных (Вашего выбора), чтобы хранить специфичные для соединения данные и должны возвратить эту структуру присвоением через void **
дескриптор передал в как третий параметр. Это значение будет передано другим обратным вызовам, когда их вызовут.
В этой точке пользовательский процесс может связаться с использованием управления getsockopt
, setsockopt
, read
/recv
, и write
/send
на сокете. За исключением recv
(который считывает данные из очереди), вызовы в пространстве пользователя к этим функциям результат в вызове пространства ядра к эквивалентным обратным вызовам в управлении, ctl_getopt_func
, ctl_setopt_func
, и ctl_send
, соответственно.
Процесс ядра может, в свою очередь, вызвать много функций для передачи данных обратно процессу пространства пользователя. Эти данные могут быть считаны пользовательским процессом с помощью read
или recv
системные вызовы. В частности можно использовать ctl_enqueuedata
и ctl_enqueuembuf
стоять в очереди данные для отправки к процессу пространства пользователя, и ctl_getenqueuespace
узнать, сколько свободного пространства доступно в очереди.
Когда пользовательский процесс закрывает коммуникационный сокет к управлению, ctl_disconnect_func
обратный вызов вызывают. В этой точке управление должно освободить любые специфичные для соединения ресурсы, которые это выделило.
Перечисление 3-1 показывает некоторые основные функции в качестве примера для использования в качестве начальной точки:
Перечисление 3-1 основное kern_control
пример
errno_t error; |
struct kern_ctl_reg ep_ctl; // Initialize control |
kern_ctl_ref kctlref; |
bzero(&ep_ctl, sizeof(ep_ctl)); // sets ctl_unit to 0 |
ep_ctl.ctl_id = 0; /* OLD STYLE: ep_ctl.ctl_id = kEPCommID; */ |
ep_ctl.ctl_unit = 0; |
strcpy(ep_ctl.ctl_name, "org.mklinux.nke.foo"); |
ep_ctl.ctl_flags = CTL_FLAG_PRIVILEGED & CTL_FLAG_REG_ID_UNIT; |
ep_ctl.ctl_send = EPHandleWrite; |
ep_ctl.ctl_getopt = EPHandleGet; |
ep_ctl.ctl_setopt = EPHandleSet; |
ep_ctl.ctl_connect = EPHandleConnect; |
ep_ctl.ctl_disconnect = EPHandleDisconnect; |
error = ctl_register(&ep_ctl, &kctlref); |
/* A simple setsockopt handler */ |
errno_t EPHandleSet( kern_ctl_ref ctlref, unsigned int unit, void *userdata, int opt, void *data, size_t len ) |
{ |
int error = EINVAL; |
#if DO_LOG |
log(LOG_ERR, "EPHandleSet opt is %d\n", opt); |
#endif |
switch ( opt ) |
{ |
case kEPCommand1: // program defined symbol |
error = Do_First_Thing(); |
break; |
case kEPCommand2: // program defined symbol |
error = Do_Command2(); |
break; |
} |
return error; |
} |
/* A simple A simple getsockopt handler */ |
errno_t EPHandleGet(kern_ctl_ref ctlref, unsigned int unit, void *userdata, int opt, void *data, size_t *len) |
{ |
int error = EINVAL; |
#if DO_LOG |
log(LOG_ERR, "EPHandleGet opt is %d *****************\n", opt); |
#endif |
return error; |
} |
/* A minimalist connect handler */ |
errno_t |
EPHandleConnect(kern_ctl_ref ctlref, struct sockaddr_ctl *sac, void **unitinfo) |
{ |
#if DO_LOG |
log(LOG_ERR, "EPHandleConnect called\n"); |
#endif |
return (0); |
} |
/* A minimalist disconnect handler */ |
errno_t |
EPHandleDisconnect(kern_ctl_ref ctlref, unsigned int unit, void *unitinfo) |
{ |
#if DO_LOG |
log(LOG_ERR, "EPHandleDisconnect called\n"); |
#endif |
return; |
} |
/* A minimalist write handler */ |
errno_t EPHandleWrite(kern_ctl_ref ctlref, unsigned int unit, void *userdata, mbuf_t m, int flags) |
{ |
#if DO_LOG |
log(LOG_ERR, "EPHandleWrite called\n"); |
#endif |
return (0); |
} |
Соединение от клиентского процесса
Добавление kern_control
поддержка в Вашем NKE является только половиной истории. Другая половина фактически использует эту поддержку со стороны клиентского приложения.
Для передачи с NKE необходимо сначала открыть a PF_SYSTEM
сокет с помощью socket
вызовите следующим образом:
fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); |
Затем, Ваше приложение должно связать сокет с определенным управлением ядром. Чтобы сделать это, клиентский процесс должен вызвать connect
с дескриптором файла, возвращенным из socket
вызовите, вместе с заполненным sockaddr_ctl
структура, содержащая ID и число модуля управления ядром NKE.
Например:
sockaddr_ctl addr; |
/* (initialize addr here) */ |
result = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); |
Второй параметр, типа sockaddr_ctl
, должно быть заполнено в следующим образом:
addr.sc_len = sizeof(struct sockaddr_ctl); |
addr.sc_family = AF_SYSTEM; |
addr.ss_sysaddr = AF_SYS_CONTROL; |
addr.sc_id = MY_ID; // set to value of ctl_id registered by the NKE in |
// the ctl_register call described above. |
addr.sc_unit = MY_UNIT; // set to the unit number registered by the NKE |
// in the ctl_register call described above. |
Конечно, в случае динамично сгенерированного управления ID, необходимо получить значение для sc_id
использование CTLIOCGINFO
ioctl, как показано в Перечислении 3-1. При использовании динамично сгенерированного управления ID проигнорировано число модуля. Штабель автоматически выберет неиспользованное число модуля и заполнит sc_unit
поле прежде, чем передать connect
вызовите к обратному вызову подключения управления ядром. В то время как сторона ядра должна отслеживать число модуля для передачи обратно данных клиенту с точки зрения клиента, число модуля не использовано.
Теперь, когда канал передачи существует, клиентский процесс может использовать setsockopt
вызовите для отправки команд в NKE, или getsockopt
вызовите для получения информации о статусе из NKE. NKE определяет, какие имена опции сокета он обработает. Клиентский процесс должен передать только поддерживаемые имена опции к NKE в setsockopt
вызвать. Однако для безопасности, это - ответственность NKE проигнорировать опции, которые это не понимает, возвращаясь EOPNOTSUPP
.
Перечисление 3-2 показывает пример кода для открытия a PF_SYSTEM
сокет для передачи с NKE.
Перечисление 3-2 , открывающееся a PF_SYSTEM
снабдите сокетом для использования с kern_control
struct sockaddr_ctl addr; |
int ret = 1; |
fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); |
if (fd != -1) { |
bzero(&addr, sizeof(addr)); // sets the sc_unit field to 0 |
addr.sc_len = sizeof(addr); |
addr.sc_family = AF_SYSTEM; |
addr.ss_sysaddr = AF_SYS_CONTROL; |
#ifdef STATIC_ID |
addr.sc_id = kEPCommID; // should be unique - use a registered Creator ID here |
addr.sc_unit = kEPCommUnit; // should be unique. |
#else |
{ |
struct ctl_info info; |
memset(&info, 0, sizeof(info)); |
strncpy(info.ctl_name, MYCONTROLNAME, sizeof(info.ctl_name)); |
if (ioctl(fd, CTLIOCGINFO, &info)) { |
perror(“Could not get ID for kernel control.\n”); |
exit(-1); |
} |
addr.sc_id = info.ctl_id; |
addr.sc_unit = 0; |
} |
#endif |
result = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); |
if (result) { |
fprintf(stderr, "connect failed %d\n", result); |
} |
} else { /* no fd */ |
fprintf(stderr, "failed to open socket\n"); |
} |
if (!result) { |
result = setsockopt( fd, SYSPROTO_CONTROL, kEPCommand1, NULL, 0); |
if (result){ |
fprintf(stderr, "setsockopt failed on kEPCommand1 call - result was %d\n", result); |
} |
} |
Используя kern_event API для Уведомлений Ядра
Механизм уведомления о событии ядра, или kern_event
, легкий механизм, позволяющий приложениям быть уведомленными, когда определенные события ядра имеют место. Это - событие с одним выстрелом от пространства ядра до пространства пользователя, широковещательно передающегося ко всем слушающим процессам. Для двунаправленной связи необходимо использовать kern_control
API, описанный в Использовании Управления Ядром API для Управления KEXT.
Этот API является относительно прямым. Во время инициализации Ваш NKE должен вызвать kev_vendor_code_find
с именем пакета Вашего NKE (до 200 символов в длине). Это возвратит уникальный идентификатор, который Ваш KEXT должен использовать для идентификации любых уведомлений, которые это отправляет. Это значение идентификатора не является персистентным через перезагрузки.
Как только у Вас есть код поставщика, Ваш NKE может отправить уведомления. Отправить уведомление, Ваши вызовы NKE kev_message_post
с a kev_msg
структура, содержащая код поставщика, полученный ранее, вместе с классом события, подклассом, кодом события и до пяти частей данных произвольной длины, связалась с событием.
Можно определить собственный класс и разделить значения на подклассы как подходящие для NKE. Определенные Apple значения класса, используемые событиями ядра, встроенными в OS X, могут быть найдены в заголовочном файле kern_event.h
.
Получение уведомлений о событии ядра
Для получения уведомлений ядра в клиентском приложении необходимо сначала создать сокет события ядра следующим образом:
fd = socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT); |
Как только Вы создали этот сокет, можно использовать это для получения уведомлений о событии. Существуют несколько ioctls доступны, чтобы помочь Вам отфильтровать уведомления:
SIOCGKEVFILT
— получите фильтр события ядра для этого сокета.SIOCGKEVID
— получите текущее событие ID, ожидающий на сокете. Каждое событие будет иметь различный ID.SIOCGKEVVENDOR
— ищите код поставщика.SIOCSKEVFILT
— установите фильтр события ядра для этого сокета.
Например, для устанавливания фильтра события для фильтрации только для сгенерированных Apple событий от AppleTalk Вы могли бы сделать следующее:
struct kev_request req; |
req.vendor_code=KEV_VENDOR_APPLE; |
req.kev_class=KEV_APPLESHARE_CLASS; |
req.kev_subclass=KEV_ANY_SUBCLASS; |
if (ioctl(fd, SIOCSKEVFILT, &req)) { |
perror(“SIOCSKEVFILT”); |
exit(-1); |
} |
Используя SIOCGKEVFILT
ioctl подобен:
struct kev_request req; |
if (ioctl(fd, SIOCGKEVFILT, &req)) { |
perror(“SIOCSKEVFILT”); |
exit(-1); |
} |
printf(“The current filter is vendor code %d, class %d, subclass %d\n”, |
req.vendor_code, req.class, req.subclass); |
Для поиска кода поставщика для другого поставщика Вы могли бы сделать следующее:
struct kev_vendor_code vc; |
strcpy(vc.vendor_string, “org.mklinux.driver.swim3”); |
if (ioctl(fd, SIOCGKEVVENDOR, &vc)) exit(-1); |
printf(“Vendor code returned was %d\n”, vc.vendor_code); |
Наконец, для получения следующего идентификатора события из сокета Вы могли бы сделать что-то вроде этого:
uint32_t id; |
if (ioctl(fd, SIOCGKEVID, &id)) exit(-1); |
printf(“ID returned was %d\n”, id); |
Реализация предпочтительного файла для NKE
Разработчики часто спрашивают, как NKE может открыть «preference file» в функции запуска NKE. Под существующей архитектурой NKE не может надежно получить доступ к предпочтительному файлу. Когда система запускает NKE, нет никакого APIs, который NKE может использовать, чтобы открыть файл и считать предпочтительные информации.
Надлежащий способ динамично сконфигурировать NKE с демоном запуска или другим процессом прикладного уровня. Демон считает NKE использованием управления ядром (kern_control
) механизм описал в Использовании Управления Ядром API для Управления KEXT и передачи в конфигурационной информации, которой может потребовать NKE.
Полезные подсказки
Для предотвращения катастрофических отказов, необъясненного поведения, и других ловушек, существует несколько простых правил, при использовании которых необходимо соблюсти kern_control
и kern_event
в Вашем NKE.
- Не зарегистрируйте свое управление.
Когда кто-то пытается говорить с Вами после того, как Ваш KEXT разгружен, паника ядра следует. Необходимо использовать
ctl_deregister
чтобы не зарегистрировать Ваше управление перед, Ваш NKE разгружен. Если будут клиенты, все еще подключенные к Вашему управлению ядром, этот вызов перестанет работать.- Максимальный размер данных для событий составляет 2 КБ.
Данные передали с
kern_event
APIs должен быть отправлен в блоках, не больше, чемmbuf
размер кластера, или 2 КБ. Иначе, усечение произойдет.