> man operating_systems
Как Linux работает с памятью.
Исследование реализации виртуальной памяти в Linux
на Вторник, 20 Июль 2004, 00:20
добавил: Stanislav Ievlev список авторов печатать элемент контента создать pdf-файл  элемент контента
категория Статьи > Unix
комментарии: 0
просмотров: 942

Рассмотрение работы ядра Linux с памятью на уровне исходных кодов.
<hr>

Как Linux работает с памятью.

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

Возникла мысль обратиться к прошлому , чтобы по крайней мере разобраться как все это развивалось (версия 0.1). Это помогло понять и современное ядро. В дальнейшем речь пойдет о ядрах серии 2.2 об изменениях в 2.4 будет сообщено особо.

Не буду углубляться в тонкости функционировани защищенного режима процессора об этом написаны целые фолианты. Посмотрим только самую суть.

Итак, в овнове всего лежат страницы памяти. В ядре они описываются структурой mem_map_t.

typedef struct page { 
        /* these must be first (free area handling) */ 
        struct page *next; 
        struct page *prev; 
        struct inode *inode; 
        unsigned long offset; 
        struct page *next_hash; 
        atomic_t count; 
        unsigned long flags;    /* atomic flags, some possibly updated asynchronously */ 
        struct wait_queue *wait; 
        struct page **pprev_hash; 
        struct buffer_head * buffers; 
} mem_map_t; 

Уже тут наблюдается наворороченност. Множество всяких ссылок. Все они используются . Одна страница может находиться в разных списках , например и всписке страниц в страничном кеше и в списке страниц относящихся к отображенному в память файлу (inode).В структуре описывающей последний можно найти и обратную ссылку,что очень удобно.

Все страницы адресуются глобальным указателем mem_map

mem_map_t * mem_map

Адресация страниц порисходит очень хитро. Если раньше в структуре page было отдельное поле указывающее на физический адрес (map_nr), то теперь он вычисляется

static inline unsigned long page_address(struct page * page) 

        return PAGE_OFFSET + PAGE_SIZE * (page - mem_map); 


Свободные страницы хранятся в особой структуре free_area

static struct free_area_struct free_area[NR_MEM_TYPES][NR_MEM_LISTS]; 

, где первое поле отвечает за тип области : Ядра, Пользователя, DMA и.т.д. И обрабатываются по очень хитрому алгоритму.

Страницы делятся на свободные непрерывные обрасти размера 2 в степени x умноженной на размер страницы ( (2^x)*PAGE_SIZE ). Области одного размера лежат в одной области массива.

.... 
|------ 
|Свободные Страницы размера PAGE_SIZE*4 ---> список свободных областей 
|------ 
|Свободные Страницы размера PAGE_SIZE*2 ---> список свободных областей 
|------ 
|Свободные Страницы размера PAGE_SIZE  ---> список свободных областей 
|------ 

Выделяет страницу функция get_free_pages(order). Она выделяет страницы составляющие область размера PAGE_SIZE*(2^order). Ищется область соответствующего размера или больше. Если есть только область большего размера то она делится на несколько маленьких и берется нужный кусок. Если свободных страниц недостаточно, то некоторые будут сброшены в область подкачки и процесс выделенения начнется снова.

Возвращает страницу функция free_pages(struct page, order). Высвобождает страницы начинающиеся с page размера PAGE_SIZE*(2^order). Область возвращается в массив свободных обрастей в соответствующую позицию и после этого происходит попытка объединить несколько областей для создания одной большего размера.

Отсутствие страницы в памяти обрабатыватся ядром особо. Страница может или вообще отсутствовать или находиться в области подкачки.

Вот собственно и вся базовая работа с реальными страницами.Самое время вспомнить, что процесс работает все-каки с виртуальными адресами, а не с физическими. Преобразование происходит посредством вичислений, используя таблицы дескрипторов, и каталоги таблиц. Linux поддерживает 3 уровня таблиц: каталог таблиц первого уровня (PGD - Page Table Directory),каталог таблиц второго уровня (PMD - Medium Page Table Diractory), и наконец таблица дескрипторов (PTE - Page Table Entry). Реально конкректным процессором могут поддерживаться не все уровни, но запас позволяет поддерживать больше возможных архитектур (Intel имеет 2 уровня таблиц, а Alpha - целых 3 ). Преобразование виртуального адреса в физический происходит соответственно в 3 этапа. Берется указатель PGD, имеющийся в структуре описывающий каждый процесс, преобразуется в указатель записи PMD, а последний преобразуется в указатель в таблице дескрипторов PTE. И наконец к реальному адресу указывающему на начало страницы прибавляют смещение от ее начала.

        page_dir = pgd_offset(vma->vm_mm, address); 
        if (pgd_none(*page_dir)) 
                return; 
        if (pgd_bad(*page_dir)) { 
                printk("bad page table directory entry %p:[%lx]\n", page_dir, pgd_val(*page_dir)); 
                pgd_clear(page_dir); 
                return; 
        } 
        page_middle = pmd_offset(page_dir, address); 
        if (pmd_none(*page_middle)) 
                return; 
        if (pmd_bad(*page_middle)) { 
                printk("bad page table directory entry %p:[%lx]\n", page_dir, pgd_val(*page_dir)); 
                pmd_clear(page_middle); 
                return; 
        } 
        page_table = pte_offset(page_middle, address); 

Вообще-то все данные об используемой процессом памяти помещаются в структуре mm_struct

struct mm_struct { 
        struct vm_area_struct *mmap;            /* Список отображенных областей  */ 
        struct vm_area_struct *mmap_avl;        /* Те же области но уже в виде дерева для более быстрого поиска*/ 
        struct vm_area_struct *mmap_cache;      /* Последняя найденная область*/ 
        pgd_t * pgd;                                                 /*Каталог таблиц*/ 
        atomic_t count; 
        int map_count;                          /* Количество областей*/ 
        struct semaphore mmap_sem; 
        unsigned long context; 
        unsigned long start_code, end_code, start_data, end_data; 
        unsigned long start_brk, brk, start_stack; 
        unsigned long arg_start, arg_end, env_start, env_end; 
        unsigned long rss, total_vm, locked_vm; 
        unsigned long def_flags; 
        unsigned long cpu_vm_mask; 
        unsigned long swap_cnt; /* number of pages to swap on next pass */ 
        unsigned long swap_address; 
        /* 
         * This is an architecture-specific pointer: the portable 
         * part of Linux does not know about any segments. 
         */ 
        void * segments; 
}; 


Сразу замечаем, что помимо вполне понятных указателей на начало данных (start_code, end_code ...) кода и стека есть указатели на данные отображенных файлов (mmap). Это надо сказать особенность Linux - тащить в себя все что только можно. Может быть это и хорошо, но с другой стороны так разбазариваться памятью (вспомним еще буфера ввода/вывода при файловой системе, которые тоже будут кушать все новую память пока она есть) Данный подход может негативно отразиться на стабильности системы, ведь для запуска какого-то жизненно необходимого процесса может потребоваться время на освобождение лишних кешей. Простенькая проверка на потерю свободной памяти: введите команду "cat /dev/mem >/image " и посмотрите сколько свободной памяти после этого осталось. Если вам это не нравится, то обратите взгляд на функцию invalidate_inode_pages(* struct_inode), освобождающую страничный кэш для данного файла.
При любом открытии файла, он сразу же отображается в память и добавляется в страничный кэш. Реальный же запрос на отображение файла только возвращает адрес на уже скешированные страницы.
На уровне процесса работа может вестить как со страницами напямую, так и через абстрактную структуру vm_area_struct


struct vm_area_struct { 
        struct mm_struct * vm_mm;       /* VM area parameters */ 
        unsigned long vm_start; 
        unsigned long vm_end; 

        /* linked list of VM areas per task, sorted by address */ 
        struct vm_area_struct *vm_next; 

        pgprot_t vm_page_prot; 
        unsigned short vm_flags; 

        /* AVL tree of VM areas per task, sorted by address */ 
        short vm_avl_height; 
        struct vm_area_struct * vm_avl_left; 
        struct vm_area_struct * vm_avl_right; 

        /* For areas with inode, the list inode->i_mmap, for shm areas, 
         * the list of attaches, otherwise unused. 
         */ 
        struct vm_area_struct *vm_next_share; 
        struct vm_area_struct **vm_pprev_share; 

        struct vm_operations_struct * vm_ops; 
        unsigned long vm_offset; 
        struct file * vm_file; 
        unsigned long vm_pte;                   /* shared mem */ 
}; 

struct vm_operations_struct { 
        void (*open)(struct vm_area_struct * area); 
        void (*close)(struct vm_area_struct * area); 
        void (*unmap)(struct vm_area_struct *area, unsigned long, size_t); 
        void (*protect)(struct vm_area_struct *area, unsigned long, size_t, unsigned int newprot); 
        int (*sync)(struct vm_area_struct *area, unsigned long, size_t, unsigned int flags); 
        void (*advise)(struct vm_area_struct *area, unsigned long, size_t, unsigned int advise); 
        unsigned long (*nopage)(struct vm_area_struct * area, unsigned long address, int write_access); 
        unsigned long (*wppage)(struct vm_area_struct * area, unsigned long address, 
                unsigned long page); 
        int (*swapout)(struct vm_area_struct *, struct page *); 
        pte_t (*swapin)(struct vm_area_struct *, unsigned long, unsigned long); 
}; 

Идея данной структуры возникла из идеи виртуальной файловой системы, поэтому все операции над виртуальными областями абстрактны и могут быть специфичными для разных типов памяти, например при отображении файлов операции чтения одни а при отображении памяти (через файл /dev/mem ) совершенно другие. Первоначально vm_area_struct появилась для обеспечения нужд отображения, но постепенно распространяется и на другие области.

Что делать, когда требуется получить новую область памяти. Есть целых 3 способа.

1. Уже знакомый get_free_page()
2. kmalloc - Простенькая (по возможностям, но отнюдь не коду) процедура с большими ограничениями по выделению новых областей и по их размеру.
3. vmalloc - Мощная процедура, работающая с виртуальной памятью, может выделять большие объемы памяти.

С каждой из двух процедур в ядре связаны еще по списку свободных/занятых областей, что еще больше усложняет понимание работы с памятью. (vmlist для vmalloc, kmem_cash для kmalloc)

Что же в 2.4.

Добавлена поддержка новой архитектуры памяти NUMA. В противовес классической UMA память делится на зоны с разным временем доступа к каждой из них . Это очень полезно и для кластерных решений. В связи с этим появились новые обертки на функции и найти суть стало еще сложнее. Появилась также поддержка памяти до 64Гб.
Раньше для всех файловых систем был один generic_file_read и generic_file_mmap в связи с тотальным засасыванием всего подряд в память при чтении (различия делались уже только на уровне inode->readpage). Теперь появился и generic_file_write. В общем еще пара таких generic и прощай виртуальная файловая система.

Но посмотрим - увидим. Ведь Linux развивается очень быстро и не всегда предсказуемо.


Комментарии доступны только авторизованным пользователям, авторизуйтесь или зарегистрируйтесь на сайте здесь

© OSRC.info, 2004-2010.
Авторские права на любые материалы, авторы которых явно указаны, принадлежат их авторам. По вопросам публикации таких материалов обращайтесь к авторам.
Авторские права на любые другие материалы принадлежат OSRC.info.
Сайт является помещением библиотеки. Копирование, сохранение на жестком диске или иной способ сохранения произведений осуществляются пользователями на свой риск.
При использовании материалов сайта ссылка на OSRC.info обязательна.