Разработка ядра Linux - Роберт Лав
Шрифт:
Интервал:
Закладка:
Рассмотрим эту структуру с комментариями, поясняющими назначение каждого поля.
struct mm_struct {
struct vm_area_struct *mmap; /* список областей памяти */
struct rb_root mm_rb; /* красно-черное дерево
областей памяти */
struct vm_area_struct *mmap_cache; /* последняя использованная
область памяти */
unsigned long free_area_cache; /* первый незанятый участок
адресного пространства */
pgd_t *pgd; /* глобальный каталог страниц */
atomic_t mm_users; /* счетчик пользователей адресного
пространства */
atomic_t mm_count; /* основной счетчик использования */
int map_count; /* количество областей памяти */
struct rw_semaphore mmap_sem; /* семафор для областей памяти */
spinlock_t page_table_lock; /* спин-блокировка
таблиц страниц */
struct list_head mmlist; /* список всех структур mm_struct */
unsigned long start_code; /* начальный адрес сегмента кода */
unsigned long end code; /* конечный адрес сегмента кода */
unsigned long start_data; /* начальный адрес сегмента данных */
unsigned long end_data; /* конечный адрес сегмента данных */
unsigned long start_brk; /* начальный адрес сегмента "кучи" */
unsigned long brk; /* конечный адрес сегмента "кучи" */
unsigned long start_stack; /* начало стека процесса */
unsigned long arg_start; /* начальный адрес
области аргументов */
unsigned long arg_end; /* конечный адрес
области аргументов */
unsigned long env_start; /* начальный адрес
области переменных среды */
unsigned long env_end; /* конечный адрес
области переменных среды */
unsigned long rss; /* количество физических страниц памяти */
unsigned long total_vm; /* общее количество страниц памяти */
unsigned long locked_vm; /* количество заблокированных страниц
памяти */
unsigned long def_flags; /* флаги доступа, используемые
по умолчанию */
unsigned long cpu_vm_mask; /* маска отложенного переключения
буфера TLB */
unsigned long swap_address; /* последний сканированный адрес */
unsigned dumpable:1; /* можно ли создавать файл core? */
int used_hugetlb; /* используются ли гигантские
страницы памяти (hugetlb)? */
mm_context_t context; /* данные, специфичные для аппаратной
платформы */
int core_waiters; /* количество потоков, ожидающих на
создание файла core */
struct completion *core_startup_done; /* условная переменная начала
создания файла core */
struct completion core_done; /* условная переменная завершения
создания файла core */
rwlock_t ioctx_list_lock; /* блокировка списка асинхронного
ввода-вывода (AIO) */
struct kioctx *ioctx_list; /* список асинхронного ввода-вывода (AIO) */
struct kioctx default_kioctx; /* контекст асинхронного ввода-
вывода, используемый по умолчанию */
};
Поле mm_users — это количество процессов, которые используют данное адресное пространство. Например, если одно и то же адресное пространство совместно используется двумя потоками, то значение поля mm_users равно двум. Поле mm_count — это основной счетчик использования структуры mm_struct. Наличие пользователей структуры, которым соответствует поле mm_users, приводит к увеличению счетчика mm_count на единицу. В предыдущем примере значение поля mm_count равно единице. Когда значение поля mm_users становится равным нулю (т.е. когда два потока завершатся), только тогда значение поля mm_count уменьшается на единицу. Когда значение поля mm_count становится равным нулю, то на соответствующую структуру mm_struct больше нет ссылок, и она освобождается, Поддержка двух счетчиков позволяет ядру отличать главный счетчик использования (mm_count) от количества процессов, которые используют данную структуру (mm_users).
Поля mmap и mm_rb — это два различных контейнера данных, которые содержат одну и ту же информацию: информацию обо всех областях памяти в соответствующем адресном пространстве. В первом контейнере эта информация хранится в виде связанного списка, а во втором — в виде красно-черного бинарного дерева. Поскольку красно-черное дерево — это разновидность бинарного дерева, то, как и для всех типов бинарного дерева, количество операций поиска заданного элемента в нем равно О(log(n)). Более детальное рассмотрение красно-черных деревьев найдете в разделе "Списки и деревья областей памяти".
Хотя обычно в ядре избегают избыточности, связанной с введением нескольких структур для хранения одних и тех же данных, тем не менее в данном случае эта избыточность очень кстати. Контейнер mmap — это связанный список, который позволяет очень быстро проходить по всем элементам. С другой стороны, контейнер mm_rb — это красно-черное дерево, которое очень хорошо подходит для поиска заданного элемента. Области памяти будут рассмотрены в этой главе несколько ниже,
Все структуры mm_struct объединены в двухсвязный список с помощью нолей mmlist. Первым элементом этого списка является дескриптор памяти init_mm, который является дескриптором памяти процесса init. Этот список защищен от конкурентного доступа с помощью блокировки mmlist_lock, которая определена в файле kernel/fork.с. Общее количество дескрипторов памяти хранится в глобальной целочисленной переменной mmlist_nr, которая определена в том же файле.
Выделение дескриптора памяти
Указатель на дескриптор памяти, выделенный для какой-либо задачи, хранится в поле mm дескриптора процесса этой задачи. Следовательно, выражение current->mm позволяет получить дескриптор памяти текущего процесса. Функция copy_mm() используется для копирования дескриптора родительского процесса в дескриптор порожденного процесса во время выполнения вызова fork(). Структура mm_struct выделяется из слябового кэша mm_cachep с помощью макроса allocate_mm(). Это реализовано в файле kernel/fork.c. Обычно каждый процесс получает уникальный экземпляр структуры mm_struct и соответственно уникальное адресное пространство.
Процесс может использовать одно и то же адресное пространство совместно со своими порожденными процессами, путем указания флага CLONE_VM при выполнении вызова clone(). Такие процессы называются потоками. Вспомните из материала главы 3, "Управление процессами", что в операционной системе Linux в этом и состоит единственное существенное отличие между обычными процессами и потоками. Ядро Linux больше никаким другим образом их не различает. Потоки с точки зрения ядра — это обычные процессы, которые просто совместно используют некоторые общие ресурсы.
В случае, когда указан флаг CLONE_VM, макрос allocate_mm() не вызывается, а в поле mm дескриптора порожденного процесса записывается значение указателя на дескриптор памяти родительского процесса. Это реализовано с. помощью следующего оператора ветвления в функции сору_mm().
if (clone_flags & CLONE_VM) {
/*
* current — это родительский процесс
* tsk — это процесс, порожденный в вызове fork()
*/
atomic_inc(¤t->mm->mm_users);
tsk->mm = current->mm;
}
Удаление дескриптора памяти
Когда процесс, связанный с определенным адресным пространством, завершается, то вызывается функция exit_mm(). Эта функция выполняет некоторые служебные действия и обновляет некоторую статистическую информацию. Далее вызывается функция mput(), которая уменьшает на единицу значение счетчика количества пользователей mm_users для дескриптора памяти. Когда значение счетчика количества пользователей становится равным нулю, то вызывается функция mmdrop(), которая уменьшает значение основного счетчика использования mm_count. Когда и этот счетчик использования наконец достигает нулевого значения, то вызывается функция free_mm(), которая возвращает экземпляр структуры mm_struct в слябовый кэш mm_cachep с помощью вызова функции kmem_cache_free(), поскольку дескриптор памяти больше не используется.