Скачиваний:
47
Добавлен:
02.05.2014
Размер:
434.18 Кб
Скачать

8.2. Взаимное исключение запретом прерываний

Большинство компьютерных архитектур предусматривают в составе своей системы команд команды запрета прерываний (иногда – селективного запрета). В микропроцессорах Intel-Pentium, например, такими командами являются CLI (запретить прерывания) и STI (разрешить прерывания). Такие команды и могут составить "скобки критической секции": запрет прерываний при входе в критическую секцию и разрешение – при выходе из нее. Поскольку вытеснение процесса возможно только по прерыванию, процесс, находящийся в критической секции, не может быть прерван. Этот метод, однако, обладает большим числом недостатков:

  • пока процесс находится в критической секции, не могут быть обработаны никакие внешние события, в том числе и события, требующие обработки в реальном времени;

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

  • процесс, находящийся внутри критической секции, не может перейти в ожидание (кроме занятого ожидания), так как механизм ожидания обеспечивается прерываниями;

  • программист должен быть весьма внимателен к тому, чтобы все возможные варианты выхода из критической секции обеспечивались разрешением прерываний;

  • вложение критических секций невозможно;

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

8.3. Взаимное исключение через общие переменные

Следующая группа решений базируется на непрерываемости памяти. Представляя эти алгоритмы, мы в основном следуем первоисточнику [11] и приводим в качестве примеров как правильные, так и неправильные или неудачные варианты решений.

Почти все примеры мы даем для двух процессов с номерами 0 и 1, их нетрудно обобщить на произвольное число процессов.

Вариант 1: общая переменная исключения.

Введем булевскую переменную mutEx, которая должна получать значение true (1), если вхождение в критическую секцию запрещено, или false (0), если вхождение разрешено. Попытка организовать "скобки критической секции" представлена следующим программным кодом:

1

2

3

4

5

6

7

8

static char mutEx = 0;

void csBegin ( void ) {

while ( mutEx );

mutEx = 1;

}

void csEnd ( void ) {

mutEx = 0;

}

При вхождении в функцию csBegin процесс попадает в цикл ожидания (строка 3), в котором находится до тех пор, пока состояние переменной исключения не разрешит ему войти в критическую секцию. Выйдя из этого цикла, процесс устанавливает эту переменную в 1, запрещая тем самым другим процессам входить в их критические секции. Процесс, который выполнялся в критической секции, при выходе из последней сбрасывает переменную исключения в 0, разрешая этим другим процессам входить в их критические секции.

Это решение базируется на непрерываемости доступа к памяти – к переменной mutEx, но оно является НЕПРАВИЛЬНЫМ. Рассмотрим такой случай. Пусть процесс A вошел в свою критическую секцию и установил mutEx=1. Пока процесс A выполняется внутри своей критической секции, два других процесса – B и C – также подошли к своим критическим секциям и обратились к функции csBegin. Поскольку переменная mutEx установлена в 1, процессы B и C зацикливаются в строке 3 кода функции csBegin. Когда процесс A выйдет из своей критической секции и установит mutEx=0, другой процесс, например B, выйдет из цикла строки 3. Но имеется вероятность того, что прежде, чем процесс B успеет выполнить строку 4 кода и этим запретить вход в критическую секцию другим процессам, выйдет из цикла строки 3 и процесс C. Таким образом, два процесса – B и C – входят в критическую секцию, задача взаимного исключения не выполняется.

Хотя это решение и не выполняет взаимного исключения, оно может быть приемлемо для решения некоторых частных задач. Например, граф синхронизации, представленный на рисунке 8.1, может быть обеспечен, если мы введем массив done, каждый элемент которого свяжем с определенным событием. События пронумерованы, и номер события является параметром функции, сигнализирующей о завершении события, – finish и функции ожидания события – waitFor:

1

2

3

4

5

6

7

8

static char done[9]

= {0,0,0,0,0,0,0,0,0};

void finish ( int event ) {

done[event] = 1;

}

void waitFor ( int event ) {

while ( ! done[event] );

}

Теперь работа процессов может быть синхронизирована таким образом (функциями типа workX() представлена работа, выполняемая процессом X):

processA() {

/* работа процесса A */

workA();

/* отметка о завершении процесса A */

finish(0);

}

processB() {

/* ожидание завершения процесса A */

waitFor(0);

/* работа процесса B */

workB();

/* отметка о завершении процесса B */

finish(1);

}

. . .

processE() {

/* ожидание завершения процесса B */

waitFor(1);

/* ожидание завершения процесса D */

waitFor(3);

/* работа процесса E */

workE();

/* отметка о завершении процесса E */

finish(4);

}

. . .

Можно сократить запись, например, так (используя естественную последовательность, заложенную в строках графа):

processABC() {

workA(); finish(0);

workB(); finish(1);

workC(); finish(2);

}

processDEF() {

waitFor(0); workD(); finish(3);

waitFor(1); workE(); finish(4);

waitFor(2); workF(); finish(5);

}

processGHI() {

waitFor(3); workG(); finish(6);

waitFor(4); workH(); finish(7);

waitFor(5); workI(); finish(8);

}

или иным образом (запишите самостоятельно) – с использованием последовательности в столбцах.

Соседние файлы в папке Системное программирование и операционные системы