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

О терминах

Если бы известный писатель был системным программистом, он почти наверняка сказал бы, что все многозадачные системы похожи друг на друга, а все однозадачные — «однозадачны» каждая по-своему. Управление задачами, или потоками (по-научному процессами1), по большому счету одинаково во всех многозадачных системах и упрощенно происходит так [1,2]. За время существования процесс проходит ряд дискретных состояний. Он находится в состоянии выполнения, если ему выделен процессор, в состоянии готовности, если он ждет выделения ему процессора, в состоянии блокировки, если ждет наступления события (например, завершения операции ввода или вывода).

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

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

Состояния показаны вершинами графа. Все

переходы отмечены дугами с названиями переходов.

Отметим, что только переход «блокирование»

процесс выполняет по своей инициативе.

Остальные переходы происходят независимо

от его желания

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

Как долго процесс может распоряжаться процессором? Возможны два варианта:

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

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

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

Испытания

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

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

Для испытаний нам потребуется бесконечный цикл двух видов: а) занимающийся выводом и б) «молчаливый». В листингах наших программ опущены не только препроцессорные директивы, но и операторы, украшающие окна. Кроме того, чтобы цикл был по-настоящему бесконечным, следует отключить проверку переполнения. На языке Паскаль эти малютки выглядят так:

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

По поведению программ а и б и самой ОС во время их исполнения попробуем определить свойства многозадачности операционной системы. Предварительные сведения о свойствах многозадачности операционных систем у нас есть, и, чтобы проверить правильность подхода, сначала испытаем систему с добровольной многозадачностью. Например, Windows 3.x2. Наши программы после трансляции превращаются в модули win16. Запустив программу а, ничего удивительного не заметим. Столбец целых чисел бежит вверх в открытом для него окне. Система несколько неохотно, но перемещает окна, запускает другие программы — словом, живет. Так и должно быть: хотя процесс бесконечный, он освобождает процессор при каждой операции вывода.

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

Все, кроме Windows 3.x

Похоже, наш метод исследований верен, и можно приступить к испытаниям Windows 95/98/NT 4.0, Linux и UnixWare 1.1 — систем с принудительной многозадачностью.

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

Интересно, остановит ли свой бесконечный вывод а в момент старта б, как это было в Windows 3.x? Не останавливает. Во всех трех системах Windows 95/98/NT первый цикл продолжает работать, несмотря на прожорливое соседство второго. Это верное доказательство того, что эти операционные системы запрещают монопольный захват процессора модулями win32. Можно запустить достаточно много бесконечных программ обоего рода, и системы будут выполнять их все. Но не станем спешить с окончательными выводами.

Такие же манипуляции проведем в Unix-подобных системах. Бесконечные циклы обоих видов перепишем на языке Cи; после трансляции и компоновки они становятся модулями ELF. Запустим сначала а, потом б и понаблюдаем за ними с помощью программы top — предшественницы программ Task Manager в Windows NT и PView в Windows 95.

Результат также ожидаемый: и Linux, и UnixWare не остановили работу и методично обслуживают все без исключения процессы, вытесняя претендентов на процессорную монополию. Постоянно изменяя текущие приоритеты процессов, системы «справедливо» делят «пирог» процессорного времени, не позволяя нашим захватчикам «откусить» слишком много.

Парадоксы Windows 95/98

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

Рис. 2. Фрагмент экрана Windows 95 с окнами

бесконечных процессов: окна c черным фоном

— модули win32, с белым — win16; окна со

столбцом чисел — программы а, пустое окно —

программа б

Запустим еще одну программу win16 вида б (она ничего не выводит в свое окно). Система Windows NT достойно справляется и с этой нагрузкой: почти все наши процессы продолжают бесполезную работу, за исключением программы win16 вида а (объяснение этому несколько ниже), а вот системы Windows 95/98 ведут себя просто невероятно.

Сразу после старта программы win16 вида б обе системы (Windows 95/98) прекращают исполнять работавшие до того модули win16 и win32: бегущие столбцы замирают, окна не переключаются, панель задач не «всплывает» — словом, системы становятся неработоспособными. Хотя указатель мыши перемещается, что, впрочем, свидетельствует не столько о жизненных отправлениях операционной системы, сколько о том, что процессор компьютера исправно откликается на аппаратные прерывания. Итак, Windows 95 и 98 ведут себя точно так же, как и их младшая сестра Windows 3.x. Пусть это будет парадокс номер один.

Если бы модуль win16 вида б останавливал своего кузена — модуль win16 вида а и только его, тогда все объяснялось бы очень легко. Дело в том, что в соответствии с архитектурой систем Windows 95/98 все модули win16 работают в одном адресном пространстве и пользуются одной очередью ввода на одной виртуальной машине, которая имитирует для них среду добровольной многозадачности системы Windows 3.x [3]. В случае захвата процессора одним из модулей win16 остальные программы этой же виртуальной машины будут простаивать (именно так произошло в Windows NT). Однако этот захват не должен мешать выполнению модулей win32, работающих в изолированных областях памяти, т. е. независимо друг от друга и от подсистемы, исполняющей коды win16. Более того, этот захват не должен влиять на функционирование операционной системы — ведь она обладает вытесняющей многозадачностью.

Останов Windows 95/98 не просто невероятен, а почти скандален, так как все официальные и неофициальные руководства твердят одно и то же, например [3, с. 361]: «...бесконечный цикл (в какой-нибудь программе) — всего лишь небольшое неудобство для пользователя» (работающего с другой программой. — Прим. авт.); «...ни одно приложение практически не способно перевести систему в состояние, когда станет невозможной работа с другими приложениями».

Загадочная остановка всех важнейших подсистем Windows 95/98 требовала объяснения. Поначалу казалось, что решение кроется в другом: стоит «зацепить» мышью любое окно и начать «возить» его по экрану, как жизненные проявления всех программ прекращаются (особенно хорошо это заметно на циклах вида а) и система обслуживает лишь перемещение активного окна. Можно предположить, что все ее ресурсы уходят на перерисовку экрана, однако система продолжает операции только с активным окном даже тогда, когда собственно перемещения уже нет и перерисовывать ничего не надо, а окно, «зацепленное» мышью, просто не «отпускают». Будем считать это явление парадоксом номер два. Подходящее объяснение второго парадокса, а вместе с ним и первого, можно связать с приоритетами процессов. Известно [3, с. 58], что когда пользователь начинает работать в каком-нибудь окне, Windows 95 автоматически повышает уровень соответствующего процесса на одну единицу. Возможно, что полный останов в случае первого парадокса и временный останов в случае второго вызваны тем, что процессор захватывается процессом с большим приоритетом. Работоспособность Windows NT в аналогичных ситуациях можно объяснить понижением приоритета в дальнейшем путем динамического регулирования, поскольку она повышает приоритет диалогового потока на две единицы.

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

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

По всей видимости, останов Windows 95/98 в случае первого парадокса вызван более глубокими особенностями их архитектуры. Причина второго парадокса, скорее всего, в бестолково написанной программе обработки прерываний от мыши. Не исключено, что уязвимость этих систем в том, что все модули win32 и win16 выполняются в общей системной виртуальной машине. Правда, внутри системной виртуальной машины для каждого модуля win32 выделяется изолированное адресное пространство, а для модулей win16 адресное пространство одно на всех. Возможно, что захват процессора какой-нибудь программой win16 «заклинивает» системную виртуальную машину, которая в свою очередь останавливает все остальное.

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

В ОС UnixWare, как и во многих других Unix-системах, есть эмуляторы Windows 3.x. Из любопытства запустим обе наши тестовые, прямо скажем «бандитские» программы win16 и посмотрим, как чувствуют себя эмулятор Windows 3.x и сама UnixWare. Windows 3.x, конечно, встала, но основная операционная система вполне ровно дышит, как бы и не замечая перегрузки. Что же получается? Unix исполняет программы win16, предназначенные для Windows, лучше, чем сама Windows?

Основные выводы

Итоги наших исследований сведены в таблицу.

Реакция системы на запуск бесконечного цикла в виде модуля win16, не имеющего операций ввода-вывода

Windows 3.xИсполняется только цикл-захватчик.

Остальные процессы простаивают.

Операционная система не может получить управление
Windows 95Исполняется только цикл-захватчик.

Остальные процессы простаивают.

Операционная система теряет контроль за процессами
Windows 98Исполняется только цикл-захватчик.

Остальные процессы простаивают.

Операционная система теряет контроль за процессами
Windows NTПроцессы — соседи цикла-захватчика простаивают.

Процессы из модулей win16 в других виртуальных DOS-машинах и

процессы из модулей win32 работают.

Операционная система «справедливо» распределяет процессорное время
UnixWareПроцессы — соседи цикла-захватчика простаивают.

Процессы из модулей win16 в других виртуальных DOS-машинах и

процессы из модулей ELF работают.

Операционная система «справедливо» распределяет процессорное время

Наши опыты завершены. Архитектурно-теоретический вывод из них состоит, видимо, в ясном понимании утверждения, будто Windows 95/98 поддерживают оба вида многозадачности: и кооперативную, и вытесняющую. Эти слова можно было бы трактовать так: у этих систем вытесняющая многозадачность, но для исполнения программ win16 они имитируют добровольную многозадачность. Однако наши испытания подтверждают правильность такого толкования только для NT и потомков Unix. Относительно Windows 95/98 можно сказать, что эти системы работают в режиме вытеснения лишь с программами DOS и win32, но переходят в режим кооперации с программами win16. При этом они теряют устойчивость к захвату процессора и могут полностью остановиться.

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

Под занавес

Об актуальности темы принято писать во введении, но коль скоро оно уже занято, займемся этим в заключении. Так вот, несмотря на широкую деятельность популяризаторов, о многозадачности обычно судят очень поверхностно. Приведу точку зрения Питера Нортона [4,с. 246]: «Многозадачность относится к тем туманным терминам, которые все используют, но никто не берется объяснить». Даже профессионалы не совсем точно выражают свои мысли. Например, в одной из лучших книг по программированию в Windows [3, с. 325] автор утверждает по поводу программ win16 в системе Windows 3.х: «...стоит какому-то участку кода попасть в бесконечный цикл — блок операционной системы <...> никогда не получит управление и система, естественно, повиснет». А мы уже видели, что бесконечный цикл с операциями ввода-вывода не остановит систему даже с добровольной многозадачностью. Чтобы погубить систему, бесконечный цикл должен быть «слепым», «глухим» и «немым».

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

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

ОБ АВТОРЕ

Эдвард Фридрихович Немцов — начальник сектора системного программирования, Российский федеральный ядерный центр, г. Саров (Нижегородская обл.), e-mail: teddy@vniief.ru

Литература

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

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

3. Рихтер Дж. Windows для профессионалов. — М.: Русская редакция, 1995.

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

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

1 Термины «задача» и «процесс» обычно отождествляют, понимая под ними находящуюся в стадии исполнения программу (или ее самостоятельную часть), которая может получить в свое распоряжение процессор. Так повелось с ОС MULTICS, продолжалось в UNIX, IBM/360 и пр. В Windows NT под процессом понимается нечто иное: «структура данных», которая «ничего не исполняет, а просто владеет <...> адресным пространством. <...> За исполнение кода, помещенного в адресное пространство процесса, отвечают потоки» [3]. Следовательно, в Windows NT именно потоки соревнуются за процессор и создают многозадачную среду. Однако в статье мы будем использовать терминологию, отличную от принятой в Windows NT, говоря о процессах как о конкурирующих задачах в многозадачной системе.

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