Что происходит, когда вы перезаписываете исполняемый файл с отображением в памяти? - PullRequest
11 голосов
/ 15 декабря 2010

После комментариев на один из моих вопросов я заинтригован тем, что происходит, когда кто-то перезаписывает исполняемый файл.Мне нужно проверить мое понимание по этому вопросу.

Скажи, что у меня есть /usr/bin/myprog.Я запускаю его, и поэтому ОС загружает /usr/bin/myprog, вероятно, через http://en.wikipedia.org/wiki/Memory-mapped_file#Common_uses.

. По какой-то причине этот процесс остается в памяти, и я решаю, что на самом деле я исправил ошибку и перезаписал /usr/bin/myprog.

Итак, насколько я понимаю:

  • Если экземпляр myprog уже загружен, и я заменяю файл, из которого myprog уже был загружен, экземпляр myprog не изменено.
  • Если я запускаю новый экземпляр myprog, он будет использовать новый код.

Правильно ли я?

Однако, согласнок статье о файлах с отображением в памяти такой метод позволяет разработчику обрабатывать части файла как физическую память.

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

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

Так какСочетание отображения памяти / пейджинга по требованию работает для выполнения программ, пожалуйста?Может ли перезапись этого файла вызвать сбой страницы на каждой из страниц исполняемого файла, чтобы убедиться, что он загружен для текущего запущенного процесса?

Я провел быстрый эксперимент с этим:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv)
{
    printf("Program resident...");
    while(1)
    {
        printf("??? Just notifying you I'm still here...\n");
        usleep(1000000);
    }

    return 0;
}

И, конечно же, я мог бы а) заменить этот исполняемый файл во время его работы и б) его вывод не изменился.

ТАК что происходит?Я был бы особенно признателен за любые предложения относительно того, что я могу сделать, чтобы увидеть, что происходит (Linux или Windows).

Спасибо всем.

Редактировать: вопрос, на который я ссылался, который вызвал этот вопрос: Обновления без перезагрузки - какие проблемы случаются на практике?

Кроме того, я знаю, что это не относится конкретно к программированию, а является результатом обновления исполняемого файла.Однако мне все еще интересно, и я не могу придумать лучшего места, чтобы спросить его.

Ответы [ 5 ]

10 голосов
/ 16 декабря 2010
  1. То, что происходит, зависит, прежде всего, от того, действительно ли вы rm /usr/bin/myprog и затем создаете новый, или open() и write() для существующих /usr/bin/myprog.

  2. Если вы rm старый /usr/bin/myprog файл и затем создадите новый файл с тем же именем, то драйвер ядра / файловой системы даст новой версии новый индекс и старый индексостается лежать в файловой системе /proc, пока процесс, который ее открыл, не закроет ее.Ваш существующий процесс /usr/bin/myprog имеет собственную частную версию файла без изменений, пока не будет close() s дескриптор файла.

  3. Все операционные системы (Windows, Linux, возможно OS X).) использовать отображение памяти по запросу (mmap() для posix, я не могу вспомнить эквивалент для Windows - VirtualAlloc()?) для загрузки процесса. Таким образом, любые части исполняемого файла, которые не затрагиваются, никогда не загружаются в память.

  4. Если бы это был обычный mmap() d-файл, идва процесса открыли / отобразили его, и ни один из них не указал MAP_PRIVATE (т.е. копирование при записи) в вызове mmap(), тогда два процесса будут по существу просматривать одну и ту же страницу физической памяти и, при условии, что обаиз них называется mmap() с PROT_READ |PROT_WRITE, они увидят модификации друг друга.

  5. Если бы это был обычный файл mmap() d, процесс 1 открыл / отобразил его, а затемпроцесс 2 начал возиться с файлом на жестком диске посредством вызовов write() (не мой mmap ing), процесс 1 действительно видит эти изменения.Я предполагаю, что ядро ​​замечает, что файл изменяется и перезагружает затронутые страницы.

  6. Я не знаю точно, есть ли какое-либо специальное поведение mmap() дляисполняемые образы?Если бы я взломал указатель на одну из моих функций и изменил код, пометил бы он страницу как грязную?Будет ли грязная страница записана обратно в /usr/bin/myprog?Когда я пытаюсь это сделать, он вызывает ошибки, поэтому я предполагаю, что хотя страницы _TEXT отображаются с MAP_SHARED, они также, вероятно, не получают PROT_WRITE и, следовательно, segfault при записи в.Конечно, разделы _DATA также загружаются в память, и их нужно модифицировать, но они могут быть помечены MAP_PRIVATE (копирование при записи) - так что они, вероятно, не сохранят свое соединение с файлом /usr/bin/myprog.,

  7. Пункт 6 касается непосредственно исполняемого файла, модифицирующего себя.Пункт 5 касался изменения произвольного файла mmap() d на уровне write().Когда я пытаюсь изменить исполняемый файл (то есть mmap() 'd) в другом процессе с помощью write(), я не получаю те же результаты, что и в пункте 5. Я могу вносить всевозможные ужасные изменения в исполняемый файл с голымwrite() звонит, и ничего не происходит.Затем, когда я выхожу из процесса и пытаюсь запустить его снова, он падает (конечно, после всего, что я делал с исполняемым файлом).Это смущает меня.Я не могу переставить параметры в mmap(), чтобы заставить его вести себя таким образом - не копировать при записи, но не затрагивать изменения в отображенном файле.

  8. Ну, я вернулся к Библии (Стивенс), и большая проблема - MAP_PRIVATE против MAP_SHARED.MAP_PRIVATE копируется при записи, а MAP_SHARED - нет.MAP_PRIVATE сделает копию сопоставленной страницы, как только вы внесете в нее изменения.Не определено, будет ли модификация исходного файла распространяться на отображенные MAP_PRIVATE страницы, но в OS X они этого не делают.MAP_SHARED поддерживает соединение с исходным файлом, позволяя обновлениям файла распространяться на страницы памяти и наоборот.Если блок памяти сопоставлен MAP_PRIVATE, никакие изменения, внесенные в него, никогда не будут записаны на диск.MAP_SHARED OTOH позволяет изменять файл путем записи на отображенные страницы.

  9. Загрузчик изображений отображает исполняемые файлы как MAP_PRIVATE. Это объясняет поведение в пункте 6 - взлом указателя на код функции и последующее его изменение, даже если у вас было на это разрешение, не записывает данные обратно на диск. Теоретически должна быть возможность изменить исполняемый файл /usr/bin/myprog сразу после загрузчика образа ОС mmap(), но всякий раз, когда я смотрю на очень большие исполняемые файлы с vmmap, их раздел TEXT всегда кажется полностью постоянным. Я не знаю, связано ли это с тем, что загрузчик образов OS X затрагивает все страницы, чтобы убедиться, что они скопированы, или же менеджер страниц OS X просто очень агрессивно настроен сделать резидентные страницы (так оно и есть), но я не смог сделать исполняемый файл в OS X, чей раздел TEXT не был полностью резидентным, как только начался main().

  10. Загрузчик изображений OS X очень агрессивно относится к загрузке отображаемых страниц. Я заметил, что когда mmap() 'файл, он должен быть очень большим, прежде чем OS X решит оставить его нерезидентным. файл 1 ГБ полностью загружается, но только около 1,7 ГБ из файла 3 ГБ становится резидентным. Это на машине с 8 ГБ ОЗУ и 64-битным ядром OS X.

9 голосов
/ 20 декабря 2010

В Linux, если вы замените исполняемый файл во время его работы, результаты будут непредсказуемыми, и это может привести к сбою. Страницы, которые были изменены (например, инициализированные данные "bss"), не будут затронуты, но страницы, которые не были изменены (например, большая часть кода), будут затронуты.

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

Однако все это происходит только в том случае, если вы на самом деле перезаписываете того же файла .

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

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

Что именно делает компоновщик со своим выводом, я не знаю. Но / usr / bin / install создает новый файл. Я ожидаю, что это поведение вполне преднамеренное.

2 голосов
/ 06 января 2012

Я нашел эту ссылку гораздо более кратким объяснением.Посмотрите на раздел «Обновление», где автор обновил свой оригинальный пост.

http://it.toolbox.com/blogs/locutus/why-linux-can-be-updated-without-rebooting-12826

Весь файл НЕ загружается в память, они считываются в буферы в кластерах(это технический термин, обычно это 4k, но вы можете установить его при настройке файловых систем).

Когда вы открываете файл, ядро ​​переходит по ссылке и назначает дескриптору дескриптора файла (aномер, который он отслеживает внутренне).Когда вы удаляете файл, вы «отменяете связь» с индексом;дескриптор файла все еще указывает на это.Вы можете создать новый файл с тем же именем, что и старый файл после его удаления, эффективно «заменив» его, но он будет указывать на другой индекс.Любые программы, у которых все еще открыт старый файл, могут получить доступ к старому файлу через дескриптор файла, но вы фактически обновили программу на месте.Как только программа завершает работу (или закрывает файл) и запускается (или пытается снова получить к нему доступ), она получает доступ к новому файлу, и там у вас это есть, полная замена файла на месте!

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

1 голос
/ 16 декабря 2010

В Mac OS X, когда я обновляю любое работающее приложение, программа обновления запрашивает, чтобы приложение было полностью закрыто для продолжения обновления, в некоторых случаях оно продолжается, но обновления не вступают в силу, пока приложение запустить снова. Иногда в нем указываются имена используемых библиотек, которые вызывают блокировку обновления. Я говорю это потому, что приложение, кажется, блокирует библиотеки, от которых оно зависит, и программа обновления, кажется, знает, что не трогает их, если они используются. Таким образом, не перезаписывать какую-либо часть или все работающее приложение.

Например, Google Chrome запрашивает перезапуск для установки обновлений, которые вступают в силу в Windows, Linux и Mac OS X. Надеюсь, это даст вам подсказку, с чего начать.

0 голосов
/ 16 декабря 2010

В целом вы правы: двоичный файл хранит только образ исполняемого файла. То, что на самом деле хранится в памяти в любой конкретный момент, является копией.

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

...