Как операционная система обычно управляет памятью ядра и обработкой страниц?

StackOverflow https://stackoverflow.com/questions/271115

Вопрос

Я работаю над дизайном ядра, и у меня есть несколько вопросов, касающихся подкачки.

Основная идея, которая у меня пока есть, заключается в следующем:Каждая программа получает свои собственные (или так она думает) 4G памяти, минус где-то раздел, который я резервирую для функций ядра, которые может вызывать программа.Итак, операционной системе необходимо придумать какой-то способ загрузки страниц в память, которые программа должна использовать во время своей работы.

Теперь, предполагая, что у нас было бесконечное количество памяти и процессорного времени, я мог загрузить / выделить любую страницу, на которую программа записывала или с которой считывала, как это происходило, используя ошибки страницы для страниц, которые не существовали (или были заменены), чтобы ОС могла быстро выделить их или поменять местами.Однако в реальном мире мне нужно оптимизировать этот процесс, чтобы у нас не было программы, постоянно потребляющей всю память, к которой она когда-либо прикасалась.

Итак, я предполагаю, что мой вопрос заключается в том, как операционная система обычно справляется с этим?Моя первоначальная мысль состояла в том, чтобы создать функцию, которую программа вызывает для установки / освобождения страниц, которыми затем она может управлять памятью самостоятельно, но обычно ли программа делает это, или компилятор предполагает, что у нее есть свобода действий?Кроме того, как компилятор обрабатывает ситуации, когда ему необходимо выделить довольно большой сегмент памяти?Нужно ли мне предоставлять функцию, которая пытается упорядочить X страниц по порядку?

Очевидно, что это вопрос не для конкретного языка, но я неравнодушен к стандартному C и хорошо разбираюсь в C ++, поэтому я бы хотел, чтобы любые примеры кода были либо в нем, либо в ассемблере.(Сборка не должна быть необходимой, я полностью намерен заставить ее работать с как можно большим количеством кода на C и оптимизировать в качестве последнего шага.)

Еще один вопрос, на который также должно быть легче ответить:Как обычно обрабатываются функции ядра, которые должна вызывать программа?Нормально ли просто иметь заданную область памяти (я думал ближе к концу виртуального пространства), которая содержит большинство базовых функций / память, специфичную для процесса, которые может вызывать программа?Моя мысль оттуда состояла бы в том, чтобы заставить функции ядра делать что-то очень необычное и менять местами страницы (чтобы программы не могли видеть чувствительные функции ядра в своем собственном пространстве), когда программам нужно было делать что-то серьезное, но на данный момент я не фокусируюсь на безопасности.

Так что, наверное, меня больше беспокоят общие дизайнерские идеи, чем конкретика.Я бы хотел сделать ядро полностью совместимым с GCC (каким-то образом), и мне нужно убедиться, что оно предоставляет все, что нужно обычной программе.

Спасибо за любой совет.

Это было полезно?

Решение

Хорошей отправной точкой для всех этих вопросов является рассмотрение того, как Unix это делает.Как гласит известная цитата, "Те, кто не понимает UNIX, обречены изобретать его заново, причем плохо".

Во-первых, о вызове функций ядра.Недостаточно просто иметь функции где-либо, которые может вызывать программа, поскольку программа, скорее всего, работает в "пользовательском режиме" (кольцо 3 на IA-32), и ядро должно работать в "режиме ядра" (обычно кольцо 0 на IA-32), чтобы выполнять свои привилегированные операции.Вы должны каким-то образом выполнить переход между обоими режимами, и это очень специфично для архитектуры.

На IA-32 традиционным способом является использование вентиля в IDT вместе с программным прерыванием (Linux использует int 0x80).У более новых процессоров есть другие (более быстрые) способы сделать это, и какие из них доступны, зависит от того, является ли процессор AMD или Intel, а также от конкретной модели процессора.Чтобы приспособиться к этому варианту, последние ядра Linux используют страницу кода, отображаемую ядром в верхней части адресного пространства для каждого процесса.Итак, в последних версиях Linux для выполнения системного вызова вы вызываете функцию на этой странице, которая, в свою очередь, сделает все необходимое для переключения в режим ядра (ядро имеет более одной копии этой страницы и выбирает, какую копию использовать при загрузке, в зависимости от возможностей вашего процессора).

Теперь перейдем к управлению памятью.Это настоящий Огромный субъект;вы могли бы написать об этом большую книгу и не раздувать тему.

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

Память ядра (зарезервированная для ядра) обычно отображается в верхней части адресного пространства каждого процесса.Однако он настроен таким образом, что к нему можно получить доступ только в режиме ядра.Нет необходимости в причудливых трюках, чтобы скрыть эту часть памяти;аппаратное обеспечение выполняет всю работу по блокировке доступа (на IA-32 это делается с помощью флагов страниц или ограничений сегмента).

Программы выделяют память в остальном адресном пространстве несколькими способами:

  • Часть памяти выделяется программным загрузчиком ядра.Это включает в себя программный код (или "текст"), инициализированные программой данные ("data"), неинициализированные программой данные ("bss", заполненные нулем), стек и еще несколько мелочей.Сколько выделить, где, каким должно быть исходное содержимое, какие флаги защиты использовать и несколько других параметров считываются из заголовков загружаемого исполняемого файла.
  • Традиционно в Unix существует область памяти, которая может увеличиваться и уменьшаться (ее верхний предел может быть изменен с помощью brk() системный вызов).Это традиционно используется кучей (распределителем памяти в библиотеке C, из которой malloc() является одним из интерфейсов, отвечающих за кучу).
  • Вы часто можете попросить ядро сопоставить файл с областью адресного пространства.Операции чтения и записи в эту область (с помощью магии подкачки) направляются в файл резервной копии.Обычно это называется mmap().С анонимным mmap, вы можете выделить новые области адресного пространства, которые не поддерживаются каким-либо файлом, но в остальном действуют таким же образом.Программный загрузчик ядра часто использует mmap выделять части программного кода (например, программный код может поддерживаться самим исполняемым файлом).

Доступ к областям адресного пространства, которые никак не выделены (или зарезервированы для ядра), считается ошибкой и в Unix вызовет отправку сигнала программе.

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

Лучший способ изучить основы всего этого - прочитать одну из нескольких книг по операционным системам, в частности те, в которых в качестве примера используется вариант Unix.Это будет намного более подробно, чем я мог бы в ответе на StackOverflow.

Другие советы

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

В x86 существует два способа реализации системных вызовов:с прерываниями и инструкциями sysenter /sysexit.С помощью прерываний ядро устанавливает таблица дескрипторов прерываний (IDT), который определяет возможные точки входа в ядро.Затем пользовательский код может использовать int инструкция по генерации мягкого прерывания для вызова ядра.Прерывания также могут генерироваться аппаратным обеспечением (так называемые жесткие прерывания).;эти прерывания, как правило, должны отличаться от мягких прерываний, но они не обязаны ими быть.

Инструкции sysenter и sysexit - это более быстрый способ выполнения системных вызовов, поскольку обработка прерываний происходит медленно;Я не очень хорошо знаком с их использованием, поэтому не могу прокомментировать, являются ли они лучшим выбором для вашей ситуации.

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

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

Я настоятельно рекомендую вам ознакомиться с некоторыми материалами из Операционные системы MIT класс.Загляните в раздел ссылок, там много хорошего.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top