Второе издание
Скачать 3.09 Mb.
|
Поле vm_mm указывает па структуру mm_struct, связанную с данной областью VMA. Заметим, что каждая область VMA уникальна для той структуры mm_struct, с которой эта область связана. Поэтому, даже если два разных процесса отобража- ют один и тот же файл на свои адресные пространства, то для каждого процесса создается своя структура vm_area_struct, чтобы идентифицировать уникальные области памяти каждого процесса. Следовательно, два потока, которые совместно используют адресное пространство, также совместно используют и все структуры vm_area_struct в этом адресном пространстве. Флаги областей VMA Поле флагов vm_flags содержит битовые флаги, которые определены в файле ко всей области памяти в целом. В табл. 14.1 приведен список возможных значений флагов vm_flags. Адресное пространство процесса 317 Таблица 1 4 . 1 . Флаги областей VMA Флаг Влияние на область УМА и на ее страницы памяти VM_READ VM_WRITE VM_EXEC VM_SHARED VM_MAYREAD VM_MAYWRITE VM_MAYEXEC VM_MAYSHARE VM_GROWSDOWN VM_GROWSUP VM_SHM VM_DENYWRITE VM_EXECUTABLE VM_LOCKED VM_IQ VM_SEQ_READ VM_RAND_READ VM_DONTCOPY VM_DONTEXPAND VM_RESERVED VM_ACCOUNT VM_HUGETLB VM_NONLINEAR Рассмотрим подробнее назначение наиболее интересных и важных флагов. Флаги VM_READ, VM_WRITE и VM_EXEC указыпают обычные права на чтение-запись и выпол- нение для страниц памяти, которые принадлежат данной области памяти. При необ- ходимости их можно комбинировать для формирования соответствующих прав до- ступа. Например, отображение выполняемого кода процесса может быть выполнено с указанием флагов VM_READ и VM_EXEC, но никак не с указанием флага VM_WRITE. С другой стороны, сегмент данных из выполняемого файла может отображаться с указанием флагов VM_READ и VM_WRITE, указывать при этом флаг VM_EXEC не име- ет смысла. Файл данных, который отображается только для чтения, должен отобра- жаться с указанием только флага VM_READ. Флаг VM_SHARED указывает на то, что область памяти содержит отображение, которое может совместно использоваться несколькими процессами. Если этот флаг установлен, то такое отображение называют совместно используемым (shared mapping), что интуитивно понятно. Если этот флаг не установлен, то такое отобра- жение доступно только одному процессу и оно называется частным отображением, (private mapping). 318 Глава 14 Из страниц памяти можно считывать информацию В страницы памяти можно записывать информацию Можно выполнять код, хранящийся в страницах памяти Страницы памяти являются совместно используемыми Можно устанавливать флаг VM_READ Можно устанавливать флаг VM_WRITE Можно устанавливать флаг VM_EXEC Можно устанавливать флаг VM_SHARED Область памяти может расширяться "вниз" Область памяти может расширяться "вверх" Область используется для разделяемой (совместно используемой) памяти В область отображается файл, в который нельзя выполнять запись В область отображается выполняемый файл Страницы памяти в области являются заблокированными В область памяти отображается пространство ввода-вывода аппаратного устройства К страницам памяти, вероятнее всего, осуществляется последовательный доступ К страницам памяти, вероятнее всего, осуществляется случайный доступ Область памяти не должна копироваться при вызове f o r k () Область памяти не может быть увеличена с помощью вызова remap () Область памяти не должна откачиваться на диск Область памяти является объектом, по которому выполняется учет ресурсов В области памяти используются гигантские ( h u g e t l b ) страницы памяти Область памяти содержит нелинейное отображение Флаг VM_IO указывает, что область памяти содержит отображение области вво- да-вывода аппаратного устройства. Этот флаг обычно устанавливается драйверами устройств при выполнении вызова mmap () для отображения в память области вво- да-вывода аппаратного устройства. Кроме всего прочего, этот флаг указывает, что область памяти не должна включаться в файл core процесса. Флаг VM_RESERVED ука- зывает, что область памяти не должна откачиваться на диск. Этот флаг также укалы- вается при отображении на память областей ввода-вывода аппаратных устройств. Флаг VM_SEQ_READ является подсказкой ядру, что приложение выполняет после- довательное (т.е. линейное и непрерывное) чтение из соответствующего отображе- ния. При этом ядро может повысить производительность чтения за счет выполнения упреждающего чтения (read-ahead) из отображаемого файла. Флаг VM_RAND_READ указывает обратное, т.е. приложение выполняет операции чтения из случайно вы- бранных мест отображения (т.е. не последовательно). При этом ядро может умень- шить или совсем отключить выполнение упреждающего чтения из отображаемого файла. Эти флаги устанавливаются с помощью системного вызова madvice () путем указания соответственно флагов MADV_SEQUENTIAL и MADV_RANDOM для этого вызо- ва. Упреждающее чтение — это последовательное чтение несколько большего коли- чества данных, чем было запрошено, в надежде на то, что дополнительно считанные данные могут скоро понадобиться. Такой режим полезен для приложений, которые считывают данные последовательно. Однако если считывание данных выполняется случайным образом, то режим упреждающего чтения не эффективен. Операции с областями VMA Поле vm_ops структуры vm_area_struct содержит указатель на таблицу опера- ций, которые связаны с данной областью памяти и которые ядро может вызывать для манипуляций с областью VMA. Структура vm_area_struct служит общим объ- ектом для представления всех типов областей виртуальной памяти, а в таблице опе- раций описаны конкретные методы, которые могут быть применены к каждому кон- кретному экземпляру объекта. Таблица операций представлена с помощью структуры vm_operations_struct, которая определена в файле struct vm_operations_struct { void (*open) (struct vm_area_struct *) ; void (*close) (struct vm_area_struct * ) ; struct page * (*nopage) (struct vm_area_struct *, unsigned long, int); int (*populate) (struct vm_area struct *, unsigned long, unsigned long, pgprot_t, unsigned long, int); }; Рассмотрим каждый метод в отдельности. • void open (struct vm_area_struct *area) Эта функция вызывается, когда соответствующая область памяти добавляется в адресное пространство. • void close(struct vm_area_struct *area) Эта функция вызывается, когда соответствующая область памяти удаляется из адресного пространства. Адресное пространство процесса 319 • struct page * nopage(struct vm_area_sruct *area, unsigned long address, int unused) Эта функция вызывается обработчиком прерывания из-за отсутствия страницы (page fault), когда производится доступ к странице, которая отсутствует в фи- зической памяти. • int populate {struct vm_area_struct *area, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock) Эта функция вызывается из системного вызова. remap_pages() для предвари- тельного заполнения таблиц страниц области памяти (prefault) при создании нового отображения. Списки и деревья областей памяти Как уже рассказывалось, к областям памяти осуществляется доступ с помощью двух структур данных дескриптора памяти: полей mmap и mm_rb. Эти две структу- ры данных независимо друг от друга указывают на все области памяти, связанные с данным дескриптором памяти. Они содержат указатели на одни и те же структуры v m _ a r e a _ s t r u c t , просто эти указатели связаны друг с другом по-разному. Первый контейнер, поле mmap, объединяет все объекты областей памяти в одно- связный список. Структуры v m _ a r e a _ s t r u c t объединяются в список с помощью сво- их полей vm_next. Области памяти отсортированы в порядке увеличения адресов (от наименьшего и до наибольшего). Первой области памяти соответствует струк- тура vm a r e a _ s t r u c t , на которую указывает само поле mmap. Указатель на самую последнюю структуру равен значению NULL. Второе поле, m m _ r b , объединяет все объекты областей памяти в красно-чер- ное (red-black) дерево. На корень дерева указывает поле mm_rb, а каждая структура vm_area s t r u c t присоединяется к дереву с помощью поля vm_rb. Красно-черное дерево — это один из типов бинарного дерева. Каждый элемент красно-черного дерева называется узлом. Начальный узел является корнем дерева. Большинство узлов имеет два дочерних узла: левый дочерний узел и правый дочер- ний узел. Некоторые узлы имеют всего один дочерний узел, и, наконец, узлы, кото- рые не имеют дочерних, называются листьями. Для любого узла все элементы дерева, которые находятся слева от данного узла, всегда меньше по своему значению, чем зна- чение данного узла, а все элементы дерева, которые находятся справа от некоторого узла, всегда больше по значению, чем значение этого узла. Более того, каждому узлу присвоен цвет (красный или черный, отсюда и название этого типа деревьев) в соот- ветствии со следующими двумя правилами: дочерние элементы красного узла являют- ся черными и любой путь по дереву от узла к листьям должен содержать одинаковое количество черных узлов. Корень дерева всегда красный. Поиск, вставка и удаление элементов из такого дерева требуют количество операций порядка О (log (n) ). Связанный список используется, когда необходимо пройти по всем узлам. Красно- черное дерево используется, когда необходимо найти определенную область памяти адресного пространства. Таким образом, ядро использует избыточные структуры данных для обеспечения оптимальной производительности независимо от того, ка- кие операции выполняются с областями памяти. 320 Глава 14 Области памяти в реальной жизни Рассмотрим пример адресного пространства процесса и области памяти в этом адресном пространстве. Для этой цели можно воспользоваться полезной файловой системой /ргос и утилитой pmар (1). В качестве примера рассмотрим следующую простую прикладную программу, которая работает в пространстве пользователя. Эта программа не делает абсолютно ничего, кроме того, что служит примером. int main(int argc, char *argv[]) return 0; } Рассмотрим список областей памяти из адресного пространства этого процесса. Этих областей немного. Мы уже знаем, что среди них есть сегмент кода, сегмент данных сегмент bss. Если учесть, что эта программа динамически скомпонована с библиотекой функций языка С, то соответствующие области существуют также для модуля l i b c . s o и для модуля l d . s o . И наконец, среди областей памяти также есть стек процесса. Результат вывода списка областей адресного пространства этого процесса из фай- ла /proc/ /maps имеет следующий вид. rml@phantasy:$ cat /proc/1426/maps 00e80000-00faf000 r-xp 00000000 03:01 208530 /lib/tls/libc-2.3.2.so 00faf000-00fb2000 rw-p 0012fOOO 03:01 208530 /lib/tls/libc-2.3.2.so 00fb2000-00fb4000 rw-p 00000000 00:00 0 08048000-08049000 r-xp 00000000 03:03 439029 /home/rml/src/example 08049000-0804a000 rw-p 00000000 03:03 439029 /home/rml/src/example 40000000-40015000 r-xp 00000000 03:01 80276 /lib/ld-2.3.2.so 40015000-40016000 rw-p 00015000 03:01 80276 /lib/ld-2.3.2.so 4001e000-4001f000 rw-p 00000000 00:00 0 bfffe000-c0000000 rwxp fffffOOO 00:00 0 Информация об областях памяти выдается в следующем формате. начало-конец права доступа смещение старший:младший номера устройства файловый индекс файл Утилита рmар (1) 4 форматирует эту информацию в следующем, более удобочита- емом виде. rml@phantasy:$ pmap 1426 example[1426] OOe8OOOO (1212 KB) r-xp (03:01 208530) /lib/tls/libc-2.3.2.so OOfafOOO (12 KB) rw-p (03:01 208530) /lib/tls/libc-2.3.2.so 00fb2000 (8 KB) rw-p (00:00 0) 08048000 (4 KB) r-xp (03:03 439029) /home/rml/src/example 08049000 (4 KB) rw-p (03:03 439029) /home/rml/src/example 40000000 (84 KB) r-xp (03:01 80276) /lib/ld-2.3.2.so 4 Утилита pmap(l) печатает форматированный список областей памяти процесса. Результат ее вы- вода несколько более удобочитаем, чем информация, получаемая из файловой системы /ргос, но это одна и та же информация. Данная утилита включена в новые версии пакета procps. Адресное пространство процесса 321 40015000 (4KB) rw-p (03:01 80276) /lib/ld-2.3.2.so 4001e000 (4 KB) rw-p (00:00 0) bfffeOOO (8 KB) rwxp (00:00 0) mapped: 1340 KB writable/private: 40 KB shared: 0 KB Первые три строчки соответствуют сегменту кода, сегменту данных и сегменту bss модуля l i b c . s o (библиотека функций языка С). Следующие две строчки описы- вают соответственно сегмент кода и сегмент данных выполняемого образа. Далее три строчки— описание сегментов кода, данных и bss модуля ld. so (динамический компоновщик). Последняя строчка описывает стек процесса. Обратите внимание, что все сегменты кода имеют права на чтение и выполне- ние, что и должно быть для выполняемых образов. С другой стороны, сегменты данных и bss, которые содержат глобальные переменные, помечаются как имеющие права на запись и чтение, а не на выполнение. Все адресное пространство составляет порядка 1340 Кбайт, но только 40 Кбайт из них имеют право на запись и соответствуют частному отображению. Если область памяти является совместно используемой и не имеет прав на запись, то ядро хранит в памяти всего одну копию отображаемого файла. Это может показаться обычным для совместно используемых отображений; однако, случай, когда при этом еще и от- сутствуют права на запись, проявляется несколько неожиданно. Если учесть факт, что когда на отображение нет прав записи, то соответствующая информация никог- да не может быть изменена (из отображения возможно только чтение), становится ясно, что можно совершенно безопасно загрузить выполняемый образ в память все- го один раз. Поэтому динамически загружаемая библиотека функций языка С и за- нимает в памяти всего 1212 Кбайт, а не 1212 Кбайт, умноженное на количество про- цессов, которые эту библиотеку используют. В связи с этим, процесс, код и данные которого имеют объем порядка 1340 Кбайт, на самом деле занимает всего 40 Кбайт физической памяти. Экономия памяти из-за такого совместного использования по- лучается существенной. Обратите внимание на области памяти, которые не имеют отображаемого файла, находятся на устройстве с номерами 00:00 и номер файлового индекса для которых равен нулю. Это отображение страницы, заполненной нулями (zero page, пулевая страница). Если отобразить страницу, заполненную нулями, на область памяти, ко- торая имеет права на запись, то побочным эффектом является инициализация всех переменных в нулевые значения. Это важно, поскольку в таком случае получается область памяти, заполненная нулями, которая нужна для сегмента bss. Каждой области памяти, связанной с процессом, соответствует структура vm_ a r e a _ s t r u c t . Так как процесс не является потоком (thread), то для него существует отдельная структура min_struct, на которую есть ссылка из структуры t a s k _ s t r u c t . Работа с областями памяти Ядру часто необходимо определять, соответствует ли та или иная область па- мяти в адресном пространстве процесса заданному критерию, например, существу- ет ли заданный адрес в области памяти. Эти операции являются основой работы функции mmap () , которая будет рассмотрена в следующем разделе, и выполнять их приходится часто. Несколько полезных для этого функций объявлены в файле 322 Глава 14 Функция find_vma() Функция f ind_vma () определена в файле mm/mmap.с. Эта функция позволяет найти в заданном адресном пространстве ту первую об- ласть памяти, для которой значение поля vm_end больше заданного адреса addr. Другими словами, эта функция позволяет найти первую область памяти, которая со- держит адрес addr или начинается с адреса, большего адреса addr. Если такой об- ласти памяти не существует, то функция возвращает значение NULL. В противном случае возвращается указатель на соответствующую структуру vm_area_struct. Обратите внимание, что найденная область VMA может начинать- ся с адреса, большего адреса addr, и этот адрес не обязательно принадлежит, най- денной области памяти. Результат выполнения функции find_vma () кэшируется в поле map_cache дескриптора памяти. Поскольку очень велика вероятность того, что после одной операции с областью памяти последуют еще операции с ней же, то процент попаданий в кэш получается достаточно большим (на практике получа- ются значения порядка 30-40%). Проверка кэшированных результатов выполняется очень быстро. Если нужный адрес в кэше не найден, то выполняется поиск по всем областям памяти, связанным с заданным дескриптором. Этот поиск выполняется с помощью красно-черного дерева следующим образом. struct vm_area_struct * find_vma(struct mm_struct *mm, unsigned long addr) { struct vm_area_struct *vma = NULL; if (mm) { vma = mm->mmap_cache; if (! (vma && vma->vm_end > addr && vma->vm start <= addr)) { struct rb node * rb_node; rb node = mm->mm_rb.rb_node; vma = NULL; while (rb_node) { struct vm_area_struct * vma_tmp; vma_tmp = rb_entry (rb_node, struct vm_area_struct, vm_rb); if (vma_tmp->vm_end > addr) { vma = vma_tmp; if (vma_tmp->vm_start <= addr) break; rb_node = rb_node->rb_left; } else rb_node = rb_node->rb_right; } if (vma) mm->mmap_cache = vma; } } return vma; } Адресное пространство процесса 323 Вначале выполняется проверка поля vma_cache на предмет того, содержит ли кэшированная область VMA необходимый адрес. Обратите внимание, что простая проверка того, является ли значение поля vm_end большим addr, не гарантирует что проверяемая область памяти является первой, в которой есть адреса, большие addr. Поэтому, для того чтобы кэш в этой ситуации оказался полезным, проверяе- мый адрес должен принадлежать кэшированной области памяти. К счастью, это как раз и соответствует случаю выполнения последовательных операций с одной и той же областью VMA. Если кэш не содержит нужную область VMA, то функция должна выполнять по- иск по красно-черному дереву Это выполняется путем проверки узлов дерева. Если значение поля vma_end для области памяти текущего узла больше addr, то текущим становится левый дочерний узел, в противном случае — правый. Функция завершает свою работу, как только находится область памяти, которая содержит адрес addr. Если такая область VMA не найдена, то функция продолжает поиск по дереву и воз- вращает ту область памяти, которая начинается после адреса addr. Если вообще не найдена ни одна область памяти, то возвращается значение NULL. Функция find_vma_prev() Функция find_vma_prev () работает аналогично функции f i n d vma () , но до- полнительно она еще возвращает последнюю область VMA, которая заканчивается перед адресом addr. Эта функция также определена в файле mma/mmap.c и объявле- на в файле struct vm_area_struct * find vma_prev (struct mm_struct *mm, unsigned long addr, struct vm_area_struct **pprev) Параметр pprev после возвращения из функции содержит указатель на предыду- щую область VMA. Функция find_VMA_intersection() Функция f ind_vma_intersection () возвращает первую область памяти, кото- рая перекрывается с указанным интервалом адресов. Эта функция определена в фай- ле static inline struct vm_area_struct * find_vma_intersection( struct mm_struct *mm, unsigned long start_addr, unsigned long end addr) { struct vm_area_struct *vma; vma = find_vma (mm, start_addr) ; if (vma && end_addr <= vma->vm_start) vma = NULL; return vma; } Первый параметр — адресное пространство, в котором выполняется поиск, пара- метр s t a r t _ a d d r — это первый адрес интервала адресов, а параметр end_addr — по- следний адрес интервала. Очевидно, что если функция find_vma() возвращает значение NULL, то это же значение будет возвращать и функция f i n d _ v m a _ i n t e r s e c t i o n ( ) . Если функция 324 Глава 14 find_vma () возвращает существующую область VMA, то функция find_vma_inter- s e c t i o n () возвратит ту же область только тогда, когда эта область не начинается после конца данного диапазона адресов. Если область памяти, которая возвращается функцией find_vma (), начинается после последнего адреса из указанного диапазо- на, то функция f ind_vma_intersection () возвращает значение NULL. |