Средства управления 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 КБ. Иначе, усечение произойдет.