В бинарнике приложения указаны имена so. При запуске бинарника система размещает их в том же адресном пространстве и связывает все точки вызова функций между модулями.
Также можно грузить so-шки прямо из кода при помощи dlopen().
В ВАП каждого работающего с ними процесса, т.к. динамическое связывание как раз предполагает создание ссылок на so и их символы, на привязыанные символы можно посмотреть через например objdump -R /bin/bash
Тут фишка в использовании виртуальной памяти, код SO загружается по некому физическому адресу и эти страницы мапятся в виртуальное пространство процессов которые хотят эту SO использовать
разные процессы могут совместно использовать загруженные модули
Не могут. Модули загружаются заново в каждый процесс. Участки памяти только для чтения могут совместно использоваться, но это по сути форма сжатия данных.
Просто помню что дреппер писал, что в старом линуксе so шки как раз загружались, а с 2.4.** уже нет, да и
Dynamically linked binaries, in contrast, are not com-
plete when they are loaded from disk. It is therefore
not possible for the kernel to immediately transfer con-
trol to the application. Instead some other helper pro-
gram, which obviously has to be complete, is loaded as
well. This helper program is the dynamic linker. The task
of the dynamic linker is it, to complete the dynamically
linked application by loading the DSOs it needs (the de-
pendencies) and to perform the relocations. Then finally
control can be transferred to the program.
Нет, so подгружает процесс если этот so не был подгружен ранее иначе он выполняет только настройку ссылок, более того одна из ключевых плюшек so как раз и состоит в экономии памяти и отсутствии необходимости заново подгружать so, не считая возможности непересобирать исполнимые файлы при обновлении so
Это называется разделяемая память и в данном случае обслуживается ядром, загрузчик всегда вызывает один и тот же mmap() хоть первый раз, хоть 100500-ый. Это никак не влияет на логику программы и является чистой оптимизацией.
Without dynamic linking, all programs would need their own copy of the these libraries and would need far more disk space and virtual memory. In dynamic linking, information is included in the ELF image’s tables for every library routine referenced. The information indicates to the dynamic linker how to locate the library routine and link it into the program’s address space.
А детали реализации - это детали реализации, ссылатся на которые нельзя, т.к. могут везде быть разными, т.к. везде разные и не поверите, но и на сисетмах без отображения в память разделяемые библиотеки не копируются к вап процессов, а отсылать к деталям реализации некорректно, т.к. описанный вами сценарий работает в случае MMU.
Что пишет Левин:
Linux adds a single uselib () system call that takes the file name and
address of a library and maps it into the program address space. The startup
routine bound into the executable runs down the list of libraries, doing a
uselib() on each.
The BSD/OS scheme uses the standard mmap () system call that maps
pages of a file into the address space and a bootstrap routine that is linked into
each shared library as the first thing in the library.
Т.е. отображение в вап без копирования, т.е. опять же физического копирования не происходит идёт лишь подстройка для ВАП и в целом физического копирования и не будет, а mmap не оптимизация копирования, а вариант реализации адресного отображения.
Linux adds a single uselib () system call that takes the file name and address of a library and maps it into the program address space.
uselib объявлен устаревшим и не всегда поддерживается:
This obsolete system call is not supported by glibc. No declaration is provided in glibc headers, but, through a quirk of history, glibc versions before 2.23 did export an ABI for this system call.
Since Linux 3.15, this system call is available only when the kernel is configured with the CONFIG_USELIB option.
Нам объясняли по другому. В памяти системы существует лишь одна копия libstdc++, а каждый процесс имеет свои изменяемые данные. А иначе зачем это все затевать, если можно собирать статически? Я вот только не поняла, как прцесс вызывает из этой памяти функции?
Нам объясняли по другому. В памяти системы существует лишь одна копия libstdc++, а каждый процесс имеет свои изменяемые данные. А иначе зачем это все затевать, если можно собирать статически? Я вот только не поняла, как прцесс вызывает из этой памяти функции?
Сильно упрощенно, картина следующая:
В памяти системы существует такой объект как «отображение файла в память». Это означает, что система в регион адресов кладёт данные из файла, а если ей не хватает оперативной памяти, может просто выкинуть их. (А когда потребуются, снова загрузить из файла.)
Эти данные, сколько раз файл не отображай, он оперативной памяти съест одинаково. Т.к. отображается в процессы из одних и тех же физических страниц.
Но в общем случае нельзя просто отобразить so в память, и она заработает. Требуется выполнить связывание, т.е. установить правильные ссылки между модулями.
Для этого загрузчик объектов so должен модифицировать память.
Поэтому поверх «отображения файлов в память» прозрачным образом существует еще одно (анонимное) отображение. Как только загрузчик so пытается изменить данные, система автоматически создаёт копию страницы, и дальше процесс работает с этой копией.
Таким образом, с точки зрения системы, есть, например, 95% страниц, которые полностью повторяют содержимое файла (и могут быть выброшены и загружены заново при необходимости) и еще 5% страниц, которые она просто так выбросить не может, т.к. они содержат изменения, необходимые для работы so.
А с точки зрения прикладного кода, он просто загрузил содержимое so-файла в свою память, и его загруженная копия не имеет отношения к другим копиям.
Всё зависит от того, на каком уровне абстракции рассматривать этот механизм.
Нам объясняли по другому. В памяти системы существует лишь одна копия libstdc++, а каждый процесс имеет свои изменяемые данные.
Плохо вам объясняли. Общие только данные для чтения такие как машинный код (секция .text) и строки/константы (секция .rodata). Глобальные переменные модуля (секция .bss) в каждом процессе свои. В каждый процесс модуль отображается по своему адресу, который обычно отличается в разных процессах.
А иначе зачем это все затевать, если можно собирать статически?
Чтобы можно было поменять файл модуля без перекомпиляции программы.
Я вот только не поняла, как прцесс вызывает из этой памяти функции?
Сошки работают в адресном пространстве процесса, они вообще могут не быть pic и будут грузиться по фиксированному адресу в этом случае. Маппинг адресов применяется ко всем исполняемым файлам. Парадигма предназначена для экономии дискового пространства а не памяти. Связавание с сошкой архитектурно-зависимо и там вообще могут генериться таблицы, спецсекции и машкод. Повторное использование секций - фича полезная, но с динамической линковкой непрсредственно не связанная.