Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Управление памятью в Linux


Cистема управления памятью в Linux осуществляет подкачку страниц по обращению в соответствии с COPY-ON-WRITE стратегией, основанной на механизме подкачки, который поддерживается 386-м процессором. Процесс получает свои таблицы страниц от родител я (при выполнении fork()) со входами, помеченными как READ-ONLY или замещаемые. Затем, если процесс пытается писать в эту область памяти, и страница является COPY-ON-WRITE страницей, она копируется и помечается как READ-WRITE. Инструкция exec() приводит к считыванию страницы или то же самое происходит при выполнении программы. В дальнейшем процессу затруднительно получить доступ к другой странице. Каждый процесс имеет директорию страниц, что подразумевает возможность доступа к 1КВ таблиц страниц, указывающих на 1МВ 4-х килобайтных страниц, которые размещаются в 4GB памяти. Директория страниц процесса инициализируется при выполнении распараллеливания посредством copy_page_tables(). Директория страниц холостого процесса инициализируется путем выполнения инициализирующей последовательности.

Каждый пользовательский процесс имеет локальную таблицу дескриптора, которая содержит сегмент кода и сегмент данные-стек. Сегментам пользователя отводится память от 0 до 3GB(0xc0000000). В пользовательском пространстве линейные адреса (см. сноску1) и логические адреса (см.сноску2) идентичны.

--- сноска1. В процессоре 80386 значение линейного адреса лежит в пределах 0GB - 4GB. Линейный адрес указывает на область памяти в этом пространстве. Линейный адрес отличен от физического адреса, он является виртуальным.

--- сноска 2. Логический адрес состоит из селектора и смещения. Селектор указывает на сегмент, а смещение говорит о том, где размещена область внутри сегмента.

Код ядра и сегменты данных являются привилегированными сегментами, определяются в таблице глобального дескриптора и размещаются в области от 3GB до 4GB. Установлена директория страниц программы подкачки (swapper_pg_dir) и таким образом логические и физические адреса в пространстве ядра являются идентичными.

Пространство выше 3GB появляется в директории страниц процесса как указатели на таблицы страниц ядра. Эта область прозрачна для процессов в режиме пользователя, однако, планирование распределения памяти становится уместным при привилегированном режиме, например, при выполнении системного вызова.

Режим супервизора вводится внутри контекста текущего процесса, так что трансляция адреса происходит относительно каталога страниц процесса, но используя сегменты ядра. Это идентично тому, как происходит управление памятью с использованием swapper _pg_dir и сегменты ядра как и директории страниц используют те же таблицы страниц в этом пространстве.

Только task[0] (холостая задача (см.сноску 3) [Этот термин должен был быть определен ранее] ) использует swapper_pg_dir напрямую.

---сноска 3. В силу исторических причин иногда называется задача подкачки, хотя при работе Linux с подкачкой она не связана.

  • Для пользовательского процесса segment_base = 0x00, page_dir для процесса своя.
  • процесс пользователя выполняет системный вызов: segment_base=0xc0000000 page_dir=та же самая page _dir пользователя
  • swapper_pg_dir содержит распределение памяти для всех физических страниц от 0xc0000000 до 0xc0000000 + end_mem, так что первые 768 входов в swapper_pg_dir раны 0 и затем имеются 4 или более входов, которые указывают на таблицы страниц ядра
  • Директории страниц пользователя имеют те же входы, как tt swapper_pg_dir свыше 768. Первые 768 входов представляют пространство пользователя.


В результате всегда, когда линейный адрес превышает 0xc0000000, используются те же таблицы страниц ядра.

Пользовательский стек размещается на вершине сегмента данных пользователя и увеличивается вниз. Стек ядра не является точным представлением структуры данных или сегмента, относительно которой я бы мог сказать "Вы находитесь в стеке ядра". Kernel_stack_frame (страница) связывается с каждым вновь создаваемым процессом и используется всякий раз, когда ядро выполняет действия с контекстом процесса. Неприятности могут произойти, если стек ядра будет расти ниже его текущего кадра.[Где размещен стек ядра? Я знаю, что для каждого процесса существует свой стек, но где он расположен, когда процесс не используется?]

Страницы пользователя могут заниматься или замещаться. Страница пользователя отражается ниже 3GB в таблице страниц пользователя. Эта область не содержит директорий страниц или таблиц страниц. Меняются местами (замещаются) только грязные страницы.

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


Ядро ОС Linux :: 6.2 Физическая память


Ниже представлена карта физической памяти перед тем, как будет выполнен любой процесс. Левый столбец представляет начальный адрес инструкции, отмеченные значения являются приблизительными. Средний столбец включает в себя название инструкции. Крайний правый столбец представляет имя соответствующей процедуры или переменной или комментарий входа.

    0x110000    свободна            memory_end или high_memory
 
                 mem_map             mem_init()
 
                 inode_table         inode_init()
 
                 ннформ. устройства  device_init()+
 
     0x100000    добав. pg_tables    paging_init()
 
 
     0x0A0000    не используется
 
     0x060000    свободна
 
                 low_memory_start
 
     0x006000    код ядра + данные
 
                 floppy_track_buffer
 
                 bad_pg_table        занято page_fault_handlers для
                 bad_page            уничтожения процесса, если он
                                     находится вне памяти.
 
     0x002000    pg0                 первая таблица страниц в ядре
 
     0x001000    swapper_pg_dir      каталог страниц ядра
 
     0x000000    нулевая страница


+ устройство, захватывающее память (main.c): profil_buffer, con_init, psaux_init, rd_init, scsi_dev_init. Заметьте, что не вся память помечена как FREE или RESERVRVED (mem_init).

Страницы, помеченные как RESERVED принадлежат ядру и никогда не освобождаются или переставляются.


Ядро ОС Linux :: 6.3 Память пользовательского процесса


      0xc0000000     невидимое ядро                       не используется
                      начальный стек
                      место для расширения стека           4 страницы
       0x60000000     стабильно записанные биьлиотеки
              brk     не используется
                      распределенная память
         end_data     не инициализированные данные
         end_code     инициализированные данные
       0x00000000     текст 


Как сегмент кода, так и сегмент данных в каждом случае размещаются в области от 0х00 - 3GB. Обычно программа контроля равильности использования страниц do_wp_page проверяет, чтобы процесс не производил запись в область кода. Однако, если перехватить SEGV сигнал, то появляется возможность писать в область кода, инициируя возникновение COPY-ON-WRITE. Программа управления do_no_page гарантирует, что ни одна новая страница, выделенная процессу, не будет принадлежать ни исполняемой области, ни разделяемой библиотеке, ни стеку, ни попадет внутрь brk значения. Пользовательский процесс может сбросить свое brk значение посредством вызова sbrk(). Это то, что делает malloc(), когда это необходимо. Части текста и данных размещаются в отдельных страницах, если не установлена опция -N компилятора. Адреса загрузки разделяемой библиотеки обычно сами берутся из разделяемого пространства. Такой адрес лежит между 1.5GB и 3GB за исключением особых случаев.

                          своппируемая    стабильная
 
     страницы кода               Y              Y
     страницы информации         Y              N?
     стек                        Y              N
     pg_dir                      N              N
     кодовая/информационная
         page_table              N              N
     стек page_table             N              N
     task_struct                 N              N
     kernel_stack_frame          N              N
     shlib page_table            N              N
     несколько shlib страниц     Y              Y?


[Что означают отметки в виде вопроса? Должны ли они означать вашу неуверенность или альтернативность решения?]

Стек, разделяемые библиотеки и данные слишком удалены друг от друга, чтобы перекрываться одной таблицей страниц. Все page_tables ядра разделяются всеми процессами и поэтому не приведены в списке. Перемещаются только грязные страницы. Чистые страницы могут заниматься, таким образом процесс может читать их снова из исполняемой области в случае такой необходимости. Обычно разделяются только чистые страницы. Грязная страница перестает разделяться в случае распараллеливания пока родитель или потомок не станет снова производить в нее запись.


Ядро ОС Linux :: 6.4 Данные управления памятью в таблице процессов


Ниже приводится краткое описание некоторых данных, содержащихся в таблице процессов, которые используются для управления памятью: [Это должно быть документировано значительно лучше. Необходима значительно большая детализация]

  • Ограничения на память процесса: ulong start_code, end_code, end_data, brk, start_stack;
  • Определение нарушения страницы: ulong min_flt, maj_flt, cmin_flt, cmaj_flt;
  • Локальная таблица дескриптора: struct desc_struct ltd[32] представляет собой локальную таблицу дескриптора задачи.
  • rss количество резидентных страниц.
  • swappable: если - 0, тогда страницы процесса не замещаются.
  • kernel_stack_page: указатель на страницу, размещенную при распараллеливании.
  • saved_kernel_stack: V86 режим работы.
  • struct tss


  • Сегмент стека esp0 указатель на стек ядра (kernel_stack_page)
    ss0 сегмент стека ядра (0х10)
    esp1 = ss1 = esp2 = ss2 = 0
    неиспользуемые привилегированные уровни.
  • Секторы сегмента: ds = es = fs = gs = ss = 0x17, cs = 0x0f все указатели на сегменты в текущем ltd[].
  • cr3: указывает на директорию страниц для данного процесса.
  • ltd: _LTD(n) селектор для LTD текущей задачи.



Ядро ОС Linux :: 6.5 Инициализация памяти


В start_kernel (main.c) имеются 3 переменные, связанные с инициализацией памяти:

  • memory_start начинается от 1MB. Изменяется посредством инициализации устройства.
  • memory_end конец физической памяти: 8MB, 16MB и т.д.
  • low_memory_start конец кода и данных ядра, загружаемых первоначально.


Каждое устройство при инициализации по-своему берет memory_stsrt и возвращает измененное значение, если оно выделяет пространство начиная с memory_stsrt (просто захватывая его). paging_init() инициализирует таблицы страниц в swapper_pg_dir (начинающиеся с 0хс0000000), чтобы накрыть всю физическую память начиная с memory_start и кончая memory_end. В действительности первые 4МВ обрабатываются в startup_32 (head.s). memory_start увеличивается, если добавляется какая-либо новая page_tables. При пребывании по обращению по пустому указателю в ядре первая страница обнуляется.

В shed_init() ltd и tss дескрипторы задачи task[0] устанавливаются в GDT, и загружаются в TR и DTR (единственный случай, когда это делается явно). TRAP GATE (0х80) устанавливается для system_call(). Флаг вложенной задачи сбрасывается при подготовке к переходу в пользовательский режим. Таймер включается. task_struct для task[0] в полном объеме появляется в .

Далее с помощью mem_init() создается mem_map, чтобы отражать текущее использование физических страниц. Это состояние, которое отражается в карте физической памяти, описанной в предыдущем разделе.

Linux переходит в пользовательский режим посредством iret после сохранения в стеке ss, esp и т.п. Естественно, что сегменты пользователя для task[0] управляются прямо через сегменты ядра, т.о. выполнение продолжается точно с того места, где оно было прервано.

task[0]:

  • pg_dir = swapper_pg_dir, что означает, что управление адресами происходит только в области от 3GB до 3GB + high_memory/
  • LTD[1] = код пользователя, base=0xc0000000, size=640K
  • LTD[2] = данные пользователя, base=0xc0000000, size=640K


Первый вызов exec() устанавливает входы LTD для task[1] в пользовательские значения с base=0x0, limit= TASK_SIZE = 0xc0000000. Согласно этому ни один процесс не видит сегменты ядра пока находится в пользовательском режиме.


Ядро ОС Linux :: 6.5.1. Процессы и программа управления памятью

Работа, связанная с памятью, производимая посредством fork():

Выделение памяти

  • 1 страница для task_struct.
  • 1 страница для стека ядра.
  • 1 для pg_dir и несколько для pg_tables (co[py_page_tables)


Другие изменения

  • ss0 установить на адрес сегмента стека ядра (0х10) чтобы быть уверенным?
  • esp0 установит на вершину вновь созданного kernel_stack_page
  • cr3 устанавливается посредством copy_page_tables() для указания на вновь размещенную директорию страниц.
  • ltd=_LTD(task_nr) создает новый дескриптор ltd.
  • дескриптор устанавливается в gdt для нового tss и ltd[].
  • Остальные регистры наследуются от родителя.


Процессы прекращают разделение их сегментов данных и кода (хотя они имеют раздельные локальные таблицы дескрипторов, входы указывают на те же сегменты). Страницы стека и данных будут скопированы, когда родитель или наследник будет писать в них (СOPY-ON-WRITE)

Работа, связанная с памятью, производимая посредством exec():

  • Выделение памяти
    • 1 страница для exec-заголовка полного файла для omagic
    • 1 страница или больше для стека (MAX_ARG_PAGES)


  • clear_page_tables() используется для удаления старых страниц.
  • change_ltd() устанавливает дескрипторы в новом LTD[]
  • ltd[1] = code base = 0x00, limit = TASK_SIZE
  • ltd[2] = data base = 0x00, limit = TASK_SIZE


Эти сегменты представляются DPL=3,P=1,S=1,G=1 type=a(code) или 2(data)

  • До MAX_ARG_PAGES грязные страницы argv и envp размещаются и сохраняются на вершине сегмента данных для вновь созданного пользовательского стека.
  • Установить указатель инструкции вызывающей программы eip = ex.a_entry.
  • Установить указатель стека вызывающей программы на созданный стек (esp = stack pointer) Данные будут выбраны из стека при возобновлении вызывающей программы.
  • Редактирование ограничений на память
    end_coe = ex.a_text
    end_data = end_code + ex.a_data
    brk = end_data + ex.a_bss


Программные и аппаратные прерывания управляются внутри контекста текущей задачи. Более детально, директория страниц текущего процесса используется при трансляции адреса. Сегменты, однако, являются сегментами ядра и таким образом все линейные адреса указывают в область памяти ядра. Предположим, например, что пользовательский процесс выполняет системный вызов и ядро хочет получить доступ к переменной по адресу 0х01. Линейный адрес будет равен 0хс0000001 (использование сегментов ядра) и физический адрес - 0х01. Буква здесь присутствует вследствие того, что директория страниц процесса отображает этот диапазон точно как page_pg_dir.

Область ядра (0хс0000000 + high_memory) отображается через таблицы страниц ядра, которые являются частью RESURVED памяти. Поэтому они разделяются всеми процессами. Во время распараллеливания copy_page_tables() напрямую обращается к таблицам RESERVED страниц. Она устанавливает указатели в директориях страниц процесса, чтобы указывать на таблицы страниц ядра и не размещает в памяти новых таблиц страниц, как это обычно делается. В качестве примера kernel_stack_page (который размещен где-то в области ядра) не нуждается в связанной page_table, размещенной в p_dir процесса, чтобы отобразить ее.

Инструкция прерывания устанавливает указатель стека и сегмент стека из привилегированных 0 значений, сохраненных в tss текущей задачи. Заметьте, что стек ядра в действительности является фрагментированным объектом - это не единственный объект, а группа стековых фреймов, каждый из которых размещается в памяти при создании процесса и освобождает память при окончании его. Стек ядра никогда не должен расти внутри контекста процесса слишком быстро, чтобы не расширится за пределы текущего фрейма.


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


Когда любой процедуре ядра требуется память, она прекращает работу, вызывая 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() сканирует таблицы страниц всех пользовательских процессов и проводит следующую политику изъятия:

  1. Не относиться легкомысленно к RESERVED страницам.
  2. Подвергать старению страницу, если она помечена как доступная (1 бит).
  3. Не трогать страницы, выделенные ранее (last_free_pages[]).
  4. Оставить грязные страницы с map_counts> 1.
  5. Снизить значение map_count для чистых страниц.
  6. Освободить чистые страницы, если для них не установлено соответствие.
  7. Заместить грязные страницы с 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 страниц не становятся изменяемыми, не изымаются и не стареют. Они отображаются в директории страниц процесса через зарезервированные таблицы страниц. Они освобождаются только пр и выходе из процесса.


Ядро ОС Linux :: 6.7 Программы контроля корректности использования страниц


Когда процесс создается посредством распараллеливания, он стартует со своей директорией страниц и своей страницей. Таким образом программа контроля корректности использования страниц следит почти за всей памятью процесса.

Программа контроля do_page_fault() считывает некорректный адрес из регистра cr2. Код ошибки (считанный в sys_call.S) позволяет определить режим доступа - пользователя/супервизора и причину ошибки - защита записи или неправильная страница. Формирователь управляется do_wp_page() и позднее do_no_page().

Если нарушение адреса превышает TASK_SIZE, процесс получает SIGKILL. [Зачем этот контроль? Такое может произойти только в режиме ядра из-за защиты на уровне сегмента]

Эти процедуры обладают тонкостями т.к. они могут вызываться по прерыванию. Вы не можете предположить, что это текущая задача.

do_no_page() контролирует три возможные ситуации:

  1. Страница замещается.
  2. Страница принадлежит исполняемой или разделяемой библиотеке.
  3. Страница некорректна - страница данных, которая не была загружена.


Во всех случаях в первую очередь вызывается get_empty_pgtable() чтобы гарантировано определить существование таблицы страниц, которая накрывает некорректный адрес. В случае 3 get_empty_page() вызывается чтобы обеспечить страницу с требуемым адресом и в случае замещаемой страницы вызывается swap_in().

В случае 2 программа контроля вызывает share_page(), чтобы посмотреть является ли страница разделяемой каким либо другим процессом. В случае неудачи она считывает страницу из исполняемой программы или библиотеки (Она повторяет вызов shre_page() в случае, если другой процесс делал тем временем то же самое). Любая часть страницы сверх значения brk обнуляется.

Считывание страницы с диска вычисляется как основная ошибка (mjr_flt). Это происходит с swap_in() или когда происходит считывание из выполняемой программы или библиотеки. Другие случаи интерпретируются как второстепенные ошибки (min_flt).

Когда найдена разделяемая страница, то она защищена для записи. Процесс, который пишет в разделяемую страницу, затем должен будет пройти через do_wp_page(), которая выполняет COPY-ON-WRIGHT.

do_wp_page() выполняет следующее:

  • посылает SIGSEGV, если какой-либо пользовательский процесс пишет в текущую code_space.
  • Если старая страница не разделяется, она становится незащищенной. Иначе get_free_page() и copy_page(). Для страницы устанавливается грязный флаг из старой страницы. Уменьшается значение счетчика карты старой страницы.



Ядро ОС Linux :: 6.8. Листание (paging)


Листание (paging) оперирует со страницей в отличии от подкачки (swapping), которая используется в отношении любых процессов. Мы будем использовать здесь термин "подкачка" для того, чтобы ссылаться на листание, т.к. Linux только листает и не замещает, но более привычным является термин "замещать" (swap), чем "листать" (page). Страницы ядра никогда не замещаются. Очищенные страницы также не читаются для замещения. Они освобождаются и перезагружаются, когда требуется. Программа подкачки поддержи вает один бит информации старения, являющимся битом PAGE_ACCESSED во входах таблицы страниц.

Linux использует множество файлов подкачки или устройства, которые могут быть включены и выключены посредством системных вызовов swapon и swapoff. Каждый файл подкачки или устройство описано посредством struct swap_info_struct (swap.c)

         static struct swap_info_struct {
                 unsigned long flags;
                 struct inode *swap_file;
                 unsigned int swap_device;
                 unsigned char *swap_map;
                 char          *swap_lockmap;
                 int           lowest_bit;
                 int           highest bit;
          } swap_info[MAX_SWAPFILES];


Поле флагов (SWP_USED или SWP_WRITEOK) используется для управления доступом к файлам подкачки. Когда флаг SWP_WRITEOK сброшен, для этого файла не будет выделяться пространство. Это используется системным вызовом swapoff, когда он делает невозможным использование файла. Когда вызов swapon добавляет новый файл подкачки, то устанавливается SWP_USED. Статическая переменная nr_swapfiles содержит количество активных файлов подкачки. Поля lowest_bit и highest_bit связывают свободные области в файле подкачки и используются, чтобы увеличить скорость поиска свободной области подкачки.

Программа пользователя mkswap инициализирует устройство подкачки или файл. Первая страница включает заголовок ("SWAP-SPACE") в последних 10 байтах и содержит область битмап. Первоначальные нулевые значения в битмап сигнализируют о плохих страница х. "1" в битмап означает, что соответствующая страница свободна. Такой странице память никогда не выделяется, таким образом сразу необходимо провести инициализацию.

Системный вызов swapon() осуществляется из программы пользователя обычно из /etc/rc. Пара страниц памяти выделяется для swap_map и swap_lockmap.

swap_map содержит один байт на каждую страницу файла подкачки. Инициализируется из битмап и содержит 0 для допустимых страниц и 128 для неиспользуемых страниц. Используется для поддержки счета запросов на замещение каждой страницы в файле подкачки. swap_lockmap содержит один бит на каждую страницу, который используется, чтобы гарантировать взаимное исключение при чтении или записи в файл подкачки.

Когда страница памяти готова к тому, чтобы быть замещенной, индекс области замещения получается путем вызова get_swap_page(). Этот индекс затем загружается в биты 1-31 входа таблицы страниц, таким образом замещаемая страница может быть размещена, когда это необходимо, программой контроля корректности использования страниц do_no_page().

Верхние 7 битов индекса дают файл подкачки (или устройство), а нижние 24 бита дают номер страницы на этом устройстве. Это позволяет создавать до 128 файлов, каждый из которых предоставляет область до 64GB, но пространство свыше этого требует, что бы swap_map было бы большим. Вместо этого размер файла подкачки ограничивается 16MB потому, что swap_map тогда занимает 1 страницу.

Функция swap_duplicate() используется в copy_page_tables(), чтобы разрешить дочернему процессу наследовать замещаемые страницы во время распараллеливания. Это сразу увеличивает значение счетчика для этой страницы, поддержанного в swap_map. Каждый процесс будет замещен в отдельной копии страницы при обращении к ней.

swap_free() уменьшает счетчик, поддерживаемый в swap_map. Когда счетчик сбрасывает в 0, страница может быть перезагружена с помощью get_swap_page(). Эта функция вызывается каждый раз, когда замещаемая страница читается в память (swap_in()) или когда страница готова к тому, чтобы быть сброшенной (free_one_table() и т.п.).


Ядро ОС Linux :: 6.9 Управление памятью в 80386


Логический адрес, задаваемый в инструкции в первую очередь транслируется в линейный адрес посредством аппаратных средств сегментации. Этот линейный адрес затем транслируется в физический адрес модулем страничной организации (paging unit).


Ядро ОС Linux :: 6.9.1 Страничная организация (paging) в 386


Существует два уровня косвенной адресации при трансляции адреса в модуле страничной организации. Директория страниц содержит указатели на 1024 таблицы страниц. Каждая таблица страниц содержит указатели на 1024 страницы. Регистр CR3 содержит физический базовый адрес директории страницы и загружается как часть TSS в task_struct и поэтому загружается при каждом переключении задачи. 32-х битный линейный адрес разделяется следующим образом:

     31                22  21                12  11                0
             DIR                   TABLE                 OFFSET


Физический адрес затем вычисляется (аппаратно) таким образом:

                CR3+DIR           указатель на table_base
        table_base+TABLE           указатель на page_base
       physical_address=           page_base + OFFSET


Директории страниц (таблицы страниц) это страница, выровненная так, что нижние 12 бит используются для загрузки полезной информации о таблице страниц (страница), указатель на которую задается посредством входа. Формат входов директории страниц и таблицы страниц:

         31              12  11  9  8  7  6  5  4  3   2    1   0
               ADDRESS          OS   0  0  D  A  0  0  U/S  R/W  P


D - "1" означает, что страница грязная (неопределенно для входа директории страниц).
R/W - "0" означает для пользователя "только для чтения".
U/S - "1" означает страницу пользователя.
P - "1" означает, что страница находится в памяти.
А - "1" означает, что к странице был доступ (устанавливается в 0 при старении).
OS - биты могут использоваться для LRU и т.п. и определяются OS.

Соответствующие определения для Linux находятся в .

Когда страница замещается, используются биты 1-31 входа таблицы страниц, чтобы отметить, куда при замещении помещается страница (бит "0" должен иметь значение 0).

Страничная организация (paging) делается доступной путем установки старшего бита в CR0 [в head.S?]. В каждой фазе трансляции адреса проверяются разрешения доступа, страницы не присутствуют в памяти и нарушение защиты приводит к их отсутствию. Затем программа контроля корректности использования страниц (в memory.c) или вносит новую страницу, или снимает защиту страницы, или делает все необходимое, что должно быть сделано.

Информация о некорректной работе со страницей

  • Регистр CR2 содержит линейный адрес, в котором было вызвано нарушение страницы.
  • Коды нарушения страницы (16 бит):


         бит                      сброшен               установлен
           0               страница не существует   защита уровня страницы
           1               нарушение при чтении     нарушение при записи
           2               режим супервизора        режим пользователя


Остальные биты не определены. Приведенная информация является выдержкой из sys_call.S

Translation Lookside Buffer (TLB) представляет собой аппаратную кэш-память для физических адресов, которые соответствуют ранее используемым виртуальным адресам. Когда транслируется виртуальный адрес, 386 в первую очередь просматривает TLB, чтобы узнать - является ли доступной необходимая информация. Если нет, то для того, чтобы получить страницу, он должен создать пару ссылок на память для доступа к директории страниц и затем таблице страниц. Три ссылки на физическую память для трансляции адрес а для каждой ссылки на логическую память убили бы систему и, следовательно, TLB.

TLB заполняется, если загружен CR3 или по переключению задач, в результате которого изменяетсяCR0. В Linux она заполняется путем вызова invalidate(), которая как раз и перезагружает CR3.


Ядро ОС Linux :: 6.9.2 Сегменты в 80386


Сегментные регистры используются при трансляции адреса для генерации линейного адреса из логического (виртуального) адреса.

  linear_address = segment_base + logical_address


Линейный адрес транслируется затем в физический адрес посредством аппаратуры страничной организации (paging)

Каждый сегмент в системе описан 8-ми байтным дескриптором сегмента, в котором содержится вся необходимая информация (база, ограничение, тип, привилегии).

Имеют место следующие сегменты:

Обычные сегменты
сегменты кода и данных
Системные сегменты
(TSS) cегменты состояния задачи
(LDT) таблицы локальных дескрипторов

Характеристики системных сегментов:

  • Системные сегменты являются спецификаторами задач
  • Имеется TSS, связанный с каждой задачей в системе. Он содержит tss_struct (sched.h). Размер этого сегмента соответствует размеру tss_struct, исключая i387_union (232 байта). Он содержит всю информацию, необходимую для перезапуска задачи.
  • Таблицы LDT содержат дескрипторы обычных сегментов, принадлежащих задаче. В Linux каждой задаче соответствует одна LDT. B Linux task_struct предусмотрено пространство для 32-х дескрипторов. Обычная LDT, созданная в Linux, имеет размер 24 байта и, следовательно, пространство для 3-х входов. Ее содержимое:


  • LDT[0] Null (принудительно)
  • LDT[1] дескриптора сегмента пользовательского кода
  • LDT[2] дескриптор сегмента пользовательских данных/стека


Все сегменты пользователя имеют базу 0х00, так что линейный адрес тот же самый, что и логический.

Для получения доступа ко всем этим сегментам 386 использует таблицу глобальных дескрипторов (GDT), которая устанавливается в памяти системой (местоположение задается регистром GDT). GDT содержит дескрипторы сегментов для каждого сегмента состоян ия задачи, каждого локального дескриптора и обычных сегментов. Linux GDT содержит входы двух обыкновенных сегментов:

  • GDT[0] нулевой дескриптор
  • GDT[1] дескриптор сегмента кода ядра
  • GDT[2] дескриптор сегмента данных/стека ядра


Оставшаяся область GDT заполнена TSS и LDT дескрипторами системы.

  • GDT[3] ???
  • GDT[4] = TSS0, GDT[5] = LDT0
  • GDT[6] = TSS1, GDT[7] = LDT1


..... и т.д......

Заметьте LDT[n] != LDTn

  • LDT[n] = n-й дескриптор в LDT текущей задачи
  • LDTn = дескриптор в GDT для LDT n-й задачи


В данном случае GDT имеет 256 входов, пространство для 126 задач. Сегменты ядра имеют базу 0хс0000000, которая задает местонахождение ядра в линейном представлении. Прежде, чем сегмент может быть использован, содержимое дескриптора для этого сегмента должно быть загружено в сегментный регистр. 386 имеет множество сложных критериев, ограничивающих доступ к сегментам, так что вы не сможете просто загрузить дескриптор в сегментный регистр. Также эти сегментные регистры имеют невидимые для программиста участки. Видимые участки - это то, что обычно называется сегментными регистрами cs, ds, es, fs, gs и ss.

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

Обычно в Linux игнорируется комплексная защита на уровне сегмента (чрезмерная?), предоставляемая 386. Она базируется на основе аппаратных средств страничной организации и объединенной защитой на уровне страницы. Правила на уровне сегмента, которые применяются к пользовательским процессам, состоят в следующем:

  1. Процесс не может напрямую обращаться к данным ядра или сегментам кода.
  2. Всегда имеет место контроль ограничения, однако, условие, что каждый пользовательский сегмент размещается от 0х00 до 0хс0000000, неудобно для применения. [Это изменено и нуждается в редактировании]



Ядро ОС Linux :: 6.9.3 Селекторы в 80386

Селектор сегмента загружается в сегментный регистр (cs, ds и т.д.), чтобы через сегментный регистр задать один из обычных сегментов в системе как один адрес.

     Формат селектора сегмента:
          15                                             3    2   1    0
                   индекс                                       TI    RPL
 
          TI - индикатор таблицы:
 
             0  означает, что индексы селектора  относятся к GDT
             1  означает, что индексы селектора относятся  к LDT
 
          RPL - привилегированный уровень. Linux использует только два
                привилегированных уровня
 
             0  означает ядро
             1  означает пользователя
 
      Примеры:
 
      Сегмент кода ядра:
 
          TI = 0, индекс = 1, RPL = 0  поэтому сектор  = 0х08 (GDT[1])
 
      Сегмент данных пользователя
 
          TI = 1, индекс = 2, RPL = 3 поэтому сектор = 0х17 (LDT[2])
 
      Cелекторы, используемые в Linux:


         TI   index  RPL   selector    segment
           0     1      0      0x08      код ядра                  GDT[1]
           0     2      0      0x10      данные/стек ядра          GDT[2]
           0     3      0      ???       ???                       GDT[3]
           1     1      3      0x0F      код пользователя          LDT[1]
           1     2      3      0x17      данные/стек пользователя  LDT[2]


Селекторы для сегментов системы не предназначены для прямой загрузки в сегментные регистры. Напротив, должны быть загружены TR или LDTR.

На входе системного вызова:

  • ds и es устанавливаются на сегмент данных ядра (0х10)
  • fs устанавливается на сегмент данных пользователя (0х17) и используется для доступа к данным, на которые указывают аргументы системного вызова
  • Указатель на вершину и сегмент стека автоматически устанавливаются в ss0 по прерыванию и старые значения восстанавливаются при возврате из системного вызова.



Ядро ОС Linux :: 6.9.4 Дескрипторы сегментов


Имеется дескриптор сегмента, используемый для описания каждого сегмента в системе. Имеются обычные и системные дескрипторы. Ниже представлен дескриптор во всей своей красоте. Такой странный формат создан специально для того, чтобы обеспечить совместимость с 286. Заметьте, что он занимает 8 байт

        63-54  55  54  53  52  51-48  47   46  45  44-40    39-16      15-0
         Base   G   D   R   U   Limit  P   DPL  S   TYPE   Segmet Base Segmet Limit
         31-24                  19-16                         23-0      15-0


Разъяснения:

Rзарезервирован (0)
DPL0 означает ядро, 3 означает пользователя
G1 означает гарантировано 4К (В Linux установлен всегда)
D1 означает 32-х битное значение операнда по умолчанию
Uопределяется программистом
P1 означает присутствие в физической памяти
S0 означает системный сегмент, 1 означает обычный сегмент кода или данных
TYPEСуществует много возможностей. Прерывания различны для системных и обычных дескрипторов.


Системные дескрипторы в Linux

         TSS:   P=1,DPL=0,S=0, type=9,  limit=231 пространство для 1 tss_struct
 
          LDT: P=1,DPL=0,S=1, type=2, limit=23 пространство для 3
               дескрипторов сегментов


База устанавливается при выполнении fork(). Для каждой задачи есть свой TSS и LDT.

Обычные дескрипторы ядра в Linux (head.S):

         код:         P=1,DPL=0,S=1,G=1,D=1, type=a, base=0xc0000000, limit=0x3ffff
 
          данные: P=1,DPL=0,S=1,G=1,D=1, type=2, base=0xc0000000, limit=0x3ffff
 
      Состав LDT для task[0]  (sched.h):
 
          код:         P=1,DPL=3,S=1,G=1,D=1, type=a, base=0xc0000000, limit=0x9f
 
          данные: P=1,DPL=3,S=1,G=1,D=1, type=a, base=0xc0000000, limit=0x9f
 
      LDT, устанавливаемые по умолчанию для оставшихся задач (exec()):
 
          код:        P=1,DPL=3,S=1,G=1,D=1, type=a, base=0, limit=0xbffff
 
          данные: P=1,DPL=3,S=1,G=1,D=1, type=2, base=0, limit=0xbffff


Размер сегментов ядра 0х40000 страниц (4КВ страниц с G=1=1GB) тип подразумевает, что для кодового сегмента разрешается read-exec, а для сегмента данных read-write.

Регистры, объединенные посредством сегментации.

Формат сегментного регистра: (для программиста видимым является только селектор)

         16 -бит         32-бит                         32-бит
          селектор   база физического адреса   ограничения сегмента атрибуты


Невидимую часть сегментного регистра более удобно рассматривать в формате, используемом во входах таблицы дескрипторов, которые устанавливает программист. Таблицы дескрипторов имеют связанные с ними регистры, которые используются при их размещении в памяти. GDTR (и IDTR) инициализируются при запуске и вместе с этим определяются таблицы. LDTR загружается при каждом переключении задачи.

     Формат GDTR (иIDTR):
 
             32-бит                               16-бит
          Линейный базовый адрес           ограничение таблицы


TR и LDTR загружаются из GDT и таким образом имеют формат других сегментных регистров. Регистр задач (TR) содержит дескриптор для TSS текущей выполняемой задачи. Выполнение перехода на селектор TSS вызывает сохранение состояния в старом TSS, в TR загружается новый дескриптор и регистры перезагружаются новым TSS. Эти действия реализуются программой-шедуллером при переключении задач пользователя. Отметим, что поле tss_struct.ltd содержит селектор для LDT этой задачи. Он используется для того, чтобы загрузить LDTR. (sched.h)


Ядро ОС Linux :: 6.9.5 Макросы, используемые при установке дескрипторов


В sched.h и system.h определены несколько ассемблерных макросов для простого доступа и установки дескрипторов. Каждый вход TSS и LDT занимает 8 байт.

Манипулирование GDT дескрипторами системы:

  • _TSS(n)
    _LDT(n) предоставляют индекс в GDT для n-ой задачи.
  • _LDT(n) загружается в поле ldt структуры tss_struct при распараллеливании.
  • _set_tssldt_desc(n, addr, limit, type)
    ulong*n указывает на вход GDT для установки (см. fork.c).


База сегмента (TSS или LDT) устанавливается в 0хс0000000 + addr. Вот специфичные экземпляры описанного выше, где ltypy ссылается на байт, содержащий P, DPL, S, и тип:

         set_ldt_desc(n,addr) ltype=0x82
 
             P=1, DPL=0, S=0, type=2 означает вход LDT. limit=23=?
             пространство для 3 дескрипторов сегмента
 
          set_ldt_desc(n,addr) ltype=0x89
 
             P=1, DPL=0, S=0, type=9 означает доступный 80386 TSS
             limit=231 пространство для 1 tss_struct


  • load_TR(n), load_ldt(n) загружает дескрипторы для задачи с номером n в регистр задачи и ldt регистр.
  • ulong get_base(struct desk_struct ldt) берет базу из дескриптора
  • ulong get_limit (ulong segment) берет ограничение (размер) из селектора сегмента. Возвращает размер сегмента в байтах.
  • set_base(struct desk_struct ldt, ulong base), set_base(struct desk_struct ldt, ulong limit)
    Установит базу и ограничения для дескрипторов (4К неделимых сегментов). Ограничением здесь является действительно существующий размер сегмента в байтах.
  • _set_seg_desc(gate_addr, type, dpl, base, limit)


Значения по умолчанию 0х00408000 = ? D=1,P=1,G=0 В данный момент рабочий размер 32 бита и максимальный размер 1М. gate_addr должен быть (ulong*)


Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье