Учебная деятельность    

Операционные системы

Самостоятельная работа №2

Исследование принципов реализации процессов и очередей многозадачного ядра
(вытесняющая многозадачность, принципы планирования процессов)

 

Цель работы: познакомиться с принципами реализации процессов и очередей в ядре многозадачной среды.

 

Общие сведения

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

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

#include <signal.h>

    sigset_t empty;
    sigemptyset(&empty);
    struct sigaction sa = {.sa_sigaction=handler, .sa_mask=empty, .sa_flags=SA_SIGINFO|SA_RESTART};

    sigaction(TIMER_SIGNAL, &sa, NULL);

Первый параметр функции sigaction — номер сигнала, который будет использоваться системой для оповещении программы о срабатывании интервального таймера. В качестве значения константы TIMER_SIGNAL можно выбрать SIGIO или SIGRTMIN.

В качестве обработчика сигнала данный пример устанавливает функцию handler, которая должна быть объявлена следующим образом:

void handler(int num, siginfo_t *info, void *context);

Запуск таймера можно осуществить при помощи timer_create и timer_settime:

#include <time.h>

    timer_t timerid;
    struct sigevent sev = {.sigev_notify=SIGEV_SIGNAL, .sigev_signo=TIMER_SIGNAL};
    struct itimerspec spec = {.it_interval.tv_sec=0, .it_interval.tv_nsec=90000000, .it_value.tv_sec=0, .it_value.tv_nsec=90000000};

    if (timer_create(CLOCK_MONOTONIC, &sev, &timerid)) {
        perror("timer_create failed!");
        exit(1);
    }
    if (timer_settime(timerid, 0, &spec, NULL)) {
        perror("time_settime failed!");
        exit(1);
    }

Функция timer_create создаёт новый таймер для программы, в структуре sigevent указывается способ оповещения (SIGEV_SIGNAL — по сигналу) и номер сигнала (объявленная ранее константа TIMER_SIGNAL).

Функция timer_settime запускает таймер с указанными в структуре itimerspec параметрами. Поле it_interval задаёт период сработки для периодического таймера, а поле it_value задаёт интервал для первой сработки (значение 0 в поле it_value означает отмену/отключение таймера). Значение времени для обоих полей задаётся в секундах и наносекундах (в данном примере — 90 мс).

 

Точная полнофункциональная реализация вытеснящей многопоточности на уровне пользователя в POSIX-системах сопряжена с определёнными сложностями.

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

Следует обратить внимание на состояние маски блокируемых сигналов при переключении контекстов. По умолчанию обрабочик сигнала вызывается в контексте, в котором маска сигналов блокирует сигнал, вызвавший этот обработчик. Именно такая маска попадёт в "старый" контекст. Если при переключении произойдёт передача управления на контекст в котором этот сигнал заблокирован, то переключение контекстов по сигналу становится невозможным.

Из-за асинхронной "природы" сигналов не все функции, описанные в стандарте POSIX, пригодны для использования в обработчике сигналов. В частности, функции манипулирования контекстом (setcontext, swapcontext) к таким не относятся. Однако longjmp (аналогичная по своей "природе") в этот список попадает. Практика показывает, что существующие системы (по крайней мере, Linux) "терпимо" относятся к такому отступлению от стандарта.

Кроме того, существует ряд функций, которые в своей работе активно задействуют Thread Local Storage (локальную память потока уровня ядра), которая для реализуемых потоков уровня пользователя является "глобальной". Такие функции становятся нереентерантными и непотокобезопасными в "самодельной" системе потоков уровня пользователя. Пример такой функции — malloc, а также функции, меняющие переменную errno.

 

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

Задание 2: Блокирущий системный вызов, использованный в одной сопрограмме, приводит к блокировке всего процесса. В системах управления потоков пользовательского уровня эта проблема решается за счёт собственных "обёрток". Реализуйте замену системному вызову sleep, позволяющую исключить вашу сопрограму из очереди на исполнение на указанное количество секунд:

    unsigned int cosleep(unsigned int sec);

Подсказка: глобальное время можно измерять при помощи clock_gettime.

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

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