Разработчики пишут программные продукты, зачастую не обращая внимания на то, что является результатом компиляции исходных файлов. Администраторы обслуживают систему в общем, не обращая внимание отдельные исполняемые файлы и на их внутреннюю структуру. Результат — снижение производительности и повышение общей нагрузки системы, включая избыточные переключения контекста, подкачку страниц, загрузку ЦПУ и пр.
Давайте попробуем разобраться — что такое процесс? Из чего он состоит и что такое адресное пространство? Кто-то скажет банальность! Все это давно известно, почитайте классику! Да общие понятия безусловно хорошо знакомы, я же хочу обратить внимание на детали, которые могут быть полезны и программисту и системному администратору.
Итак, процесс это совокупность программы и данных, системных библиотек, используемых программой, состояние регистров общего назначения и устройства управления памяти MMU, называемое контекстом, а также распределения виртуальной памяти.
Карту памяти и загруженные системные библиотеки показывает команда pmap. Что интересного можно узнать из нее? Вот вывод для характерного 32 битного приложения, широко известной утилиты Disk Duplication (dd):
bash-3.00# pmap -x 526
526: dd if=/dev/zero of=/dev/null bs=1024k
Address Kbytes RSS Anon Locked Mode Mapped File
08047000 4 4 4 - rw--- [ stack ] #STACK
08050000 12 12 - - r-x-- dd #TEXT
08063000 4 4 4 - rw--- dd #DATA
08064000 1032 1032 1032 - rw--- [ heap ] #HEAP
FEE80000 1080 784 - - r-x-- libc.so.1 #SHARED LIB TEXT
FEF90000 24 12 12 - rwx-- [ anon ] #SHARED LIB DATA
FEF9E000 32 32 28 - rwx-- libc.so.1
FEFA6000 8 8 8 - rwx-- libc.so.1
FEFC3000 160 160 - - r-x-- ld.so.1
FEFF0000 4 4 4 - rwx-- [ anon ]
FEFF5000 4 4 - - rwxs- [ anon ]
FEFFB000 8 8 8 - rwx-- ld.so.1
FEFFD000 4 4 4 - rwx-- ld.so.1
-------- ------- ------- ------- -------
total Kb 2376 2068 1104 -
Известно, что любая доступная пользователю виртуальная память разделена на страницы. Стандартный размер страницы — 4 к для архитектуры x86 и x64, 8к для архитектуры SPARCv9. Страницы могут быть бОльшего размера. Драйвер, управляющий сегментами и страницами пользовательских процессов, называется seg_vn.
Обратите внимание на общие поля:
Address — собственно адрес начала сегмента в виртуальной памяти процесса.
Kbytes — размер сегмента.
RSS - Resident Set Size — количество памяти процесса, расположенных в настоящий момент времени в физической памяти системы. Дело в том, что если страница не используется, нет смысла ее держать в оперативной памяти. При необходимости ее можно подгрузить или из файла данных, например исполняемого файла /bin/dd, (именованная память) или swap области (анонимная память).
Anon — Количество анонимной памяти, т.е. не связанной, с каким либо файлом в файловой системе, а расположенной в области подкачки. Обычно, такая память появляется в результате вызова malloc, когда процессу необходимо разместить свои данные в памяти, или операции COW - Copy On Write — когда необходимо создать копию данных по первому требованию записи в адресное пространство..
Locked — количество килобайт в сегменте, не вытесняемых в область подкачки в результате процессов пейджинга (paging) или свопинга(swaping), и поэтому всегда находящихся в оперативной памяти. Для установки такой блокировки используется вызов mlock().
Mode — r — процесс может читать содержимое сегмента; w — процесс может писать в сегмент; x — инструкции; содержащиеся в сегменте, могут быть исполнены; s — сегмент используется совместно с другим адресным пространством; R — в области подкачки не зарезервировано место для данного сегмента (используется, например в процессах, которые работают с устройствами напрямую — пример: сегмент видео буфера)
Строка №5 - это сегмент кода программы или по англцки TEXT. Адрес сегмента в виртуальной памяти 0x08050000. Собственно — это сама программа, машинные коды функций main(), mach(), number() и другие, которые составляют программу dd (надо сказать, что dd это почти что одна большая функция main =))) ). Как можно видеть, сегмент связан (именован) с файлом в формате ELF. Если место в оперативной памяти в системе станет мало, все страницы сегмента могут быть просто удалены из памяти и использованы другим процессом. Когда процессу потребуется исполнить код из этой страницы — он просто загрузит его из файла /usr/bin/dd! Права — r-x--. Писать нельзя! Иначе это было-бы раздолье для хакеров всех мастей.
Сегмент данных — DATA предназначен для размещенных данных программы. Здесь расположены место для хранения всех переменных, сделанных вне определений функций. Для нашего dd это переменные ibs, obs, svr4_etoa[], utol[] и другие. Как видно, данный сегмент тоже связан с файлом. В ELF файле dd есть специальная секция данных, которая при первом запуске программы dd размещается в памяти в режиме r----, а при первой попытке записи в страницу сегмента данных драйвер seg_vn выполняет операцию COW и создает измененную копию страницы с правами rw---, резервируя при этом анонимную память в swap области. Все следующие экземпляры dd, запущенные во время работы первого dd, будут просто будут просто выполнять подключение к первому r---- сегменту, а затем, выполнять новую операцию COW, экономя при этом время и ресурсы.
Стек (STACK) представляет собой область анонимной памяти, в котором располагаются адреса возвратов из подпрограмм (функций) и прерываний, передаваемые параметры, а также локальные переменные процедур. Например, при каждом вызове функции static unsigned long long number(long long big) зарезервирует место для хранения параметра big и локальных переменных cs, n и cut. Это называется фреймом.
#mdb /usr/bin/dd
> number/20i
number:
number: pushl %ebp
movl %esp,%ebp
pushl %edi
pushl %esi
pushl %ebx
subl $0x4c,%esp
# размер всего фрейма
movl $0xcccccccc,-0x28(%ebp)
# long long cut = BIG/10
movl $0xccccccc,-0x24(%ebp)
#
movl 0x8063d44,%eax
# адрес strings в DATA
movl %eax,-0x14(%ebp)
# cs = strings
movl $0x0,-0x20(%ebp)
# long long n
movl $0x0,-0x1c(%ebp) #
Стек имеет особенность — он растет к началу адресного пространства. Размер стека в нашем dd всего 4 килобайта.
Сегмент HEAP содержит динамически выделяемую память в результате работы процесса. Для выделения существуют вызовы семейства malloc. В dd выделение происходит при помощи ibuf = (unsigned char *)valloc(ibs + 10). Обратите внимание на размер данного сегмента — он коррелируется с параметром командной строки bs=1024k. Действительно, алгоритм работы dd очень простой:
шаг1. прочитать данные в буфер из if
шаг2. Записать из буфера в of.
шаг3. Повторять пока не закончится if или of
Для этого необходим буфер соответствующего размера. Что мы и наблюдаем в pmap.
Далее мы можем наблюдать сегменты кода и данных разделяемых (shared) библиотек. Для dd это только libc и собственно сам линкер — программа ld.so.
На закуску давайте посмотрим отличия в распределении сегментов для 64 битной программы.
bash-3.00# gcc -m64 -o dd dd.c
bash-3.00# ./dd if=/dev/zero of=/dev/null bs=1024k &
[1] 534
bash-3.00# pmap -x 534
534: ./dd if=/dev/zero of=/dev/null bs=1024k
Address Kbytes RSS Anon Locked Mode Mapped File
0000000000400000 16 16 - - r-x-- dd
0000000000413000 8 8 8 - rw--- dd
0000000000415000 1056 1044 1044 - rw--- [ heap ]
FFFFFD7FFF210000 64 64 64 - rwx-- [ anon ]
FFFFFD7FFF230000 24 12 12 - rwx-- [ anon ]
FFFFFD7FFF240000 1272 780 - - r-x-- libc.so.1
FFFFFD7FFF380000 4 4 4 - rwx-- [ anon ]
FFFFFD7FFF38E000 36 36 36 - rw--- libc.so.1
FFFFFD7FFF397000 16 8 8 - rw--- libc.so.1
FFFFFD7FFF3AA000 4 4 - - rwxs- [ anon ]
FFFFFD7FFF3AE000 244 244 - - r-x-- ld.so.1
FFFFFD7FFF3F0000 4 4 4 - rwx-- [ anon ]
FFFFFD7FFF3FB000 8 8 8 - rwx-- ld.so.1
FFFFFD7FFF3FD000 8 8 8 - rwx-- ld.so.1
FFFFFD7FFFDFE000 8 8 8 - rw--- [ stack ]
---------------- ---------- ---------- ---------- ----------
total Kb 2772 2248 1204 -
Какие можно сделать из всего вышеописанного выводы? Об этом в следующих сериях...