Материал: metod_ukaz

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Методические указания к лабораторным работам по дисциплине «Операционные системы» Содержание

Введение

Раздел 1. Параллельное выполнение потоков в ОС

1. СОЗДАНИЕ И УНИЧТОЖЕНИЕ ПОТОКОВ

2. СИНХРОНИЗАЦИЯ ПОТОКОВ С ПОМОЩЬЮ МЬЮТЕКСОВ И НЕИМЕНОВАННЫХ СЕМАФОРОВ

3. ВЗАИМОДЕЙСТВИЕ ПОТОКОВ ЧЕРЕЗ НЕИМЕНОВАННЫЕ КАНАЛЫ

Раздел 2. Параллельное выполнение процессов в ОС

4. СОЗДАНИЕ И УНИЧТОЖЕНИЕ ПРОЦЕССОВ

5. СИНХРОНИЗАЦИЯ ПРОЦЕССОВ С ПОМОЩЬЮ ИМЕНОВАННЫХ СЕМАФОРОВ

6. ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ ЧЕРЕЗ РАЗДЕЛЯЕМУЮ ПАМЯТЬ

7. ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ ЧЕРЕЗ ИМЕНОВАННЫЕ КАНАЛЫ

8. ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ ЧЕРЕЗ ОЧЕРЕДИ СООБЩЕНИЙ

Раздел 3. Управление коммуникациями в ОС

9. СЕТЕВОЕ ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ ЧЕРЕЗ СОКЕТЫ

Раздел 4. Управление информацией в ОС

10. СОЗДАНИЕ И ИСПОЛЬЗОВАНИЕ БИБЛИОТЕК

Раздел 5. Последовательное выполнение программ в ОС

11. СОПРОГРАММЫ КАК МОДЕЛЬ НЕВЫТЕСНЯЮЩЕЙ МНОГОЗАДАЧНОСТИ

Раздел 6. Мониторы синхронизации процессов

12. ВЗАИМОДЕЙСТВИЕ ПОТОКОВ ЧЕРЕЗ БУФЕР, РЕАЛИЗОВАННЫЙ НА УСЛОВНЫХ ПЕРЕМЕННЫХ

Заключение

Введение

Задания выполняются в ОС Linux.

При выполнении заданий необходимо использовать библиотеку «pthread».

В разделах «Общие сведения» каждой лабораторной работы приведены системные вызовы, которые необходимо использовать для реализации программы. Приведенные сведения являются минимальными. Для подробной информации следует обратиться к руководству программиста системы Linux (команда man).

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

Полагая, что программа содержится в файле lab.cpp, ее компиляция и сборка может быть выполнена следующей последовательностью команд.

Компиляция программы:

g++ -c lab.cpp,

в результате компиляции получается объектный код lab.o.

Сборка программы с включением библиотеки pthread:

g++ -o lab lab.o –lpthread,

в результате сборки получается исполняемая программа.

Шаблоны, предложенные в каждой лабораторной работе, являются приблизительными. Студент самостоятельно должен выбрать типы данных, подходящие для объявления переменных, а также операторы языка и системные функции, подходящие для выполнения предписанных действий программы.

Раздел 1. Параллельное выполнение потоков в ос

1. Создание и уничтожение потоков

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

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

Базовая структура многопоточной программы, взятая за основу всех работ, выглядит следующим образом:

  1. Описываются поточные функции, соответствующие потокам программы.

  2. В основной программе создаются потоки на основе поточных функций.

  3. После создания потоков основная программа приостанавливает выполнение и ожидает команды завершения.

  4. При поступлении команды завершения основная программа формирует команды завершения потоков.

  5. Основная программа переходит к ожиданию завершения потоков.

  6. После завершения потоков основная программа завершает свою работу.

Создание потока в стандарте POSIX осуществляется следующим вызовом:

int pthread_create(pthread_t *thread,

const pthread_attr_t *attr,

void *(*start_routine) (void *),

void *arg),

где:

thread – указатель на идентификатор потока;

attr – указатель на структуру данных, описывающих атрибуты потока;

start_routine – имя функции, выполняющей роль потока;

arg - указатель на структуру данных, описывающих передаваемые в поток параметры.

Завершение работы потока может быть выполнено несколькими способами.

  1. Вызовом оператора return из функции потока;

  2. Вызовом функции: int pthread_cancel(pthread_t thread); из другого потока;

  3. Вызовом функции: int pthread_exit(void *value_ptr).

В последнем случае появляется возможность через переменную value_ptr передать в основной поток “код завершения”.

При этом необходимо синхронизировать завершение с основным потоком, используя следующую функцию:

int pthread_join(pthread_t thread, void **retval),

где:

thread – идентификатор потока;

retval – код завершения потока, переданный через функцию pthread_exit.

Функция, выполняющая роль потока, создается на основе следующего шаблона:

static void * thread_start(void *arg).

Указания к выполнению работы

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

Шаблон программы представлен ниже:

объявить флаг завершения потока 1;

объявить флаг завершения потока 2;

функция потока 1()

{

пока (флаг завершения потока 1 не установлен) делать

{

выводить символ ‘1’ на экран;

задержать на время;

}

}

функция потока 2()

{

пока (флаг завершения потока 2 не установлен) делать

{

выводить символ ‘2’ на экран;

задержать на время;

}

}

основная программа()

{

объявить идентификатор потока 1;

объявить идентификатор потока 2;

создать поток из функции потока 1;

создать поток из функции потока 2;

ждать нажатия клавиши;

установить флаг завершения потока 1;

установить флаг завершения потока 2;

ждать завершения потока 1;

ждать завершения потока 2;

}

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

При завершении потоков выставить некоторые значения кодов завершения, а затем прочитать эти коды завершения в функции main().

Вопросы для самопроверки

  1. В чем состоит различие между понятиями «поток» и «процесс»?

  2. Как осуществить передачу параметров в функцию потока при создании потока?

  3. Какие способы завершения потока существуют?

  4. На какие характеристики потока можно влиять через атрибуты потока?

  5. В каких состояниях может находиться поток?

  6. Какие способы переключения задач используются в ОС?

  7. Объясните суть параметров, входящих в вызов pthread_create().

  8. Объясните суть параметров, входящих в вызов pthread_join().

  9. Опишите трассу выполнения программы.

2. Синхронизация потоков с помощью мьютексов и неименованных семафоров

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

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

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

Семафоры можно разделить на двоичные и общие, а также на неименованные и именованные.

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

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

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

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

При начальном состоянии счетчика семафора, равном 1, семафор эквивалентен мьютексу.

Мьютексы и неименованные семафоры используются для синхронизации потоков в рамках одного процесса.

Именованные семафоры используются для синхронизации процессов и будут рассмотрены позже.

Вызов, которым создается мьютекс, выглядит следующим образом:

int pthread_mutex_init(pthread_mutex_t *mutex,

const pthread_mutexattr_t *attr),

где:

mutex – идентификатор мьютекса;

attr – указатель на структуру данных, описывающих атрибуты мьютекса.

При входе в критический участок необходимо осуществить следующий вызов:

int pthread_mutex_lock(pthread_mutex_t *mutex).

При выходе из критического участка необходимо осуществить следующий вызов:

int pthread_mutex_unlock(pthread_mutex_t *mutex).

Вызов, которым удаляется мьютекс, выглядит следующим образом:

int pthread_mutex_destroy(pthread_mutex_t *mutex).

Создание неименованного семафора производится вызовом:

int sem_init(sem_t *sem, int shared, unsigned int value),

где:

sem – идентификатор семафора;

shared – индикатор использования семафора потоками или процессами;

value – начальное значение счетчика семафора.

При входе в критический участок необходимо осуществить следующий вызов:

int sem_wait(sem_t *sem).

При выходе из критического участка необходимо осуществить следующий вызов:

int sem_post(sem_t *sem).

Удаление семафора производится вызовом:

int sem_destroy(sem_t *sem).

Устранение блокировок

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

Устранить проблему позволяют неблокирующие операции проверки занятости ресурсов.

Для мьютексов это следующие операции:

  1. int pthread_mutex_trylock(pthread_mutex_t *mutex);

Если ресурс свободен, то функция работает также как и функция pthread_mutex_lock(). Если ресурс занят, то функция не блокируется в ожидании освобождения ресурса, а сразу же возвращает управление с кодом ошибки EBUSY.

  1. int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abs_timeout);

Если ресурс свободен, то функция работает также как и функция pthread_mutex_lock(). Если ресурс занят, то функция блокируется в ожидании освобождения ресурса до времени abs_timeout. Если в течение времени ожидания ресурс не освободится, функция завершается с кодом ошибки ETIMEDOUT.

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

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

int clock_gettime(clockid_t clk_id, struct timespec *tp);

где:

clockid_t clk_id тип часов, например, CLOCK_REALTIME,

время задается в виде структуры:

struct timespec {

time_t tv_sec; /* секунды */

long tv_nsec; /* наносекунды */

};

Например, чтобы получить время ожидания 1 сек, надо выполнить следующие действия:

  1. вызовом clock_gettime получить время в структуру tp,

  2. выполнить операцию tp.tv_sec += 1,

  3. передать модифицированное значение tp в функцию pthread_mutex_timedlock.

Для семафоров неблокирующими являются следующие функции:

int sem_trywait(sem_t *sem);

Если ресурс свободен, то функция работает также как и функция sem_wait(). Если ресурс занят, то функция не блокируется в ожидании освобождения ресурса, а сразу же возвращает управление с кодом ошибки EAGAIN.

И функция:

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

Время ожидания устанавливается аналогично предыдущему варианту.

Указания к выполнению работы

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

Необходимо убедиться, что в случае отсутствия мьютекса (семафора) потоки выводят символы в произвольном порядке, например:

121212121212121212121212121212121212121212121212121212121212121.

В случае использования мьютекса (семафора) потоки выводят символы в определенном порядке, например:

111111111122222222221111111111222222222211111111112222222222.

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

Источник: https://studfile.net/preview/16676187/