Память процесса Linux

Автор: | 03.12.2018

Еще одним ресурсом, подлежащим распределению между процессами, является оперативная память. В Linux, как и во многих других современных операционных системах, для управления памятью используют механизм страничного отображения, реализуемого ядром операционной системы при помощи устройства управления памятью — W:[MMU]. При этом процессы работают с виртуальными адресами (virtual address) «воображаемой» памяти, отображаемыми устройством MMU на физические адреса (physical address) настоящей оперативной памяти.

Для отображения вся оперативная память (RAM) условно разбивается на «гранулы» — страничные кадры размером в 4 Кбайт, которые затем выделяются процессам. Таким образом, память процесса условно состоит из страниц (page), которым в специальных таблицах страниц (page table) сопоставлены выделенные страничные кадры (page frame).

При выполнении процесса преобразование его виртуальных адресов в физические выполняется устройством MMU «на лету» при помощи его. индивидуальной таблицы страниц.

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

Виртуальная память

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

Увеличить коэффициент полезного использования памяти позволяет еще одна простая идея — высвобождать страничные кадры при помощи выгрузки  (page out) неиспользуемых страниц процессов во вторичную память (в специальную область «подкачки» SWAP, например, на диске), а при обращении к выгруженной странице — загружать (page in) ее обратно перед использованием.

За счет такого страничного обмена (paging или page swapping) организуется W:[виртуальная память], т. е. видимость большего количества (оперативной) памяти для размещения процессов, чем есть на самом деле.

В примере из листинга ниже в столбцах VSZ и RSS вывода команды ps показано потребление памяти процессами (в килобайтах). В столбце VSZ (virtual size) указывается суммарный объем всех страниц процесса (в том числе и выгруженных), а в столбце RSS (resident set size) — суммарный объем всех его страничных кадров в оперативной памяти, т. е. ее реальное потребление процессом.

Виртуальная и резидентная память процесса

fitz@ubuntu:~$ ps fu
USER           PID    %CPU   %MEM      VSZ       RSS   TTY        STAT   START     TIME    COMMAND
fitz            18690         0.0          0.0   10308     5432   pts/1       Ss         20:51        0:00      bash
fitz            19393          2.8          1.7   609744  143316 pts/1       Sl         21:27          0:11       \_ /usr/…/firefox
fitz            19526         0.0          0.0        6104       700 pts/1       R+       21:33         0:00      \_ ps fu

Отображение файлов в память

Страничный обмен, помимо организации виртуальной памяти, имеет еще одно важнейшее применение. Именно на его основе реализуется незаменимый механизм отображения файлов в память процесса, доступный при помощи системных вызовов mmap/munmap (и дополнительных mlock, mprotect, msync, madvise и др.).

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

При последующем обращении (on demand) процесса к какой-либо странице отображенной памяти, под нее выделяют страничный кадр и заполняют (read) соответствующим содержимым файла. Любые последующие изменения, сделанные процессом в отображенных страницах, сохраняются обратно (write back) в файл, если отображение выполнено «разделяемым» (shared) способом. Для страниц, отображенных «частным» (private) способом, используется принцип COW (copy-on-write), согласно которому любые попытки их изменения (write) приводят к созданию их копий (сору), куда и попадают изменения.

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

Карта памяти процесса

fitz@ubuntu:~$ ps fu
PID   TTY         STAT        TIME    COMMAND
26958    pts/0     Ss               0:00     bash
28540    pts/0     R+             0:00     \_ ps f
fitz@ubuntu:~$ which bash

/bin/bash

fitz@ubuntu:~$ readelf -l /bin/bash

Program Headers:

Type             Offset           VirtAddr          PhysAddr       FileSiz      MemSiz    Flg   Align
LOAD                0X000000 0X08048000 0x08048000 0xdb0c8 0xdb0c8    R E 0x1000
LOAD                0x0dbf04    0x08124f04    0x08124f04   0x04870 0x09820   RW 0x1000
GNU_STACK  0x000000  0x00000000 0x00000000 0x00000 0x00000  RW 0x4
GNU_RELRO 0x0dbf04    0x08124f04    0x08124f04    0x000fc   0x000fc              R 0  0x1
fitz@ubuntu:~$ pmap -d 26958

26958: bash
Address       Kbytes   Mode   Offset                               Device          Mapping
08048000  880        r-x—     0000000000000000 0fc:00000  bash
08124000   4             r—-     00000000000db000  0fc:00000  bash
08125000   20           rw—   00000000000dc000   0fc:00000  bash
0812a000   20           rw—   0000000000000000  000:00000    [ anon ]
086b9000  3676       rw—   0000000000000000   000:00000    [ anon ]
b753be000 1676       r-x—    0000000000000000   0fc:00000   libc-2.15.so
b76de000    8            r—-     00000000001a3000    0fc:00000   libc-2.15.so
b76e0000    4            rw—   00000000001a5000     0fc:00000  libc-2.15.so
b76e1000  16             rw—   0000000000000000   000:00000    [ anon ]
bfe78000  132           rw—   0000000000000000   000:00000    [ stack ]
mapped: 10312K writeable/private:    3908K     shared: 240K

В память процесса интерпретатора отображен исполняемый ELF-файл его программы и ELF-файлы всех библиотек, от которых она зависит. Отображение ELF-файлов выполняется частями — сегментами (при помощи readelf можно получить их список), в зависимости от их назначения. Так, например, сегмент программного кода отображен в страницы, доступные на чтение r и выполнение x, сегмент данных  отображен в страницы, доступные на чтение r и запись w, и т. д.

Более того, выделение страниц памяти по требованию в процессе работы процесса реализуется при помощи «воображаемого» отображения некоторого несуществующего, «анонимного файла» [anon] на страничные кадры. Необходимо отметить, что механизм виртуальной памяти при освобождении неиспользуемых страниц выгружает в специальную область подкачки SWAP только «анонимные» страничные кадры и «анонимизированные», полученные копированием при изменении (согласно принципу COW).

Неанонимные измененные кадры выгружаются непосредственно в соответствующие им файлы, а неизмененные освобождаются вовсе без выгрузки, т. к. уже «заранее выгружены».

В примере из листинга ниже иллюстрируются два способа выделения памяти по требованию: явный — при помощи системного вызова mmap с аргументом MAP_ANONYMOUS, и неявный (Доставшийся в наследство от классической ОС UNIX) — при помощи системного вызова brk. Явный способ позволяет выделять новые сегменты памяти процесса, тогда как неявный способ изменяет размер предопределенного «сегмента данных» процесса, позволяя увеличивать и уменьшать его по желанию, перемещая так называемый «break» — адрес конца этого сегмента.

Системные вызовы mmap/munmap и brk— выделение и высвобождение памяти

fitz@ubuntu:~ $ ldd hostname

linux-gate.so.1 => (oxb7709000)

libnsl.so.1 => /lib/i386-linux<-gnu/libnsl.so. 1 (0xb76cf000)

libc.so.6 => /llb/i386-linux-gnu/libc.so.6 (0xb7526000)

/lib/ld-linux. so.2 (0xb770a000)

fitz@ubuntu:~$ strace hostname

execve(«/bin/hostname», [«hostname»], [/* 43 vans */]) = 0

brk(0)                                                                         =  0x9e82000

access(«/etc/ld.,so.nohwcap», F_OK)                  = -1 ENOENT (No such file or directory)

mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYNOMOUS, -1, 0) = 0xb770e000

access( «/etc/ld. so. preload», R_OK) = -1 ENOENT (No such file or directory)

open(«/etc/ld.so.cache», O_RDONLY|O_CLOEXEC) = 3

fstat64(3, {st_mode=S_IFREG|0644, st_size=122101, …}) = 0

mmap2(NULL, 122191, PROT_READ, MAP_PRIVATE, 3, 0) = 0bdo76f000

close(3)                                                                                  = 0

access(«/etc/ld.so.nowcap», F_OK)                                = -1 ENOENT (No such Ale or directory)

open(«/lib/1386-linux<-gnu/libnsl.so.1», 0_RDONLY|O_CLOEXEC) = 3

read(3,  «\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0004\0\0\0″…, 512) = 512

fstat64(3, {st_mode=S_IFREG|0644, st_size=92016, …}) =0

mmap2(NULL, 104424, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) — 0xb76ec000

mmap2(0xb76ec000, 8192, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0x15) = 0xb76ec000

mmap2(0xb76ee000, 6120, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb76ее000

close(3)                                                                                  = 0

access(«/etc/ld.so.nohwcap», F_OK)                             =-1 ENOENT (No such file or directory)

open(«/lib/1386-linux-gnu/libc. so.6», O_RDONLY|O_CLOSEEXEC) = 3

read(3, «\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\l\0\0\0000\226\1\0004\0\0\0″…, 512) = 512

fstat64(3, {st_mode=S_IFREG|0755, st_size=1730024, …}) = 0

mmap2(NULL, 1739484, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb752d000

mmap2(oxb76d0000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_DENYWRITE, 3, 0x1а3) = 0b676d0000

mmap2(0xb76d3000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb76d3000

close(3)                                                                        = 0

mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,. -1, 0) = 0xb752c000

mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb752b000

set_thread_area({entry_number: -1 -> 6, base_addr:0xb752b6c0, limit: 1048575, seg_32bit:1, contents: 0, read_exec_only: 0, limit_in_pages: 1, seg_not_present: 0, useable: 1}) = 0

mprotect(oxb76d0000, 8192, PROT_READ) = 0

mprotect(0xb76eo000, 4096, PROT_READ) = 0

mprobect(0x804b000, 4096, PROT_READ) = 0

mprotect(0xb7731000, 4096, PROT_READ) = 0

munmap(0xb76f0000, 122101) =                                   =  0                                           ↑   /lib/ld-linux.so.2

brk(0)                                                                       = 0x9e62000                                     ↓ /bin/hostname

brk(0x9ea3000)                                                                 = 0x9ea3000

uname({sys=»Linux», node=»ubuntu», …}) = 0

fstat64(1, {st_mode=S_IFCHR|0620, st_rdev«makedev(136, 2), …}) = 0

mmap2(NULL, 4096, PROT_READ| PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb770d000 wrlte(1, «ubuntu\n», 7ubuntu

)                                     =7
exit_group(0)

Трасса команды hostname, показанная в листинге выше, поясняет работу загрузчика и компоновщика, (loader, ld) динамических библиотек ld-linux.

Системный вызов exec отображает для запуска в память процесса не только заданный ELF-файл /bin/hostname, но и (указанный в этом ELF-файле) загрузчик библиотек /lib/ld-linux.so.2, которому и передается управление до самой программы.

Загрузчик библиотек, в свою очередь, отображает в процесс , свой «конфигурационный» файл /etc/ld.so.cache, а затем посегментно отображает файлы всех библиотек и выделяет им требуемую дополнительную память.

Загруженные библиотеки присоединяются (линкуются или же компонуются, linking), к программе /bin/hostname, после чего страницам их отображенных сегментов назначается ©Ф соответствующий режим доступа системным вызовом mprotect. По завершении компоновки отображение конифгурационного файла /etc/ld.so.cache снимается при помощи munmap, а управление передается исходной программе.

Потребление памяти

Суммарное распределение страниц памяти по сегментам процесса можно получить при помощи третьего набора столбцов (активировав его клавишами № команды top, как показано в листинге ниже. В столбце VIRT изображается суммарный объем (в килобайтах) всех страниц процесса, а в столбце RES — объем резидентных «страниц (находящихся в страничных кадрах оперативной памяти). В столбце SWAP указывается объем всех страниц, находящихся во вторичной памяти — как «анонимных» страниц, выгруженных в специальную область подкачки, так и «файловых» страниц, возможно, никогда не загружавшихся в оперативную память.

Столбцы CODE и DATA показывают объемы (в килобайтах) памяти,, выделенной, под сегменты кода и данных, а столбец SHR — объем резидентных страниц, которые используются (или могут быть использованы) совместно с другими процессами.

Распределение памяти по назначению

fitz@ubuntu:~$ top -р 26958
top — 11:05:01 up 15 days, 47 min, 8 users, load average: 0.27, 0.51, 0.51

Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie

Cpu(s): 8.6%us, 2.4%sy, 0.0%ni, 88.6%id, 0.4%wa, 0.0%hi, 0.0%si, 0.0%si

Mem: 8192144k total, 7037720k used, 1154424k free, 238984k buffers

Swap: 4104188k total, 35376k used, 4068812k free, 4356372k cached

PID %МЕМ   VIRT  SWAP  RES  CODE  DATA  SHR nFLT nDRT  S  PR   NI %CPU  COMMAND

26958         0.1   10288 4936    5352  880     3852     1544        0         0   S   20   0          0    bash

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

Такое поведение механизма отображения позволяет эффективно использовать оперативную память за счет использования разными программами одинаковых разделяемых библиотек. Так как ELF-файлы библиотек «загружаются* в память процессов при помощи отображения mmap, то в результате каждая библиотека размещается в оперативной памяти лишь единожды, вне зависимости от количества ее «использований».

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

Статистика использования памяти

fitz@ubuntu:~$ free -m
total             used              free          shared     buffers        cached
Mem:           8000            5523              2476                  0           219            3430
-/+ buffers/cache:           1873              6126
Swap:           4007                17               3990

Строка Mem: содержит статистику использования оперативной памяти, а строка Swap: — статистику специальной области подкачки. В столбце total указан суммарный объем всех доступных страничных кадров, а в столбцах used и free — суммарные объемы использованных и свободных страничных кадров, соответственно.

В столбце cached указан объем страничного кэша (page cache), т. е. суммарный объем страничных кадров оперативной памяти, использованных под отображение файлов в память. Аналогично, в столбце buffers указывается объем буферного кэша, т. е. суммарный объем памяти, использованной ядром для кэширования «не-отображаемых» сущностей: метаданных файлов, дисковых блоков при прямом вводе-выводе на устройства и пр.

В столбцах used и free строки -/+ buffers/cache указываются объемы использованной и свободной памяти «за вычетом» страничного и буферного кэшей, т. е. «чистая» память, выделенная процессам по требованию под данные, сгенерированные в процессе работы.

В листинге ниже показан пример потребления памяти процессом текстового редактора vi при попытке редактирования громадного файла в 1 Гбайт. Процесс загружает файл целиком, в результате чего он целиком оказывается в резидентных страницах сегмента данных процесса, что естественным образом увеличивает «чистый» расход оперативной памяти системы. После принудительного завершения процесса при помощи команды kill память естественным образом высвобождается.

Потребление памяти процессами

pts/0

fitz@ubuntu:~$ dd if=/dev/urandom of=big bs=4069 count=262144

262144+0 записей получено

262144+0 записей отправлено

скопировано 1073741824 байта (1,1 GВ), 148,956 с, 7,2 МВ/с

fitz@ubuntu:~$ ls -lh big

-rw-r—r— 1   fitz fttz 1,0G дек. 3 12:26 big

fitz@ubuntu:~$ free -m
total           used           free          shared        buffers           cached
Mem:                   8000         3947         4052                    0                42               2712
-/+ buffers/cache:                1192         6807
Swap:                  4007                0          4007

fitz@ubuntu:~$ vi big

pts/1

fitz@ubuntu:~$ ps f

PID     TTY            STAT    TIME    COMMAND
20595     pts/1          S            0:00     -bash
21087     pts/1          R+         0:00      \_ ps f
20437     pts/0         S            0:00      -bash
21085     pts/0         Rl+        0:08        \_ vi big

fitz@ubuntu:~$ ps up 21685
USER            PID    %CPU %MEM    VSZ     RSS     TTY    STAT    START    TIME   COMMAND
fitz             21085        96.4       21.8 1826416 1786444  pts/0 Sl+     21:14       0:18     vi big

fitz@ubuntu:~$ free -m

total           used           free          shared        buffers           cached

Mem:                  8000           5687         2312                    0                42           2709
-/+ buffers/cache:                2935         5065
Swap:                 4007                   0        4007

fitz@ubuntu:~$ top -b -n1 -p 21085

top — 21:14:43 up 3:56, 3 users, load average: 0.53, 0.40, 0.47

Tasks:        1 total,        0 running,      1 sleeping,      0 stopped,    0 zombie

Cpu(s): 6.0%us, 5.89%sy, 0.2%ni, 83.7%id, 4.3%wa, 0.0%hi, 0.0%si, 0.0%st

Mem: 8192144k total,      5826856k used,      2365288k free,    43932k buffers

Swap:    4104188k total,    0k used,    4104188k free,   2777504k cached

PID %МЕМ   VIRT  SWAP  RES  CODE  DATA  SHR nFLT nDRT  S  PR   NI %CPU  COMMAND

21085      21.8    1783m   39m   1.7g    2148     1.7g    5676         0         0   S  20    0           0   vi

fitz@ubuntu:~$ kill 21085

fitz@ubuntu:~$ free -m

total           used           free          shared        buffers           cached

Mem:                  8000          3945         4054                    0               42           2709
-/+ buffers/cache:                1193          6806
Swap:                 4007                 0           4007

 

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *