В статье дается обзор основных отличий двух операционных систем и рассматриваются возможные варианты преодоления трудностей переноса программного обеспечения.
До недавнего времени UNIX была идеальной средой разработчиков, с улыбкой наблюдавших за миром MS-DOS/Windows 3.x. Появление NT вызвало миграцию некоторых пользователей UNIX на эту платформу. Действительно, с точки зрения ряда предоставляемых возможностей ядра NT и UNIX внешне очень похожи - поддержка вытесняемой многозадачности, виртуальной памяти, механизмы синхронизации процессов и межпроцессорные коммуникации. Правда, NT не поддерживает многопользовательский режим, имеет убогую командную строку, отсутствует X Window и т.д. Но причина этого не в ядре - в NT есть все необходимое для многопользовательского режима - и атрибуты защиты всех объектов ядра и раздельные адресные пространства процессов - остается написать оболочку, способную взаимодействовать с несколькими пользователями одновременно.
Командная строка в NT поддерживается путем вызова AllocConsole, создающего новую консоль - виртуальный интеллектуальный терминал, функционально превосходящий аналогичные конструкции UNIX. Так, например, с каждой консолью связан экранный буфер, допускающий прямое чтение (запись). Сама консоль - это объект ядра NT и управляется дескриптором с учетом атрибутов защиты, автоматически разрушаясь операционной системой, при завершении всех, использующих ее процессов. В UNIX экранный ввод-вывод относится к подсистеме ввода-вывода и ядро о консолях ничего не знает.
А по поводу X Window уместно привести высказывание Маркуса Ранума из корпорации Digital Equipment: «Если бы конструкторы X Window строили машины, в них было бы не меньше пяти ведущих колес, спрятанных под кабиной, ни одно из которых не следовало бы одним принципам - но зато вы могли бы менять колеса, используя автомобильную стереосистему». Нужен ли NT сервер X Window, это вопрос иного плана, но во всяком случае ядро NT предоставляет все необходимые для этого функции и возможности.
К сожалению, переход с UNIX на Windows часто оказывается очень болезненным - полностью меняется идеология работы на компьютере. Естественный сегодня визуальный интерфейс порой вызывает отвращение у профессионалов. Свою любовь к командной строке и редактору vi они ни за что на свете не променяют на красивые окошки. Можно ли каким-то образом перебраться на другую платформу, сохранив прежнюю среду разработки?
Microsoft предпринимает определенные усилия для облегчения переноса программного обеспечения с платформы UNIX в свои операционные системы. Так, в Service Pack 3 появились волокна (fiber), добавленные для упрощения переноса серверных приложений. В отличие от потоков (thread), коммутируемых и управляемых ядром, программист имеет полную власть над волокнами. Это позволяет эмулировать поведение UNIX-процессов и эмулировать системные функции exec и fork. Однако Windows 95 не поддерживает волокна и их использование ограничивает рынок потенциальных потребителей.
Теоретически возможно написать полноценный эмулятор UNIX, правда, вряд ли кому-то удастся добиться хорошей производительности. Да и зачем? Ведь исходные тексты большинства приложений доступны, - остается перекомпилировать их под новую платформу и все! Но на самом деле это далеко не так просто. Как бы архитектуры UNIX и Windows не были близки, незначительные отличия часто не позволяют запустить «чужой» код без переработки программы. Не только системные, но и прикладные приложения, невольно используют особенности UNIX, становясь ее «заложниками». Поэтому, приходится выкручиваться иначе, - добавлять к Windows еще одну библиотеку, сглаживающую различия системных функций обоих операционных систем. Именно так и поступил Дэвид Корн, автор одноименной оболочки, создатель UWIN.
Рис. 1. Внешний вид Midnight Commander |
На первый взгляд, рис. 1 очень похож на знаменитый «Norton Commander», но, присмотревшись, можно заметить непривычный символ - разделитель каталогов. Да, это «Midnight Commander», «Нортон» для UNIX, да и названия каталогов характерными для Windows никак не назовешь.
Стоит развернуть окно консоли на полный экран и не всякий разберется, в какой операционной системе он работает. Конечно, «живая» UNIX все же лучше, но для большинства повседневных задач эмулятор вполне подойдет.
Разумеется, UWIN не единственное приложение в своем роде. Существуют еще CYGWIN, NUTCRACKER [1] и ряд других аналогичных программ. Какую из них предпочесть - личное дело читателя, в статье речь пойдет лишь о UWIN и CYGWIN. Для некоммерческого использования они бесплатны, остальные же требуют порой значительных денежных затрат.
Немного об отличиях
Прежде всего, полезно рассмотреть, в чем заключаются различия между Windows и UNIX, и как их можно преодолеть. Это пригодится разработчикам, не желающим связываться с эмуляторами и зависеть от сторонних производителей. Еще Кен Томпсон говорил, что никогда нельзя полностью доверять программам, написанным другими: помимо юридических проблем распространения чужого эмулятора или его компонентов вместе с вашим программным обеспечением, существует риск столкновения с ошибками эмулятора. Поэтому, в особо ответственных ситуациях перенос приложений приходится осуществлять самостоятельно.
Даже опытные разработчики на первых порах испытают значительные трудности, порой шаг за шагом проходя весь код отладчиком, никак не понимая, что же именно мешает ему работать. Большинство руководств, в том числе MSDN (Microsoft Developer Network), ограничивается описанием концептуальных различий системных вызовов UNIX и Windows, не затрагивая многочисленных тонкостей и особенностей обеих платформ. Отмахнуться от проблемы, приписывая использование тонкостей исключительно экзотическим и системным программам, не получится. Системно-зависимый код содержится во множестве прикладных приложений, таких, например, как популярный текстовой редактор EMACS.
Хуже всего то, что не все особенности поведения системных функций и архитектуры ядра описаны в документации. Например, в UNIX каждый открытый файл ассоциирован с дескриптором, а в Windows используют HANDLE. В первом приближении одно идентично другому - это «магические» числа, интерпретировать которые может только операционная система, а для приложений они представляется «черными ящиками». Сказанное справедливо для Windows, но в UNIX, как и MS-DOS значения дескрипторов упорядочены и предсказуемы. Напротив, HANDLE представляют собой случайные 32-разрядные числа, поэтому программы, написанные с учетом особенностей представления дескрипторов UNIX, откажутся работать в Windows. Все учебники твердят, что программисты не должны делать никаких предположений относительно содержимого дескрипторов, однако на практике многие приложения, иногда явно, иногда в результате допущенных ошибок, содержат следующий псевдокод:
h=OpenFile(...); ReadFile(h,...); Close(h); OpenFile(...); ReadFile(h,...); Close(h);
Рис. 2. Создание эмулятором упорядоченной таблицы дескрипторов файлов |
В UNIX гарантируется совпадение дескрипторов обоих файлов, но, попав в Windows, такой код начинает работать нестабильно. И причина заключается не только в многопоточности Windows - обработчиками владеет не поток, а процесс, следовательно, в промежутке между закрытием одного файла и открытием другого, «нужный» дескриптор может оказаться захваченным чужим потоком. Первые версии NT и Windows 95 интерпретировали обработчики как смещения в таблице объектов (NT как смещение, а Windows 95 - как индекс). Потом кому-то из разработчиков пришло в голову использовать простейшее шифрование, дабы обработчик одного процесса не имел смысла в контексте другого. Но в очередной версии все вернулось на свои места. Поэтому, переносимое приложение может устойчиво работать на платформе разработчика, но непредсказуемо вести себя у клиента. Возможный выход из такой ситуации заключается в создании собственной таблицы обработчиков, идентичной для всех процессов и хранящей упорядоченный список дескрипторов (рис. 2).
Рис. 3. Локальные таблицы HANDLE для двух Windows-процессов и их отображение в глобальную таблицу дескрипторов UNIX-эмулятора |
Один и тот же дескриптор может ассоциироваться с несколькими HANDLE. Например, оба дескриптора консоли, одновременно открытой как на запись, так и на чтение должны управляться всего одним обработчиком. Это обстоятельство крайне важно, ибо в Windows не существует глобальной таблицы обработчиков общей для всех процессов (точнее сказать, она существует, но прикладным программам недоступна). Каждый процесс имеет собственную локальную таблицу, поэтому бессмысленно пытаться использовать HANDLE чужого процесса. Такой подход повышает надежность системы, но затрудняет межпроцессорные взаимодействия, поэтому все UNIX приложения, не рассчитывающие на такой поворот событий, тут же откажут в работе. Поэтому необходимо собрать все HANDLE в одну таблицу, общую для всех UNIX-процессов (рис. 3).
Намного более существенны различия реализаций процессов. Поддержка многозадачности в UNIX реализована достаточно бедно. Системный вызов exec, запуская новый процесс, «подминает» текущий, поэтому до запуска exec обычно используется функция fork, расщепляющая один процесс на два - родительский и дочерний. Процесс-потомок наследует все открытые файлы родителя, сегмент данных и продолжает выполнение с той же самой точки, в которой завершился вызов fork. Отличие между ними заключается лишь в возвращаемом функцией fork значении. Родительский процесс получает в качестве результата идентификатор потомка, а сам потомок — всегда нулевое значение. Поэтому код, порождающий новый процесс, в UNIX-приложениях обычно выглядит так:
«if (fork()==0) exec(«/bin/vi/»,»/etc/passwd»,0);»
В Windows вызов CreateProcess порождает новый процесс, не затирая текущий. При этом сохраняется возможность наследования всех обработчиков установкой флага bInheritHandles. (Важно заметить, в Windows наследуются те и только те объекты ядра, при создании которых был явно установлен флаг наследования, в противном случае ничего не произойдет, даже если приказать дочернему процессу наследовать обработчики родительского процесса.) Поэтому функция CreateProcess практически эквивалентна последовательным вызовам fork + exec. Но аналога fork в Windows нет, как и возможности расщепления процессов. По большому счету это сегодня просто не нужно, но часто использовалось раньше разработчиками UNIX-приложений.
Любой эмулятор UNIX должен уметь имитировать вызов fork, благо архитектура Windows это позволяет. Чаще всего создается приостановленный (suspend) процесс, с той же самой стартовой информацией, что и текущий. До выполнения функции main() из родительского процесса потомку копируется сегмент данных, содержимое стека и дублируются все обработчики. В последнюю очередь модифицируется контекст процесса, хранящий значения регистров, в том числе и регистра указателя очередной выполняемой команды.
Гораздо проще имитировать exec: достаточно вызвать CreateProcess и «удалить» текущий процесс, имитируя его замещение новым. Остается всего лишь подменить идентификатор нового процесса на идентификатор прежнего. Если этого не сделать, вновь порожденный процесс станет потомком процесса, вызвавшего exec, а в оригинальной системе UNIX функция.exec, не создает дочернего процесса и сохраняет идентификатор текущего. Но идентификатор процесса выдается операционной системой и не может быть изменен по желанию приложения! Другими словами, если «Процесс 0» породил «Процесс 1» и запомнил его идентификатор, а «Процесс 1», имитируя вызов exec, обратился к функции CreateProcess и вызвал exit для завершения своей работы, то «Процесс 0» будет по-прежнему ссылаться на уже несуществующий «Процесс 1», ничего не зная о порожденном «Процессе 2», обладающим иным идентификатором.
Ситуация кажется безнадежной, но она элегантно разрешается созданием собственной таблицы идентификаторов эмулятором UNIX. Родительский процесс в качестве идентификатора получает индекс ячейки такой таблицы, содержащей настоящий идентификатор дочернего процесса. В результате появляется возможность «подменить» идентификатор «Процесса 1» на «Процесс 2» незаметно для родительского процесса. (рис. 4).
Рис. 4. Эмуляция вызова exec |
В отличие от Windows, операционная система UNIX поддерживает сигналы - удобное средство межпроцессорного взаимодействия, своеобразный аналог программно-аппаратных прерываний. Механизм же сообщений, реализуемый Windows, требует постоянного опроса очереди сообщений на предмет выявления новых поступлений. Так, например, при закрытии окна приложению посылается сообщение WM_CLOSE, но если оно в этот момент не читает очередь, а занято чем-то другим, скажем форматированием очередного трека дискеты или сортировкой данных, то проигнорирует попытку закрытия и продолжит работу вплоть до следующей проверки очереди. Поэтому практически в каждом Windows-приложении можно встретить следующий код:
while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); }
Исключения составляют консольные приложения - во всех остальных случаях никакое однопоточное приложение не может «забыть» о проверке сообщений больше чем на десятую долю секунды, не вызывая протестов со стороны пользователя, ругающегося на «зависшую» программу.
В UNIX все гораздо проще - операционная система автоматически прерывает работу процесса-получателя и передает управление на подпрограмму обработки сигнала, а после ее завершения возобновляет выполнение прерванного процесса с того же самого места.
Сейчас в Windows 9x/Winwos NT наличествует многопоточность и выбор межпроцессорных взаимодействий, поэтому имитировать поддержку сигналов вполне реально. Для этого в каждом эмулируемом приложении необходимо выделить отдельный поток, ожидающий, например, события (Event) и передающий управление на соответствующую подпрограмму при его наступлении (рис. 5). События выгодно отличаются возможностью «заморозить» ожидающий поток, не теряя впустую драгоценного процессорного времени. Следует отметить, функция «kill» вовсе не убивает процесс, как это следует из ее названия, а направляет ему сигнал, который тот может либо проигнорировать, либо обработать по своему усмотрению.
Рис. 5. Эмуляция функции kill |
Другое существенное отличие заключается в поведении кучи (heap) при изменении размеров выделенного блока. Куча - это динамически распределяемая область памяти. Не вникая в технические тонкости той или иной реализации, достаточно отметить три важнейшие операции, совершаемые над кучами - выделение блока памяти, освобождение и изменение его размеров. Первые две никаких проблем не вызывают, но вот динамическое изменение размера - штука интересная. Легко уменьшить размер блока, но намного чаще программистам требуется его увеличить (для того кучи и задуманы, чтобы сначала запросить немножечко памяти, а по мере потребности набирать ее все больше и больше). А как поступить, если к концу одного блока вплотную примыкает следующий? Ситуация кажется безнадежной, но на самом деле существует решение, да к тому же не одно. UNIX в этом случае использует трансляцию адресов, определенным образом отображая физические адреса на логические, создавая видимость непрерывности всего блока памяти, хотя на самом деле он состоит из множества беспорядочно разбросанных фрагментов. А вот Windows находит подходящий по размеру свободный блок памяти, проецирует в его начало содержимое увеличиваемого блока и возвращает новый указатель начала блока. Программы Windows, рассчитанные на такое поведение, выполняются успешно, но вот, попробуй-ка, научи этим фокусам UNIX-приложения. Поэтому, эмулятор UNIX должен иметь свой собственный менеджер куч, работающий с памятью Windows на низком уровне функциями VirtualAlloc и VirtualFree.
Некоторые приложения оказываются чувствительны к вариациям реализации стека в UNIX и Windows. Выделение памяти под стек в
Windows происходит очень сложным образом. Сначала выделяется всего несколько страниц, а все остальные предоставляются процессу по мере разрастания стека. На границе между выделенной и невыделенной памятью расположена сторожевая страница (PAGE_GUARD), при попытке обращения к которой генерируется исключение и операционная система выделяет память, перемещая сторожевую страницу вверх. Это великолепный механизм, но... Windows «падает» при выполнении следующего тривиального кода:
function() { char buff[xxx]; sub esp - xxx * sizeof (char) buff[0]=0; mov Byte prt ss:[esp],0 } ret;
Если ?xxx? окажется больше размера сторожевой страницы (32768 для Windows 95), то произойдет попытка обращения к невыделенной области памяти, со всеми вытекающими отсюда последствиями. Впрочем, если читатель усомниться в здравомыслии автора и не поленится откомпилировать указанный пример, к его глубокому удовлетворению все будет работать. На самом же деле, заботу о контроле стека берет на себя компилятор [5] и результат компиляции окажется намного сложнее приведенных справа ассемблерных мнемоник, но компиляторы не безгрешны, а в результате ошибки некоторые приложения могут отказать в работе.
Описанные выше различия относились к тонкостям реализации, скрытым от простого пользователя, отличающего Windows от UNIX только по наклону черты-разделителя. Совершенно непонятно, почему разработчики MS-DOS вопреки всем соглашениям, решили проявить оригинальность, применив не прямой, а обратный слеш, зарезервированный в языке Си для управляющих символов. К счастью, эмуляторы позволяют избежать этого, автоматически переформатировав строку (в простейшем случае) или установив новый драйвер файловой системы (для обеспечения полной имитации). Не следует забывать - файловая система в UNIX монтируемая, объединяющая в одну логическую структуру файлы и каталоги, физически расположенные не только на разных носителях, но и компьютерах.
Гораздо больше неудобств доставляет «двойной» перенос строки в MS-DOS (Windows), задаваемый символами « », тогда как UNIX ожидает лишь одиночного символа « ». Из-за этого большинство UNIX-приложений не могут корректно обрабатывать тексты, созданные редакторами MS-DOS (Windows) и наоборот. Текст программы, набранный в редакторе edit, вызовет протест со стороны UNIX-версии интерпретатора Perl. Универсального выхода из этой ситуации не существует, но посредственных решений проблемы можно предложить сколько угодно. Чаще всего эмуляторы при открытии файла в текстовом режиме самостоятельно обрабатывают символы перевода строки, следуя UNIX-соглашению. Файл, открытый в двоичном режиме читается «как есть», поскольку невозможно различить действительный перевод строки, от совпадения последовательности символов. К сожалению, многие приложения обрабатывают текстовые файлы, открывая их в двоичном режиме. Поэтому, лучше всего переносить файлы данных вручную, при необходимости заменяя символы переноса строки.
Точно так, в MS-DOS и UNIX не совпадают наименования устройств. Например, для подавления вывода сообщений на экран в MS-DOS используется конструкция «>nul» (echo «Это сообщение никогда не появится на экране» >nul), а в UNIX - «>/dev/null» (echo «Это сообщение никогда не появится на экране» > /dev/null). Другие примеры различий собраны в таблице 1.
Любой эмулятор должен предоставлять собственную библиотеку функций для работы с файлами, имитирующую наличие указанных устройств. Это реализуется простым преобразованием имен и контролем доступа в операциях записи и чтения (например, в стандартный ввод нельзя записывать, хотя MS-DOS это позволяет, совмещая и ввод, и вывод в одном устройстве под названием ?con? - сокращение от console).
К сожалению, существуют и такие различия, сгладить которые невероятно трудно. Так, например, система UNIX чувствительна к регистру в именах файлов и допускает мирное сосуществование «myfile» и «MyFile» в одном каталоге. Кажется, единственный способ приучить к этому Windows, - реализовать собственную файловую систему, но существуют более простые, хотя и менее элегантные решения. Некоторые эмуляторы ассоциируют файлы с таблицами виртуальных имен, обрабатываемых по правилам UNIX (рис. 6).
Рис. 6. Эмуляция чувствительности к регистру в именах файлов |
Намного хуже дело обстоит с поддержкой «сырых сокетов» (SOCK_ RAW). Если говорить просто, сырые гнезда позволяют прикладной программе самостоятельно сформировать заголовок IP-пакета, действие редко используемое в нормальной жизни, но необходимое для множества атак. Спецификация WINSOCK 2.x как будто бы подтверждает их наличие в Windows, но на самом деле, соответствующие функции реализованы неправильно и посылают подложный пакет, помещая пользовательский заголовок в область данных. Не то чтобы ситуация была полностью безнадежна, но написание собственного драйвера TCP/IP требует надлежащей квалификации разработчиков и ни один из известных автору эмуляторов сырых сокетов не поддерживает. К слову сказать, помимо гнезд семейства AF_INET, в UNIX существуют и сокеты, используемые для межпроцессорного взаимодействия, не поддерживаемые WINSOCK, но реализуемые подавляющим большинством эмуляторов.
Рассмотрев теоретические различия между UNIX и Windows, перейдем к сравнению конкретных реализаций эмуляторов. Для этого возьмем двух ярких представителей, UWIN от корпорации AT&T (кто знает UNIX лучше самой AT&T?) и CYGWIN, поддерживаемый в рамках проекта GNU.
Эмулятор UWIN
Для некоммерческого использования полноценную копию UWIN можно получить бесплатно (http://www.research.att.com/sw/tools/uwin/). UWIN содержит множество популярных командных оболочек, свыше трехсот необходимых для работы утилит и даже позволяет запускать Apache WEB сервер и DNS сервер. Администраторов NT не может не порадовать наличие полноценного демона telnetd (как известно в штатную поставку Windows NT не входит никаких средств удаленного управления компьютером).
Разработчикам пригодится компилятор GNU C/C++ и отладчик gdb, или любой другой инструментарий - по выбору, например, perl, awk, tcl. Кстати, в UWIN входит «обертка» для Microsoft Visual C++, дополняющая его возможностью обработки исходных текстов UNIX-приложений. Разумеется, откомпилированный текст можно отлаживать чем угодно, в том числе и Soft-Ice, не имеющим аналогов в среде UNIX.
А пользователи со временем обнаружат, что пользоваться командными оболочками UNIX, намного удобнее, чем елозить мышью. Благо, UWIN позволяет запускать не только UNIX, но и Windows-приложения, умело обращаясь не только с дисками, но и с реестром. Да, отныне ветви реестра будут представлены каталогами, а ключи - файлами. Корневой раздел реестра монтируется в каталог «/reg». Можно показать, например, как вывести на экран содержимое ветви реестра «HKEY_CURRENT_USERNetworkPersistentH»:
$ ls -l /reg total 1 drwxrwxrwx1767 root Everyone 0 Apr 22 2009 classes_root drwxrwxrwx 11 root Everyone 0 Apr 22 2009 current_user drwxrwxrwx 10 root Everyone 38 Apr 22 2009 local_machine drwxrwxrwx 3 root Everyone 0 Apr 22 2009 users $ $ cat /reg/current_user/Network/ Persistent/H RemotePath=SERVERC UserName=KPNC ProviderName=Microsoft Network
Но этим возможности UWIN не ограничиваются. В реестре становится возможным хранить и обрабатывать файлы, точь-в-точь как на обычном жестком диске.
Ранее был упомянут термин «монтируется» - UWIN эмулирует монтируемую файловую систему, позволяя произвольным образом объединять в одну логическую структуру, файлы и каталоги, физически расположенные на разных дисках и компьютерах. По умолчанию корневым назначается каталог, в который инсталлирован UWIN, корень диска «С» становится каталогом «/C/», и соответственно «/A/», «/B/», «/D/», «/E» для остальных дисков (если они есть). Каталог Windows доступен как «/C/Windows», так и через короткий псевдоним «/Win». Сетевые компьютеры автоматически монтируются так же, как и в Windows, но с наклоном черты в обратную сторону, т.е. «SERVERC» станет называться «//SERVER/C». Вообще же узнать, как смонтирован тот или иной ресурс, можно с помощью команды mount. Например, на компьютере автора результат ее работы выглядел так:
C:Program FilesUWIN on / type FAT32 (ic,text,grpid,suid,rw) C:Program FilesMicrosoft Visual Studiovc98 on /msdev type FAT32 (ic,text,grpid,suid,rw) A: on /A type FAT (ic,text, grpid,suid,rw) C: on /C type FAT32 (ic,text, grpid,suid,rw) D: on /D type FAT32 (ic,text, grpid,suid,rw) E: on /E type FAT32 (ic,text, grpid,suid,rw) F: on /F type FAT32 (ic,text, grpid,suid,rw) //SERVER/C on /H type FAT () /usr/bin on /bin type LOFS (ic,text,grpid,suid,rw) /usr/lib on /lib type LOFS (ic,text,grpid,suid,rw) /usr/etc on /etc type LOFS (ic,text,grpid,suid,rw) /usr/dev on /dev type LOFS (ic,text,grpid,suid,rw) /C/WINDOWS on /win type FAT32 (ic,text,grpid,suid,rw) /C/WINDOWS/SYSTEM on /sys type FAT32 (ic,text,grpid,suid,rw) /usr/proc on /proc type PROC (ic,text,grpid,suid,rw) /usr/reg on /reg type REG (ic,text,grpid,suid,noexec,rw)
Однако монтируемая файловая система видна только из-под UWIN, а Windows-приложения даже и не подозревают о ней. Поэтому, попытка вызвать notepad для просмотра фала /win/readme.txt провалится, а правильный вариант должен выглядеть так: «/win/notepad C:windows readme.txt». Обратите внимание на двойную косую черту. Если ее убрать, Windows сообщит о невозможности открыть файл «C:windowsreadme.txt». Так происходит потому, что в UNIX (и UWIN), косая черта «» зарезервирована за управляющими символами, а когда требуется отобразить сам символ обратной черты, прибегают к его дублированию.
Описание UWIN останется не полным, если не снять с него крышку, заглянув под капот. Архитектурно эмулятор состоит всего из двух динамических библиотек POSIX.DLL и AST5x.DLL. В POSIX реализовано множество системных вызовов UNIX таких, как fork, exec, malloc; фактически образующих ядро виртуальной системы UNIX. Ядро заведует памятью, управляет процессами и отвечает за операции ввода-вывода. Роль AST5x гораздо скромнее -это всего лишь аналог стандартной Си-библиотеки stdio, написанной с учетом особенностей эмуляции UNIX.
Рис. 7. Все UWIN-приложения разделяют общую память |
При этом все UWIN-приложения разделяют общий регион памяти, содержащий таблицу открытых файлов и кучу [3] (рис. 7). Следовательно, одно UWIN-приложение потенциально способно «уронить» другое, а потому некорректно работающий код может предоставить злоумышленнику привилегированный доступ к системе. Поэтому, если UWIN используется в качестве серверной платформы, не следует запускать приложения сомнительного происхождения.
В остальном же, UWIN-приложения ничем не отличаются от обычных исполняемых файлов Windows и могут запускаться непосредственно из Explorer, минуя среду UIWN.
Эмулятор CYGWIN
Другой популярный эмулятор UNIX - CYGWIN (http://www.cygnus.com/misc/gnu-win32/) по многим показателям заметно уступает UWIN, зато распространяется вместе с исходными текстами и, разумеется, абсолютно бесплатен. Зато плохо документирован и рассчитан на опытного пользователя. Собственно, CYGWIN - это никакой не эмулятор UNIX, а всего лишь набор функций, облегчающих перенос UNIX приложений в среду Windows, помещенных в одну динамическую библиотеку «cygwin1.dll». Для получения навыков работы в командных оболочках он, конечно, подойдет, но для изучения тонкостей UNIX - вряд ли. Сравните результаты работы команды «cat /etc/passwd», выводящей содержимое файла /etc/passwd на экран, в UWIN и CYGWIN:
UWIN $ cat /etc/passwd root:x:0:13:Built-in account/ domain:/tmp:/usr/bin/ksh telnetd:x:1:1:telnetd:/:/dev/null CYGWIN $ cat /etc/passwd /etc/passwd: No such file or directory
В CYGWIN вообще нет файла «/etc/passwd», он не эмулирует подсистему безопасности UNIX. Конечно, это не мешает написать соответствующие процедуры самому, но не лучше ли воспользоваться готовым инструментарием UWIN? Кстати, в UWIN изначально входит базовый набор утилит и оболочек, автоматически устанавливаемых программой инсталляции. Напротив, пользователи CYGWIN вынуждены самостоятельно скачивать необходимые компоненты с сервера, порой исправляя грубые ошибки, часто приводящие к полной неработоспособности кода.
Больше всего огорчает отсутствие в CYGWIN механизма поддержки демонов. Так, на данный момент не известно ни одной реализации telnet-сервера под CYGWIN. В остальном архитектуры этих двух эмуляторов достаточно близки. Как и в UWIN используется разделяемая память для всех открытых дескрипторов и хранения данных по для fork и exec [4]. Реализация fork сводится к созданию приостановленного процесса с последующим копированием сегмента данных, а выполнение exec приводит к образованию нового процесса и подмене идентификаторов в локальной таблице эмулятора. Поэтому в точности воспроизведения ядра UNIX оба эмулятора практически идентичны и все отличия выпадают на долю прикладных утилит. Проигрывая в этом вопросе, CYGWIN значительно превосходит UWIN в производительности, особенно в операциях ввода-вывода. К сожалению, UWIN слишком медленно обращается с экраном и работа с Mortal Commander на нем превращается в сплошное мучение.
Оба эмулятора UWIN и CYGWIN не поддерживает сырых гнезд, причем CYGWIN никак не отмечает этот прискорбный факт в документации. В результате, многие программы, работающие с IP протоколом на низком уровне (большей частью предназначенные для атак на чужие системы) не смогут корректно выполнятся в среде CYGWIN.
Об авторе
Крис Касперски — независимый автор. С ним можно связаться по электронной почте по адресу: kpnc@sendmail.ru
Литература
[1] Д. Волков. Дума о миграции. «Открытые системы», 1999, № 4, с. 13-19
[2] Wipro UWIN Version 2.0 User Guide
[3] Wipro UWIN Version 2.0 Developer Guide
[4] Geoffrey J. Noer Cygwin: A Free Win32 Porting Layer for UNIX Applications
[5] Джефри Рихтер. «Windows для профессионалов. Программирование в Win32 API для Windows NT4.0 и Windows 95»
Любопытно, но то, что все привыкли называть Windows NT, в действительности является одной из ее подсистем, известной разработчикам под именем «win32 API». «Настоящая» операционная система находится уровнем ниже - в менеджере виртуальных машин (virtual machine manager). Низкоуровневая обертка - NTDLL.DLL содержит множество функций с префиксом «Zw». Среди разработчиков ходит легенда, что якобы первоначальный вариант системы назывался «Zero Way», но затем компания погребла его под могильной плитой win32 API, и лишь две буквы напоминают о некогда существовавшей операционной системе. Кстати, теоретически, NT может выполнять родные приложения UNIX и OS/2 одновременно со своими собственными программами - ядро перекрывается любым интерфейсом, не теряя функциональности. Впрочем, в NT 4.0 некоторые компоненты подсистемы Win32 были перемещены в ядро для улучшения производительности ценой потери стройности архитектуры
Устройство | Название в UNIX | Название в MS-DOS |
Консоль | /dev/tty | con |
Стандартный ввод | /dev/stdin | con |
Стандартный вывод | /dev/stdout | con |
Черная дыра | /dev/null | nul |
Привод гибких дисков | /dev/fd | А: (B:) |
Параллельный порт | /dev/lp | com |
Последовательный порт | /dev/mod | lpt |