Ядро ОС Linux

       

Выделение и освобождение памяти: политика страничной организации


Когда любой процедуре ядра требуется память, она прекращает работу, вызывая get_free_page(). Это представляет собой более низкий уровень, чем kmalloc() (в действительности kmalloc() использует get_free_page(), когда возникает необходимость в расширении памяти).

get_free_mem() использует один параметр, приоритет. Допустимыми значениями являются GFP_BUFFER, GFP_KERNEL и GFP_ATOMIC. Она берет страницу из free_page_list, редактирует mem_map, обнуляет страницу и возвращает физический адрес страницы (заметте, что kmalloc() возвращает физический адрес. Логика mm зависит от идентичности отображения между логическими и физическими адресами).

Само по себе это достаточно просто. В действительности проблема состоит в том, что free_pagr_list может быть пуст. Если вы не испытывали потребности в использовании примитивных операций на этой стадии, вы попадаете в область вопросов изъятия стр аниц, которую мы будем тщательно рассматривать немного позже. Как крайнее средство (и это касается примитивных операций) страница изымалась из secondary_page_list (как вы могли догадаться, когда страницы освобождаются, в первую очередь происходит заполнение secondary_page_list).

В действительности манипуляции с page_lists и mem_map происходят в результате действий этого загадочного макроса, называемого REMOVE_FROM_MEM_QUEUE(), внутрь которого вы, возможно, никогда не захотите заглянуть. Достаточно сказать, что прерывания запрещены.

Теперь немного назад, к вопросу изъятия страниц. get_free_page() вызывает try_to_free_page, которая повторяет вызов shrink_buffers() и swap_out() в данной последовательности до тех пор, пока освобождение страниц не будет закончено успешно. Приоритет снижается при каждой успешной итерации, так что эти две процедуры чаще выполняют свои циклы по изъятию страниц.

Рассмотрим один проход swap_out():

  • Пройти по таблице процесса и получить ответ Q от замещаемой задачи.
  • Найти таблицу страницы пользователя (не RESERVED) в области, принадлежащей Q.
  • Для каждой page в таблице выполнить try_to_swap_out(page).
  • Выход, когда страница освобождена.

  • Отметим, что swap_out() (вызываемая из try_to_free_page()) поддерживает статические переменные, таким образом можно получить ответ на вопрос где она была освобождена при предыдущем вызове.

    try_to_swap_out() сканирует таблицы страниц всех пользовательских процессов и проводит следующую политику изъятия:

  • Не относиться легкомысленно к RESERVED страницам.
  • Подвергать старению страницу, если она помечена как доступная (1 бит).
  • Не трогать страницы, выделенные ранее (last_free_pages[]).
  • Оставить грязные страницы с map_counts> 1.
  • Снизить значение map_count для чистых страниц.
  • Освободить чистые страницы, если для них не установлено соответствие.
  • Заместить грязные страницы с map_count = 1.


  • Результатом действий 6 и 7 будет остановка процесса при текущем освобождении физической страницы. Действие 5 вызывает потерю одним из процессов чистой неразделяемой страницы, к которой ранее не было доступа (снижение Q->rss), что вовсе неплохо, но действия накопления за несколько циклов могут значительно замедлить процесс. В данном случае происходит 6 итераций, таким образом страница, разделяемая шестью процессами, может быть изъята, если она чистая.

    Входы таблицы страниц изменяются и TLB становится недействительной.[Последнее вызывает интерес. В этом, кажется, нет необходимости т.к. доступные страницы не удаляются и многие таблицы страниц могут быть просмотрены между итерациями.... возможно, в результате прерывания, если такое произошло и имела место попытка урезать наиболее раннюю страницу?]

    Текущая работа по освобождению страницы выполняется free_page(), являющегося дополнением get_free_page(). Она игнорирует RESERVED страницы, редактирует mem_map, затем освобождает страницу и изменяет page_lists, если для него не установлено соответствие. Для подкачки (см. п.6 выше) write_swap_pege() принимает вызов, но не делает ничего значительного с точки зрения дальнейшего управления памятью.

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

    Заметим, что директории страниц и таблицы страниц наряду с RESERVED страниц не становятся изменяемыми, не изымаются и не стареют. Они отображаются в директории страниц процесса через зарезервированные таблицы страниц. Они освобождаются только пр и выходе из процесса.


    Содержание раздела