Многозадачное ядро реального времени RTKernel

RTKernel представляет из себя библиотеку для разработки 16-разрядных приложений, которая может быть слинкована с программой. Она содержит множество функций по управлению задачами, семафорами, почтовыми ящиками, прерываниями и т.д. Все задачи нормально работают внутри одной программы. RTK приложение состоит из одного файла, включающего ядро, требуемые драйвера и все задачи. Данная программа может выполняться на любой вычислительной системе, содержащей операционную систему MS-DOS.


Основные характеристики

Количество задач, выполняемых под управлением RTKernel, ограничивается общим объемом оперативной памяти. Для каждой задачи RTKernel дополниетльно требуется около 1 кбайт памяти.
Время переключения задачи не зависит от количества задач и составляет около 20 мкс (80386-25).
Количество приоритетов задач - 64.
Виды планирования: коллективное (Cooperative), с вытеснением (Pre-emptive), с выделением квантов времени (Time-Slicing).
Переключение задач по событию или прерыванию.
Возможность активизации задачи при возникновении аппаратного прерывания.
Возможность изменения временного интервала генерации программного прерывания по таймеру от 0,1 до 55,0 мс.
Возможность измерения временных интервалов с разрешением 1 мкс.
Поддержка арифметического сопроцессора и его программной эмуляции.
Семафоры: двоичные, счетные, ресурсов.
Обмен данными между задачами с использованием буферов сообщений (иначе mailbox - “почтовый ящик”).
Непосредственный обмен данными между задачами с использованием механизма передачи сообщений (message-passing, или обмен через буфер нулевой длины).
Коммуникационный драйвер обслуживания до 36 последовательных портов с использованием прерываний.
Поддержка аппаратного буфера универсальных асинхронных приемо-передатчиков (УАПП) семейства 16С550.
Драйверы для работы с таймером, видеоподсистемой, клавиатурой, принтером и локальными вычислительными сетями (ЛВС) Novell с протоколом IPX.
Использование простоев клавиатуры и дисковых накопителей для предоставления процессора другим задачам.
Отсутствие проблем повторной входимости, свойственных MS-DOS.
Возможность создания приложений RTKernel в виде резидентных программ.
Возможность запуска других DOS- программ, в том числе Widows 3.0/3.1, из приложения RTKernel. 
Возможность компиляции с добавлением отладочной информации в формате Turbo Debugger или Code View для удобства отладки.
Возможность создания приложений, загружаемых из ПЗУ.
Возможность поставки версии с полным комплектом исходных текстов ядра.
Отсутствие лицензионных отчислений при распространении разработанных программ любыми тиражами.


Многозадачность, реальное время и RTK

В данном разделе изложено описание основных терминов и концепций RTK.

Многозадачность

Задача это отдельный процесс. Отдельные выражения задачи обрабатываются последовательно в соответствии с семантикой языка C.

Термин многозадачность означает, что несколько задач обрабатываются параллельно. Однако, на однопроцессорных системах, разные задачи не могут выполняться одновременно. Поэтому должны выполняться переключения задач. Осуществление таких переключений является функцией ядра многозадачной системы.

Во многих случаях задачи не могут выполняться корректно независимо друг от друга. Например, может быть, что одна задача не должна выполняться до тех пор пока другая не закончит определенное действие. В этих случаях выполнение задач должно быть синхронизировано. Для синхронизации используется межзадачный обмен данными.

Кооперативная и вытесняющая многозадачность

Вытесняющая многозадачность означает, что переключение задач может быть инициировано непосредственно из обработчиков прерываний. При кооперативной многозадачности переключения задач могут быть осуществлены только при непосредственных обращениях к ядру. RTK поддерживает оба способа планирования переключения задач. По умолчанию используется кооперативная многозадачность.

Реальное время

Термин “реальное время” часто используется, но редко определяется. Одно из возможных определений есть: Программа реального времени может работать синхронно с последовательностью событий, внешних для процессора. При этом максимальное время реакции на внешнее событие может быть определено заранее.


Планировщик задач

Планировщик задач определяет какая задача должна выполнятся. В этом процессе учитываются только состояния задач и их приоритеты. Планировщик задач RTK изначально не основан на таймером прерывании. RTK является системой управляемой событиями. Событием является межзадачный обмен который может быть инициирован задачей или обработчиком прерываний. События могут приводить к смене состояний задач. Таймерное прерывание является одним из обработчиков прерываний. Его задачей является перевести задачи, ожидающие некоторой временной точки, в состояние “Ready”. Некоторые многозадачные программы могут работать вообще без обработчика таймерного прерывания.

Планировщик следует следующим правилам:

1. Из всех задач, имеющих состояние “Ready”, выполняется задача с наивысшим приоритетом
2. Если планировщик должен выбирать между несколькими задачами в состоянии “Ready” имеющими одинаковый приоритет, активизируется задача которая не получала управление процессором в течении самого продолжительного времени
3. Если несколько задач с разными приоритетами ожидают одного события, при возникновении этого события они активизируются в последовательности определяемой их приоритетами.
4. За исключением случаев разделения времени переключение задач осуществляется только в случае нарушения правила 1.

Обычно, правила, приведенные выше, выполняются точно, т.е. они никогда не нарушаются. Всякий раз при изменении состояния задач планировщик проверяет необходимость переключения задач для выполнения правил планирования. Только при кооперативной многозадачности правило 1 может быть нарушено на время между возникновением прерывания и следующим обращением к ядру.

Переключение задач при разделении времени выполняется при выполнении следующий условий:

1. Разделение времени должно быть разрешено (по умолчанию - запрещено)
2. Как минимум одна задача с приоритетом равным приоритету текущей должна иметь состояние “Ready”
3. С момента предыдущего переключения должно пройти как минимум TimeSlice тиков таймера. Предыдущее переключение может быть вызвано также событием отличным от завершения временного кванта.

Значение TimeSlice устанавливается функцией RTKTimeSlice. Если оно равно 1 условие 3 выполняется всегда, и переключение задач выполняется при каждом прерывании от таймера.
Разделение времени играет наименьшую роль в планировании и не может приводить к нарушению правил приведенных выше.


Переключение задач

RTK осуществляет три типа переключения задач: блокировки, активизации и разделения времени. Задача всегда продолжает выполняться с того места на котором она была прервана переключением задач. Три типа переключения задач выполняются при выполнении следующих условий:

Переключение блокировки

Переключение блокировки выполняется, когда задача блокируется, т.е. не может выполняться далее. Это может происходить, например, когда задача пытается получить данные из пустого почтового ящика или прерывает свое выполнение на определенное время вызовом функции RTKDelay. В этом случае для дальнейшего выполнения выбирается задача имеющая состояние “Ready” с тем же или меньшим приоритетом. Если готовых к выполнению задач нет, активизируется “Нулевая” задача.

Переключение активизации

Переключение активизации выполняется, когда задача с приоритетом большим чем у текущей переходит в состояние “Ready”. Переключение активизации выполняется, например, при записи данных в почтовый ящик, если задача с высоким приоритетом ожидает данных из этого почтового ящика. Блокированная ранее задача может теперь выполняться далее, и она немедленно активизируется. Переключения активизации могут быть временно запрещены вызовом SchedulerOFF.

Переключение разделения времени

Переключение разделения времени активизируется обработчиком таймерного прерывания когда выполняются приведенные ранее условия. Переключение разделения времени может быть инициировано непосредственно вызовом RTKDelay(0).

В дополнение к упомянутому выше различают кооперативные и вытесняющие переключения. Вытесняющие переключения инициируются обработчиками прерываний, кооперативные - задачами. Переключения блокировки всегда кооперативные, т.е. они не могут инициироваться обработчиками прерываний. Вытесняющие переключения блокировки рассматриваются ядром как ошибки. Переключения активизации могут выполнятся в обоих случаях с использованием одинаковых механизмов. Например, обработчик прерываний или задача, помещая данные в почтовый ящик, могут вызвать переключение активизации. В первом случае это будет вытесняющее переключение, во втором - кооперативное.


Задачи

Задача это C-функция без параметров со своим собственным стеком. Задача имеет приоритет между 1 и 63. Большему значению соответствует больший приоритет. Внутри программы имеет значение только соотношение приоритетов, т.е. поведение программы с двумя задачами будет одинаковым если их приоритеты равны 1 и 2 или 50 и 60. Для ссылок на задачи используются заголовки задач. Ядро формирует уникальный заголовок для каждой создаваемой задачи, который может быть использован в дальнейшем для адресации задачи (например при передаче сообщений).

Все локальные переменные задачи размещаются в ее стеке (за исключением статических). Это справедливо также для всех функций, вызываемых задачей. Поэтому несколько задач может быть запущено с использованием одного кода задачи, и каждая из них получит свой собственный стек и свои локальные переменные. Функция может быть вызвана несколькими задачами. Это приведет к повторному исполнению кода, но проблем реентерабельности не возникает, т.к. каждая задача имеет свой стек.

Правила видимости принятые в C выполняются в многозадачных программах. Все задачи имеют доступ к глобальным переменным. Во время инициализации ядра создаются две задачи: Нулевая (Idle Task) и Главная (Main Task). Нулевая задача имеет приоритет 0.

Поскольку пользовательские задачи имеют приоритет от 1 до 63, Нулевая задача имеет наименьший приоритет в программе. Она выполняется в тех случаях когда никакая другая задача не может выполняться. Нулевая задача необходима планировщику, т.к. он требует как минимум одну задачу в состоянии “Ready” в каждый момент времени.

Приоритет Главной задачи определяется при вызове RTKernelInit. Стек Главной задачи не модифицируется ядром. В отладочной версии кроме того создается задача монитора (Monitor), позволяющая во взаимодействии с программой WMONI, работающей на персональном компьютере, получить информацию о выполнении программы. Эта задача создается с максимальным приоритетом.

В стандартной версии для активизации этой задачи в программу необходимо включить следующий код:

RTKCOMInit();
RTKCreateTask(MonitorTaskCode, Prio, 256, "Monitor");
Где Prio - требуемый приоритет задачи. После создания задача монитора монополизирует работу с входной очередью последовательного порта канала A.

Любая задача всегда имеет одно из следующий состояний:
Current Это состояние имеет текущая исполняемая задача. Только одна задача может иметь это состояние в каждый момент времени (это может быть Нулевая задача).
Ready Это состояние имеют все задачи готовые к исполнению. Все готовые задачи имеют приоритет не выше, чем у текущей.
Suspended Отложенные задачи не могут исполняться т.к. они были остановлены вызовом функции RTKSuspend. Они могут быть переведены в состояние готовности вызовом RTKResume.
Blocked Блокированные задачи не могут исполняться т.к. они ожидают какого-либо события. Эти задачи могут быть переведены в состояние готовности другой задачей или обработчиком прерывания.
Delaying Эти задачи блокированы на определенный интервал времени. Они переводятся в состояние готовности обработчиком таймерного прерывания по истечению задержки.
Timed Это состояние имеют задачи ожидающие события с контролем времени. Они переводятся в состояние готовности либо событием либо по истечению времени ожидания. Все задачи не исполняемые в данный момент помещаются ядром в различные очереди. Это, например, очередь готовых задач. Другая очередь содержит задачи, ожидающие некоторых временных точек. Очереди также строятся для семафоров и почтовых ящиков и содержат задачи, блокированные ими.


Межзадачный обмен

Термин межзадачный обмен включает все механизмы обмена информацией между задачами. RTK поддерживает три механизма обмена данными: семафоры, почтовые ящики и передача сообщений.

Семафоры позволяют обмениваться сигналами между задачами для активизации или прерывания задач. Сигналы могут быть сохранены в или считаны из семафора. Переключение задач может иметь место, когда происходит обращение к семафору не содержащему сигналов.

Почтовые ящики расширяют концепцию семафоров. Кроме сигналов данные любых типов также могут быть сохранены в и прочитаны из почтового ящика. Переключение задач происходит когда доступ осуществляется к пустому или полному почтовому ящику. Максимальное число записей сохраняемое в ящике может настраиваться. Почтовые ящики полностью подходят для обмена данными между задачами.

Сообщения используются для прямого обмена данными между задачами. При этом никакой буферизации данных или сигналов не происходит. Это обеспечивает быстрый обмен данными, если выполнение задач синхронизировано с обменом данными.


Рентерабельность

Термин реентерабельность обозначает круг проблем, которые возникают при одновременном выполнении несколькими задачами одного участка кода или осуществлении доступа к одним данным. Реентерабельность кода означает что задача может начать выполнять участок кода раньше, чем другая закончит его выполнять. В многозадачном окружении необходимо позаботиться о том, чтобы как можно большая часть кода была реентерабельной.

Проблемы, возникающие при использовании глобальных переменных, могут быть проиллюстрированы маленьким примером. В нем подразумевается, что две задачи должны считать некоторые события. Программа содержит глобальную переменную Counter типа int инициализированную нулем. Обе задачи содержат выражение:

Counter = Counter + 1;

Это выражение может транслироваться компилятором в:

MOV AX, Counter
ADD AX, 1
MOV Counter, AX

Далее предположим, что обе задачи имеют одинаковый приоритет, вытеснение разделение времени разрешены. Тогда может произойти следующее: Задача 1 улавливает событие и начинает выполнение кода. После выполнения строки 1 (AX содержит 0) кончается временной квант и управление передается задаче 2. Задача 2 также улавливает событие и увеличивает значение Counter до 1. Несколько позже задача 1 возобновляется (со строки 2), увеличивает значение AX с 0 до 1 и сохраняет его в Counter. Таким образом место двух увеличений счетчика мы имеем одно.

Этот пример показывает, что две или более задач не должны осуществлять доступ к глобальным переменным одновременно если одна из них модифицирует эти данные. Вообще, всегда, когда выражения типа приведенного выше включаются в обе задачи, проблем реентерабельности не избежать. Следовательно, разделения глобальных данных следует избегать. Это же относится и к статическим объектам. Если избежать этого невозможно, доступ к глобальным данным должен быть защищен семафорами.

Также, некоторые участки кода, которые не контролируются программистом, например стандартные библиотеки, должны быть защищены.


Ядро

Ядро содержит все константы, определения типов и все функции, необходимые для многозадачности. Многозадачные программы должны включать заголовочный файл RTKERNEL.H. Он содержит все декларации, обсуждаемые в данной главе.

Все глобальные объекты ядра начинаются с символов “RTK”.

Инициализация ядра

Прежде чем вызовы ядра могут быть использованы, оно должно быть проинициализировано функцией RTKernelInit. Если это не оговорено особо, все операции с ядром могут выполняться только после вызова RTKernelInit.

Функция RTKernelInit

Функция RTKernelInit инициализирует ядро:

TaskHandle RTKernelInit(unsigned MainPrio);

Параметр MainPrio определяет приоритет Главной задачи. Возвращается заголовок Главной задачи. При инициализации выполняются следующие действия:

1. Инициализируются внутренние данные.
2. Вектора прерываний, которые будут модифицированы сохраняются.
3. Главная программа преобразуется в Главную задачу.
4. Создается Нулевая задача.
5. Внутренние часы инициализируются нулем.
6. Вектора прерываний, обрабатываемых ядром, устанавливаются на соответствующие процедуры.
7. Обработчики прерываний нижнего уровня устанавливаются для всех 9 источников прерывания (IRQ). Существующие обработчики преобразуются в обработчики верхнего уровня .
8. Функция завершения устанавливается с использованием функции atexit.

До вызова RTKernelInit могут быть вызваны только следующие функции ядра: RTKDebugVersion, RTKCreateSemaphore, RTKCreateMailbox и RTKSetMessageHandler.

Функция выхода

Функция выхода ядра вызывается при выполнении функции exit или при возврате из функции main.

При выполнении функции выхода останавливаются все задачи, кроме текущей и Нулевой. В конце выполнения функции восстанавливаются все вектора прерываний, модифицированные ядром.

Управление задачами

В этом разделе описаны функции создания, завершения и управления задачами.

Функция RTKCreateTask

Любая задача программы может создать другую задачу используя функцию:

TaskHandle RTKCreateTask(void (*TaskCode) (void),
unsigned Priority,
unsigned Stack,
char *Name);

TaskCode это функция C без параметров, содержащая код новой задачи. Произвольное число задач может быть создано с использованием одного кода. Все эти задачи будут исполнять один код, но иметь разные локальные данные. Следует помнить, что методы классов не могут быть использованы в качестве задач.

Priority определяет базовый приоритет задачи как целое от MIN_PRIO (1) до MAX_PRIO (64). Привилегии задачи тем выше, чем больше приоритет. Если приоритет создаваемой задачи больше приоритета текущей, она немедленно активизируется.

Ядро различает базовый приоритет и приоритет выполнения. Для целей планирования задач используется приоритет выполнения. Приоритет выполнения есть максимальное значение из базового приоритета задачи и приоритетов всех ресурсов захваченных задачей. Приоритет ресурса определяется максимальным из приоритетов задач, ожидающих доступа к этому ресурсу. (Более детальное описание ресурсов см. в главе Семафоры).

Stack определяет размер стека в байтах, требуемый для задачи. Ядро выделяет требуемый блок из кучи и использует его в качестве стека для новой задачи. Механизм контроля стека будет работать для моделей памяти с дальними данными.

Реальный размер стека, выделяемого задаче есть сумма параметра Stack, 256 байт для обслуживания прерываний и 27 байт для внутреннего использования ядром.

Name указывает на имя задачи. Имя задачи используется только для более легкой идентификации задач и не может быть длиннее 15 символов. Ядро сохраняет только указатель на имя, и, поэтому, имя не должно модифицироваться после создания задачи.

http://www.kirsoft.com.ru/www_discret/ds1001-rtkernel.htm

Категории: