![]() |
ОСРВ MULTEX-ARM
Руководство программиста
|
Особенностью построения систем, реализующих программную обработку в реальном времени, является необходимость одновременного выполнения нескольких процедур, причем каждая из них выполняется по своему алгоритму и синхронизируется тем или иным внешним событием (момент поступления данных от устройства ввода/вывода, срабатывание таймера, аппаратное прерывание от внешнего источника, готовность внешней системы к получению данных и т.д.) При этом часто бывает необходимо обеспечить минимальное время реакции программы на внешнее событие. Ситуация усложняется тем, что внешние события могут возникать независимо друг от друга в произвольные моменты времени.
Ядро MULTEX-ARM позволяет достаточно легко создавать такие системы и обеспечивает гибкое взаимодействие параллельно выполняющихся процессов с минимальными накладными расходами. Многозадачная среда MULTEX-ARM позволяет представлять прикладной проект в виде набора независимых задач и обеспечить кажущуюся одновременность их выполнения. В зависимости от общих требований прикладной проект может быть построен следующими способами (либо их комбинацией):
Недостатком режима карусельного планирования является зависимость эффективного быстродействия (а значит и времени выполнения для отдельной задачи) от комбинации состояний остальных задач системы. Однако, MULTEX-ARM позволяет динамически изменять режим работы ядра – включать или выключать карусельный режим, а также менять квант времени исполнения задачи, либо полностью блокировать переключение задач на время выполнения критичной ко времени секции процедуры. Кроме того, имеется возможность динамической смены приоритета любой задачи. Благодаря этому в среде MULTEX-ARM возможно эффективное построение самых разнообразных прикладных проектов.
Любая С-подпрограмма в среде MULTEX-ARM может быть запущена как отдельная задача со своим собственным контекстом и стеком. Базовые средства MULTEX-ARM позволяют создавать, приостанавливать, возобновлять, задерживать и уничтожать задачи. Задачи могут инициировать внутренние события, на которые будут реагировать другие задачи. Каждая задача MULTEX-ARM может находиться в одном из следующих состояний:
Ядро MULTEX-ARM манипулирует задачами с помощью специальных двусвязных циклических динамических списков – каруселей. При инициализации ядра создаются две таких структуры – карусель активных задач и карусель задержанных задач. Каждая задача системы помещается в одну из них, за исключением выполняемой задачи, которая до очередного переключения не отнесена ни к одной из каруселей. Существует еще одна, скрытая задача, которая выполняет пустой "вечный цикл" и служит только для того, чтобы выполняться тогда, когда все остальные задачи приостановлены. В эту задачу вырождается процедура запуска ядра MULTEX-ARM после завершения выполнения функции инициализации kernelInit(). Процесс переключения с задачи на задачу в MULTEX-ARM сводится к следующим шагам:
Настройка приоритетов аппаратных прерываний в комплексе с назначением приоритетов выполняемых задач позволяет реализовать достаточно гибкую и эффективную многозадачную среду на программно-аппаратном уровне. Так, чистое время переключения задач сравнимо с временем вызова "пустой" процедуры языка С. Однако, следует иметь в виду, что большое количество задач в системе может привести к увеличению накладных расходов на манипуляции с каруселями.
Прерывания играют важнейшую роль в системах реального времени. Аппаратное прерывание, вызванное поступлением электрического импульса на один из специализированных входов контроллера является сигналом к немедленным действиям, которые должна выполнить система в ответ на это событие. Ядро MULTEX-ARM позволяет обеспечить незамедлительное выполнение при возникновении прерывания пользовательской процедуры, подключенной к этому прерыванию. Такая процедура называется обработчиком прерывания. Все действия по сохранению / восстановлению контекста прерываемой задачи берет на себя операционная система. Ядро MULTEX-ARM настраивает контроллер прерываний таким образом, чтобы отличать аппаратные прерывания от исключений процессора и особых случаев, которые могут возникать при выполнении программы.
При планировании распределения вычислительных ресурсов в проекте следует учитывать иерархию выполнения аппаратных прерываний и пользовательских задач и настраивать их приоритеты соответствующим образом. В MULTEX-ARM существует два вида приоритетов. Их описание дано ниже.
В ARM процессорах со встроенным контроллером прерываний GIC реализована поддержка групп и приоритетов прерываний. Прерывания объединяются в группы внутри которых распределяются приоритеты обработки прерываний. Прерывания более приоритетной группы могут вытеснять (прерывать) обработку менее приоритетных групп. Приоритет внутри группы позволяет получить преимущество при возникновении нескольких прерываний одновременно.
Такая политика обработки прерываний поддержана в MULTEX-ARM. Для использования доступно 4 группы аппаратных прерываний и 8 уровней приоритетов. Макросы, описывающие порядок распределения прерываний по группам и назначения приоритетов, собраны в отдельную группу. Основной группой для всех прерываний является INTERRUPT_GROUP_MAJOR. Большинство прерываний используемых системой включены именно сюда. В эту же группу рекомендуется добавлять пользовательские обработчики прерываний, подключаемые с помощью interruptConnect(). Более приоритетной группой является INTERRUPT_GROUP_SYSTEM. В неё обычно входит всего одно прерывание — Системный таймер. В группу с наивысшим приоритетом INTERRUPT_GROUP_TIME_CRITICAL рекомендуется помещать прерывания, обеспечивающие мгновенную реакцию на событие, либо строго задающие период следования импульсов на внешних линиях. Обработчики таких прерываний должны выполняться максимально быстро. Например, задействовав TIMER_2 и аппаратный модуль ШИМ в импульсном режиме можно получить сигнал с переменной скважностью и жёстко заданным периодом. Вид такого сигнала с периодом 1,5 мкс приведён на верхнем графике осциллограммы. На нижнем графике показан тестовый импульс сгенерированный системным таймером. В данном случае высокоприоритетное прерывание, запускающее каждый импульс ШИМ, 3 раза прерывает обработку системного таймера. Наивысший приоритет в данному случае позволяет точно выдержать заданную частоту следования импульсов.
Выделением процессорного времени для каждой задачи занимается Системный таймер реализованный на аппаратном TIMER_1. На каждом тике таймера принимается решение какая из задач будет выполняться в настоящий момент. Каждый раз предпочтение отдаётся наиболее приоритетной из активных задач. В то же время не следует путать приоритеты задач и приоритеты прерываний. Во время исполнения даже самой приоритетной задачи может возникнуть аппаратное прерывание, обработчик которого прервёт текущую задачу.
В зависимости от модели процессора в нём может быть реализовано от 3 до 6 аппаратных таймеров. В MULTEX-ARM принято распределение таймеров, описанное соответствующей группе макросов. Как правило таймеры TIMER_0 и TIMER_1 заняты системой. Остальные таймеры могут быть задействованы под конкретный проект.
Системный таймер (TIMER_1) используется ядром MULTEX-ARM для обеспечения синхронизации внутренних функций, регистрации таймаутов и организации режима карусельного планирования. Частота системного таймера устанавливается при инициализации ядра равной 1000Гц. Однако она может быть изменена пользовательской задачей на любое целое значение из диапазона 20Гц-1000 Гц. Так как все временные интервалы (таймауты или задержки) задаются в тиках таймера, то при использовании констант они будут изменяться при перестройке частоты таймера. Рекомендуется в значение задержки включать функцию опроса частоты таймера, для получения задержек, независящих от настройки таймера.
Сигналы — это ограниченная форма межзадачного взаимодействия. Сигналом называется асинхронное уведомление, отосланное задаче для того, чтобы сообщить ей о каком-то событии.
В библиотеках ядра MULTEX-ARM имеются средства для поддержки сигналов, совместимых с UNIX-подобными операционными системами. Стандарт Cи определяет всего шесть сигналов, которые могут быть обработаны. Все они описаны в группе Стандартные для Си сигналы файла signal.h.
Кроме того, есть сигналы, которые не могут быть обработаны, пойманы или игнорированы, такие как SIGKILL и SIGSTOP.
Семафоры в MULTEX-ARM – это основной механизм синхронизации задач в реальном времени и организации взаимоисключающего доступа задач к общим ресурсам.
Ядро MULTEX-ARM поддерживает три типа семафоров:
Для семафоров MULTEX-ARM реализован единый универсальный интерфейс управления. Функции управления семафорами могут применяться к семафору любого типа, различаются только функции создания семафоров. Функция создания семафора возвращает идентификатор семафора, который используется для ссылок на этот семафор из других функций. При создании семафора указывается тип очереди, в которую будут выстраиваться задачи, ждущие у этого семафора. Очередь может быть двух типов: в порядке приоритета задач (опция SEM_Q_PRIORITY) или в порядке поступления (SEM_Q_FIFO).
Очереди сообщений – удобный механизм межзадачного взаимодействия. С помощью очередей сообщений одна задача может передавать данные другой, не заботясь о синхронизации моментов выдачи и получения данных. Данные должны быть объединены в общую группу – сообщение. При этом структура сообщения может быть произвольной, достаточно только, чтобы одно сообщение занимало непрерывный участок памяти, имело предсказуемый размер и его структура была известна как передающей, так и принимающей задаче. Очередь автоматически буферизует заданное при ее создании количество сообщений, поэтому, если принимающая задача вовремя не опросит очередь, то сообщение не будет потеряно. Если задача пытается получить данные из пустой очереди, то она может быть задержана до появления в очереди сообщения. Впрочем, задача может ждать сообщение заданное время или вовсе не ждать. Таким образом, очередь сообщений выполняет не только функции транспортировки данных, но и функции синхронизации задач в реальном времени. И помещать сообщения в очередь, и извлекать их из нее может одновременно несколько задач. Внутренние механизмы очередей обеспечивают корректный множественный доступ к ее данным. Для ссылок на очередь из разных задач используется идентификатор очереди MSG_Q_ID, возвращаемый при ее создании. Помещать сообщения в очередь задача может двумя способами:
Таким образом, обеспечивается возможность передачи двух типов сообщений – обычных и внеочередных. Задачи, ожидающие получения сообщений из очереди, могут получать данные в порядке их обращений к очереди, или в соответствии с их приоритетами. Режим задается при создании очереди опциями MSG_Q_FIFO и MSG_Q_PRIORITY соответственно.
Ядро MULTEX-ARM поддерживает драйверы символьных (последовательного доступа) и блочных устройств ввода / вывода. Используя простые соглашения, пользователь может создавать свои драйверы символьных устройств и подключать их в систему самостоятельно. При этом устройство становится доступным как по его дескриптору – небольшому целому числу, так и по символьному имени, задаваемому устройству при регистрации его драйвера в системе. Устройства с дескрипторами 0, 1 и 2 являются стандартными устройствами ввода, вывода и вывода ошибок (STDIN, STDOUT и STDERR). Каждая задача в своем блоке управления TCB имеет соответствующие поля – s_in, s_out и s_err. При создании корневой задачи в эти поля заносятся значения STDIN, STDOUT и STDERR. Функции scanf() , printf() и printerr() осуществляют ввод-вывод на эти устройства. В таблице дескрипторов для стандартных устройств указываются ссылки на их адреса, поэтому возможно перенаправление ввода / вывода как глобально, для всех задач, так и отдельно, для конкретной задачи. В базовой системе ввода / вывода MULTEX-ARM используются две таблицы:
Эти таблицы создаются функцией kernelInit(), причем размеры таблиц задаются следующим образом: максимальное число драйверов в системе — 100, а максимальное число открытых файлов — 256.
В ядре MULTEX-ARM поддерживается работа с блочными устройствами, такими как SD карты, USB флэш накопители, SATA устройства. Эти устройства оперируют данными, как блоками фиксированной длины, отсюда и название. Драйверы таких устройств должны иметь как минимум две обязательные функции:
При создании блочного устройства его драйвер заносит указатели на эти функции в специальную структуру BLK_DEV. Так, например, при создании устройства типа MMC в ядре MULTEX-ARM выполняются следующие действия:
Далее, для того, чтобы получить доступ к файлам, хранящимся на устройстве, требуется создать еще одну специальную структуру — файловую систему. В ядре MULTEX-ARM создается такая структура — файловая система MSDOS FAT12 / FAT16 / FAT32. Файловые системы создаются вызовом процедуры iosDrvInstall() точно так же, как создаются символьные устройства, описанные ранее. Дескриптор файловой системы FAT12 / FAT16 / FAT32 уже создан ядром при запуске и находится в глобальной переменной DosDriver.
Чтобы привязать конкретное блочное устройство к файловой системе в MULTEX-ARM достаточно вызвать функцию монтирования mountVolume().
Ядро MULTEX-ARM включает комплекс процедур, обеспечивающих работу с динамически выделяемыми блоками памяти (Heap-memory). Этот комплекс, называемый диспетчером динамической памяти, позволяет задачам ядра и пользовательским задачам выделять для собственных нужд блоки памяти требуемого размера и освобождать их по мере надобности. Функции диспетчера памяти используются ядром MULTEX-ARM при создании задач, семафоров и очередей.
Особенностью реализации диспетчера памяти в MULTEX-ARM является принцип выделения блоков памяти от старших адресов к младшим. При этом наибольший свободный сегмент памяти всегда оказывается первым в цепочке. Это очень важно для повышения быстродействия системы. Диспетчер обеспечивает защиту от сегментации памяти – при высвобождении блоков производится слияние смежных свободных блоков.
Для обеспечения защиты от множественного доступа к диспетчеру памяти принято наиболее простое и эффективное решение:
Встроенная КЭШ-память процессора обеспечивает существенное повышение быстродействия вычислительной системы. Ядро MULTEX при запуске размещает в памяти таблицу виртуализации памяти и активирует MMU (модуль управления памятью) в соответствии с этой таблицей. При этом для каждой страницы размером в 1М память может быть настроена как некэшируемая, кэшируемая с записью, или кэшируемая с отложенной записью. Для этого в составе BSP для конкретного модуля назначается специальная структура SYS_MEM_MAP, описывающая то, как нужно настраивать память. Так как MULTEX работает с использованием кэш, то в некоторых случаях возможно рассогласование между содержимым памяти и данными в кэш. Это бывает, например, когда какой-либо блок заносит данные в память прямым доступом.
В ядре MULTEX-ARM содержится библиотека стандартной поддержки нелокальных переходов setjmp. Библиотека объявляет тип данных jmp_buf, который является массивом и который может использоваться для сохранения и восстановления контекста выполнения программы. Для установки точки сохранения служит функция setjmp(). Вернуться в точку сохранения можно с помощью функции longjmp(). Для сохранения и восстановления контекста при переключении между задачами служат функции setexit() и exit().
Часто пользователю приходится конфигурировать конкретную систему, целевое ПО, работающее в среде MULTEX-ARM, производя его настройку с помощью каких-либо переменных. Эти переменные определяют общее поведение системы. Их значение не должно теряться при отключении питания, а напротив — при включении питания и запуске системы конфигурационные переменные должны принимать те значения, которые были заданы при конфигурировании. С другой стороны, при проведении программных настроек пользователь должен иметь возможность изменять значения каких-либо конфигурационных переменных таким образом, чтобы после повторных включений системы вновь присвоенные значения не терялись. Для этих целей в MULTEX-ARM разработан единый механизм хранения (и модификации при необходимости) данных в специальных ini–файлах, по аналогии с тем, как это было сделано в ранних версиях ОС Windows. Ini-файл - это обыкновенный текстовый файл, который может быть подготовлен как средствами MULTEX-ARM, так и обыкновенным текстовым редактором, а затем записан на диск, доступный операционной системе. Для работы с такими файлами разработана специальная библиотека, входящая в состав ядра MULTEX-ARM.
Эта библиотека позволяет пользователю отыскивать в ini-файле значения переменных по их именам. При этом значения могут представляться как в виде текстовых строк, так и в десятичном виде, либо в виде булевых значений (типа да-нет). Кроме этого, ряд функций позволяет записывать новые значения в этот файл по имени переменной. После закрытия файла на диске MULTEX-ARM будет создан новый ini-файл со всеми внесенными изменениями и дополнениями. Старый ini-файл будет при этом удален.
В ini-файле переменные сгруппированы в секции. Каждая секция также имеет уникальное имя. Для того, чтобы отыскать, или записать требуемое значение, необходимо задать имя секции и имя переменной. При чтении также задается значение по умолчанию, а при записи — то значение, которое требуется присвоить.
Для работы с портами общего назначения как с простыми линиями ввода/вывода их следует сконфигурировать — настроить мультиплексор используемой линии, подключив её к регистру чтения или записи. Такая настройка выполняется с помощью функций gpio_direction_input() и gpio_direction_output(). После настройки уровень сигнала на линии может быть считан с помощью gpio_get_value() или выставлен с помощью gpio_set_value(). Линии, сконфигурированные как входные, могут быть подтянуты к нулю или плюсу питания с помощью функций gpio_pull_up() и gpio_pull_down(). Выбор конфигурируемой линии осуществляется как сумма имени порта из набора макросов и номера линии.
В следующем примере PE1 настраивается как выход с установкой высокого уровня на линии. Далее уровень изменяется на низкий.
Пример настройки PD5 как линии ввода с подтяжкой к плюсу питания.
Для подключения линий портов ввода/вывода к аппаратным модулям процессора используется функция gpio_set_mux(). Эта же функция может использоваться для настройки линий как входных и выходных, так как предоставляет возможность управления мультиплексором в общем виде. Для настройки мультиплексора каждой линии порта используется 4 бита, а следовательно линия может иметь до 16 вариантов подключения. Возможные значения мультиплексора каждой линии описаны в документации на используемый процессор.
Пример подключения линии PB4 к выходу PWM0 модуля ШИМ. В данном примере используется значение мультиплексора PWM_MUX, определённое как 2.
Поддержка аппаратных модулей ШИМ в MULTEX-ARM реализована в двух режимах — импульсном и непрерывном. Для каждого режима необходимо сначала вызвать функцию инициализации аппаратного модуля для выбранного канала. Далее в непрерывном режиме следует указать коэффициент заполнения ШИМ. Изменяя коэффициент заполнения в непрерывном режиме можно, например, управлять яркостью дисплея. В импульсном режиме для запуска каждого импульса следует вызывать запускающую функцию. Подробное описание функций можно найти в файле pwm.h.
В различных процессорах для выходных линий ШИМ используются разные пины. Конфигурация линий конкретного процессора считывается при старте операционной системы. И далее, при вызове функции инициализации для одного из каналов ШИМ, соответствующий каналу вывод процессора будет настроен должным образом.
Шина SPI - последовательный синхронный интерфейс передачи данных в режиме полного дуплекса, предназначенный для обеспечения простого высокоскоростного сопряжения микроконтроллеров и периферии. SPI также иногда называют четырёхпроводным (four-wire) интерфейсом.
SPI является синхронным интерфейсом, в котором любая передача синхронизирована с общим тактовым сигналом, генерируемым ведущим устройством (процессором). Принимающая (ведомая) периферия синхронизирует получение битовой последовательности с тактовым сигналом. К одному последовательному периферийному интерфейсу ведущего устройства может присоединяться несколько ведомых. Ведущее устройство выбирает ведомое для передачи, активируя сигнал CS (Chip Select) на ведомой микросхеме. Периферия, не выбранная процессором, не принимает участия в передаче по SPI. В процессорах Allwinner может содержаться до четырёх аппаратных интерфейса SPI и для каждого из них предусмотрено до четырёх сигналов CS. Число линий CS может быть увеличено путём программного использования линий ввода/вывода общего назначения GPIO.
Интерфейс SPI использует следующие сигналы:
В MULTEX-ARM реализован драйвер, позволяющий работать с несколькими интерфейсами SPI в режиме ведущего устройства (MASTER). Для подключения одного из имеющихся интерфейсов SPI в MULTEX-ARM нужно инициализировать нужный модуль с помощью spiInit() и затем вызывать функцию spiTransfer() для запуска цикла обмена данными с выбранным устройством. Для подключения аппаратных линий CS следует использовать функцию инициализации spiInitChipSelectLine() для каждой используемой линии. При подключении выбранного аппаратного модуля SPI будут использованы настройки интерфейса по умолчанию. Для изменения настроек следует воспользоваться функциями из соответствующей группы. Изменять данные настройки можно перед каждым циклом обмена данными с устройством. Это может понадобится, если к одному интерфейсу подключены устройства с разными параметрами передачи данных.
Ниже приведён пример инициализации интерфейса SPI0 и получение данных от подключенного устройства. При инициализации выбирается основной набор линий ввода/вывода и подключается линия выбора ведомого устройства CS0. Тактовая частота CLK устанавливается равной 4 МГц. Остальные настройки используются по умолчанию. Далее выполняется чтение из устройства по адресу 0x10 (это частный случай определённый протоколом обмена конкретного устройства). Для этого в первый байт массива данных кладётся нужный адрес и задаётся длина значащих передаваемых байт равная единице. Остальные передаваемые данные будут иметь нулевое значение. Такое поведение задаётся аппаратным модулем SPI. Общее количество обменов задаётся равным трём. После получения первого байта ведомое устройство начнёт передачу данных, таким образом прочитанное значение после окончания обмена будет лежать в последних двух байтах массива.
Интерфейс I2C — это широко распространенный последовательный синхронный полудуплексный двухпроводный внутрисхемный интерфейс, используемый для управления многими устройствами, например, CMOS видеокамерами, или часами реального времени RTC.
В составе встроенных аппаратных модулей ARM процессоров Allwinner может содержаться до пяти контроллеров последовательных шин I2C. В ОС MULTEX-ARM для подключения драйвера одного из аппаратных интерфейсов следует вызвать функцию инициализации i2cInit() с указанием тактовой частоты сигнала CLK. Как правило, устройства на шине I2C работают на частотах 100 или 400 кГц и в этих случах для выбора частоты шины можно воспользоваться значениями из группы макросов. На самом деле при инициализации можно использовать и другие частоты работы шины. В этом случае частоту следует указать в виде числа в герцах. Далее можно читать и писать в подключенные по данному интерфейсу устройства по адресу с помощью функций i2cRead() и i2cWrite(). Адрес устройства должен быть указан в документации на само устройство.
Ниже приведён пример инициализации драйвера интерфейса I2C0 и запись массива данных в устройство по адресу 0x10.
Опорная частота 24 МГц, получаемая с помощью внутренней схемы генерации и стабилизируемая с помощью внешнего кварцевого резонатора, повышается внутри процессора и далее распределяется по внутренним шинам и аппаратным модулям. Как именно происходит распределение частот зависит от конфигурации конкретного процессора. Управление умножением и делением частот и распределение их между шинами и модулями осуществляется с помощью регистров конфигурации PLL.
Функции по настройке PLL регистров полностью берёт на себя операционная система. Однако при проектировании новых драйверов устройств, а так же для проверки правильной работы уже подключенных аппаратных модулей имеет смысл воспользоваться функциями, описанными в файле pll.h. Для вывода в консоль таблицы используемых тактовых частот можно воспользоваться функцией pll(). Для получения значения частоты конкретного аппаратного модуля или шины в программе можно воспользоваться функциями pllGet(), pllAhbGet() и им подобными. Следует учесть, что драйверы устройств могут изменять частоты общих шин, поэтому для получения актуальных значений следует перед использованием get-функций вызвать функцию сбора данных pllUpdate().
В состав MULTEX-ARM включен драйвер, обеспечивающий работу с последовательными портами UART в режиме потоковой записи / чтения. После настройки порт следует открыть с помощью функции open(), которая вернет дескриптор выбранного устройства. Используя этот дескриптор, записывать и читать данные можно стандартными средствами ОС – функциями read() и write().
Ниже приведён пример использования драйвера для приёма и пересылки обратно принятых данных. В начале инициализируется выбранный для передачи аппаратный модуль UART2. При инициализации он получает стандартные настройки, некоторые из которых бывает необходимо изменить. В примере изменяется скорость передачи данных на 38400 бит в секунду. Так же имеет смысл изменить таймаут на приём данных, чтобы задача периодически получала управление при отсутствии данных. Таймаут на добавление данных в очередь отправки можно сделать нулевым (NO_WAIT), так как переполнений выходного буфера не предвидится. Размер буферов приёма и отправки можно было бы оставить без изменений, так как при создании каждому из них выделяется по 4096 байт, но на деле достаточно выделить под буферы память просто большую, чем ожидаемый размер пакетов. В примере буферы получают размер вдвое больший, чем нужно для приёма и передачи данных.
После инициализации аппаратного модуля происходит открытие устройства с помощью open(). В качестве имени устройства следует использовать функцию получения имени uartName(). После успешного открытия порта данные из порта можно читать порциями с помощью read() с указанием полученного дескриптора. Если данных достаточно они будут считаны сразу. Если данных не достаточно функция будет ждать накопления данных до истечения указанного при инициализации таймаута. По истечении заданного времени функция вернёт количество прочитанных байт. Если же указан таймаут WAIT_FOREVER – функция может не возвращать управление задаче разбора пакетов до накопления нужных данных. Отправку данных следует производить с помощью функции write() с указанием полученного при открытии дескриптора.