ROS Newsletter93

Материал из Русский WINE
Перейти к: навигация, поиск

Выпуск новостей ReactOS №93

Окна и Рабочие столы

В операционных системах семейства Windows рабочий стол, который видит пользователь после окончания загрузки компьютера, состоит из трёх составных частей: объект режима ядра, окно пользовательского режима, и поток. В объекте "Desktop" нет ничего особенного, а вот окно и поток представляют куда больший интерес. В Windows, окна рабочего стола создаются несколько иначе, чем обычные окна, и используют единый поток обработки данных. Этот поток обрабатывает системные сообщения, отправленные окну рабочего стола даже в том случае, если оболочка проводника не запущена и пользователи видят лишь пустое окно рабочего стола.

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

Также, в ReactOS существует ещё одна, куда более серьёзная, проблема с обработкой создания рабочего стола. При создании окна, из нераспределённой памяти текущего рабочего стола выделяется часть памяти для хранения структуры WND, предназначенной для приёма системных сообщений для этого окна. При создании уже существующим окном нового окна рабочего стола, память для структуры WND выделяется из кучи старого окна, а не нового. Если старый объект рабочего стола уничтожается, его куча также изымается диспетчером памяти и впоследствии может быть использована повторно для каких-либо других целей. Однако новый объект рабочего стола всё ещё существует и при приёме сообщений полагается на существование структуры WND, находящейся в этом участке памяти. Это может вызвать повреждение памяти и сбой в работе нового рабочего стола со всеми вытекающими отсюда неприятными последствиями.

Яннис Адамопулос (Giannis Adamopoulos) потратил немало времени переписывая NtUserCreateDesktop, чтобы исправить все описанные выше проблемы. Самым крупным изменением стало объединение всех потоков рабочего стола в единый поток, который избавлял от необходимости многократного дублирования дескрипторов и ссылок на окна рабочего стола для передачи их соответствующим потокам. В теории, он мог бы создать механизм уничтожения потоков рабочего стола для удаления ненужных дескрипторов и ссылок и в старом коде, но объединение всех потоков рабочего стола в один поток устраняет саму необходимость дублирования дескрипторов и ссылок.

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

Так получилось, что необходимые данные о курсорах находятся в библиотеке user32, а код, отвечающий за обработку сообщений курсора находится в библиотеке win32k. Как только Яннис разберётся в том, как получить необходимую ему информацию, в ReactOS будет устранена ещё одна крупная архитектурная проблема.

Многим было бы интересно, зачем вообще нужно было связываться с этими очистками, ведь создание и уничтожение окна рабочего стола явно далеко не самое частое и обычное явление. В качестве одного из ответов на этот вопрос можно сказать, что ошибки такого рода приводят к утечке ресурсов, и хорошей практикой при программировании является устранение таких проблем, поскольку в непредусмотренной ситуации они могут привести к большим сложностям при отладке. Другим ответом может быть тот факт, что некоторые приложения в целях обеспечения безопасности используют несколько объектов "Window Station", и, следовательно, окон рабочего стола.

Для любопытных можно пояснить, что оболочка, которую пользователи видят в Windows, прежде всего состоит из сеансов, содержащих объекты "Window Station", в которых содержатся рабочие столы. Например, каждое соединение службы терминалов имеет свою собственную сессию, а в NT6 службы Windows выполняются в другом объекте "Window Station", чем рабочий стол пользователя. Это может оказаться полезным при создании изолированной от основной системы среды обработки данных (т.н. песочницы), и применяться в программах, которым необходимо защитить процесс от вмешательства простого пользователя и наоборот. Таким образом, создание и уничтожение окон рабочего стола является не такой уж и редкостью, как можно было бы предположить.

PSEH

Портируемая библиотека структурной обработки исключений (Portable Structured Exception Handling, PSEH) изначально была написана KJK::Hyperion, бывшим разработчиком проекта ReactOS, и предназначена для обеспечения структурной обработки исключений в компиляторах, созданных не в Microsoft. Для достижения этой цели, KJK пришлось пользоваться пользоваться недокуменитрованными функциями и особенностями поведения компилятора GCC, что, несомненно, является впечатляющим достижением, однако во многом зависит от прихотей разработчиков GCC, которые легко могут поменять в своём коде что-либо из того, что необходимо PSEH.

Тимо Кройцер (Timo Kreuzer) начал работу над новой версией PSEH, о завершении которой он объявил в прошлом месяце. Если вкратце, Тимо сделал различные изменения для обеспечения лучшей оптимизации и снижения сложности поддержки SEH. Прежде всего, пожалуй стоит объяснить, что собой представляют SEH и PSEH, чтобы облегчить понимание сути следующего абзаца. Обратите внимание на то, что последующий текст предполагает знания о том, какую роль имеют стеки в функциях программы, и общее понимание того, для чего необходимы исключения.

SEH - это функция уровня компилятора, поддерживаемая Microsoft, это означает, что её поддержка должна быть или встроена в компилятор, или же её реализация должна быть написана и размещена поверх него. Компилятор C++, созданный Microsoft, разумеется уже содержит в себе её поддержку, в то время как в GCC и Clang такая поддержка отсутствует. Разработка встроенной поддержки SEH в компиляторе представляет собой далеко не самую тривиальную работу, альтернативой этому является использование PSEH - довольно интересного хака, располагающегося поверх GCC. Поддержка SEH в коде программы обеспечивается посредством расширений языка программирования, в частности при использовании ключевых слов __try, __except, и __finally.

Те, кто знаком с обработкой исключений в C++, заметят отсутствие ключевого слова catch и добавление блока finally. Здесь ключевое слово __except выступает в роли эквивалента catch. Во многих аспектах это значительно усложняет функциональность __except, что, в свою очередь, может усложнить поддержку SEH. Ключевое слово __except позволяет программисту задать фильтр исключений, ответственный за принятие решения касаемо того, должен ли быть выполнен блок кода поддержки исключений, обёрнутый __except, или нет. В обычном C++, ключевое слово catch определяет единственное исключение, которое должно быть обработано соответствующим ему блоком.

В SEH, фильтр исключения может представлять собой свою собственную функцию. Дополнительным преимуществом является то, что любая функция, указанная как фильтр, не выполняется непосредственно. Вначале управление передаётся другой функции, которая и выполняет фильтр в зависимости от того, является ли он функцией или блоком встроенного кода. Каждая функция, содержащая в себе блоки SEH, получает в своё распоряжение специальную выделенную в стеке структуру, содержащую всю необходимую информацию обо всех блоках SEH внутри этой функции. Эта информация включает в себя адреса фильтров исключений, необходимые SEH для их фактического выполнения при получении ей управления от программы.

Помимо того, фильтры исключений и блоки finally должны иметь доступ к переменным функции, в которую они встроены, так как вся суть исключения состоит в том, чтобы справляться с ошибками, которые возникают из-за состояния какой-либо конкретной функции. Интуитивно можно было бы ожидать, что это не будет представлять трудностей, поскольку фильтры и блоки finally являются всего лишь видом вложенных функций, однако это не так. Как уже говорилось выше, фильтры и блоки finally вызываются не самой функцией, а функцией-посредником. Этот уровень абстракции фактически вносит беспорядок в стеки функций и требует доработки.

При использовании компилятора, поддерживающего SEH, поддержка всей этой инфраструктуры происходит незаметно. При использовании же, например, GCC, что-то должно заставить GCC сгенерировать код и данные способом, который может быть применим для воссоздания функциональности SEH, и это что-то - PSEH. В дополнение к обеспечению определения для ключевых слов SEH, PSEH также отвечает за настройку инфраструктуры, позволяющей удостовериться, что код в блоках SEH try/except/finally работает правильно. В частности, PSEH использует вложенные функции для реализации фильтров выражений и блоков finally.

Также, стоит отметить то, что ни в C, ни в C++ нет собственной настоящей поддержки вложенных функций. GCC достигает этого с помощью расширения, на которое полагается PSEH, так что PSEH, в своём текущем виде, специфичен для GCC. Если кто-либо захочет попытаться портировать PSEH на, например, Clang, то он столкнётся с необходимостью поиска эквивалентных функций для достижения тех же результатов, что выльется в полное переписывание кода PSEH.

Немалая часть изменений, внесённых Тимо в PSEH3, связана с предоставлением GCC большего количества подсказок с целью получения более качественного кода, вместо того, чтобы вручную задавать их в библиотеке PSEH. Несколько других изменений предназначены для упрощения оптимизации генерируемого GCC кода поддержки блоков SEH. Вероятно, наиболее архитектурно примечательным изменением было удаление т.н."батутов" вложенных функций. Эти батуты представляют собой небольшие динамически генерируемые и помещаемые в стек кусочки кода, ответственные за предоставление доступа к родительскому стеку до передачи управления вложенной функции.

Это было способом работы PSEH2 в условиях беспорядка в стеках функций, вызванным косвенным выполнением вложенных функций. В PSEH3 функции батутов были разделены. Прежде всего, Тимо заставил GCC компилировать вложенные функции со статическими адресами и создавал их таблицу, что избавило от необходимости вычислять их адреса во время их выполнения и позволило функции обработки исключений SEH легко их находить. Указатель кадра стека, необходимый вложенной функции всё ещё нужно вычислять динамически, поэтому вложенная функция сейчас вызывается дважды.

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

Управление службами

Диспетчер управления службами (Service Control Manager, SCM) осуществляет запуск, остановку и контроль служб в Windows. В NT5 службы могут попытаться заблокировать базу данных служб, когда они запускаются, что бы быть уверенными, что никакая другая служба не запускается вместе с ними. В NT6 и старше эта возможность отсутствует, так как все зависимости, которые могли бы осложнить одновременный запуск нескольких служб, теперь обрабатываются корректно и в этой функциональности нет необходимости.

С другой стороны, ReactOS поддерживал только блокировку базы данных SCM для создания и удаления служб. Эрмес Белуска Маито, новый разработчик присоединившейся к проекту, реализовал необходимые механизмы блокировки. Теперь, службы, которые пытаются заблокировать уже заблокированную базу данных, получат корректное сообщение об ошибке.

Другое направление, над которым работал Эрмес, заключается в том, как SCM должен реагировать на системный сбой. Обычно SCM может сделать одну из четырех вещей, когда служба неожиданно останавливается: ничего не делать, выполнить другую программу, перезапустить службу или перезагрузить систему. Эти действия могут быть указаны пользователем или установщиком службы, хотя проще всего положиться на значение по умолчанию: "ничего не делать". Остановка критической службы, такой как PnP, инициирует перезагрузку системы. Сейчас Эрмес реализовал только набросок обработки сбоя, и необходима доработка, прежде чем SCM сможет собственно находить проблемы.

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

Параметры

Поддержка пробелов в путях в Windows может сделать жизнь труднее при обработке путей в параметрах командной строки. Фактически, проблема может быть обобщена к разделению пробелом отдельных элементов одного параметра вместо разделения разных параметров. Обходным решением является окружение параметров кавычками, которые, как предполагается, должны неизменными передавать параметры программе, и позволять программе самой думать, что с ними сделать. ReactOS, однако, удалял кавычки, что в значительной степени повлияло на каждую существующую программу, которая полагалась на кавычки, чтобы разграничить параметры с пробелами. Гермес отследил проблему до кода в среде выполнения C в ReactOS и справился с проблемой.

Newsletters
30-39 #30#31#32#33#34#35#36#37#38#39
40-49 #40#41#42#43#44#45#46#47#48#49
50-59 #50#51#52#53#54#55#56#57#58#59
60-69 #60#61#62#63#64#65#66#67#68#69
70-79 #70#71#72#73#74#75#76#77#78#79
80-89 #80#81#82#83#84#85#86#87#88#89
90-99 #90#91#92#93#94#95#96#97#98#99