Загрузка общих библиотек и использование оперативной памяти

41

Мне интересно, как Linux управляет общими библиотеками. (на самом деле я говорю о Maemo Fremantle, дистрибутиве на основе Debian, выпущенном в 2009 году и работающем на 256 МБ ОЗУ).

Предположим, у нас есть два исполняемых файла, ссылающихся на libQtCore.so.4 и использующих его символы (используя его классы и функции). Для простоты давайте назовем их aи b. Мы предполагаем, что оба исполняемых файла ссылаются на одни и те же библиотеки.

Сначала мы запускаем a. Библиотека должна быть загружена. Загружается ли он целиком или загружается в память только в той части, которая требуется (поскольку мы не используем каждый класс, загружается только код, касающийся используемых классов)?

Затем мы запускаем b. Мы предполагаем, что aвсе еще работает. bтакже ссылается на libQtCore.so.4 и использует некоторые классы, которые aиспользуются, но также некоторые, которые не используются a. Будет ли библиотека загружена дважды (отдельно для aи отдельно для b)? Или они будут использовать тот же объект уже в оперативной памяти. Если bновые символы не используются и aуже запущены, увеличится ли объем оперативной памяти, используемой общими библиотеками? (Или разница будет незначительной)

marmistrz
источник

Ответы:

54

ПРИМЕЧАНИЕ. Я предполагаю, что на вашей машине установлен модуль отображения памяти (MMU). Существует версия для Linux (µClinux), для которой не требуется MMU, и этот ответ там не применим.

Что такое MMU? Это аппаратное обеспечение - часть процессора и / или контроллера памяти. Понимание связывания с общей библиотекой не требует от вас точного понимания того, как работает MMU, просто MMU позволяет различать адреса логической памяти (используемые программами) и физические адреса.адреса памяти (те, которые действительно присутствуют на шине памяти). Память разбита на страницы, обычно размером 4K в Linux. Для страниц 4k логические адреса 0–4095 - это страницы 0, логические адреса 4096–8191 - страницы 1 и т. Д. MMU отображает их на физические страницы ОЗУ, и каждая логическая страница обычно может быть отображена на 0 или 1 физических страницах. Данная физическая страница может соответствовать нескольким логическим страницам (так распределяется память: несколько логических страниц соответствуют одной и той же физической странице). Обратите внимание, что это относится независимо от ОС; это описание оборудования.

При переключении процессов ядро ​​изменяет отображения страниц MMU, чтобы у каждого процесса было свое пространство. Адрес 4096 в процессе 1000 может быть (и обычно) полностью отличным от адреса 4096 в процессе 1001.

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

Теперь есть несколько способов создания библиотек. Допустим, программа вызывает функцию foo()в библиотеке. Процессор ничего не знает о символах или вызовах функций на самом деле - он просто знает, как перейти к логическому адресу и выполнить любой код, который он там найдет. Это можно сделать несколькими способами (и аналогичные вещи применяются, когда библиотека обращается к своим глобальным данным и т. Д.):

  1. Он может жестко закодировать некоторый логический адрес для вызова. Это требует, чтобы библиотека всегда загружалась по одному и тому же логическому адресу. Если двум библиотекам требуется один и тот же адрес, динамическое связывание завершается ошибкой, и вы не можете запустить программу. Для библиотек могут потребоваться другие библиотеки, поэтому для каждой библиотеки в системе должны быть уникальные логические адреса. Это очень быстро, хотя, если это работает. (Это то, как все делалось, и тип настройки, которую выполняет предварительная ссылка, вроде).
  2. Он может жестко закодировать поддельный логический адрес и указать динамическому компоновщику редактировать правильный адрес при загрузке библиотеки. При загрузке библиотек это занимает немало времени, но после этого происходит очень быстро.
  3. Это может добавить слой косвенности: используйте регистр ЦП для хранения логического адреса, по которому загружена библиотека, а затем получите доступ ко всему как смещение от этого регистра. Это накладывает затраты производительности на каждый доступ.

Почти никто больше не использует # 1, по крайней мере, не в системах общего назначения. Сохранение этого уникального списка логических адресов невозможно в 32-разрядных системах (их недостаточно для обхода) и административный кошмар в 64-разрядных системах. Предварительное связывание делает это, однако, для каждой системы.

Будет ли использоваться # 2 или # 3, зависит от того, была ли библиотека построена с -fPICопцией GCC (позиционно-независимый код). № 2 без, № 3 с. Как правило, библиотеки построены с -fPIC, так что # 3 это то, что происходит.

Для получения более подробной информации см. Ульрих Дреппер « Как писать общие библиотеки» (PDF) .

Итак, наконец, на ваш вопрос можно ответить:

  1. Если библиотека построена с -fPIC (как это почти наверняка должно быть), подавляющее большинство страниц одинаково для каждого процесса, который ее загружает. Ваши процессы aи bвполне могут загружать библиотеку по разным логическим адресам, но те будут указывать на одни и те же физические страницы: память будет распределена. Кроме того, данные в ОЗУ точно совпадают с данными на диске, поэтому они могут быть загружены только тогда, когда это необходимо обработчику ошибок страницы.
  2. Если библиотека построена без -fPIC , то оказывается, что большинство страниц библиотеки будут нуждаться в редактировании ссылок, и будут отличаться. Следовательно, они должны быть отдельными физическими страницами (поскольку они содержат разные данные). Это означает, что они не являются общими. Страницы не соответствуют тому, что находится на диске, поэтому я не удивлюсь, если вся библиотека загружена. Впоследствии он, конечно, может быть выгружен на диск (в файле подкачки).

Вы можете проверить это с помощью pmapинструмента или непосредственно, проверив различные файлы в /proc. Например, вот (частичный) вывод pmap -xдвух разных вновь порожденных bcs. Обратите внимание, что адреса, отображаемые pmap, как правило, являются логическими адресами:

pmap -x 14739
Address           Kbytes     RSS   Dirty Mode  Mapping
00007f81803ac000     244     176       0 r-x-- libreadline.so.6.2
00007f81803e9000    2048       0       0 ----- libreadline.so.6.2
00007f81805e9000       8       8       8 r---- libreadline.so.6.2
00007f81805eb000      24      24      24 rw--- libreadline.so.6.2


pmap -x 17739
Address           Kbytes     RSS   Dirty Mode  Mapping
00007f784dc77000     244     176       0 r-x-- libreadline.so.6.2
00007f784dcb4000    2048       0       0 ----- libreadline.so.6.2
00007f784deb4000       8       8       8 r---- libreadline.so.6.2
00007f784deb6000      24      24      24 rw--- libreadline.so.6.2

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

Из разницы в размерах (Кбайт) и резидентном размере (RSS) видно, что весь сегмент библиотеки не был загружен. Наконец, вы можете видеть, что для больших отображений грязное значение равно 0, что означает, что оно точно соответствует тому, что находится на диске.

Вы можете повторно запустить с pmap -XX, и он покажет вам - в зависимости от используемой версии ядра, так как вывод -XX зависит от версии ядра - что первое отображение имеет значение Shared_Clean176, что в точности соответствует RSS. Sharedпамять означает, что физические страницы совместно используются несколькими процессами, и, поскольку она соответствует RSS, это означает, что вся библиотека, находящаяся в памяти, является общей (см. также раздел «См. также ниже» для более подробного объяснения общего и частного):

pmap -XX 17739
         Address Perm   Offset Device   Inode  Size  Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous AnonHugePages Swap KernelPageSize MMUPageSize Locked                   VmFlagsMapping
    7f784dc77000 r-xp 00000000  fd:00 1837043   244  176  19          176            0             0             0        176         0             0    0              4           4      0       rd ex mr mw me sd  libreadline.so.6.2
    7f784dcb4000 ---p 0003d000  fd:00 1837043  2048    0   0            0            0             0             0          0         0             0    0              4           4      0             mr mw me sd  libreadline.so.6.2
    7f784deb4000 r--p 0003d000  fd:00 1837043     8    8   8            0            0             0             8          8         8             0    0              4           4      0       rd mr mw me ac sd  libreadline.so.6.2
    7f784deb6000 rw-p 0003f000  fd:00 1837043    24   24  24            0            0             0            24         24        24             0    0              4           4      0    rd wr mr mw me ac sd  libreadline.so.6.2


Смотрите также

derobert
источник
Это означает, что предварительные ссылки больше не нужны (и что -fPICиспользование некоторое время назад полностью изменилось)?
Хауке Лагинг
@crisron Спасибо за исправления. К вашему сведению, Markdown будет рассчитывать на вас - вывод моего повторения 1. был правильным. Кроме того, я внес несколько изменений в то, что вы сделали: «начальный адрес» - это технический жаргон, я, вероятно, вызвал путаницу, поставив «логический» в середине. Я изменил его, чтобы избавиться от жаргона. Кроме того, страницы эквивалентны этим адресам, AFAIK невозможно, чтобы эти адреса когда-либо были другой страницей. Я попробовал еще раз, меняя порядок, надеюсь, это будет понятнее.
Дероберт
блин, вот и ответ !!!
Эван Кэрролл