Лекция 4 Процессы и алгоритмы работы с ними в централизованных ОС (продолжение) 1 Изучаем централизованные ОС Что надо знать о процессах? Определение, разновидности, состояния, поддержка многопоточности Средства коммуникации Взаимные исключения и блокировки Низкоуровневые и высокоуровневые средства синхронизации Проблема тупиков Планирование и диспетчеризация Безопасность 2 Было на предыдущей лекции Коммуникация процессов Процессы: определение, разновидности, состояния, поддержка многопоточности Коммуникация процессов – передача информации от одного процесса другому Передача может осуществляться следующими способами: Посредством разделяемой памяти Посредством передачи сообщений или потоков данных: Сигналы (только в Unix) это программное средство, с помощью которого может быть прервано функционирование процесса. Каналы (трубы) – это псевдофайл, в который один процесс пишет, а другой из него читает. Сокеты (гнезда) – это поддерживаемый ядром механизм, скрывающий особенности среды и позволяющий единообразно взаимодействовать процессам, как на одном компьютере, так и в сети. Удаленный вызов процедур, процесс может вызвать процедуру в другом процессе (в том числе и на другом компьютере), и получить обратно данные. … Синхронизация процессов: взаимные исключения и блокировки 3 Было на предыдущей лекции Гонки Гонки (race conditions) — ситуация, когда два или более процессов обрабатывают разделяемые данные и конечный результат зависит от соотношения скоростей их исполнения. 4 Этика в работе программиста Профессиональная ответственность программиста: компетентность (понимание программистом реального уровня своих знаний), грамотное использование ресурсов (в целях выполняемого проекта), честность (приобретение и владение ИС), конфиденциальность (неразглашение секретов) Облучение смертельной дозой радиации на медицинском ускорителе Therac-25 в 1985-87 годах связано с целым рядом причин, включая и программные (например, ситуацию типа гонки) и дефекты в постановке задач. Nancy Leveson, Clark Turner. An Investigation of Therac-25 Accidents//Computer, July,1993 P.18–41. 5 План лекции Процессы: определение, разновидности, состояния, поддержка многопоточности Коммуникация процессов: простейшие средства (на примере сигналов в ОС Unix) Синхронизация процессов: взаимные исключения и блокировки Синхронизация процессов: низкоуровневые средства (HW) Синхронизация процессов: высокоуровневые средства (SW API) Решение задачи передачи данных между процессами "читатель" и "писатель" Процессы и ресурсы: проблема тупиков Планирование и диспетчеризация процессов Процессы: безопасность 6 Задача взаимного исключения Необходимо согласовать работу n>1 параллельных потоков при использовании некоторого критического ресурса таким образом, чтобы удовлетворить следующим требованиям: одновременно внутри критической секции должно находиться не более одного потока; критические секции не должны иметь приоритеты в отношении друг друга; остановка какого-либо процесса вне его критической секции не должна влиять на дальнейшую работу процессов по использованию критического ресурса; решение о вхождении потоков в их критические секции при одинаковом времени поступления запросов на такое вхождение и равной приоритетности потоков не откладывается на неопределенный срок, а является конечным во времени; относительные скорости развития потоков неизвестны и произвольны; любой поток может переходить в любое состояние, отличное от активного, вне пределов своей критической секции; освобождение критического ресурса и выход из критической секции должны быть произведены потоком, использующим критический ресурс, за конечное время. 7 Алгоритм Деккера enum state { UNLOCKED, LOCKED }; typedef struct { char status[2]; /* байт статуса для каждого из двух процессов */ char turn; /* какой из процессов будет следующим */ } lock_t; void init_lock(lock_t* lock) { lock->status[0] = UNLOCKED; lock->status[1] = UNLOCKED; lock->turn = 0; } void d_lock(volatile lock_t* lock) { /* устанавливаем блокировку для текущего процесса */ lock->status[cur_proc_id()] = LOCKED; /* проверяем, не установлена ли блокировка другим процессом */ while ( lock->status[other_proc_id()] == LOCKED) { /* если другой процесс уже установил блокировку, проверяем — чья очередь войти в критический участок */ if ( lock->turn != cur_proc_id()) { lock->status[cur_proc_id()] = UNLOCKED; while ( lock->turn == other_proc_id()) ; lock->status[cur_proc_id()] = LOCKED; } } } void d_unlock(lock_t* lock) { lock->status[cur_proc_id()] = UNLOCKED; lock->turn = other_proc_id(); } 8 Алгоритм Петерсона int turn; int interested[2]; void peterson_lock(int process) { int other = 1 – process; interested[process] = TRUE; turn = process; while (turn == process && interested[other] == TRUE) ; } void peterson_unlock(int process) { interested[process] = FALSE; 9 } План лекции Процессы: определение, разновидности, состояния, поддержка многопоточности Коммуникация процессов: простейшие средства (на примере сигналов в ОС Unix) Синхронизация процессов: взаимные исключения и блокировки Синхронизация процессов: низкоуровневые средства (HW) Синхронизация процессов: высокоуровневые средства (SW API) Решение задачи передачи данных между процессами "читатель" и "писатель" Процессы и ресурсы: проблема тупиков Планирование и диспетчеризация процессов Процессы: безопасность 10 Механизмы обеспечения синхронизации Атомарные операции для синхронизации Низкоуровневые (Hardware) Высокоуровневые (Software API) Команда test&set Крутящиеся блокировки Запрет прерываний Семафоры Мониторы Средства ЯВУ (рандеву, …)11 Аппаратная поддержка взаимоисключений test_and_set(a, b) (проверить и установить). Выполнение команды заключается в следующих двух действиях: значение переменной b копируется в a; значение переменной b устанавливается в истину. Практически все основные архитектуры имеют подобную команду в своем составе. В SPARC-архитектуре существует команда ldstub (load store unsigned byte): ldstub [addr],reg В результате выполнения этой команды содержимое памяти по адресу addr копируется в регистр reg, а все биты памяти addr устанавливаются в единицу. В x86 архитектуре существует команда xchg … 12 Программный интерфейс команды test_and_set Программный интерфейс команды test_and_set. Используется реальная ассемблерная команда ldstub. int test_and_set(volatile int* addr) { asm(ldstub [addr],reg); if ( reg == 0) return 0; return 1; } 13 Блокировка с запретом прерываний enum state { UNLOCKED, LOCKED }; typedef int lock_t; void init_lock(lock_t* lock) { *lock = UNLOCKED; } void lock(volatile lock_t* lock) { disable_interrupts; while ( lock != UNLOCKED) { enable_interrupts; disable_interrupts; } lock = LOCKED; enable_interrupts; } void unlock(lock_t* lock) { disable_interrupts; lock = UNLOCKED; enable_interrupts; } /* запрет прерываний */ /* разрешение прерываний */ 14 План лекции Процессы: определение, разновидности, состояния, поддержка многопоточности Коммуникация процессов: простейшие средства (на примере сигналов в ОС Unix) Синхронизация процессов: взаимные исключения и блокировки Синхронизация процессов: низкоуровневые средства (HW) Синхронизация процессов: высокоуровневые средства (SW API) Решение задачи передачи данных между процессами "читатель" и "писатель" Процессы и ресурсы: проблема тупиков Планирование и диспетчеризация процессов Процессы: безопасность 15 Механизмы обеспечения синхронизации Атомарные операции для синхронизации Низкоуровневые (Hardware) Высокоуровневые (Software API) Команда test&set Крутящиеся блокировки Запрет прерываний Семафоры Мониторы Средства ЯВУ (рандеву, …)16 Крутящаяся блокировка typedef int lock_t; void init_lock(lock_t* lock_status) { *lock_status = 0; } void spin_lock(volatile lock_t* lock_status) { while ( test_and_set(lock_status) == 1) ; } void spin_unlock(lock_t* lock_status) { *lock_status = 0; } 17 Семафоры: определение Семафор — это защищенная переменная, значение которой можно запрашивать и менять только при помощи специальных операций P и V и при инициализации. Концепция семафоров была предложена Дейкстрой в начале 60х годов XX века. P(S): if ( S>0) then S:=S-1 else ожидать_в_очереди(S) V(S): if ( есть_процессы_в_очереди(S) ) then одному_продолжить(S) else S:=S+1. Value=2 0 1 18 Семафоры: применение Конечно, операции P и V должны быть неделимыми (например, защищенные крутящейся блокировкой). Как правило, семафоры реализуются в ядре операционной системы. Собственно название операций происходит от голландских слов Proberen — проверить и Verhogen — увеличить. Три классические задачи, в которых применяются семафоры, таковы: решение проблемы взаимного исключения при помощи семафоров. Входом взаимоисключения будет операция P, а выходом — V; синхронизация при помощи семафоров. Если одному процессу необходимо, чтобы он получал уведомление от другого процесса о наступлении некоторого события и только после этого продолжал свою работу, то процессы должны иметь операции P и V соответственно, причем инициализироваться семафор должен нулем; выделение нескольких однотипных ресурсов из пула ресурсов при помощи семафоров. В этом случае надо применять общие или считающие семафоры. 19 Семафоры: реализация typedef struct { lock_t lock; /* байт статуса для каждого из двух процессов */ int count; /* какой из процессов будет следующим */ proc_t* head; proc_t* tail } sema_t; void init_sema(sema_t* sema, int initial_count) { init_lock(&sema->lock); sema->head = NULL; sema->tail = NULL; sema-> count = initial_count; } void P(sema_t* sema) { lock(&sema->lock); sema->count--; if ( sema->count < 0) { if ( sema->head == NULL) sema->head = u.u_procp; else sema->tail->p_next = u.u_procp; u.u_procp->p_next = NULL; sema->tail = u.u_procp; unlock(&sema->lock); switch(); return; } unlock(&sema->lock); } void V(sema_t* sema) { proc_t* p; lock(&sema->lock); sema->count++; if ( sema->count <= 0) { p = sema->head; sema->head = p->p_next; if ( sema->head == NULL) sema->tail = NULL; unlock(&sema->lock); enqueue(&runqueue, p); return; } unlock(&sema->lock); } 20 Мониторы Монитор — это механизм организации параллелизма, который содержит как данные, так и процедуры, необходимые для динамического распределения конкретного общего ресурса или группы общих ресурсов. Для выделения нужного ресурса процесс должен обратиться к конкретной процедуре монитора. В каждый конкретный момент времени монитором может пользоваться только один процесс. Процессам, желающим войти в монитор в тот момент, когда он занят, приходится ждать, причем процессом ожидания управляет монитор. Принцип работы монитора может быть описан следующим образом. Процесс, обращающийся к монитору за получением некоторого ресурса, обнаруживает, что ресурс занят. При этом процедура монитора выдает команду "ждать" (wait), по которой процесс будет ждать вне монитора, пока ресурс освободится. Когда ресурс освобождается, то монитор выдаст команду "сигнал" (signal). Если очередь ждущих процессов не пуста, то по этой команде один из процессов может воспользоваться ресурсом монитора. Обычно очередь организуется по принципу "первый 21 пришел — первый получил доступ к ресурсу". Реализация двоичного семафора при помощи монитора monitor sema; var s: integer = 1; /* переменная, представляющая семафор */ res_is_free: cond_t; procedure P; /* захват ресурса */ begin if s = 1 then s := 0 else wait(res_is_free) end; procedure V; /* освобождение ресурса */ begin s := 1; signal(res_is_free) end; end monitor; 22 Языковые подходы к программированию для параллельных вычислительных систем Программирование на параллельном языке программирования. Такие языки могут быть: универсальными (например, Ada); для конкретных типов компьютеров, позволяющих эффективно транслировать программы на параллельном языке именно в эту архитектуру (например, язык Occam изначально разрабатывался для транспьютеров); Программирование на широко распространенном языке программирования (например, C, C++, Pascal), который расширен языковыми (на уровне языка программирования) распараллеливающими конструкциями; Программирование с использованием дополнительных указаний компилятору на уровне языка прагм (например, по стандарту OpenMP); Программирование на широко распространенном языке программирования с использованием коммуникационных библиотек и интерфейсов для организации межпроцессорного взаимодействия. В этом случае конструкции параллелизма вынесены с языкового уровня на уровень операционной системы; Применение средств автоматического распараллеливания последовательных программ такими инструментами, как компиляторы. 23 Средства ЯВУ (рандеву, …) Параллельные языки различаются способом организации процессов: сопрограммы. Например, в языках Modula-2 и BLISS; разветвления (fork) и объединения (join). Например, в языках PL/1 и Mesa; параллельные скобки (cobegin/coend). Например, в языках Algol-68 и Edison; объявления процессов. Например, в языках Ada и Concurrent Pascal; Параллельные языки различаются способом синхронизации процессов: семафоры. Например, в языке Algol-68; мониторы. Например, в языке Concurrent Pascal; рандеву. Например, в языке Ada; критические участки. Например, в языке Edison; передача сообщений. Например, в языке CSP; удаленный вызов процедур. Например, в языке DP; атомарные транзакции. Например, в языке Argus. 24 План лекции Процессы: определение, разновидности, состояния, поддержка многопоточности Коммуникация процессов: простейшие средства (на примере сигналов в ОС Unix) Синхронизация процессов: взаимные исключения и блокировки Синхронизация процессов: низкоуровневые средства (HW) Синхронизация процессов: высокоуровневые средства (SW API) Решение задачи передачи данных между процессами "читатель" и "писатель" Процессы и ресурсы: проблема тупиков Планирование и диспетчеризация процессов Процессы: безопасность 25 Решение задачи передачи данных между процессами "читатель" и "писатель" Классическая задача передачи данных между процессами "читатель-писатель". Процесс "писатель" генерирует информацию, а "читатель" ее потребляет. Обмен блоками информации осуществляется через специальные буферы, число которых ограничено. Одним из возможных вариантов буфера может быть циклический список. Особая разновидность задачи "читатель-писатель" – случай информационной базы данных, который характеризуется тем, что процессов-"читатель" гораздо больше, чем процессов-"писатель". "Читатели" не изменяют содержимое базы данных, следовательно, их может быть несколько, при этом они одновременно имеют доступ к базе данных. "Писатели" должны иметь монопольный доступ к базе данных. 26 Решение задачи с помощью крутящихся блокировок char buf[N]; int head = 0, tail = 0, n = 0; lock_t lock_status; void put(char c) { while ( n == N) wait(); spin_lock(lock_status); buf[head] = c; head = (head + 1)%N; n++; spin_unlock(lock_status); } char get() { while ( n == 0) wait(); spin_lock(lock_status); c = buf[tail]; tail = (tail + 1)%N; n--; spin_unlock(lock_status); return c; } 27 Решение задачи с помощью семафоров char buf[N]; int head = 0, tail = 0; sema_t holes, chars; init_sema(holes, N); init_sema(chars, 0); void put(char c) { P(holes); buf[head] = c; head = (head + 1)%N; V(chars); } char get() { P(chars); c = buf[tail]; tail = (tail + 1)%N; V(holes); return c; } 28 Решение задачи с помощью мониторов char buf[N]; int head = 0, tail = 0, n = 0; cond_t not_empty, not_full; void put(char c) { while ( n == N) wait(not_full); buf[head] = c; head = (head + 1)%N; n++; signal(not_empty); } char get() { while ( n == 0) wait(not_empty); c = buf[tail]; tail = (tail + 1)%N; n--; signal(not_full); return c; } 29 Решение задачи на языке программирования Ada task body Buffer is buf: array(0..N-1) of CHAR; head, tail: INTEGER range 0..N-1 := 0; n: INTEGER range 0..N := 0; begin loop select when n < N => accept put(in c: CHAR) do buf[head] = c; end; n +:= 1; head = (head + 1) mod N; or when n > 0 => accept get(out c: CHAR) do c = buf[tail]; end; n -:= 1; tail = (tail + 1) mod N; end select; end loop; end Buffer; 30 План лекции Процессы: определение, разновидности, состояния, поддержка многопоточности Коммуникация процессов: простейшие средства (на примере сигналов в ОС Unix) Синхронизация процессов: взаимные исключения и блокировки Синхронизация процессов: низкоуровневые средства (HW) Синхронизация процессов: высокоуровневые средства (SW API) Решение задачи передачи данных между процессами "читатель" и "писатель" Процессы и ресурсы: проблема тупиков Планирование и диспетчеризация процессов Процессы: безопасность 31 Процессы: проблема тупиков Процессы и потоки управления (программные ресурсы) — активные объекты. Аппаратные ресурсы: процессор (вытесняемый ресурс) и дисковое пространство (не вытесняемый ресурс) — не являются активными объектами. Во время своей работы процесс (поток управления) может попасть в два неприятных состояния: зависание и тупик. Зависание — это состояние неопределенного ожидания каких-либо ресурсов, из которого рано или поздно процессы выходят. Тупик — это состояние ожидания некоторого события, которое никогда не произойдет (как правило, это круговое ожидание ресурсов). Система находится в тупиковой ситуации, если один или более процессов находятся в состоянии тупика. 32 Четыре необходимых условия для возникновения тупика Условие взаимоисключения. Процессы требуют монопольного владения ресурсами, предоставляемыми им; Условие ожидания. Процессы удерживают уже выделенные им ресурсы, ожидая выделения дополнительных); Условие нераспределяемости. Ресурсы нельзя отобрать у удерживающих их процессов, пока они не будут использованы; Условие кругового ожидания. Существует кольцевая цепь процессов, в которой каждый процесс удерживает за собой один или более ресурсов, требующихся следующему процессу. 33 Спасибо! Вопросы? 34 P.S. Экзаменационные вопросы: Синхронизация процессов: низкоуровневые средства (HW) Синхронизация процессов: высокоуровневые средства (крутящаяся блокировка, семафоры) Синхронизация процессов: высокоуровневые средства (мониторы, средства ЯВУ) Решение задачи передачи данных между процессами "читатель" и "писатель" (крутящаяся блокировка, семафоры) Решение задачи передачи данных между процессами "читатель" и "писатель" (мониторы, рандеву) 35