Skip to content
Perestoronin Pavel edited this page Jan 9, 2021 · 9 revisions

1. ОС с монолитным ядром. Переключение в режим ядра. Диаграмма состояний процесса и переход из одного состояние в другое — причины каждого перехода. Диаграмма состояний процесса в UNIX. Переключение контекста. Система прерываний.

Монолитное ядро - такая структура ядра ОС, при которой все его компоненты являются модулем одной программы. Все компоненты ОС используют общие СД и взаимодействуют друг с другом путем вызова процедур.

Монолитное ядро - один процесс, который работает в одном адресном пространстве и в привилегированном режиме. Представляет собой статический двоичный файл. Взаимодействие приложений с ядром происходит с помощью системных вызовов.

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

Переключение в режим ядра

Процесс - программа в стадии выполнения.

В UNIX:

  • Процесс выполняет собственный код - выполняется в режиме задачи.
  • Процесс выполняет реентерабельный код (код чистой процедуры) ОС - выполняется в режиме ядра.

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

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

События, переводящие систему в систему ядра:

  • Системные вызовы
  • Исключения
  • Аппаратные прерывания

Диаграмма состояний процесса и переход из одного состояние в другое

ДСП

Диаграмма состояний процесса в UNIX.

Screenshot

Переключение контекста

Понятие контекста является важнейшим в системе.

Переключение аппартного контекста - сохранение содержимого регистров процессора. Такое переключение поддерижвается аппаратно, для этого существует команда pusha. При переходе из режима ядра в режим задачи, переключается аппаратный контекст.

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

Система прерываний

Важнейшей для работы систем с монолитным ядром является система прерываний, состоящая из:

  • системные вызовы (часто используется термин API - те функции, которые может запросить пользователь у системы).
  • исключения - аварийные ситуации в системе (исправимые - page fault, неисправимые - деление на 0).
  • аппаратные прерывания - ассинхронное событие, пред. 2 - синхронные. Предназначены для информирования процессора об окончании операции ввода-вывода.

1-2 - синхронные события, 3 - асинхронное событие.

Реализована идея распараллеливания функций - внешними устройствами управляет не процессор. Канальные, шинные внешние устройства - контроллеры. Команды от процессора переводят контроллер в нужный режим. В результате нажатия клавиши она поступает в буфер клавиатуры, для этого необходимо проинформировать процессор и он запустит нужный обработчик прерывания.

Приложение никогда не должно уметь обращаться напрямую к внешним устройствам, иначе оно бы смогло изменять код ОС и такую ОС невозможно было бы защитить. В операционной системе read/write главные системные вызовы.

2. Задача: читатели-писатели — монитор Хоара, решение с использованием семафоров Unix и разделяемой памяти, пример реализаци из лабораторной работы.

Для этой задачи характерно наличие двух типов процессов:

  • «читатели», которые могут только читать данные. Могут работать параллельно, поскольку они друг другу не мешают
  • «писатели», которые могут только изменять данные. Работают только в режиме монопольного доступа: только один писатель может получить доступ к разделяемой переменной, причем, когда работает писатель, то другие писатели и читатели не могут получить доступ к этой переменной.

В мониторе Хоара "Читатели-писатели" характерно наличие 4 процедур: start_read(), stop_read(), start_write() и stop_write().

#define ACTIVE_READER 0
#define ACTIVE_WRITER 1
#define WRITE_QUEUE 2
#define READ_QUEUE 3

struct sembuf READER_LOCK[] = { 
    { READ_QUEUE, 1, 0 }, 
    { ACTIVE_WRITER, 0, 0 }, 
    { WRITE_QUEUE, 0, 0 },
    { ACTIVE_READER, 1, 0 }, 
    { READ_QUEUE, -1, 0 }, 
};

struct sembuf READER_RELEASE[] = { 
    { ACTIVE_READER, -1, 0 }, 
};

struct sembuf WRITER_LOCK[] = {  
    { WRITE_QUEUE, 1, 0 }, 
    { ACTIVE_READER, 0, 0 }, 
    { ACTIVE_WRITER, 0, 0 }, 
    { ACTIVE_WRITER, 1, 0 }, 
    { WRITE_QUEUE, -1, 0 },
};

struct sembuf WRITER_RELEASE[] = { 
    { ACTIVE_WRITER, -1, 0 }, 
}; 

int start_read(int sid) {
    return semop(sid, READER_LOCK, 5) != -1;
}

int stop_read(int sid) {
    return semop(sid, READER_RELEASE, 1) != -1;
}

int start_write(int sid) {
    return semop(sid, WRITER_LOCK, 5) != -1;
}

int stop_write(int sid) {
    return semop(sid, WRITER_RELEASE, 1) != -1;
}

int reader_run(int *const shared_mem, const int sid, const int rid) {
    srand(time(NULL) + rid);

    if (!shared_mem) {
        return 1;
    }

    for (size_t i = 0; i < 8; i++) {
        int sleep_time = rand() % TIME_RANGE + TIME_START;
        sleep(sleep_time);

        if (!start_read(sid)) {
            return 2;
        }

        int readed = *shared_mem;
        fprintf(stdout, "Reader %d read: %d\n", rid + 1, readed);

        if (!stop_read(sid)) {
            return 3;
        }
    }

    return 0;
}

int writer_run(int *const shared_mem, const int sid, const int wid) {
    if (!shared_mem) {
        return 1;
    }

    for (size_t i = 0; i < ITER_CNT; i++) {
        int sleep_time = rand() % TIME_RANGE + TIME_START;
        sleep(sleep_time);

        if (!start_write(sid)) {
            return 4;
        }

        int updated = ++(*shared_mem);
        fprintf(stdout, "Writer %d write: %d\n", wid + 1, updated);

        if (!stop_write(sid)) {
            return 5;
        }
    }

    return 0;
}

Clone this wiki locally