LINUX.ORG.RU

Если мы запускаем бинарь N раз, он N раз целиком загружается память (Y/N)?

 ,


0

4

Сабж. Линукс. Уясняю для себя. Есть жирный статический бинарь (несколько десятков мегабайт). Если он запускается N раз и работает одновременно над разными задачами, образ файла (секции кода и данных) N раз дублируются в оперативной памяти?

Подозреваю, что да, что ядро пока не настолько хитровывернутое, чтобы смекать и экономить на таком, разделяя такие страницы памяти между независимыми процессами.

Ядро именно что настолько хитровывернутое. Если бинарь без релокаций или PIC/PIE, то секции кода и rodata совершенно точно будут просто замаплены в несколько процессов.

Разнообразные таблицы символов (динамические), всякие там GOT и PLT — это да. Стек и r/w данные — разумеется, тоже дублируются.

intelfx ★★★★★ ()
Ответ на: комментарий от beastie

KSM тут не работает. В линуксе он включается специальным madvise()'ом; я уверен, что загрузчик ELF не помечает каждый загружаемый эльф как mergeable.

intelfx ★★★★★ ()

На самом деле реализовать такое поведение (и оно скорее всего реализовано) - проще простого. В исполняемом файле есть секции. Некоторые из них read-only (код, rodata). Их можно вообще без задней мысли маппить на одни и те же физические адреса. С data ситуация сложнее и в принципе можно их продублировать. Хотя если очень хочется экономить память и на этот случай есть решение - copy on write, который аппаратно умеют все нормальные процессоры (можно пометить страницу памяти как r/o, в итоге при попытке записи будет исключение, которое ядро может обработать копированием страницы в новое место и снятием r/o флага).

То есть банально оперировать флагами секций исполняемого файла и страниц в памяти. Никаких супер-пупер-эврестических алгоритмов нафиг не нужно.

Ещё есть такая фигня (не знаю как в Linux, но в Windows такое активно используется) - использование исполняемого файла в качестве файла подкачки. То есть r/o секции, если мало памяти, не выгружаются в обычную подкачку, а просто удаляются из ОЗУ. При необходимости загрузка происходит из исполняемого файла. Поэтому на офтопике exe-файл оказывается занятым, пока приложение работает.

KivApple ★★★★★ ()
Ответ на: комментарий от intelfx

Зачем это нужно? Нам известен путь к ELF + флаги секций. Можно при загрузке ELF, который уже запущен, просто не грузить R/O секции, а смаппить их на те же физические адреса, что и у первого файла. Освобождать память с помощью подсчёта ссылок (чтобы данные выгрузились только после завершения последнего экземпляра приложения). По идее алгоритм очень простой к реализации, оверхед (при запуске приложения столько всего происходит, пробежаться по списку уже загруженных файлов ничего не стоит, один счётчик для подсчёта ссылок тоже ничего не стоит) микроскопический.

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

использование исполняемого файла в качестве файла подкачки

Аж дух захватило! Это неиспользование подкачки для исполняемых файлов, а не использование исполняемых файлов в качестве подкачки.

Поэтому на офтопике exe-файл оказывается занятым, пока приложение работает.

В линуксе та же фигня. Но только сам файл, а не его место в иерархии ФС.

const86 ★★★★★ ()

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

SZT ★★★★★ ()

Не нужен тут никакой KSM, работает тот же механизм, что и при форке. Ядро отслеживает, что у него в page cache уже есть нужные страницы в неизмененном виде и просто мапит их в твое виртуальное адресное пространство.

annulen ★★★★★ ()
Последнее исправление: annulen (всего исправлений: 2)

Сначала они статически линкуют бинари, а потом героически преодолевают проблемы…

anonymous ()
Ответ на: комментарий от i-rinat

Вот, поэкспериментируй:

(echo -n '"'; head -536870912c /dev/urandom | xxd -ps | tr -d '\n'; echo '"' ) > largeconstant.h
// q.c
#include <unistd.h>
#include <string.h>
const char *c =
#include "largeconstant.h"
;

int main(void) { strlen(c); sleep(10000); return 0; }
gcc q.c
for i in `seq 100`; do ./a.out &  done
i-rinat ★★★★★ ()
Ответ на: комментарий от RiseOfDeath

Я почему-то такое не на всех бинарниках замечал. Хотя вот достоверно подтвердить сейчас не готов.

Andrey_Utkin ★★ ()
Ответ на: комментарий от intelfx

Вопрос был скорее про «хитровывернутость». И она таки есть.

А вот про мульти-маппинг rodata статически скомпелированных програм я не совсем уверен. В динамических это точно используется. И при форке память не дублируется.

beastie ★★★★★ ()
Ответ на: комментарий от beastie

Ну rodata нужно релоцировать? Так-то маппится по умолчанию всё, вне зависимости от того, где какая линковка, а уже потом если что-то где-то нужно отрелоцировать или пропатчить, то происходит CoW отдельных страниц.

intelfx ★★★★★ ()
Последнее исправление: intelfx (всего исправлений: 1)
Ответ на: комментарий от beastie

Насколько я знаю при запуске бинарь даже не сразу весь в память грузится, а по мере выполнения. Естественно все процессы одного бинаря используют общую память под код независимо от статической/динамической сборки. Страницы кода могут также выкидываться и читаться заново если свободной памяти мало.

sergej ★★★★★ ()
Ответ на: комментарий от i-rinat

Спасибо за модель для разбирательства.

Но что-то у меня подозрения, что бинарь таки не сто раз загрузился отдельно, либо он не стал целиком грузиться, иначе бы не хватило памяти. https://gist.github.com/andrey-utkin/1bf88af5800d03a20a64

Andrey_Utkin ★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.