вторник, 22 декабря 2009 г.

Как Windows 95 меняла базовый адрес DLL?

Это перевод How did Windows 95 rebase DLLs? Автор: Реймонд Чен.

Windows 95 обрабатывала смену базового адреса DLL (DLL-rebasing) совершенно другим способом, нежели в Windows NT.

Когда Windows NT определяла, что DLL нужно загрузить по адресу, отличному от предпочтительного базового адреса, она проецировала всю DLL с флагом copy-on-write, правила её адреса (что приводило к выгрузке всех страниц с правками (fix up) в файл подкачки), после чего восстанавливала флаг read-only/read-write на страницы (Ларри Остерман в этом году подробно это разбирал).

Windows 95, с другой стороны, перебазировала DLL последовательно. Это было ещё одной уступкой Windows 95 из-за очень сильных ограничений на требования к памяти. Вспомните, что она должна была работать на машине с 4 Мб оперативной памяти. Если бы она обрабатывала перебазирование DLL тем же способом, что и Windows NT, то загрузка 4-х мегабайтной DLL и её исправление заняло бы всю память машины, приводя к выгрузке в файл подкачки всей памяти, которую лучше было бы придержать в памяти!

Когда DLL нуждалась в смене базового адреса, Windows 95 просто делала отметку о новом базовом адресе DLL, не делая ничего сверх этого. Настоящая работа случалась, когда страницы этой DLL подгружались в память. Когда сырая страница загружалась с диска в память, то все правки (fix up) применялись к странице на лету, производя её перемещение. Эти правки проецировались в адресное пространство процесса, и программа могла продолжать своё выполнение.

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

И вот, пожалуйста: перебазирование DLL по-странично по требованию вместо перебазирования DLL целиком при загрузке. Что могло тут пойти не так?

Подсказка: это проблема, которая свойственна только для x86.

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

Чтобы сохранить эту информацию, менеджер памяти ассоциирует с каждой страницей перемещаемой DLL флаг, который показывает, создала ли страница перенос на своём конце. Флаг может иметь один из следующих состояний:
  • Да, есть перенос на конце.
  • Нет, на конце переноса нет.
  • Я не знаю, был ли перенос на конце.
Чтобы исправить страницу, которая содержит правку через начало страницы, вы проверяете флаг предыдущей страницы. Если флаг говорит "Да", вы добавляете единичку к вашей правке, если флаг говорит "Нет", вы ничего не добавляете.

Но что, если флаг говорит "Я не знаю"?

Если вы не знаете, то вам нужно это выяснить. Потрогайте предыдущую страницу, чтобы загрузить её и сделайте в ней правки. Как часть вычисления поправок, будет установлен флаг, говорящий, есть ли перенос на конце. Когда предыдущая страница исправлена, вы теперь можете проверить флаг (который теперь не будет в состоянии "Я не знаю"), и он сообщит вам, надо ли вам добавлять единичку.

И вот, пожалуйста: перебазирование DLL по-странично по требованию вместо перебазирования DLL целиком при загрузке. Даже в присутствии правок через границу страниц. Что могло тут пойти не так?

Подсказка: что может пойти не так с рекурсией?

Проблема в том, что предыдущая страница в свою очередь может иметь правку, пересекающую начало страницы, а флаг для предыдущей предыдущей страницы может также быть в состоянии "Я не знаю". Теперь вам надо обработать третью страницу.

К счастью, на практике редко бывают случаи правки больше трёх страниц за раз. Три страницы исправлений по цепочке были рекордом.

(Конечно же, другим способом остановить рекурсию было бы выполнение только части правок на предыдущей странице - только минимум, необходимый для вычисления нужен ли перенос на конце или нет, без попыток исправить всю страницу. Но Windows 95 шла вперёд и исправляла всю страницу, потому что она считала: "эй, я тут столько заплатила за эту страницу, почему бы мне её не использовать").

В чём была мораль сегодняшней истории? Не думаю, что она у меня была. Это была просто историческая закавырка, которая, как я надеюсь, будет кому-то интересной.

Читать далее.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.