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

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

Для приложений реального времени, функционирующих на аппаратных конфигурациях с ограниченными ресурсами, может оказаться полезной возможность (распространяющаяся на все сигналы ) выполнять функции обработки сигналов не на основном, а на альтернативном стеке. Специфицировать использование альтернативного стека можно с помощью флага SA_ONSTACK, установленного в поле sa_flags структуры типа sigaction при обращении к функции sigaction(). Функция sigaltstack() (см. листинг 3.14) позволяет установить и/или опросить характеристики альтернативного стека.

#include <signal.h>
int sigaltstack (const stack_t *restrict ss, 
    stack_t *restrict oss);
Листинг 3.14. Описание функции sigaltstack().

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

void   *ss_sp;    /* Адрес стека  */
size_t ss_size;   /* Размер стека */
int    ss_flags;  /* Флаги        */

Если значение аргумента ss отлично от NULL, то заданные им характеристики альтернативного стека возымеют действие после возврата из функции sigaltstack(). Поле ss_flags определяет состояние нового стека. Флаг SS_DISABLE в этом поле по сути означает отказ от альтернативного стека ; в таком случае значения ss_sp и ss_size игнорируются.

Альтернативный стек располагается в диапазоне адресов от ss_sp до (но не включая) ss_sp + ss_size. Стандарт не специфицирует, с какого конца и в каком направлении растет стек.

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

SS_ONSTACK

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

SS_DISABLE

Этот флаг означает, что в данный момент альтернативный стек обработки сигналов отключен.

Константа SIGSTKSZ задает подразумеваемый, а MINSIGSTKSZ – минимально допустимый размер альтернативного стека для функции обработки сигналов. Обычно, чтобы учесть системные накладные расходы, нужно сложить потребности приложения и MINSIGSTKSZ.

Естественно, все заботы по контролю за переполнением и исчерпанием альтернативного стека возлагаются на приложение.

На листинге 3.15 показана типичная схема определения альтернативного стека.

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
        . . .
stack_t sighstk;
    . . .

if ((sighstk.ss_sp = malloc(
        SIGSTKSZ)) == NULL) {
    perror ("malloc (SIGSTKSZ)");
    /* Аварийное завершение */
}
sighstk.ss_size = SIGSTKSZ;
sighstk.ss_flags = 0;
if (sigaltstack (&sighstk, 
       (stack_t *) NULL) != 0) {
    perror ("SIGALTSTACK");
            . . .
}
    . . .
Листинг 3.15. Типичная схема определения альтернативного стека.

Обработка сигналов (не обязательно реального времени ) нередко сочетается с нелокальными переходами. В таких случаях могут оказаться полезными функции sigsetjmp() и siglongjmp() (см. листинг 3.16). Они аналогичны функциям setjmp() и longjmp() с единственным содержательным отличием: если значение аргумента savemask функции sigsetjmp() отлично от нуля, маска сигналов вызывающего потока управления сохраняется как часть его окружения и восстанавливается после выполнения siglongjmp().

#include <setjmp.h>

int sigsetjmp (sigjmp_buf env, 
    int savemask);

void siglongjmp (sigjmp_buf env, int val);
Листинг 3.16. Описание функций sigsetjmp() и siglongjmp().

В качестве примера использования сигналов реального времени приведем еще один вариант реализации обеда философов (см. листинг 3.17).

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

#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_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);
    }
}

/* * * * * * * * * * * * * * * * * * * * * * */
/* Попытка выполнить заявку на захват вилок     */
/* от философа номер 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.sival_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) {
    int fo;         /* Время до конца обеда     */
    int t;         /* Время очередного отрезка еды или беседы     */
    time_t tbe;    /* Время, когда философу понадобились вилки     */
    union sigval sval;     /* Значение посылаемого сигнала:     */
                                /* (int) no – заказ вилок     */
                                /* -(int) no – освобождение вилок     */

    /* Запомним значение аргумента в качестве     */
    /* индивидуальных данных потока     */
    (void) pthread_setspecific (phil_key, no);

    /* Подготовка к обеду */
    fo = FO;

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

    while (fo > 0) {
        printf ("Философ %d беседует\n", (int) no);
        t = trnd; sleep (t); fo -= t;

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

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

    return (NULL);
}

/* * * * * * * * * * */
/* Организация обеда */
/* * * * * * * * * * */
int main (void) {
    int no;                         /* Номер философа     */
    struct sigaction sact;     /* Структура для обработки     */
                                    /* обычных сигналов     */
    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, 
            (void (*) (void *)) NULL)) != 0) {
        perror ("PTHREAD_KEY_CREATE");
        return (errno);
    }

    /* Все – к столу */
    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) pthread_cancel (pt_wt);
    (void) pthread_join (pt_wt, NULL);

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

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

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

В качестве небольшой тонкости обратим внимание на значение второго аргумента (оно отлично от нуля) в вызове функции sigsetjmp(). Таким образом обеспечивается восстановление маски сигналов после нелокального перехода из функции обработки сигнала SIG_PHIL. Если этого не сделать, сигнал SIG_PHIL останется блокированным и второй раз философ вилок уже не получит (точнее, он не узнает о том, что вилки для него захвачены).

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

Философ 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 беседует
Философ 4 закончил обед
Философ 1 беседует в ожидании вилок
Философ 5 ест
Философ 2 беседует в ожидании вилок
Философ 2 ест
Философ 3 беседует в ожидании вилок
Философ 3 закончил обед
Философ 1 закончил обед
Философ 5 закончил обед
Философ 2 закончил обед
Листинг 3.18. Возможные результаты выполнения программы, реализующей обед философов с использованием сигналов реального времени.