Главная страница  Межпроцессное взаимодействие (состязание) 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [ 21 ] 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187

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

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

Но что произойдет, если поток родительского процесса будет блокирован вызовом read с клавиатуры, а у дочернего процесса столько же нитей, сколько у родительского? Будут ли теперь блокированы две нити - одна из родительского процесса, другая из дочернего? И если с клавиатуры поступит строка, получат ли обе нити ее копию? Или только одна - тогда какая? Эта же проблема возникает при работе с открытыми сетевыми соединениями.

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

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

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

Другие сигналы, такие как прерывание с клавиатуры, не связаны с нитями. Кто должен их перехватывать? Одна назначенная нить? Все нити? Специально созданная всплывающая нить? Что случится, если одна нить изменит обработчик сигнала, не предупредив об этом остальные нити?

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



наращивать при переполнении. Ядро может даже не связать ошибки памяти с переполнением стеков.

Разумеется, эти проблемы не являются непреодолимыми, но на их примере хорошо видно, что введение нитей в существующую систему неразумно без тщательной и продуманной реконструкции всей системы. По крайней мере, придется изменить семантику системных запросов и переписать библиотеки. И результат ваших трудов должен быть совместим с существующими программами для процессов с одним потоком управления. Дополнительную информацию о нитях см. в [40, 59].

2.2. Межпроцессное взаимодействие

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

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

2.2.1. Состояние состязания

в некоторых операционных системах процессы, работающие совместно, сообща используют некое общее хранилище данных. Каждый из процессов может считывать что-либо из общего хранилища данных и записывать туда информацию. Это хранилище представляет собой засток в основной памяти (возможно, в структуре данных ядра) или файл общего доступа. Местоположение разделяемой памяти не влияет на суть взаимодействия и возникающие проблемы. Рассмотрим межпроцессное взаимодействие на простом, но очень распространенном примере: систему буферизации - спулер - печати. Если процессу требуется вывести на печать файл, он помещает имя файла в специальный каталог спулера. Другой процесс, демон печати, периодически проверяет наличие поданных на печать файлов, печатает их и удаляет их имена из каталога.

Представьте, что каталог спулера состоит из большого числа сегментов, пронумерованных последовательно (О, 1, 2, ...), в каждом их которых может храниться имя файла. Также есть две совместно используемые переменные: out.



указывающая на следующий файл для печати, и in, указывающая на следующий свободный сегмент. Эти две переменные можно хранить в одном файле (состоящем из двух слов), доступном всем процессам. Пусть в данный момент сегменты с О по 3 пусты (эти файлы уже напечатаны), а сегменты с 4 по 6 заняты (эти файлы ждут своей очереди на печать). Более или менее одновременно процессы А и В решают поставить файл в очередь на печать. Описанная ситуация схематически изображена на рис. 2.5.

Директория спулера

([Процесс А


(ПроцессБ

out=4

ргод.с

prog.n

in=7

Рис. 2.5. Два процесса хотят одновременно получить доступ к совместно используемой памяти

Б соответствии с законом Мерфи (он звучит примерно так: Если что-то плохое может случиться, оно непременно случится ), возможна следующая ситуация. Процесс А считывает значение (7) переменной in и сохраняет его в локальной переменной next free slot. После этого происходит прерывание по таймеру, и процессор переключается на процесс В. Процесс В, в свою очередь, считывает значение переменной in и сохраняет его (опять 7) в своей локальной переменной next free slot. В данный момент оба процесса считают, что следующий свободный сегмент - седьмой.

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

Наконец, управление переходит к процессу А, и он продолжает с того места, на котором остановился. Он обращается к переменной next free slot, считывает ее значение и записывает в седьмой сегмент имя файла (разумеется, удаляя при этом имя файла, помещенное туда процессом В). Затем он заменяет значение in на 8 (next free slot +1 = 8). Структура каталога спулера не нарушена, поэтому демон печати не заподозрит ничего плохого, но файл процесса В не будет напечатан. Пользователь, связанный с процессом В, может в этой ситуации полдня описывать круги вокруг принтера, ожидая результата. Ситуации, в которых два (и более) процесса считывают или записывают данные одновременно и конечный результат зависит от того, какой из них был первым, называются состояниями состязания. Отладка программы, в которой вероятно возникновение состояния состязания, вряд ли может доставить удовольствие. Результаты боль-



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [ 21 ] 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187

© 2000 - 2018 ULTRASONEX-AMFODENT.RU.
Копирование материалов разрешено исключительно при условии цититирования.