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

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

Для удаления таймера служит функция timer_delete() (см. листинг 3.23). Взведенный таймер перед удалением автоматически разряжается.

#include <time.h>
int timer_delete (timer_t timerid);
Листинг 3.23. Описание функции timer_delete().

Для выполнения содержательных действий с таймерами служат функции timer_gettime(), timer_settime() и timer_getoverrun() (см. листинг 3.24).

#include <time.h>

int timer_gettime (timer_t timerid, 
    struct itimerspec *value);

int timer_settime (timer_t timerid, 
    int flags,
    const struct itimerspec *restrict value, 
    struct itimerspec *restrict ovalue);

int timer_getoverrun (timer_t timerid);
Листинг 3.24. Описание функций timer_gettime(), timer_settime() и timer_getoverrun().

Согласно стандарту POSIX-2001, структура типа itimerspec содержит по крайней мере следующие поля.

struct timespec it_interval;     
    /* Интервал таймера     */
struct timespec it_value;     
    /* Текущие показания таймера     */
    /* (ведется обратный отсчет)     */

Значение поля it_value (если оно отлично от нуля) показывает время, оставшееся до срабатывания таймера ; нулевое значение it_value свидетельствует о том, что таймер разряжен. После срабатывания таймер запускается вновь с начальным значением поля it_value, равным it_interval, если последнее отлично от нуля; в таком случае мы имеем периодический таймер.

Функция timer_gettime() запоминает текущие характеристики таймера с заданным идентификатором в структуре, на которую указывает аргумент value.

Функция timer_settime() взводит или снимает таймер со взвода, устанавливая новые характеристики, пользуясь значением аргумента value, и сохраняет старые по указателю ovalue (если он отличен от NULL ).

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

Если при обращении к timer_settime() заданный абсолютный момент времени уже прошел, таймер тут же срабатывает.

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

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

Отметим, что наличие функции timer_getoverrun() позволяет избавиться от неопределенно большого расхода ресурсов на постановку в очередь сигналов реального времени, которые процесс не успевает обрабатывать.

Применим таймер для контроля длительности обеда философов (см. листинг 3.25).

/* * * * * * * * * * * * * * * * * * * * * * * */
/* Многопотоковый вариант обеда философов     */
/* с использованием сигналов реального времени     */
/* и таймера для контроля длительности обеда     */
/* * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>

/* Число обедающих философов */
#define QPH                 5

/* Время (в секундах) на обед */
#define FO                 15

/* Длительность еды */
#define ernd                 (rand () % 3 + 1)

/* Длительность разговора */
#define trnd                 (rand () % 5 + 1)

/* Номер сигнала, используемого для захвата     */
/* и освобождения вилок         */
#define SIG_FORK         SIGRTMIN

/* Номер сигнала, используемого таймером     */
/* для окончания обеда             */
#define SIG_DEND         (SIGRTMIN + 1)
/* Номер сигнала, используемого     */
/* для информирования философа     */
#define SIG_PHIL         SIGINT

static pthread_t pt_id [QPH];    /* Массив идентификаторов    */
                                            /* потоков-философов     */
static int fork_busy [QPH] = {0, }; /* Состояние вилок    */
static int phil_req [QPH] = {0, }; /* Невыполненные     */
                                            /* заявки на вилки     */
static sigjmp_buf phil_env [QPH];     /* Массив буферов для    */
                                            /* нелокальных переходов    */

static pid_t pid_wt;     /* Идентификатор процесса,     */
                                /* контролирующего вилки     */

static pthread_key_t phil_key;     /* Ключ индивидуальных     */
                                        /* данных потоков-философов     */

/* * * * * * * * * * * * * * * * * * */
/* Функция обработки сигнала SIG_PHIL     */
/* * * * * * * * * * * * * * * * * * */
static void phil_eat (int signo) {
    int no; /* Номер философа, которому достался сигнал */

    no = (int) pthread_getspecific (phil_key);
    if ((no > 0) && (no <= QPH)) {
        siglongjmp (phil_env [no – 1], signo);
    }
}

/* * * * * * * * * * * * * * * * * * */
/* Деструктор индивидуальных данных     */
/* потоков-философов     */
/* * * * * * * * * * * * * * * * * * */
static void phil_destructor (void *no) {
    printf ("Философ %d закончил обед\n", (int) no);
}

/* * * * * * * * * * * * * * * * * * * * * * * */
/* Функция, вызываемая при срабатывании таймера, */
/* контролирующего длительность обеда     */
/* * * * * * * * * * * * * * * * * * * * * * ** */
static void end_ph_dinner (union sigval dummy) {
    int i;

    for (i = 0; i < QPH; i++) {
        (void) pthread_cancel (pt_id [i]);
    }
}

/* * * * * * * * * * * * * * * * * * * * * */
/* Попытка выполнить заявку на захват вилок     */
/* от философа номер no, если она есть     */
/* * * * * * * * * * * * * * * * * * * * * */
static void fork_lock (int no) {
    if (phil_req [no – 1] != 0) {
        /* Заявка есть.    */
        /* Вилки свободны? */
        if ((fork_busy [no – 1] == 0) && (fork_busy 
                [no % QPH] == 0)) {
            /* Выполним заявку */
            fork_busy [no – 1] = fork_busy [no % QPH] = 1;
            phil_req [no – 1] = 0;
            (void) pthread_kill (pt_id [no – 1], SIG_PHIL);
        }
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока, обслуживающего заявки на     */
/* захват и освобождение вилок.     */
/* Заявка передается в виде значения, ассоциированного    */
/* с сигналом signo.             */
/* Значение no > 0 запрашивает захват вилок для философа */
/* с номером no, no < 0 – освобождение вилок философа -no     */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *start_waiter (void *signo) {
    siginfo_t sinfo;     /* Структура для получения данных    */
                                /* о сигнале     */
    int no;                 /* Номер философа, приславшего     */
                                /* заявку     */
    sigset_t s_sgno;     /* Маска ожидаемых сигналов     */

    pid_wt = getpid ();
/* Сформируем маску ожидаемых сигналов */
    if ((sigemptyset (&s_sgno) != 0) || 
            (sigaddset (&s_sgno, (int) signo) != 0)) {
        perror ("SIGEMPTYSET/SIGADDSET");
        return (NULL);
    }

    while (1) {
        if (sigwaitinfo (&s_sgno, &sinfo) != (int) signo) {
            return (NULL);
        } else {
            /* Поступила заявка.     */
            /* Посмотрим, что от нас хотят */
            if ((no = sinfo.si_value.sigval_int) > 0) {
    /* Заявка на захват вилок.   */
    /* Запомним ее ...           */
    phil_req [no – 1] = 1;
    /* ... и попробуем выполнить */
    fork_lock (no);
            } else {
            /* Освобождение вилок */
            no = -no;
            fork_busy [no – 1] = fork_busy [no % QPH] = 0;
            /* Попробуем выполнить заявки от соседей */
            fork_lock (no % QPH + 1);
            fork_lock (no == 1 ? QPH : (no – 1));
            }
        } /* Другие сигналы нас не интересуют */
    } /* while (1) */
}

/* * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока-философа.     */
/* Аргумент – номер философа     */
/* * * * * * * * * * * * * * * * * * */
void *start_phil (void *no) {
    union sigval sval; /* Значение посылаемого сигнала:     */
                                /* (int) no – заказ вилок     */
                                /* -(int) no – освобождение вилок    */

    /* Запомним значение аргумента в качестве     */
    /* индивидуальных данных потока     */
    (void) pthread_setspecific (phil_key, no);
if (sigsetjmp (phil_env [(int) no – 1], 1) != 0) {
        /* Сюда придем после нелокального перехода    */
        /* из обработчика сигнала SIG_PHIL.     */
        /* Философ просил вилки и получил их      */
        printf ("Философ %d ест\n", (int) no);
        if (sleep (ernd) != 0) {
            return (NULL);
        }
        /* Отдает вилки */
        sval.sival_int = -((int) no);
        (void) sigqueue (pid_wt, SIG_FORK, sval);
    }

    { /* Обед */
        printf ("Философ %d беседует\n", (int) no);
        if (sleep (trnd) != 0) {
            return (NULL);
        }

        /* Пытается взять вилки */
        sval.sigval_int = (int) no;
        (void) sigqueue (pid_wt, SIG_FORK, sval);

        /* Пока вилки заняты, приходится беседовать... */
        printf ("Философ %d беседует в ожидании вилок\n", 
                    (int) no);
        sleep (FO);
    } /* Конец обеда */

    return (NULL);
}

/* * * * * * * * * * */
/* Организация обеда */
/* * * * * * * * * * */
int main (void) {
    int no;                     /* Номер философа     */
    struct sigaction sact;    /* Структура для обработки     */
                                    /* обычных сигналов     */
    struct sigevent dend;     /* Структура для генерации     */
                                    /* сигнала таймером     */
    timer_t dend_tmrid;     /* Идентификатор таймера,     */
                                    /* контролирующего длительность     */
                                    /* обеда     */
    struct itimerspec dend_tmrsp  = {{0, 0}, {FO, 0}}; 
                            /* Структура для взведения таймера,     */
                            /* контролирующего длительность обеда    */
    pthread_t pt_wt; /* Идентификатор потока, управляющего     */
                            /* вилками     */

    /* Блокируем сигнал SIG_FORK */
    if ((sigemptyset (&sact.sa_mask) == 0) &&
            (sigaddset (&sact.sa_mask, SIG_FORK) == 0)) {
        (void) pthread_sigmask (SIG_BLOCK, &sact.sa_mask, 
            (sigset_t *) NULL);
    }

    /* Установим для сигнала SIG_FORK флаг SA_SIGINFO */
    sact.sa_flags = SA_SIGINFO;
    sact.sa_sigaction = (void (*) (int, siginfo_t *, 
        void *)) SIG_DFL;
    (void) sigaction (SIG_FORK, &sact, 
        (struct sigaction *) NULL);

    /* Установим реакцию на сигнал SIG_PHIL */
    sact.sa_handler = phil_eat;
    sigemptyset (&sact.sa_mask);
    sact.sa_flags = 0;
    (void) sigaction (SIG_PHIL, &sact, 
        (struct sigaction *) NULL);

    /* Создадим поток, захватывающий и освобождающий вилки */
    if ((errno = pthread_create (&pt_wt, NULL, 
            start_waiter, (void *) SIG_FORK)) != 0) {
        perror ("PTHREAD_CREATE-1");
        return (errno);
    }

    /* Создадим ключ индивидуальных данных */
    if ((errno = pthread_key_create (&phil_key, 
            phil_destructor)) != 0) {
        perror ("PTHREAD_KEY_CREATE");
        return (errno);
    }
/* Создадим и взведем таймер,     */
    /* контролирующий длительность обеда */
    dend.sigev_notify = SIGEV_THREAD;
    dend.sigev_signo = SIG_DEND;
    dend.sigev_notify_function = end_ph_dinner;
    dend.sigev_notify_attributes = NULL;
    if (timer_create (CLOCK_REALTIME, &dend, 
            &dend_tmrid) != 0) {
        perror ("TIMER_CREATE");
        return (-1);
    }
    if (timer_settime (dend_tmrid, 0, &dend_tmrsp, 
            NULL) != 0) {
        perror ("TIMER_SETTIME");
        return (-1);
    }

    /* Все – к столу */
    for (no = 1;  no <= QPH; no++) {
        if ((errno = pthread_create (&pt_id [no – 1], NULL, 
                start_phil, (void *) no)) != 0) {
            perror ("PTHREAD_CREATE");
            return (no);
        }
    }

    /* Ожидание завершения обеда */
    for (no = 1; no <= QPH; no++) {
        (void) pthread_join (pt_id [no – 1], NULL);
    }

    (void) pthread_key_delete (phil_key);
    (void) timer_delete (dend_tmrid);

    /* Поток, контролирующий вилки,     */
    /* можно не терминировать и не ждать */

    return 0;
}
Листинг 3.25. Пример реализации обеда философов с использованием сигналов реального времени и таймера.

Обратим внимание на применение вызова функции end_ph_dinner() в качестве способа уведомления о срабатывании таймера. Существенно, что эта функция выполняется в контексте отдельного потока; в противном случае с терминированием потоков-философов могли возникнуть проблемы.

Отметим также использование деструктора индивидуальных данных, ассоциированных с ключом phil_key. По сути они играют роль обработчиков завершения потоков-философов; возможно, так их и следовало оформить, но тогда могло возникнуть нежелательное взаимодействие с нелокальными переходами. Кроме того, их можно было сделать более содержательными, позаботившись об освобождении ресурсов (вилок), захваченных философом, но в данном конкретном случае это не требуется.

Читателю предлагается самостоятельно объяснить, почему важно, что философы нумеруются с единицы, а не с нуля.

Довольно тонким вопросом является терминирование извне потоков управления. В разных операционных системах оно реализовано по-разному. В принципе, согласно стандарту POSIX-2001, если запрос на терминирование поступает, когда поток приостановлен в точке терминирования (каковой является, например, функция sleep() ), его "будят" и он приступает к обработке запроса. Если, однако, между пробуждением и планируемым началом обработки происходит событие, которого поток ожидал, стандарт не специфицирует, будет ли обработка все же начата, или возобновится нормальное выполнение, а запрос останется ждущим (до достижения потоком следующей точки терминирования). Такого рода оговорки до некоторой степени оправдывают реализацию, при которой поступление запроса прерывает ожидание, но вместо немедленного терминирования происходит возврат из функции с результатом, свидетельствующим об ошибке, и значением errno, равным EINTR. Чтобы сделать приложение мобильным по отношению к подобным вариациям, в программе проверяется статус завершения функций sleep() и sigwaitinfo() с соответствующей реакцией на ошибки.

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

Философ 1 беседует
Философ 2 беседует
Философ 3 беседует
Философ 4 беседует
Философ 5 беседует
Философ 4 беседует в ожидании вилок
Философ 4 ест
Философ 2 беседует в ожидании вилок
Философ 2 ест
Философ 3 беседует в ожидании вилок
Философ 4 беседует
Философ 1 беседует в ожидании вилок
Философ 5 беседует в ожидании вилок
Философ 5 ест
Философ 2 беседует
Философ 3 ест
Философ 5 беседует
Философ 1 ест
Философ 2 беседует в ожидании вилок
Философ 4 беседует в ожидании вилок
Философ 3 беседует
Философ 4 ест
Философ 5 беседует в ожидании вилок
Философ 1 беседует
Философ 2 ест
Философ 2 беседует
Философ 1 беседует в ожидании вилок
Философ 4 беседует
Философ 1 ест
Философ 2 беседует в ожидании вилок
Философ 3 беседует в ожидании вилок
Философ 3 ест
Философ 1 беседует
Философ 5 ест
Философ 4 беседует в ожидании вилок
Философ 5 беседует
Философ 1 закончил обед
Философ 2 закончил обед
Философ 3 беседует
Философ 4 закончил обед
Философ 5 закончил обед
Философ 3 беседует в ожидании вилок
Философ 3 закончил обед
Листинг 3.26. Возможные результаты выполнения программы, реализующей обед философов с использованием сигналов реального времени и таймера.