Rationale for Ada 2005: Tasking and Real-Time
RUSTOPBACKNEXT
ENG |
6. CPU clocks and timers
@ Ada 2005 introduces three different kinds of timers. Two are concerned with monitoring the CPU time of tasks – one applies to a single task and the other to groups of tasks. The third timer measures real time rather than execution time and can be used to trigger events at specific real times. We will look first at the CPU timers because that introduces more new concepts. @ The execution time of one or more tasks can be monitored and controlled by the new package Ada.Execution_Time plus two child packages. @ Ada.Execution_Time – this is the root package and enables the monitoring of execution time of individual tasks. @ Ada.Execution_Time.Timers – this provides facilities for defining and enabling timers and for establishing a handler which is called by the run time system when the execution time of the task reaches a given value. @ Ada.Execution_Time.Group_Budgets – this enables several tasks to share a budget and provides means whereby action can be taken when the budget expires. @ The execution time of a task, or CPU time as it is commonly called, is the time spent by the system executing the task and services on its behalf. CPU times are represented by the private type CPU_Time. This type and various subprograms are declared in the root package Ada.Execution_Time whose specification is as follows (as before we have added some use clauses in order to ease the presentation)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale for Ada 2005: Tasking and Real-Time
@ENGRUSTOPBACKNEXT6. Процессорное время и таймеры
@ Ада 2005 вводит три различных вида таймеров. Два из них связаны с контролем процессорного времени задач - первый из которых применяется к единственной задаче, другой к группам задач. Третий таймер измеряет реальное время, а не время выполнения и может использоваться, чтобы вызвать события в определенный момент реального времени. Мы сначала рассмотрим таймеры центрального процессора, потому что там вводится много новых понятий.
@ Время выполнения одной или более задач может отслеживаться и управляться новым пакетом Ada.Execution_Time вместе с двумя своими дочерними пакетами.
@ Дочерний пакет Ада. Execution_Time - это корневой пакет осуществляющий контроль времени выполнения индивидуальных задач.
@ Дочерний пакет Ада.Execution_Time.Timers обеспечивает средства для определения параметров и запуска таймеров и обработчиков события когда время выполнения задачи достигает данного значения.
@ Пакет Ada.Execution_Time.Group_Budgets позволяет нескольким задачам совместно использовать бюджет и определяет действие которое может быть предпринято, когда бюджет истекает.
@ Время выполнения задачи, или процессорное время, как его обычно называют, является время затраченное системой для выполнения задачи и службы от своего лица. Процессорные времена представлены приватным типом CPU_Time. Этот тип и различные подпрограммы объявлены в корневом пакете Ada.Execution_Time, спецификация которого следующая (как и прежде, мы добавили некоторые выражения использования, чтобы повысить читабельгость):
|
@ Процессорное время специфической задачи может быть получено вызовом функции Clock с задачей в качестве параметра. Оно установлено в нуль при создании задачи.
@ Константы CPU_Time_First и CPU_Time_Last дают диапазон значений CPU_Time.
@ CPU_Tick дает средний интервал во время которого последовательные вызовы Clock дают одно и то же значение, и таким образом, это точность с которой CPU_Time_Unit дает еденицы времени, измеренного в секундах. Мы уверены, что CPU_Tick не больше чем одна миллисекунда и что диапазон значений CPU_Time составляет по крайней мере 50 лет (определяется реализацией системы).
@ Различные подпрограммы выполняют очевидные операции с типом CPU_Time и типом Time_Span пакета Ada.Real_Time.
@ Значение типа CPU_Time может быть преобразовано к Seconds_Count плюс остаточный Time_Span функцией Split, которая подобна аналогичной из пакета Ada.Real_Time. Функция Time_Of так же работает в противоположном направлении. Отметим значение по умолчанию Time_Span_Zero для второго параметра - это позволяет временам точных чисел секунд быть данными более удобно таким образом:
|
@ Чтобы узнать, когда задача достигает специфического процессорного времени, мы можем использовать средства дочернего пакета Ada.Execution_Time.Timers спецификация которого:
|
@ Основная идея состои в том, что мы объявляем объект типа Timer дискриминант которого идентифицирует задачу, которая будет отслеживаться (отметим использование конструкций not null и constant в дискриминанте). Мы также объявляем защищенную процедуру, которая берет таймер в качестве параметра и которая будет запущена, когда CPU_Time задачи иссякнет. Таким образом, чтобы предпринять некоторое действие (возможно abort например, хотя это было бы безжалостно), когда CPU_Time задачи My_Task достигает 2.5 секунд, мы могли бы сначала объявить:
|
@ и тогда:
|
@ Наконец мы приводим таймер в движение вызывая процедуру Set_Handler, которая берет таймер, значение времени и ссылку на защищенную процедуру таким образом:
|
@ и затем когда процессорное время задачи достигает Time_Max выполняется защищенная процедура Control.Alarm. Отметьте, как объект таймера включает информацию относительно соответствующей задачи используя ссылочный дискриминант T и что его передают в обработчик через его параметр ТМ.
@ Прерывание задачи возможно немного сильно. Другая возможность состоит в том, чтобы просто уменьшить свой приоритет так, чтобы задача больше не выполнялась, таким образом:
|
@ Другая версия Set_Handler позволяет таймеру быть установленным для данного интервала (типа Time_Span).
@ Обработчик, связанный с таймером, может быть получен вызовом функции Current_Handler. Она возвращает пустой указатель, если таймер не установлен, тогда мы говорим, что таймер чист.
@ Когда таймер истекает, и прежде, чем вызвать защищенную процедуру, таймер устанавливается в состояние "чист". Одно возможное действие обработчика при истечении таймера это установить обработчик снова или возможно другой обработчик. Таким образом, мы могли бы иметь:
|
@ В этом сценарии мы обращаем внимание на факт, что задача завершена и затем даём еще 500 миллисекунд, но уже с обработчиком Control.Kill который окончательно прекратит выполнение задачи.
@ Установка значения 500 миллисекунд непосредственно в вызове является немного грубоватым. Было бы лучше параметризовать защищенный тип таким образом:
|
@ и затем вызов Set_Handler в защищенной процедуре Alarm был бы:
|
@ Заметим, что разрешающая способность перегрузки аккуратно различает, вызываем ли мы Set_Handler с абсолютным временем или относительным временем.
@ Процедура Cancel_Handler может использоваться, чтобы очистить таймер. Функция выдаёт True если таймер был фактически установлен и False, если он находится в очищеном состоянии. Функция Time_Remaining возвращает Time_Span_Zero, если таймер не установлен и иначе возвращает оставшееся время.
@ Отметим также констату Min_Handler_Ceiling. Это - минимальный приоритет потолка который защищённая процедура гарантирует, что потолок нарушения не может произойти.
@ Это средство таймера может быть осуществлено под управлением системы POSIX. Может быть предел количества таймеров, которые могут быть поддержаны, и попытка превысить этот предел вызывает исключение Timer_Resource_Error.
@ В заключении суммируем общие принципы. Таймер может быть установлен или быть очищенным. Если он установлено тогда, у этого есть связанный (ненулевой) обработчик, который будет вызван после подходящего времени. Ключевые подпрограммы - Set_Handler, Cancel_Handler и Current_Handler. У защищенной процедуры есть параметр, который идентифицирует событие при котором она вызвана. Та же самая защищенная процедура может быть обработчиком для многих событий. Та же самая общая структура относится к другим видам таймеров, которые будут здесь описаны.
@ Чтобы программировать различные так называемые апериодические серверы, это необходимо для задач совместно использующих бюджет центрального процессора.
@ Это может быть сделано, используя дочерний пакет Ada.Execution_Time.Group_Budgets, спецификация которого:
|
@ Здесь имеется много общего с братским пакетом Timers, но есть много важных различий.
@ Первое различие состоит в том, что мы здесь считаем бюджет центрального процессора разделенным среди нескольких задач. Тип Group_Budget идентифицирует группу задач и размер бюджета.
@ Различные подпрограммы позволяют управлять задачами в группе. Процедуры Add_Task и Remove_Task добавляют или удаляют задачу. Функция Is_Member идентифицирует, принадлежит ли задача определенной группе, тогда как Is_A_Group_Member идентифицирует, принадлежит ли задача любой группе. Задача не может быть членом больше чем одной группы. Попытка добавить задачу к больше чем одной группе или удалить ее из неправильной группы и так далее вызывает исключение Group_Budget_Error. Наконец, функция Members возвращает всех членов группы как массив.
@ Значение бюджета (первоначально Time_Span_Zero) может быть загружено процедурой Replenish и увеличено процедурой Add. Всякий раз, когда бюджет не является нулевым, он считается в обратном порядке, поскольку задачи в группе выполняются и так бы употребляют процессорное время. Всякий раз, когда бюджет уменьшается до значения Time_Span_Zero, и как говорят, становится исчерпанным и не может быть уменьшен далее. Отметим, что Add с отрицательным параметром, может уменьшить бюджет так, что может даже заставить его стать исчерпанным, но не сделать его негативным.
@ Функция Budget_Remaining просто возвращает оставшееся количество, и Budget_Has_Expired возвращает True, если бюджет исчерпан и значение Time_Span_Zero - также.
@ Всякий раз, когда бюджет становится опустошенным (это когда значение близко к нулю) вызывается обработчик, если он был установлен. Обработчик - защищенная процедура как и прежде, и процедуры Set_Handler, Cancel_Handler, и функция Current_Handler такие же как и раньше. Но главное различие в том, что Set_Handler не устанавливает значение времени бюджета, так как это делается посредством Replenish и Add. Урегулирование бюджета и урегулирование обработчика расцеплены в этом пакете. Действительно, обработчик может быть установлен даже при том, что бюджет исчерпан, и бюджет может считать в обратном порядке даже при том, что никакой обработчик не установлен. Причина для различного подхода просто отражает парадигму использования этой особенности.
@ Таким образом, мы можем установить механизм для контроля использования процессорного времени группой из трех задач TA, TB, и TC первым объявлением объекта типа Group_Budget, добавлением этих трех задач к группе и затем установкой соответствующего обработчика. Наконец, мы вызываем Replenish, который запускает механизм подсчета:
|
@ Напомним, что функции Seconds и Minutes были добавлены к пакету Ada.Real_Time.
@ Защищенная процедура могла бы быть:
|
@ Процедура Monitor регистрирует факт, что бюджет был исчерпан и затем добавляет дополнительные 10 секунд в бюджет. Напомним, что обработчик остается установленным все время в случае группового бюджета, тогда как в случае единственной задачи таймер автоматически становится чистым и должен быть установлен снова если это необходимо.
@ Если задача заканчивается тогда она удаляется из группы как часть процесса завершения.
@ Отметим, что тут также имеется константа Min_Handler_Ceiling.
@ Последний вид таймеров касающегося реального а не процессорного времени предоставлен дочерним пакетом Ada.Real_Time, в то время как другие таймеры пока были предоставлены дочерними пакетами пакета Ada.Execution_Time. Спецификация пакета Ada.Real_Time.Timing_Events:
|
@ Этот пакет обеспечивает средство очень низкого уровня и не вовлекает задачи Ады вообще. У него есть очень похожий шаблон к пакету Execution_Time.Timers. Обработчик может быть установлен процедурой Set_Handler которая имеет две версии: первая, в относительном времени и другая, в абсолютном времени. Есть также подпрограммы Current_Handler и Cancel_Handler. Если никакой обработчик не установлен тогда Current_Handler возвращает пустой указатель.
@ Set_Handler также определяет защищенную процедуру, которая будет вызвана когда время будет исчерпано. Таймеры определяются при помощи типа Real_Time, а не CPU_Time.
@ Незначительное различие состоит в том, что у этого пакета есть функция Time_Of_Event, а не Time_Remaining.
@ Простой пример был описан в вводной статье. Мы повторяем это здесь для удобства. Идея состоит в том, что мы желаем зазвонить в pinger когда наше яйцо будет варится больше четырех минут. Защищенная процедура могла бы выглядеть так:
|
@ и тогда:
|
@ Это ненадежно, потому что, если мы будем прерваны между вызовами Put_Egg_In_Water и Set_Handler тогда яйцо будет вариться слишком долго. Мы можем исправить дело добавив дальнейшую процедуру к защищенному объекту так, чтобы это стало:
|
@ Это уже гораздо лучше. Механизм выбора времени теперь полностью формируется в защищенном объекте и процедура Is_Done больше не видима снаружи. Таким образом, все что мы должны сделать:
|
@ Конечно, если звонок pinger закончится раньше чем у нас будет возможность съесть яйцо тогда, это все еще становится преувеличенным. Одно из решений состоит в том, чтобы съесть яйцо в пределах защищенной процедуры Is_Done. Действительно, джентльмен никогда не позволял бы телефонному звонку нарушать его завтрак.
@ Одна защищенная процедура может использоваться, чтобы ответить на несколько событий. В случае таймера центрального процессора дискриминант параметра идентифицирует задачу; в случае группы и таймеров в реальном времени, параметр идентифицирует событие.
@ Если мы хотим использовать тот же самый таймер для нескольких событий тогда, возможны различные методики. Отметим, что таймеры ограничены так, мы не можем проверить их непосредственно. Однако, т.к. они являются тегового типа то они могут быть быть соответственно расширены. Кроме того, мы знаем, что они передаются по ссылке и что параметры считаются aliased.
@ Предположим, что мы кипятим шесть яиц в одной из тех французских вещей для завтрака с разноцветным держателем для каждого яйца. Мы можем написать:
|
@ Мы можем тогда установить обработчик для яйца в красном держателе:
|
@ и затем защищенная процедура могла бы быть:
|
@ Хотя это и работает, но это не совсем красивое решение, т.к. приходится сравнивать значения ссылок и, кроме того, это требует, чтобы цикл видел, какой случай произошел.
@ Намного лучший подход должен использовать расширение типа и преобразования представления. Сначала мы расширяем тип Timing_Event, чтобы включать дополнительную информацию о событии (в этом случае цвет) так, чтобы мы могли идентифицировать специфический случай изнутри обработчика:
|
@ Мы тогда объявляем массив этих расширенных событий (они не должны быть aliased):
|
@ Мы можем теперь вызвать процедуру Set_Handler для яйца в красном держателе:
|
@ Это - фактически вызов Set_Handler для типа Egg_Event унаследованный от Timing_Event. Но это - тот же самый код так или иначе.
@ Напомним, что значения теговых типов всегда передается по ссылке. Это означает, что изнутри процедуры Is_Done мы можем возвратить основной тип и так обнаружить информацию в расширении. Это сделано при использовании преобразований представления.
@ Фактически мы должны использовать два преобразования представления, сначала мы преобразовываем в наклассовый тип Timing_Event'Class и затем в определенный тип Egg_Event. И затем мы можем выбрать компоненту Event_Colour. Фактически мы можем сделать эти операции в одном операторе таким образом:
|
@ Отметим, что производится проверка при преобразовании из надклассового типа Timing_Event'Class в определенный тип Egg_Event чтобы гарантировать что объект действительно типа Egg_Event (или дальнейшее расширение этого). Если при проверке окажется что это не так возбуждается исключение Tag_Error. Чтобы избежать этой опасности, мы можем использовать тест членства. Например:
|
@ Тест членства гарантирует, что событие имеет соответствующий тип Egg_Event. Мы могли бы избежать двойного преобразования в надклассовый тип введя промежуточную переменную.
@ Важно ценить, что никакая диспетчеризация не вовлечена в эти операции вообще - все является статическим кроме теста членства.
@ Конечно, это было бы немного более гибко, если бы различные подпрограммы брали параметр типа Timing_Event'Class, но это находилось бы в противоречии с Restrictions идентификатором No_Dispatch. Отметим, что сам режим Ravenscar не налагает No_Dispatch, но ограничение находится в приложении Высокой целостности и, таким образом, могло бы быть введено для некоторых приложений высокой целостности, которые могли бы однако желать использовать таймеры простым способом.
@ Несколько незначительных различий между таймерами стоит суммировать.
@ У обоих таймеров центрального процессора есть константа Min_Handler_Ceiling. Это предотвращает нарушение потолка. Это не необходимо для таймера реального времени, потому что запрос защищенной процедуры обрабатывается как прерывание и таким образом в прерывании, перекрывающем уровень.
@ У таймера бюджета группы и таймера реального времени нет исключения, соответствующего Timer_Resource_Error для таймера центрального процессора еденичной задачи. Как упоминалось выше, ожидается, что единственный таймер мог бы быть осуществлен на вершине системы POSIX, когда мог бы быть предел числу таймеров тем более, что каждая задача могла использовать несколько таймеров. В случае группы задача может быть только в одной группе, таким образом число таймеров группы - обязательно меньше, чем число задач и никакого предела, вероятно, не будет превышено. В случае реального времени события просто помещаются в очередь задержки, и никакие другие ресурсы не требуются так или иначе.
@ Нужно также отметить, что таймер группы может использоваться, чтобы контролировать время выполнения единственной задачи. Однако, задача может быть только в одной группе и, таким образом, только один таймер может быть применен к задаче, тогда как (как только было упомянуто), единственный таймер центрального процессора является весьма различным, так как у данной задачи может быть несколько наборов таймеров истекающих в разное время. Таким образом, у обоих видов таймеров есть свои собственные отличные шаблоны использования.
2010-10-24 00:26:55
бижутерия из металла . .