Linux Kernel (Ядро линукса) (часть 2)

04.11.2021 в 23:12 Просмотров: 18 Комментариев: 0 Полезные книги

Копирование при записи

В системах Unix первых поколений создание процесса было реализовано довольно неуклюже: получив системный вызов fork о, ядро в буквальном смысле дублировало все адресное пространство родителя и присваивало копию процессу-потомку. Такая операция занимало очень много времени, поскольку для нее требовалось:

  • выделение страничных кадров под Таблицы Страниц процесса-потомка;
  • выделение страничных кадров под страницы процесса-потомка;
  • инициализация Таблиц Страниц процесса-потомка;
  • копирование страниц родителя в соответствующие страницы потомка.

Такой способ создания адресного пространства включал много обращений к памяти, использовал много рабочих циклов процессора и существенно «портил” содержимое кэша. Кроме всего прочего, это часто не имело никакого смысла, потому что многие процессы-потомки начинают свою работу с загрузки новой программы и полностью отказываются от унаследованного адресного пространства Современные ядра Unix, в том числе ядро Linux, предпринимают более эффективный подход, называемый копированием при записи.

Идея достаточно проста: страничные кадры не копируются, а используются совместно родителем и потомком. Однако при совместном использовании их нельзя модифицировать. Как только родитель или потомок попытается записать данные в совместно используемый страничный кадр, возникнет исключение. В этот момент ядро скопирует страницу в новый страничный кадр и пометит его как доступный для чтения.

Оригинальный страничный кадр остается недоступным для записи. Когда другой процесс попытается что-то записать в него, ядро проверит, является ли пишущий процесс единственным владельцем страничного кадра. Если является, оно сделает страничный кадр доступным для записи для этого процесса.

Поле count дескриптора страницы служит для отслеживания количества процессов, совместно использующих страничный кадр. Когда какой-то процесс освобождает страничный кадр или над кадром выполняется операция копирования при записи, значение в поле count этого кадра уменьшается.

Страничный кадр освобождается, только когда значение count сравняется с -1
Опишем, как копирование для записи реализовано в Linux. Когда функция handie pte fauit распознает, что исключение «ошибка обращения к странице” было вызвано обращением к странице, присутствующей в памяти, она выполняет следующий фрагмент кода:
if (pte_present(entry)) { if (write_access) {
if (!pte_write(entry))
return do_wp_page(mm, vma, address, pte, pmd, entry); entry = pte_mkdirty(entry);}
entry = pte_mkyoung(entry); set_pte(pte, entry); flush_tlb_page(vma, address); pte_unmap(pte);
spin_unlock(&mm->page_table_lock); return VM_FAULT_MINOR;
}

Функция handieptef auitявляется архитектурно-независимой; она рассматривает все возможные нарушения прав доступа к страницам. Однако в архитектуре 80×86, если страница находится в памяти, значит, имела место попытка записи, а страничный кадр для записи недоступен. Таким образом, функция do wp pageвызывается всегда.

Функция do wp page о11 начинает с получения дескриптора страницы того страничного кадра, на который ссылается запись Таблицы Страниц, вовлеченная в обработку исключения. Затем функция определяет, действительноли необходимо копировать страницу. Если страницей владеет только один процесс, копирование при записи не применяется, и процесс волен записывать данные на страницу.

Грубо говоря, функция читает поле count дескриптора страницы: если оно содержит 0 (единственный владелец), копирование при записи не производится. Впрочем, на практике эта проверка немного сложнее, потому что поле count увеличивается также, когда страница заносится в кэш выгрузки (, и когда устанавливается флаг PG private.

Однако когда копирование при записи делать не нужно, страничный кадр помечается как доступный для записи, и при следующих попытках записать в него исключение «ошибка обращения к странице” не возникает:
set_pte (page_table, maybe_mkwrite (pte_mkyoung (pte_mkdirty (pte) ), vma) ) ;
flush_tlb_page(vma, address);
pte_unmap(page_table);
spin_unlock(&mm->page_table_lock);
return VM_FAULT_MINOR;

Если страница совместно используется несколькими процессами с применением техники копирования при записи, функция копирует содержимое старого страничного кадра (oid page) в только что выделенный (new page). Во избежание конфликтов параллельного обращения вызывается функция get page, которая увеличивает счетчик ссылок страничного кадра oid page до начала копирования:
old_page = pte_page(pte);
pte_unmap(page_table);
get_page(old_page);
spin_unlock(&mm->page_table_lock);
if (old_page == virt_to_page(empty_zero_page))
new_page = alloc_page(GFP_HIGHUSER | GFP_ZERO);
} else {
new_page = alloc_page(GFP_HIGHUSER); vfrom = kmap_atomic (old_page, KM_USER0) vto = kmap_atomic (new_page, KM_USER1) ; copy_page(vto, vfrom); kunmap_atomic(vfrom, KM_USER0); kunmap_atomic(vto, KM_USER0);
}

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

Поскольку выделение страничного кадра может блокировать процесс, функция проверяет, была ли модифицирована запись Таблицы Страниц после начала ВЫПОЛНеНИЯ фуНКЦИИ (раВНЫ ЛИ Значения pte И page_table). В этом случае новый страничный кадр освобождается, счетчик ссылок кадра oid page уменьшается (для отмены ранее сделанного увеличения), и функция завершает работу.

Если все выглядит нормально, физический адрес нового страничного кадра, в конце концов, заносится в запись Таблицы Страниц, и содержимое соответствующего регистра TLB становится недействительным:
spin_lock(&mm->page_table_lock);
entry = maybe_mkwrite(pte_mkdirty(mk_pte(new_page,
vma->vm_page_prot) ), vma) ;
set_pte(page_table, entry); flush_tlb_page(vma, address); lru_cache_add_active(new_page); pte_unmap(page_table); spin_unlock(&mm->page_table_lock);

Функция iru cache add active заносит новый страничный кадр в структуры, имеющие отношение к выгрузке на дискНаконец, функция do_wp_page дважды уменьшает счетчик ссылок кадра oid page. Первое уменьшение отменяет увеличение, сделанное в целях безопасности перед копированием содержимого страничного кадра, а второе отражает тот факт, что текущий процесс больше не владеет этим страничным кадром.

Поделитесь с друзьями:



Комментарии 0

Добавить комментарий

Имя *:
Email:

Copyright © 2021