Большая Энциклопедия Нефти и Газа
Память ядра не используется напрямую, так как передача по линии должна производиться с постоянной скоростью. Драйвер не может гарантировать, что он сможет получать доступ к памяти с постоянной скоростью, так как каналы DMA и другие устройства ввода-вывода могут внезапно захватить много циклов шины. Если драйвер не сможет предоставить контроллеру вовремя хотя бы одно слово данных, весь пакет будет разрушен. Этой проблемы удается избежать при помощи буферизации пакета целиком внутри контроллера. [1]
Пространство памяти ядра DSP56300 делится на пространство памяти программ Р, пространства памяти данных X и Y. Пространство памяти данных делится на области X и Y для работы с двумя адресными блоками АЛУ и подкачки в АЛУ данных двух операндов одновременно. Каждое пространство памяти может включать внутреннее ОЗУ, внутреннее ПЗУ и может быть расширено вне кристалла с помощью программного управления. [3]
Гбайт предназначены для кода и данных процесса; верхние 2 Гбайт разрешают ограниченный доступ к памяти ядра . Исключение составляют версии NT для предприятий, в которых разделение памяти может быть другим: 3 Гбайт — для пользователя и 1 Гбайт — для ядра. [4]
Ошибка, действительно, может произойти по вине корпорации Microsoft, но на самом деле некоторые ошибки бывают в неверно написанных драйверах, которые корпорация Microsoft не может контролировать и которые устанавливаются в память ядра и получают полный доступ к таблицам ядра, а также и ко всему аппаратному обеспечению. [5]
Кроме этого, операционная система Linux поддерживает динамически загружаемые модули, в основном драйверы устройств. Они могут быть произвольного размера и каждому из них должен быть выделен непрерывный участок в памяти ядра . Для выполнения всех этих требований система Linux управляет памятью таким образом, что она может получить по желанию участок памяти произвольного размера. Для этого используется алгоритм, известный как дружественный алгоритм. [7]
Команда SET предназначена для установки, отмены или изменения некоторых режимов и функций ПДО на время одного сеанса работы. Команда позволяет определить строку символов, отображаемую на пульте ВМ каждые две секунды интервального таймера ( функция BLIP), либо отменить данную функцию; указать необходимую перекодировку символов при вводе и выводе на пульт ВМ; изменить имя хранимого сегмента в таблице системных имен и загрузить новый сегмент; подключить или отменить подключение таблицы усечений имен системных команд и усечений синонимов пользователя; организовать работу с файлами EXEC как с командами; организовать или отменить защиту памяти ядра ПДО ; установить режим освобождения страничных рамок памяти; организовать двухцветную печать для сообщений об ошибках; установить режим вывода сокращенной формы сообщений. [8]
Виртуальные адреса 32-разрядные, поэтому у каждого процесса 4 Гбайт виртуального адресного пространства. Нижние 2 Гбайт за вычетом около 256 Мбайт доступны для программы и данных процесса; верхние 2 Гбайт защищенным образом отображаются на память ядра . Страницы виртуального адресного пространства имеют фиксированный размер ( 4 Кбайт на компьютере с процессором Pentium) и подгружаются по требованию. Конфигурация виртуального адресного пространства для трех пользовательских процессов в слегка упрощенном виде показана на рис. 11.11. Белым цветом на рисунке изображена область приватных данных процесса. Затененные области представляют собой память, совместно используемую всеми процессами. [10]
Виртуальные адреса 32-разрядные, поэтому у каждого процесса 4 Гбайт виртуального адресного пространства. Нижние 2 Гбайт за вычетом около 256 Мбайт доступны для программы и данных процесса; верхние 2 Гбайт защищенным образом отображаются на память ядра . Страницы виртуального адресного пространства имеют фиксированный размер ( 4 Кбайт на компьютере с процессором Pentium) и подгружаются по требованию. Конфигурация виртуального адресного пространства для трех пользовательских процессов в слегка упрощенном виде показана на рис. 11.11. Белым цветом на рисунке изображена область приватных данных процесса. Затененные области представляют собой память, совместно используемую всеми процессами. [12]
Пользователи могут создавать новые объекты или открывать уже существующие объекты при помощи вызовов Win32, таких как CreateSemaphore и OpenSemaphore. Эти вызовы являются библиотечными процедурами, которые в конечном итоге обращаются к настоящим системным вызовам. При успешном выполнении первый вызов создает, а второй открывает объект, создавая в результате 64-разрядную запись в таблице дескрипторов, хранящуюся в приватной таблице дескрипторов процесса в памяти ядра . Пользователю для последующей работы возвращается 32-разрядный индекс, указывающий положение дескриптора в таблице. [13]
Память ядра что это
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 напрямую.
- Для пользовательского процесса 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 в таблице страниц пользователя. Эта область не содержит директорий страниц или таблиц страниц. Меняются местами (замещаются) только грязные страницы.
Необходимы незначительные изменения в некоторых местах (например, контроль ограничений на память процесса), чтобы обеспечить возможность программисту определять свои сегменты.
6.2 Физическая память
Ниже представлена карта физической памяти перед тем, как будет выполнен любой процесс. Левый столбец представляет начальный адрес инструкции, отмеченные значения являются приблизительными. Средний столбец включает в себя название инструкции. Крайний правый столбец представляет имя соответствующей процедуры или переменной или комментарий входа.
+ устройство, захватывающее память (main.c): profil_buffer, con_init, psaux_init, rd_init, scsi_dev_init. Заметьте, что не вся память помечена как FREE или RESERVRVED (mem_init).
Страницы, помеченные как RESERVED принадлежат ядру и никогда не освобождаются или переставляются.
6.3 Память пользовательского процесса
Как сегмент кода, так и сегмент данных в каждом случае размещаются в области от 0х00 — 3GB. Обычно программа контроля равильности использования страниц do_wp_page проверяет, чтобы процесс не производил запись в область кода. Однако, если перехватить SEGV сигнал, то появляется возможность писать в область кода, инициируя возникновение COPY-ON-WRITE. Программа управления do_no_page гарантирует, что ни одна новая страница, выделенная процессу, не будет принадлежать ни исполняемой области, ни разделяемой библиотеке, ни стеку, ни попадет внутрь brk значения. Пользовательский процесс может сбросить свое brk значение посредством вызова sbrk(). Это то, что делает malloc(), когда это необходимо. Части текста и данных размещаются в отдельных страницах, если не установлена опция -N компилятора. Адреса загрузки разделяемой библиотеки обычно сами берутся из разделяемого пространства. Такой адрес лежит между 1.5GB и 3GB за исключением особых случаев.
[Что означают отметки в виде вопроса? Должны ли они означать вашу неуверенность или альтернативность решения?]
Стек, разделяемые библиотеки и данные слишком удалены друг от друга, чтобы перекрываться одной таблицей страниц. Все page_tables ядра разделяются всеми процессами и поэтому не приведены в списке. Перемещаются только грязные страницы. Чистые страницы могут заниматься, таким образом процесс может читать их снова из исполняемой области в случае такой необходимости. Обычно разделяются только чистые страницы. Грязная страница перестает разделяться в случае распараллеливания пока родитель или потомок не станет снова производить в нее запись.
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 текущей задачи.
6.5 Инициализация памяти
- 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] управляются прямо через сегменты ядра, т.о. выполнение продолжается точно с того места, где оно было прервано.
- 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. Согласно этому ни один процесс не видит сегменты ядра пока находится в пользовательском режиме.
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)
- Выделение памяти
- 1 страница для exec-заголовка полного файла для omagic
- 1 страница или больше для стека (MAX_ARG_PAGES)
- До 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 текущей задачи. Заметьте, что стек ядра в действительности является фрагментированным объектом — это не единственный объект, а группа стековых фреймов, каждый из которых размещается в памяти при создании процесса и освобождает память при окончании его. Стек ядра никогда не должен расти внутри контекста процесса слишком быстро, чтобы не расширится за пределы текущего фрейма.
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() в данной последовательности до тех пор, пока освобождение страниц не будет закончено успешно. Приоритет снижается при каждой успешной итерации, так что эти две процедуры чаще выполняют свои циклы по изъятию страниц.
- Пройти по таблице процесса и получить ответ Q от замещаемой задачи.
- Найти таблицу страницы пользователя (не RESERVED) в области, принадлежащей Q.
- Для каждой page в таблице выполнить try_to_swap_out(page).
- Выход, когда страница освобождена.
Отметим, что swap_out() (вызываемая из try_to_free_page()) поддерживает статические переменные, таким образом можно получить ответ на вопрос где она была освобождена при предыдущем вызове.
- Не относиться легкомысленно к RESERVED страницам.
- Подвергать старению страницу, если она помечена как доступная (1 бит).
- Не трогать страницы, выделенные ранее (last_free_pages[]).
- Оставить грязные страницы с map_counts> 1.
- Снизить значение map_count для чистых страниц.
- Освободить чистые страницы, если для них не установлено соответствие.
- Заместить грязные страницы с 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 страниц не становятся изменяемыми, не изымаются и не стареют. Они отображаются в директории страниц процесса через зарезервированные таблицы страниц. Они освобождаются только пр и выходе из процесса.
6.7 Программы контроля корректности использования страниц
Когда процесс создается посредством распараллеливания, он стартует со своей директорией страниц и своей страницей. Таким образом программа контроля корректности использования страниц следит почти за всей памятью процесса.
Программа контроля do_page_fault() считывает некорректный адрес из регистра cr2. Код ошибки (считанный в sys_call.S) позволяет определить режим доступа — пользователя/супервизора и причину ошибки — защита записи или неправильная страница. Формирователь управляется do_wp_page() и позднее do_no_page().
Если нарушение адреса превышает TASK_SIZE, процесс получает SIGKILL. [Зачем этот контроль? Такое может произойти только в режиме ядра из-за защиты на уровне сегмента]
Эти процедуры обладают тонкостями т.к. они могут вызываться по прерыванию. Вы не можете предположить, что это текущая задача.
- Страница замещается.
- Страница принадлежит исполняемой или разделяемой библиотеке.
- Страница некорректна — страница данных, которая не была загружена.
Во всех случаях в первую очередь вызывается 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.
- посылает SIGSEGV, если какой-либо пользовательский процесс пишет в текущую code_space.
- Если старая страница не разделяется, она становится незащищенной. Иначе get_free_page() и copy_page(). Для страницы устанавливается грязный флаг из старой страницы. Уменьшается значение счетчика карты старой страницы.
6.8. Листание (paging)
Листание (paging) оперирует со страницей в отличии от подкачки (swapping), которая используется в отношении любых процессов. Мы будем использовать здесь термин «подкачка» для того, чтобы ссылаться на листание, т.к. Linux только листает и не замещает, но более привычным является термин «замещать» (swap), чем «листать» (page). Страницы ядра никогда не замещаются. Очищенные страницы также не читаются для замещения. Они освобождаются и перезагружаются, когда требуется. Программа подкачки поддержи вает один бит информации старения, являющимся битом PAGE_ACCESSED во входах таблицы страниц.
Linux использует множество файлов подкачки или устройства, которые могут быть включены и выключены посредством системных вызовов swapon и swapoff. Каждый файл подкачки или устройство описано посредством struct swap_info_struct (swap.c)
Поле флагов (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() и т.п.).
6.9 Управление памятью в 80386
Логический адрес, задаваемый в инструкции в первую очередь транслируется в линейный адрес посредством аппаратных средств сегментации. Этот линейный адрес затем транслируется в физический адрес модулем страничной организации (paging unit).
6.9.1 Страничная организация (paging) в 386
Существует два уровня косвенной адресации при трансляции адреса в модуле страничной организации. Директория страниц содержит указатели на 1024 таблицы страниц. Каждая таблица страниц содержит указатели на 1024 страницы. Регистр CR3 содержит физический базовый адрес директории страницы и загружается как часть TSS в task_struct и поэтому загружается при каждом переключении задачи. 32-х битный линейный адрес разделяется следующим образом:
Физический адрес затем вычисляется (аппаратно) таким образом:
Директории страниц (таблицы страниц) это страница, выровненная так, что нижние 12 бит используются для загрузки полезной информации о таблице страниц (страница), указатель на которую задается посредством входа. Формат входов директории страниц и таблицы страниц:
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 бит):
Остальные биты не определены. Приведенная информация является выдержкой из sys_call.S
Translation Lookside Buffer (TLB) представляет собой аппаратную кэш-память для физических адресов, которые соответствуют ранее используемым виртуальным адресам. Когда транслируется виртуальный адрес, 386 в первую очередь просматривает TLB, чтобы узнать — является ли доступной необходимая информация. Если нет, то для того, чтобы получить страницу, он должен создать пару ссылок на память для доступа к директории страниц и затем таблице страниц. Три ссылки на физическую память для трансляции адрес а для каждой ссылки на логическую память убили бы систему и, следовательно, TLB.
TLB заполняется, если загружен CR3 или по переключению задач, в результате которого изменяетсяCR0. В Linux она заполняется путем вызова invalidate(), которая как раз и перезагружает CR3.
6.9.2 Сегменты в 80386
Сегментные регистры используются при трансляции адреса для генерации линейного адреса из логического (виртуального) адреса.
Линейный адрес транслируется затем в физический адрес посредством аппаратуры страничной организации (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, так что линейный адрес тот же самый, что и логический.
- GDT[0] нулевой дескриптор
- GDT[1] дескриптор сегмента кода ядра
- GDT[2] дескриптор сегмента данных/стека ядра
- GDT[3] .
- GDT[4] = TSS0, GDT[5] = LDT0
- GDT[6] = TSS1, GDT[7] = LDT1
- LDT[n] = n-й дескриптор в LDT текущей задачи
- LDTn = дескриптор в GDT для LDT n-й задачи
В данном случае GDT имеет 256 входов, пространство для 126 задач. Сегменты ядра имеют базу 0хс0000000, которая задает местонахождение ядра в линейном представлении. Прежде, чем сегмент может быть использован, содержимое дескриптора для этого сегмента должно быть загружено в сегментный регистр. 386 имеет множество сложных критериев, ограничивающих доступ к сегментам, так что вы не сможете просто загрузить дескриптор в сегментный регистр. Также эти сегментные регистры имеют невидимые для программиста участки. Видимые участки — это то, что обычно называется сегментными регистрами cs, ds, es, fs, gs и ss.
Программист загружает один из этих регистров 16-ти битным значением, называемым селектором. Селектор однозначно идентифицирует дескриптор сегмента в одной из таблиц. Доступ подтверждается и соответствующий дескриптор загружается посредством аппар атных средств.
- Процесс не может напрямую обращаться к данным ядра или сегментам кода.
- Всегда имеет место контроль ограничения, однако, условие, что каждый пользовательский сегмент размещается от 0х00 до 0хс0000000, неудобно для применения. [Это изменено и нуждается в редактировании]
6.9.3 Селекторы в 80386
Селектор сегмента загружается в сегментный регистр (cs, ds и т.д.), чтобы через сегментный регистр задать один из обычных сегментов в системе как один адрес.
Селекторы для сегментов системы не предназначены для прямой загрузки в сегментные регистры. Напротив, должны быть загружены TR или LDTR.
- ds и es устанавливаются на сегмент данных ядра (0х10)
- fs устанавливается на сегмент данных пользователя (0х17) и используется для доступа к данным, на которые указывают аргументы системного вызова
- Указатель на вершину и сегмент стека автоматически устанавливаются в ss0 по прерыванию и старые значения восстанавливаются при возврате из системного вызова.
6.9.4 Дескрипторы сегментов
Имеется дескриптор сегмента, используемый для описания каждого сегмента в системе. Имеются обычные и системные дескрипторы. Ниже представлен дескриптор во всей своей красоте. Такой странный формат создан специально для того, чтобы обеспечить совместимость с 286. Заметьте, что он занимает 8 байт
Разъяснения:
R зарезервирован (0) DPL 0 означает ядро, 3 означает пользователя G 1 означает гарантировано 4К (В Linux установлен всегда) D 1 означает 32-х битное значение операнда по умолчанию U определяется программистом P 1 означает присутствие в физической памяти S 0 означает системный сегмент, 1 означает обычный сегмент кода или данных TYPE Существует много возможностей. Прерывания различны для системных и обычных дескрипторов. Системные дескрипторы в Linux
База устанавливается при выполнении fork(). Для каждой задачи есть свой TSS и LDT.
Обычные дескрипторы ядра в Linux (head.S):
Размер сегментов ядра 0х40000 страниц (4КВ страниц с G=1=1GB) тип подразумевает, что для кодового сегмента разрешается read-exec, а для сегмента данных read-write.
Регистры, объединенные посредством сегментации.
Формат сегментного регистра: (для программиста видимым является только селектор)
Невидимую часть сегментного регистра более удобно рассматривать в формате, используемом во входах таблицы дескрипторов, которые устанавливает программист. Таблицы дескрипторов имеют связанные с ними регистры, которые используются при их размещении в памяти. GDTR (и IDTR) инициализируются при запуске и вместе с этим определяются таблицы. LDTR загружается при каждом переключении задачи.
TR и LDTR загружаются из GDT и таким образом имеют формат других сегментных регистров. Регистр задач (TR) содержит дескриптор для TSS текущей выполняемой задачи. Выполнение перехода на селектор TSS вызывает сохранение состояния в старом TSS, в TR загружается новый дескриптор и регистры перезагружаются новым TSS. Эти действия реализуются программой-шедуллером при переключении задач пользователя. Отметим, что поле tss_struct.ltd содержит селектор для LDT этой задачи. Он используется для того, чтобы загрузить LDTR. (sched.h)
6.9.5 Макросы, используемые при установке дескрипторов
В sched.h и system.h определены несколько ассемблерных макросов для простого доступа и установки дескрипторов. Каждый вход TSS и LDT занимает 8 байт.
- _TSS(n)
_LDT(n) предоставляют индекс в GDT для n-ой задачи. - _LDT(n) загружается в поле ldt структуры tss_struct при распараллеливании.
- _set_tssldt_desc(n, addr, limit, type)
ulong*n указывает на вход GDT для установки (см. fork.c).
- 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*)
Глава 11. Управление памятью ядра
В системе Linux все адреса памяти являются виртуальными. Они не указывают напрямую ни на какие физические адреса в имеющейся оперативной памяти. Всякий раз когда кто- либо осуществляет доступ к некоторому расположению в памяти, выполняется механизм трансляции для подбора соответствующей физической памяти.
Давайте начнём с короткого повествования введения в применяемое понятие виртуальной памяти. Получая номер в отеле, в каждой из комнат может иметься некий частный номер. Все установленные телефоны, само собой, принадлежат данному отелю. Никто не имеет возможности подключаться напрямую извне с данным отелем.
Если вам необходимо осуществит связь с кем-то, кто занимает эту комнату, допустим, с вашим другом, тот должен выдать вам номер панели переключения в отеле и собственно номер той комнаты, в которой вы остановились. Соответствие данного часного номера знают только располагающийся на стойке ресепшена и самого занимающего комнату:
Всякий раз, когда кто-то в этом городе (или по всему миру) желает соединиться с занимающим комнату, он должен пройти имеющуюся линию оперативной поддержки. Ему необходимо знать верный номер оперативной связи с самим отелем, а также необходимый номер комнаты. Таким образом, номер панели переключения + номер комнаты = виртуальному адресу, в то время как private phone number соответствует искомому физическому адресу. Имеется ряд правил, связанных с моделью отеля, которые также относятся и к Linux:
Вы не имеете возможности контактировать с частным телефоном того, кто занимает комнату. Даже нет варианта осуществить такую покупку. Ваш вызов внезапно будет оборван.
Вы не можете получить доступ к несуществующей памяти в вашем адресном пространстве. Это вызовет отказ сегментации.
Вы не можете осуществить соединение с несуществующим жильцом комнаты или с тем, о ком не знает персонал как о поселившимся в данном отеле, либо о ком- то, о ком нет информации в имеющейся панели переключения.
Если вы осуществляете доступ к не поставленной в соответствие памяти, имеющийся ЦПУ возбуждает отказ страницы и сама ОС отрабатывает его.
Вы не можете осуществить соединение с тем, чьё пребывание в отеле завершено.
Вы не можете получить доступ к высвобождённой памяти. Может оказаться, что она уже была выделена другому процессу.
Многие отели имеют один и то же бренд, однако они расположены в различных местоположениях, причём каждый из них имеет различные номера оперативной связи.
Различные процессы могут иметь одни и те же виртуальные адреса, которые соответствуют их адресному пространству, однако указывающие на другие физические адреса.
Существует некая книга (или программное обеспечение с базой данных), содержащая соответствие между необходимым номером комнаты и необходимым частным номером, с которой консультируется при необходимости оператор.
Виртуальные адреса устанавливают соответствие необходимой физической памяти в соответствии с таблицами страниц., которые сопровождаются самим ядром операционной системы и выступают в качестве консультанта их процессора.
Именно так можно себе представлять то, как работают все виртуальные адреса в системе Linux.
В этой главе мы будем иметь дело со всей системой управления памяти Linux, охватывая следующие разделы:
Схемы памяти, применяемые при трансляции адресов и MMU
Механизмы выделения памяти (блок распределения страниц, блок распределения листов — slab, блок распределения kmalloc и так далее)
Доступ ввода/ вывода памяти
Установление соответствия памяти ядра пространству пользователя и реализации функции обратного вызова mmap()
Введение в систему кэширования Linux
Введение в инфраструктуру ресурсов управления имеющимися устройствами ( devres )
Схема памяти системы — пространство ядра и пространство пользователя
На протяжении данной главы такие термины как пространство ядра (kernel space) и пространство пользователя (user space) будут относиться к их виртуальному адресному пространству. В системах Linux всякий процесс обладает собственным виртуальным адресным пространством. Это некий вид песочница памяти на время жизни данного процесса. В системах с 32 битами это адресное пространство составляет 4ГБ в размере (даже для систем с физическим размером менее 4ГБ). Для каждого процесса это адресное пространство в 4ГБ расщепляется на две части:
Виртуальные адреса пространства пользователя
Виртуальные адреса пространства ядра
Тот способ, которым осуществляется данное расщепление зависит от специальной опции настройки ядра, CONFIG_PAGE_OFFSET , которая определяет где начинаются адреса самого ядра в адресном пространстве некоего процесса. Обычным значением является значение по умолчанию 0xC0000000 в 32- битных системах, однако его можно изменять, как это имеет место в случае процессоров семейства i.MX6 от NXP, которые используют 0x80000000 . Во всей данной главе мы будем считать значением по умолчанию адрес 0xC0000000 . Такое расщепление именуется как расщепление 3G/1G, при котором пространству пользователя предоставляются нижние 3ГБ виртуального адресного пространства, а само ядро применяет оставшийся 1ГБ верхних адресов. Некая типичная схема виртуального адресного пространства процесса выглядит следующим образом:
Оба адреса, применяемые и пространством ядра, и пространством пользователя, являются виртуальными адресами. Единственная разница состоит в том, что доступ к некоторому адресу ядра требует какого- то привилегированного режима. Привилегированные режим имеет расширенные полномочия. Когда ЦПУ осуществляет исполнение кода на стороне пространства пользователя, такой активный процесс именуется как исполняемый в определённом режиме пользователя; когда этот ЦПУ исполняет код на стороне своего ядра, такой активный процесс именуется исполняемым в режиме ядра.
Определяя некий адрес (естественно, виртуальный), можно различить будет ли это адрес пространства ядра или адрес пространства пользователя, применяя приведённую выше схему процесса. Всякий адрес, попадающий в 0-3ГБ поступает из пространства пользователя, в противном случае он из своего ядра.
Имеется некоторая причина почему имеющееся ядро разделяет своё адресное пространство со всеми процессами: а именно, потому что всякий отдельный процесс в некий определённый момент времени применяет системные вызовы, которые вовлечены в имеющееся ядро. Установление соответствия такого виртуального адреса памяти ядра адресному пространству каждого процесса позволяет нам избегать стоимости переключения адресного пространства памяти при каждом входе в (и выходе из) имеющееся ядро. Именно по этой причине данное адресное пространство ядра непрерывно ставится в соответствие в верхних адресах каждого процесса, чтобы ускорить доступ к ядру при вызовах системы.
Управление элементами всей памяти организовано в виде единиц фиксированного размера, именуемых страницами. Страница содержит 4 096 байт (4кБ). Даже хотя данный размер может отличаться в прочих системах, он фиксирован в ARM и x86, архитектурами которых мы интересуемся:
некая памяти страница, виртуальная страница или просто страница являются терминами, применяемыми для ссылки на непрерывный блок виртуальной памяти фиксированной длины. Аналогичное наименование, page , применяется как некая структура данных ядра для представления какой- то страницы памяти.
С другой стороны, кадр (frame, или страничный блок) указывает на некий непрерывный блок фиксированного размера физической памяти в верхних адресах, в которых данная операционная система ставит в соответствие некую страницу памяти. Каждый страничный блок определяется неким номером, PFN ( page frame number , номером страничного блока). Задавая некую страницу можно легко получить её PFN и наоборот, применяя макросы page_to_pfn и pfn_to_page , что подробно будет обсуждено в следующих разделах.
Некая таблица страниц является определяющей структурой данных ядра и архитектуры, применяемой для имеющегося соответствия между виртуальными адресами и физическими адресами. Определённый ключ пары страница/ кадр задаёт некую единичную запись в данной таблице страниц. Это и представляет из себя соответствие.
Так как некая страница памяти ставится в соответствие страничному блоку, само собой разумеется, что страницы и страничные блоки имеют один и тот же размер, в нашем случае 4кБ. Данный размер страницы определяется в самом ядре посредством макроса PAGE_SIZE .
Существуют ситуации, при которых необходимо выравнивать память на границы страниц. Говорят, что некая память является выравненной на границу страниц, если её адреса начинаются в точности в самом начале какой- то страницы. Например, в системе с размером страницы 4K, 4 096, 20 480, 409 600 представляют собой адреса памяти выравненные на границу страниц. Другими словами, всякая память, адрес которой является произведением имеющегося размера страницы именуется выравненным на границу страницы.
Адресация ядра — понятие нижних и верхних адресов памяти
Ядро Linux имеет своё собственное виртуальное адресное пространство, как это имеется в случае режима каждого процесса пользователя. Такое виртуальное адресное пространство самого ядра (с размером в 1ГБ при расщеплении 3G/1G) подразделяется на две части:
Ядро операционной системы: что собой представляет и что входит в состав

Ядро операционной системы — это «главный» набор инструкций, благодаря которому работает компьютер. Ядро ОС означает, что оно лежит в основе работы любой операционной системы. На него накладываются все возможности ОС и все дополнительное программное обеспечение. От эффективности работы ядра зависит производительность и эффективность операционной системы. От эффективности ОС зависит работа компьютерного устройства.
Мы будем очень благодарны
если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.