10 июля 2020

Многозадачность в лицах

С прологом, испытаниями и эпилогом

Многозадачная гидра
 

Презумпция невиновности

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

 

Главная героиня

Многозадачность, здесь все о ней.

Если бы известный писатель был системным программистом, он почти наверняка сказал бы: “Все многозадачные системы похожи друг на друга, а все однозадачные - однозадачны каждая по-своему”. Управление задачами или потоками или, по-научному, процессами [1] по большому счету одинаково во всех многозадачных системах и упрощенно происходит так [1Краковяк,2Дейтел].

За время своего существования процесс проходит ряд дискретных состояний. Он находится в состоянии выполнения , если ему выделен процессор. Процесс находится в состоянии готовности , если он ждет выделения ему процессора. И, наконец, процесс находится в состоянии блокировки , если он ждет наступления некоторого события (например, завершения операции ввода или вывода).

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

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

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

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

Состояния показаны вершинами графа. Все переходы отмечены дугами с названиями переходов. Отметим, что только переход “блокирование” процесс выполняет по своей инициативе. Остальные переходы проводятся независимо от его желания.

Рис. 1

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

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

Если в системе есть механизм принудительного перевода процессов из состояния выполнения в состояние готовности (имеется дуга ”истечение кванта” на рис. 1), то такая ОС имеет вытесняющую многозадачность , если такого механизма нет, то у ОС добровольная (кооперативная) многозадачность .

Это вступление было, собственно говоря, способом договориться о терминологии. [2] А наша задача - проверить опытным путем, какого рода многозадачность обеспечивается в широко распространенных системах, и насколько устойчивы эти системы к попыткам монопольного захвата процессора. Как это сделать?

 

Испытания

Методика и ее проверка

Для испытаний нам потребуются программы двух видов: (а) бесконечный цикл, занимающийся выводом, и (б) он же, только молчаливый. Если опустить разные препроцессорные заклинания [3], то на Pascal’е эти малютки выглядят так [4].

program circle;
var
I : word;
begin
I := 0;
while true do
begin
writeln( I );
I := I + 1;
end;
end.
program circle;
var
I : word;
begin
I := 0;
while true do
begin
I := I + 1;
end;
end.
(а) (б)

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

Это Windows 3.x [5]. Наши программы после трансляции превращаются в программы win16. Сначала запустим цикл с операциями вывода (а). Ничего удивительного не произошло. Столбец целых чисел бежит вверх в открытом для него окне. Система несколько неохотно, но перемещает окна, запускает другие программы, словом, живет. Так и должно быть: хотя процесс бесконечный, он освобождает процессор при каждой операции вывода.

Попробуем теперь одновременно с процессом (а) запустить его молчаливого брата (б). Тоже ничего удивительного. Все замерло: и доселе живой (а), и система. Однако, первое впечатление обманчиво. На самом деле процессор напряженно трудится, выполняя бесконечный код процесса (б). Ведь этому циклу не на что отвлекаться, поэтому он никогда не перейдет в состояние блокировки, а другого перехода (см. рис. 1) из состояния выполнения в Windows 3.x просто нет. Посему ни система, ни другие процессы вмешаться в произвол, творимый (б), не могут. Формально система действует, фактически она мертва. Таковы особенности добровольной многозадачности.

 

Картина вторая

Похоже, что наш метод исследований верен, и можно приступить к испытаниям Windows 95, 98, NT (версии 4.0), Linux и UnixWare (версии 1.1) - систем с принудительной многозадачностью. Если можно, давайте сократим некоторые названия до W95, W98, WNT и UW.

После трансляции в системах Windows программы стали модулями win32. Будем выполнять их в том же порядке: сначала цикл (а), который выводит в своем окне номер итерации, а потом цикл (б), который ничего не выводит. Интересен момент старта второго цикла: остановит ли свой бесконечный вывод первый цикл, как это было в Windows 3.x?

Не останавливает. Во всех трех системах (W95, W98, WNT) первый цикл продолжает работать, несмотря на прожорливое соседство второго. Это верное доказательство того, что эти операционные системы запрещают монопольный захват процессора программами win32. Можно запустить достаточно много бесконечных программ обоего рода, и системы будут выполнять их все. Но не будем спешить делать окончательные выводы.

Такие же манипуляции проведем в UNIX-подобных системах. Бесконечные циклы обоих видов перепишем на языке C; после трансляции и компоновки они становятся модулями ELF. Теперь запустим - сначала (а), потом (б) - и понаблюдаем за ними с помощью программы top - предшественницы программ Task Manager в WNT и PView в W95.

Результат также весьма банален. И Linux, и UW не остановили работу и методично обслуживают все без исключения процессы, вытесняя претендентов на процессорную монополию. Постоянно изменяя текущие приоритеты процессов [6], системы “справедливо” делят “пирог” процессорного времени, не позволяя нашим захватчикам “откусить” слишком много.

Вернемся к системам семейства Windows.

 

Парадоксы W95 и W98. Картина третья

К уже работающим в этих системах бесконечным циклам win32 добавим бесконечные циклы win16, подготовленные для испытаний Windows 3.x. Исполнительная подсистема W95, W98, WNT умеет выполнять эти программы. Сначала запустим “говорящий” цикл (а). Появится еще одно окно с бегущими числами. Система и все остальные окна-процессы вполне дееспособны (см. рис. 2).

Фрагмент экрана Windows 95 с окнами бесконечных процессов

В каждом окне выполняется бесконечный цикл. Окна c черным фоном - программы win32, с белым - win16. Окна со столбцом чисел - программы вида (а), пустое окно - программа вида (б). Пока система жива, числа в столбцах бегут вверх, поверьте на слово.

Рис. 2

Теперь запустим еще одну программу win16 вида (б) (она ничего не выводит в свое окно). Система WNT достойно справляется и с этой нагрузкой: почти все наши бесполезные процессы продолжают “крутить динамо”, за исключением программы win16 вида (а), объяснение этому - несколько ниже. А вот системы W95 и W98 - обе - ведут себя просто невероятно.

Сразу после старта программы win16 без операций ввода/вывода - т.е. вида (б) - обе системы (и W95, и W98) прекращают исполнять уже работающие программы win16 и win32. Бегущие столбцы замирают, окна не переключаются, панель задач не “всплывает”, словом - системы становятся неработоспособными. [7] Они ведут себя точно также, как и Windows 3.x - их младшая сестра. Пусть это будет парадокс номер 1.

Если бы программа win16 вида (б) останавливала свою кузину - программу win16 вида (а) и только ее, тогда все объяснялось бы очень легко. Дело в том, что в соответствии с архитектурой систем W95 и W98, все программы win16 работают в одном адресном пространстве и пользуются одной очередью ввода на одной виртуальной машине, которая имитирует для них среду добровольной многозадачности системы Windows 3.x [3Рихтер]. В случае захвата процессора одной из программ win16 остальные программы этой же виртуальной машины будут простаивать (именно так произошло в WNT). И этот захват не должен распространяться на программы win32, работающие в изолированных областях памяти независимо друг от друга и от подсистемы, исполняющей коды win16. Тем более, этот захват не должен мешать операционной системе - ведь у нее вытесняющая многозадачность.

Останов W95 и W98 не просто невероятен, а почти скандален, т.к. все официальные и неофициальные руководства твердят одно и тоже. Например, Джеффри Рихтер [3Рихтер 361] утверждает по поводу W95 и WNT, что “бесконечный цикл [в какой-нибудь программе] - всего лишь небольшое неудобство для пользователя”, работающего с другой программой; “ни одно приложение практически не способно перевести систему в состояние, когда станет невозможной работа с другими приложениями”.

Загадочная остановка всех важнейших подсистем W95 и W98 требовала объяснения.

Поначалу казалось, что решение кроется в другом парадоксе обеих систем: стоит “зацепить” мышью любое окно и начать “возить” его по экрану, как жизненные проявления всех программ прекращаются [8], и система обслуживает лишь перемещение активного окна. Можно предположить, что все ее ресурсы уходят на перерисовку экрана, однако система продолжает обслуживать только активное окно даже тогда, когда собственно перемещения уже нет и перерисовывать ничего не надо, а “зацепившееся” за мышь окно просто не “отпускают”. Подходящее объяснение второго парадокса, а вместе с тем и первого, можно связать с приоритетами процессов. Известно [3Рихтер 58], что когда пользователь начинает работать в каком-нибудь окне, W95 автоматически повышает уровень соответствующего процесса (потока) на 1 единицу. Возможно, что полный останов в случае первого парадокса и временный останов в случае второго вызваны тем, что процессор захватывается высокоприоритетным процессом. Нормальность работы WNT [9] в аналогичных ситуациях можно объяснить понижением приоритета в дальнейшем путем динамического регулирования.

Это предположение, к сожалению, не подтвердилась. Все попытки понизить приоритет программы win16 вида (б) средствами “рабочего стола” или с помощью функций Windows API, спрятав ее окно за другими окнами, свернув окно и т.п., не вернули системам работоспособность.

Гипотезу о приоритетах опровергает и еще один эксперимент. Программы видов (а) и (б), странслированные для DOS, старательно трудятся и в W95, и W98, не останавливая операционную систему, несмотря на свой высокий диалоговый приоритет.

По всей видимости, останов W95 и W98 в случае первого парадокса вызван более глубокими особенностями их архитектуры. [10] Не исключено, что уязвимость этих систем в том, что все программы win32 и win16 выполняются в общей системной виртуальной машине. Правда, внутри системной виртуальной машины для каждой программы win32 выделяется изолированное адресное пространство, а у программ win16 адресное пространство одно на всех. Возможно, что захват процессора какой-нибудь программой win16 заклинивает системную виртуальную машину, которая, в свою очередь, останавливает все остальное.

UnixWare и win16. Почти лирическое отступление

В операционной системе UW, как и во многих других UNIX’ах, есть эмуляторы Windows 3.x. Из любопытства запустим обе бандитские программы win16 в эмуляторе. Как чувствуют себя эмулятор Windows 3.x и сама UW?

Windows 3.x конечно встала, но материнская система вполне ровно дышит, как бы и не замечая перегрузки. Получается, что UNIX исполняет программы win16, предназначенные для Windows, лучше, чем сама Windows?

 

Под занавес

Об актуальности темы принято писать во введении, но введение уже занято объяснением достаточно случайного повода к статье - попыткой показать студентам многозадачность в действии. Кстати, я не пугаю студентов “парадоксами номер 1 и 2”, они проводят лабораторные работы только с программами win32 и видят вполне объяснимые экраны. Так вот, возвращаясь к актуальности темы, следует упомянуть два обстоятельства.

Первое, несмотря на широкую деятельность популяризаторов, разновидности многозадачности мы понимаем очень поверхностно. Эту точку зрения высказывает и Питер Нортон [4Нортон,246]: ”Многозадачность относится к тем туманным терминам, которые все используют, но никто не берется объяснить”.

Второе, даже профессионалы не совсем точно выражают свои мысли. Например, в одной из лучших книг по программированию в Windows [3Рихтер,325] автор утверждает по поводу программ win16 в системе Windows 3.х: “стоит какому-то участку кода попасть в бесконечный цикл - блок операционной системы ... никогда не получит управление и система, естественно, повиснет”. А мы уже видели, что каким бы бесконечным ни был бесконечный цикл, если у него есть операции ввода или вывода, то он не остановит даже систему с добровольной многозадачностью. Чтобы погубить систему, цикл должен быть слепым, глухим и немым.

А нужно ли понимать механизмы многозадачности? Вопрос, скорее, риторический, но его можно задать и так: можно ли написать “хорошую” многопользовательскую программу или программу с параллельными процессами, не понимая этих механизмов?

Завершив доказательство актуальности, оформим итог исследований таблицей, в которой покажем, как влияет на работоспособность операционных систем “пустой” цикл в виде модуля win16, оказавшийся лакмусовой бумажкой.

 

  Windows 3.x Windows 95 Windows 98 Windows NT UnixWare
Реакция системы на запуск бесконеч-ного цикла в виде модуля win16, не имеющего операций ввода и вывода Исполняется только цикл-захватчик. Остальные процессы простаивают. Исполняется только цикл-захватчик. Остальные процессы простаивают. Исполняется только цикл-захватчик. Остальные процессы простаивают. Процессы -соседи цикла-захватчика простаивают. Процессы из модулей win16 в других виртуальных DOS-машинах и процессы из модулей win32 работают. Процессы -соседи цикла-захватчика простаивают. Процессы из модулей win16 в других виртуальных DOS-машинах машинах и процессы из модулей ELF работают.
Операционная система не может получить управление. Операционная система теряет контроль за процессами. Операционная система теряет контроль за процессами. Операционная система “справедливо” распределяет процессорное время. Операционная система “справедливо” распределяет процессорное время.

 

Наши опыты завершены. Архитектурно-теоритический вывод из них состоит, видимо, в ясном понимании часто повторяющейся фразы (например, [4]): “W95 и W98 поддерживают оба вида многозадачности: и кооперативную, и вытесняющую”. До опытов можно было трактовать эти слова так: “у системы вытесняющая многозадачность, но для исполнения программ win16 она имитируют добровольную многозадачность”. Однако наши испытания подтверждают правильность такого толкования только для WNT и потомков UNIX’а. Относительно W95 и W98 можно лишь сказать, что эти системы работают в режиме вытеснения с программами DOS и win32, но переходят в режим кооперации с программами win16. В режиме кооперации они теряют устойчивость к захвату процессора и могут полностью остановиться.

Практический итог испытаний в том, что работа с программами win16 в Windows 95 и Windows 98 не дает никаких преимуществ по сравнению с работой в Windows 3.х в части надежности информационно-вычислительной системы в целом. Более новые системы все также уязвимы для захвата процессора программами этого класса, но более тяжелы. Экономически выгоднее оставаться в Windows 3.х, а уж если невмоготу, то работать с win16 надо либо в Windows NT, либо, как это ни забавно, в Unix. Так надежнее.

 

Я очень признателен своим коллегам - Стасевичу Александру Анатольевичу и Череуте Светлане Эдуардовне, которые помогли мне в программировании тестовых задач.

 

 

Ссылки

Краковяк С. Основы организации и функционирования ОС ЭВМ. - М.: Мир, 1988.

Дейтел Г. Введение в операционные системы. - М.: Мир, 1987.

Рихтер Джеффри. Windows для профессионалов. - М.: Издательский отдел “Русская редакция”, 1995.

Мюллер Дж., Нортон П. Полное руководство по Windows 95 Питера Нортона. – М.: Бином, 1998.

NT Workstation. Учебное руководство для специалистов MCSE. Перклис Ч. и др. - М.: Лори, 1997.

 


[1] Под задачей и процессом понимают обычно одно и то же: находящиеся в стадии исполнения программу или самостоятельную часть программы, которая может получить процессор. Так повелось с MULTICS’а, продолжалось в UNIX’е, IBM/360 и прочая. В Windows NT под процессом понимается нечто гораздо менее живое и трепетное, а именно [3Рихтер 7, 25]: “структура данных”, которая “ничего не исполняет, а просто владеет ... адресным пространством. ... За исполнение кода, помещенного в адресное пространство процесса, отвечают потоки”. Следовательно, в Windows NT именно потоки соревнуются за процессор и создают многозадачную среду. Однако, ради единства терминологии повсюду в статье мы будем говорить о процессах как о конкурирующих задачах в многозадачной системе.

[2] Поэтому во вступлении не упоминаются прерывания и диспетчеризация процессов по приоритетам.

[3] Чтобы цикл был по-настоящему бесконечным, следует отключить проверку переполнения.

[4] Здесь опущены не только препроцессорные директивы, но и операторы, украшающие окна.

[5] Не судите за то, что назвал ее операционной системой.

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

[7] В таком ступорном состоянии перемещается только указатель мыши, что свидетельствует не столько о жизненных отправлениях операционной системы, сколько о том, что процессор компьютера исправно откликается на аппаратурные прерывания.

[8] Особенно наглядно останавливаются циклы вида (а).

[9] WNT повышает приоритет диалогового потока на 2 единицы.

[10] Причина второго парадокса, скорее всего, в бестолково написанной программе обработки прерываний от мыши.

Немцов Э. Ф.
Впервые опубликовано в журнале «Мир ПК», 1999, № 6.
Авторские права на фотографии, статьи и рисунки принадлежат Эдварду Немцову, если это не оговорено особо.