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

Z98, “ReactOS Newsletter: Newsletter 93”, public translation into Russian from English More about this translation.

Translate into another language.

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

В операционных системах семейства 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. Эрмес Белуска Маито (Hermès Bélusca-Maïto), наш новый разработчик, недавно присоединившийся к проекту, реализовал необходимые механизмы блокировки. Теперь, службы, которые пытаются заблокировать уже заблокированную базу данных, получат корректное сообщение об ошибке и смогут повторить попытку через некоторое время.

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

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

Параметры командной строки

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

© ReactOS Team

Original (English): ReactOS Newsletter: Newsletter 93

Translation: © evilslon, Сергей, Knivy .

translatedby.com crowd

Like this translation? Share it or bookmark!