Saturday, July 25, 2020

МЕХАНИЗМ ПОТРЕБЛЕНИЯ ПАМЯТИ В LINUX.

оригинал
Эта статья для тех, кто когда-либо задавался вопросом: "Какого черта обычный текстовый редактор  KDE потребляет 25 МБ памяти?" многие считают, что приложения в Линукс, особенно программы KDE или GNOME, раздуты, основываясь в основном на выводе команды ps. Тем временем, это может быть как правдой, так и нет, в зависимости от программы. В общем случае это не так - многие программы гораздо более эффективно расходуют память, чем кажется.

Команда ps может выводить разную информацию о процессе, например,  process id, текущий статус, потребление ресурсов. Есть 2 показателя - VSZ and RSS, что означает “virtual set size” - количество виртуальной памяти, потребляемой процессом; и “resident set size” - количество физической памяти, потребляемой процессом.

Например:

USER       PID  %CPU  %MEM    VSZ     RSS    TTY  STAT   START   TIME   COMMAND
dbunker    3468     0.0          2.7      25400   14452    ?        S         20:19       0:00     kdeinit:kedit

Согласно выводу ps, Kedit потребляет около 25 МБ виртуальной памяти, а в физической занимает около 14 МБ (числа выше указаны в КиБ). Похоже, большинству людей  нравится случайным образом выбирать одно из этих чисел и считать его показателем реального использования памяти процессом. Я не буду сейчас объяснять разницу между VSZ & RSS, но, разумеется, это неверный подход. Ни одно из чисел не дает точного представления о том, сколько памяти требуется для работы Kedit.

PS не сообщает о реальном использовании памяти. На самом деле он показывает, сколько памяти потреблял бы каждый процесс, если бы он был единственным запущенным. Естественно, на реальной машине одновременно запущены десятки процессов, то есть числа, демонстрируемые VSZ & RSS почти наверняка неверные. Чтобы понять, почему, необходимо узнать, как Линукс работает с разделяемыми библиотеками программ.

Большинство основных программ в Линукс использует разделяемые библиотеки, чтобы обеспечить определенную функциональность. Например, редакторы текста из KDE будут использовать разделяемые библиотеки KDE (для взаимодействия с другими компонентами KDE), несколько библиотек Х (для отображения картинок и возможности копипастить). Многие из этих библиотек, особенно общего использования, как libc, используются также другими программами, работающими в системе. Так как библиотеки разделяемые, Линукс загружает в память всего по одной копии библиотеки, и каждая программа, ссылающаяся на неё, использует эту копию.

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

К сожалению, точное количество памяти, потребляемой процессом, не просто получить. Вы должны не только знать, как работает система, но и решить, как ответить на сложные вопросы. Должна ли учитываться при расчете потребления памяти процессом разделяемая библиотека, необходимая только для одного процесса? Если разделяемая библиотека используется несколькими процессами, следует ли разделить память, которую она потребляет, равномерно между разными процессами, или просто проигнорировать её? Здесь нет однозначного критерия - в зависимости от вашей ситуации ответы могут быть разными.

Учитывая неоднозначность, легко понять, почему ps не старается вычислить "правильные" значения.

Просмотр карты памяти процесса скажет достаточно. Давайте посмотрим на ситуацию с этим "огромным" Kedit. Для этого используем программу pmap -d:

Address Kbytes Mode Offset Device Mapping
0804800   40 r-x – 0000000000000000 0fe:00000 kdeinit
08052000 4 rw— 0000000000009000 0fe:00000 kdeinit
08053000 1164 rw— 0000000008053000 000:00000 [ anon ]
40000000 84 r-x– 0000000000000000 0fe:00000 ld-2.3.5.so
40015000 8 rw— 0000000000014000 0fe:00000 ld-2.3.5.so
40017000 4 rw— 0000000040017000 000:00000 [ anon ]
40018000 4 r-x– 0000000000000000 0fe:00000 kedit.so
40019000 4 rw— 0000000000000000 0fe:00000 kedit.so
40027000 252 r-x– 0000000000000000 0fe:00000 libkparts.so.2.1.0
40066000 20 rw— 000000000003e000 0fe:00000 libkparts.so.2.1.0
4006b000 3108 r-x– 0000000000000000 0fe:00000 libkio.so.4.2.0
40374000 116 rw— 0000000000309000 0fe:00000 libkio.so.4.2.0
40391000 8 rw— 0000000040391000 000:00000 [ anon ]
40393000 2644 r-x– 0000000000000000 0fe:00000 libkdeui.so.4.2.0
40628000 164 rw— 0000000000295000 0fe:00000 libkdeui.so.4.2.0
40651000 4 rw— 0000000040651000 000:00000 [ anon ]
40652000 100 r-x– 0000000000000000 0fe:00000 libkdesu.so.4.2.0
4066b000 4 rw— 0000000000019000 0fe:00000 libkdesu.so.4.2.0
4066c000 68 r-x– 0000000000000000 0fe:00000 libkwalletclient.so.1.0.0
4067d000 4 rw— 0000000000011000 0fe:00000 libkwalletclient.so.1.0.0
4067e000 4 rw— 000000004067e000 000:00000 [ anon ]
4067f000 2148 r-x– 0000000000000000 0fe:00000 libkdecore.so.4.2.0
40898000 64 rw— 0000000000219000 0fe:00000 libkdecore.so.4.2.0
408a8000 8 rw— 00000000408a8000 000:00000 [ anon ]
…. (trimmed) …
mapped: 25404K writeable/private: 2432K shared: 0K

Вырезанные строки аналогичны тем, которые вы видите. Даже из неполного вывода можно увидеть очень интересные вещи. Важно отметить, что каждая разделяемая библиотека отображается в списке дважды: один раз - сегмент её кода, второй раз - сегмент данных. Права доступа к сегменту кода "r-x--", а данных - "rw---". Нас интересуют только  поля Kbytes, Mode, и Mapping, так как остальные не имеют отношения к делу.

Если вы просмотрите вывод, то обнаружите, что записи с бОльшими значениями Kbytes - это обычно сегменты кода разделяемых библиотек. Они хороши тем, что процессы могут разделять их между собой. Если исключить все части, разделенные между процессами,  получится значение   “writeable/private”, которое фигурирует в конце вывода. Это можно считать дополнительными издержками процесса, исключая разделяемые библиотеки. Таким образом, стоимость запуска этого экземпляра Kedit (учитывая, что все разделяемые библиотеки уже были загружены) около 2 мегабайт. Это довольно сильно отличается от 14 или 25 МБ, которые показала ps.

Что все это означает?

Мораль истории такова, что использование памяти процессами в Линукс - сложная материя. Не получится просто запустить ps и узнать, что происходит. Особенно это касается программ, которые создают много идентичных дочерних процессов, как Apache. Ps может показать, что каждый экземпляр Апача потребляет 10 МБ памяти, а на самом деле может быть, что предельное значение 1 МБ. Эта информация критична при настройке Apache’s MaxClients, которая задает, сколько запросов может одновременно обрабатывать сервер.

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