Page Table Management
Линукс поддерживает концепцию 3-уровневой page-tables.
Различие между различными типами страниц
расплывчато и определимо с помощью флагов .
Основные термины :
Memory Management Unit (MMU)
Page Middle Directory (PMD)
Page Global Directory (PGD)
Translation Lookaside Buffer (TLB)
У каждого процесса есть указатель mm_struct->pgd на свою PGD .
PGD физически - страница памяти .
Он включает массив pgd_t . При загрузке mm_struct->pgd копируется в регистр cr3 .
В свою очередь , каждая строка PGD включает в себя указатель
на страницу памяти с массивом PMD типа pmd_t ,
а та в свою очередь указывает на страницы PTE типа pte_t , которые указывают
наконец-то на реальные адреса пользовательских данных .
|
|
На рисунке видно , что линейный адрес состоит из 4 частей .
Есть группа макросов - SHIFT,SIZE,MASK . Макрос SHIFT определяtт битовую
длину каждой части .
Макрос MASK используется как коэффициент для подправки с учетом
выравнивания.
Макрос SIZE определяет количество байт у адреса для каждого уровня .
Связь между макросами MASK и SIZE показана ниже .
|
|
#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE-1))
PAGE_SHIFT=12 - это длина смещения в линейном адресе в битах .
Не для интеловской архитектуры это число может быть другим .
PAGE_SIZE = 2^PAGE_SHIFT. PAGE_MASK вычисляется как логическое
отрицание от (PAGE_SIZE-1).
PMD_SHIFT , PMD_SIZE , PMD_MASK берутся из page table 2-го уровня
и вычисляются аналогично .
PGDIR_SHIFT , PGDIR_SIZE , PGDIR_MASK берутся из page table 1-го ,
верхнего уровня и вычисляются аналогично .
Макрос PTRS_PER_x - это число строк в page table .
PTRS_PER_PGD - число строк в PGD , которое обычно фиксировно и равно 1024 .
PTRS_PER_PMD - число строк в PMD , которое = 1 .
PTRS_PER_PTE - число строк для page table самого низкого уровня и обычно = 1024 .
Следующая таблица показывает назначение отдельных битов в pte_t :
Page Table Entry Protection and Status Bits
|
Bit
|
Function
|
_PAGE_PRESENT
|
Page is resident in memory and not swapped out.
|
_PAGE_PROTNONE
|
Page is resident, but not accessible.
|
_PAGE_RW
|
Set if the page may be written to
|
_PAGE_USER
|
Set if the page is accessible from userspace
|
_PAGE_DIRTY
|
Set if the page is written to
|
_PAGE_ACCESSED
|
Set if the page is accessed
|
|
В файле определены макросы для работы с page table .
3 макроса для работы с page directory :
pgd_offset() - возвращает адрес PGD
pmd_offset() - возвращает адрем PMD
pte_offset() - возвращает адрес PTE
pte_none(), pmd_none() and pgd_none() - эти макросы обрабатывают
отсутствие адреса в page table
pte_present(), pmd_present() and pgd_present() - аналогично для наличия
pte_clear(), pmd_clear() and pgd_clear() - убирают строку в page table
Пример использования :
pgd_t *pgd;
pmd_t *pmd;
pte_t *ptep, pte;
pgd = pgd_offset(mm, address);
if (pgd_none(*pgd) || pgd_bad(*pgd)) goto out;
Еще одна группа макросов проверяет статус адресов и их права
на запись,выполнение :
pte_read(), set with pte_mkread() , pte_rdprotect()
pte_write(), set with pte_mkwrite(),pte_wrprotect()
pte_exec(), set with pte_mkexec(),pte_exprotect().
Следующая группа функций и макросов имеет отношение к маппингу адресов PTE.
Макрос mk_pte() формирует pte_t путем комбинации структуры page и protection bits .
Макрос pte_page() наоборот возвращает структуру page по данному pte_t .
Последний набор функций выделяет память для page tables .
Выделение памяти под page tables - критический момент,
в течение которого прерывания должны
быть задисэйблены.Это очень популярная процедура и должна
выполняться максимально быстро .
Страницы кэшируются в различные списки , которые называются quicklists.
PGDs, PMDs и PTEs имеют 2 набора функций для размещения и удаления страниц .
Это соответственно pgd_alloc(), pmd_alloc() ,pte_alloc() и pgd_free(),
pmd_free() and pte_free().
В основе формирования этих списков лежит структура LIFO - Last In, First Out .
Если страница не может быть помещена в кеш , она размещается
в памяти с помощью page allocator .
Специальный механизм следит за тем , чтобы кеш находился
в пределах фиксированного обьема .
Инициализация page tables разделена на 2 части .
При старте системы инициализируются только первые 8 метров,остальное позже .
В файле arch/i386/kernel/head.S лежит функция startup_32() .
Стартовый загрузочный адрес , по которому загружается ядро - 0x00100000.
Первый мегабайт используется устройствами для работы с биосом .
Инициализация page tables начинается со статического массива swapper_pg_dir , который
размещается по адресу 0x00101000 . Затем инициализируются 2 таблицы pg0 и pg1 .
Если процессор поддерживает Page Size Extension (PSE) ,
размер страницы будет установлен
в 4 метра, а не в 4 килобайта , как обычно .
Остальную работу по инициализации page table
выполняет paging_init. Граф инициализации :
|
|
При иницмализации page tables должна стать доступной
вся память в зоне ZONE_DMA и ZONE_NORMAL .
При загрузке системы для каждой pgd_t , которая используется ядром ,
выделит страницу PGD .
Функция fixrange_init() выделяет память
для Advanced Programmable Interrupt Controller (APIC).
Статический адрес swapper_pg_dir для PGD загружатеся в CR3 .
Функция kmap_init() инициализирует PTE.
Перевод виртуального адреса в физический должен быть максимально быстрым ,
и это достигается с помощью глобального массива mem_map .
Физический адрес может быть получен из виртуального
простым вычитанием PAGE_OFFSET :
/* from */
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
/* from */
static inline unsigned long virt_to_phys(volatile void * address)
{
return __pa(address);
}
Ядро загружается по адресу 1MiB и берет на свои нужды 8 метров .
Ядро пытается зарезервировать 16 метров в ZONE_DMA ,
и стартовый виртуальный адрес будет соответствовать физическому 0xC1000000.
Таблица mem_map содержит индексы физических адресов -
Page Frame Number (PFN) - который получается
путем сдвига виртуального адреса :
#define virt_to_page(kaddr) (mem_map + (__pa(kaddr) >> PAGE_SHIFT))
Для ускорения обращения к памяти , существует специальная таблица -
Translation Lookaside Buffer (TLB) -
которая представляет из себя набор наиболее часто используемых адресов памяти .
Использование этой таблицы позволяет избегать многократного повторения
вычисления одних и тех же физических адресов ,
которые уже имеются в этом буфере .
Интеловские процессоры имеют встроенный 2-уровневый кеш .
Кеш 2-уровня больше , но медленнее , чем 1-й .
Линукс использует кеш 1-го уровня .
Обращение к кешу быстрее на порядок , чем к памяти .
|
|