Опубликован: 06.12.2004 | Доступ: свободный | Студентов: 1180 / 143 | Оценка: 4.76 / 4.29 | Длительность: 20:58:00
ISBN: 978-5-9556-0021-5
Лекция 3:

Мобильное программирование приложений реального времени

Часы и таймеры

Стандартом POSIX-2001 предусмотрены средства создания таймеров для процессов, которые будут генерировать уведомления о наступлении заданного момента в виде рассмотренных выше сигналов реального времени. При этом разрешающая способность общесистемных часов реального времени CLOCK_REALTIME и монотонных часов CLOCK_MONOTONIC, а также основанных на них сервисов времени должна быть не хуже, чем заданное конфигурационной константой _POSIX_CLOCKRES_MIN значение 20 мсек (1/50 секунды). Естественно, реализация может обеспечивать более высокую разрешающую способность.

Если определена конфигурационная константа _POSIX_CPUTIME, значит, реализация дополнительно поддерживает для процессов часы процессорного времени с идентификатором типа clockid_t и значением CLOCK_PROCESS_CPUTIME_ID.

Если определена константа _POSIX_THREAD_CPUTIME, то аналогичные часы с идентификатором CLOCK_THREAD_CPUTIME_ID поддерживаются и для потоков управления.

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

Для приложений реального времени важна возможность использования не только процессорных, но и монотонных часов. В основном из этих соображений в стандарте POSIX-2001 присутствует расширенный аналог рассмотренной в курсе [1] функции nanosleep()clock_nanosleep() (см. листинг 3.19).

#include <time.h>
int clock_nanosleep (clockid_t clock_id, 
    int flags, const struct timespec *rqtp,
    struct timespec *rmtp);
Листинг 3.19. Описание функции clock_nanosleep().

Аргумент rqtp задает момент времени (по часам с идентификатором clock_id ), до наступления которого приостанавливается выполнение текущего потока управления. Если в аргументе flags установлен флаг TIMER_ABSTIME, этот момент трактуется как абсолютный , в противном случае – как относительный.

Разумеется, "наносон" может быть прерван доставкой обрабатываемого сигнала. Если при этом значение аргумента rmtp отлично от NULL, а момент возобновления выполнения задан как относительный, то в указуемую структуру типа timespec помещается "недоспанное" время.

Отметим, что функция clock_nanosleep() полностью аналогична nanosleep(), если не устанавливать флаг TIMER_ABSTIME, а в качестве часов использовать общесистемные часы реального времени CLOCK_REALTIME.

Возможность приостановки выполнения до наступления абсолютного момента времени полезна, например, для периодических процессов, когда, после завершения всех операций текущего периода, нужно заснуть до начала следующего периода. Для nanosleep() в таком случае необходимо узнать текущее время и вычесть его из расчетного времени возобновления выполнения; clock_nanosleep() с флагом TIMER_ABSTIME позволяет сразу задать время возобновления. Более сложное вычисление аргумента функции nanosleep() плохо не столько само по себе, сколько из-за возможного вытеснения потока управления с процессора перед самым вызовом nanosleep() ; в результате поток позже заснет и, соответственно, проснется позже запланированного момента времени. (Нетрудно видеть, что это общая проблема относительных таймеров.)

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

Естественно, попытка использования в clock_nanosleep() часов процессорного времени вызывающего потока управления считается ошибкой.

Следующая программа (см. листинг 3.20) иллюстрирует типичную схему применения функции clock_nanosleep() для организации периодических процессов.

/* * * * * * * * * * * * * * * * * * * */
/* Организация периодических процессов     */
/* с помощью функции clock_nanosleep()     */
/* * * * * * * * * * * * * * * * * * * */

#define _XOPEN_SOURCE 600

#include <time.h>
#include <stdio.h>
#include <unistd.h>

/* * * * * * * * * * * * * * * * * * * * */
/* Сложение двух структур типа timespec     */
/* * * * * * * * * * * * * * * * * * * * */
static void tmspc_add (struct timespec *a1, 
        struct timespec *a2, struct timespec *res) {
    res->tv_sec =     a1->tv_sec  + a2->tv_sec +
        (a1->tv_nsec + a2->tv_nsec) / 1000000000;
    res->tv_nsec = (a1->tv_nsec + a2->tv_nsec) % 1000000000;
}

/* * * * * * * * * * * * * * * * * * * */
/* Организация периодического процесса     */
/* * * * * * * * * * * * * * * * * * * */
int main (void) {
    struct timespec t_bp;        /* Время начала очередного     */
                                        / *периода выполнения     */
    struct timespec prd = {1, 250000000}; /* Период     */
                                                 /* выполнения: 1.25 сек     */
    clockid_t clk_id = CLOCK_REALTIME; /* Идентификатор     */
                                            /* используемых часов     */
    struct timespec t_tmp;
    int i;
/* Запомним время начала выполнения */
    (void) clock_gettime (clk_id, &t_bp);
    printf ("Начало выполнения: %ld сек %ld нсек\n", 
                    t_bp.tv_sec, t_bp.tv_nsec);

    for (i = 0; i < 8; i++) {
        /* Содержательные действия.     */
        /* Предполагается, что они укладываются в период     */
        sleep (1);

        /* Доспим до конца периода */
        tmspc_add (&t_bp, &prd, &t_bp);
        (void) clock_nanosleep (clk_id, TIMER_ABSTIME, 
            &t_bp, NULL);

        (void) clock_gettime (clk_id, &t_tmp);
        printf ("Конец периода: %ld сек %ld нсек\n", 
                    t_tmp.tv_sec, t_tmp.tv_nsec);
    }

    return 0;
}
Листинг 3.20. Пример применения функции clock_nanosleep() для организации периодического процесса.

На листинге 3.21 показаны возможные результаты выполнения приведенной программы.

Начало выполнения: 1079080828 сек 194254000 нсек 
Конец периода: 1079080829 сек 460021000 нсек 
Конец периода: 1079080830 сек 710023000 нсек 
Конец периода: 1079080831 сек 960020000 нсек 
Конец периода: 1079080833 сек 210021000 нсек 
Конец периода: 1079080834 сек 460023000 нсек 
Конец периода: 1079080835 сек 710021000 нсек 
Конец периода: 1079080836 сек 960094000 нсек 
Конец периода: 1079080838 сек 210022000 нсек
Листинг 3.21. Возможные результаты выполнения программы, использующей функцию clock_nanosleep() для организации периодического процесса.

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

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

#include <signal.h>
#include <time.h>
int timer_create (clockid_t clockid, 
    struct sigevent *restrict evp, 
    timer_t *restrict timerid);
Листинг 3.22. Описание функции timer_create().

Таймер создается на основе часов с идентификатором clockid ; идентификатор таймера (уникальный в пределах вызывающего процесса) записывается по указателю timerid. Разумеется, сразу после создания таймер оказывается в невзведенном состоянии.

Аргумент evp, указывающий на структуру типа sigevent, определяет характер уведомлений о срабатывании таймера. Если его значение равно NULL, то в качестве способа уведомления принимается SIGEV_SIGNAL, при срабатывании генерируется сигнал реального времени с подразумеваемым номером и значением, равным идентификатору таймера.

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