null

Анатомия процесса. Часть 1

 

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

 

Давайте попробуем разобраться — что такое процесс? Из чего он состоит и что такое адресное пространство? Кто-то скажет банальность! Все это давно известно, почитайте классику! Да общие понятия безусловно хорошо знакомы, я же хочу обратить внимание на детали, которые могут быть полезны и программисту и системному администратору.

 

Итак, процесс это совокупность программы и данных, системных библиотек, используемых программой, состояние регистров общего назначения и устройства управления памяти 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          - 

 

 

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