Ядро Linux в комментариях

       

Do_page_fault


do_page_fault — это функция ядра, вызываемая при возникновении ситуаций отсутствия страницы (это установлено в строке ). Когда возникает ситуация отсутствия страницы, процессор настраивает регистры процесса так, чтобы после разрешения этой ситуации процесс продолжил сою работу с команды, которая активизировала эту ситуацию. Таким образом, попытка доступа к памяти, вызвавшая нарушение, автоматически повторяется после того, как ядро обеспечивает возможность ее выполнения. Иначе, если этот отказ действительно вызван ошибкой и не может быть устранен, ядро сообщает об этом процессу-нарушителю. Если ситуация отсутствия страницы возникла в самом ядре, то применяется аналогичная, но не идентичная стратегия, как будет показано ниже.

Регистр управления 2 (CR2) — это регистр процессора Intel, содержащий линейный адрес, который вызвал ситуацию отсутствия страницы. Этот адрес считывается непосредственно из указанного регистра в локальную адресную переменную.

Функция find_vma (строка ) возвращает первую область VMA, диапазон адресов которой заканчивается после этого адреса. Как известно, одно это не гарантирует наличия данного адреса в области VMA: мы знаем, что адрес расположен перед концом области VMA, но он может также находиться перед началом VMA. Поэтому выполняется и эта проверка. Если адрес успешно проходит проверку, то есть находится в пределах VMA, управление передается вперед на метку good_area (строка ); вскоре будет дано ее описание.

Если значение, возвращенное из функции find_vma, представляет собой NULL, то адрес находится после всех областей VMA данного процесса, другими словами, за пределами всей памяти, относящейся к данному процессу.

И начало, и конец VMA расположены строго после адреса; следовательно, адрес находится перед этой областью VMA. Но еще не все потеряно. Если область VMA входит в число объектов такого типа, которые возрастают в сторону младших адресов, другими словами, если она является стеком, этот стек может просто вырасти в сторону младших адресов, чтобы захватить и этот адрес.


Выполняется проверка бита 2 кода ошибки error_code, полученного от процессора. Этот бит устанавливается, если ситуация отсутствия страницы возникает в режиме пользователя, а не в режиме супервизора (ядра). В режиме пользователя функция do_page_fault проверяет, не находится ли данный адрес в пределах области стека, отведенной для процесса, в соответствии с установкой регистра ESP. (Это может произойти, если, например, код выходит за пределы массива, размещенного в стеке.) В режиме супервизора (ядра) последняя проверка не выполняется и просто предполагается, что ядро действует правильно.

Выполняется расширение области VMA для включения нового адреса с использованием функции expand_stack (строка ), если это возможно. В случае успеха член vm_start объекта VMA будет скорректирован для включения адреса.

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

Так или иначе, теперь можно проверить два самых младших бита кода ошибки error_code, которые включают дополнительную информацию о том, почему возникла ситуация отсутствия страницы. Бит 0 — это бит присутствия/защиты: если он равен 0, то страница просто не находилась в памяти; если он равен 1, то страница присутствовала в памяти, но попытка доступа противоречила установкам битов защиты уровня страницы. Бит 1 — это бит чтения/записи: 0 обозначает чтение, 1 — запись.

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

  • Случай 2 или 3. Проверяет, разрешена ли запись в области VMA, включающей данный адрес. Если да, то это была запись на странице с копированием при записи; значение переменной write увеличено (путем установки ее в 1) так, что последующий вызов функции handle_mm_fault приведет к попытке выполнить копирование при записи.


  • Случай 1. Это значит, что ситуация отсутствия страницы возникла при попытке чтения со страницы, которая присутствовала в памяти, но не была доступна для чтения; попытка была отвергнута.






  • Случай 0. Он означает, что ситуация отсутствия страницы возникла при попытке чтения со страницы, которая не присутствовала в памяти. Если признаки защиты области VMA, включающей этот адрес, говорят о том, что эта область не подлежит ни чтению, ни выполнению, то попытка чтения с этой страницу была бы только потерей времени: если бы эта попытка была повторена, возникла бы еще одна ситуация отсутствия страницы и функция do_page_fault закончила бы свою работу в соответствии со случаем 1, поэтому такая попытка отвергается. Иначе, функция do_page_fault продолжает свою работу и пытается считать эту страницу с диска.


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

    Код очистки для большинства функций ядра не заслуживает особого внимания. Функция do_page_fault составляет исключение; мы рассмотрим ее код очистки более подробно. Эта метка, bad_area, достигается в любом из следующих обстоятельств:

  • Вызываемый адрес находится после всей памяти, распределенной (или зарезервированной) для процесса.


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


  • Страница не предназначалась для режима копирования при записи.


  • Если код пользователя допустил любую из перечисленных выше ошибок, в него отправляется ужасающее сообщение SIGSEGV — нарушение сегментации. (Обратите внимание, что здесь термин «сегментация» применяется по традиции, а не по смыслу: когда речь идет о процессоре, технически — это нарушение страничного обмена, а не обязательно нарушение сегментации.) Этот сигнал обычно приводит к уничтожению процесса, как описано в .

    В процессоре Pentium компании Intel (и в некоторых из его разновидностей) есть так называемая ошибка f00f, которая позволяет любому процессу остановить работу процессора, выполнив неверную команду 0xf00fc7c8. Здесь реализован обходной маневр, предложенный компанией Intel.



    Часть таблицы дескрипторов прерываний IDT (interrupt descriptor table) (см. ) была заранее отмечена как предназначенная только для чтения, поскольку это заставляет процессор активизировать ситуацию отсутствия страницы вместо останова процессора в случае выполнения недопустимой команды. Здесь do_page_fault проверяет, не находился ли адрес, который привел к возникновению ситуации отсутствия страницы, в том месте IDT, которое связано с выполнением этой недопустимой команды. Если это так и есть, процессор пытается обслуживать прерывание «Invalid Opcode» — ошибка в процессоре не позволяет выполнить это действие правильно, но данный код исправляет ситуацию, непосредственно вызывая функцию do_invalid_op. В ином случае, процессор никогда бы не пытался писать в IDT (то есть писать в ней, если она предназначена только для чтения), поэтому при неудачном завершении проверки в строке недопустимая команда так и не будет выполнена.

    Метка no_context будет достигнута в одном из следующих случаев:

  • В режиме ядра (а не пользователя) была достигнута метка bad_area и процессор не выполнил недопустимую команду, которая вызывает ошибку f00f.


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


  • Выполнение функции handle_mm_fault окончилось неудачей и система находилась в режиме ядра (автор с таким случаем еще не сталкивался).


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

    Иначе, ядро пыталось обратиться к недопустимой странице и функция do_page_fault не знала, что с этим делать. Эта попытка все еще могла быть преднамеренной. Код запуска ядра проверяет, правильно ли работает защита записи MMU; ошибка, возникающая при такой проверке, не является настоящей ошибкой, и функция do_page_fault может просто вернуть управление.

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

    Последняя рассматриваемая метка — do_sigbus, которая достигается, только если функция handle_mm_fault не смогла обработать ситуацию. Этот случай относительно прост; в основном он сводится к отправке ошибки SIGBUS задаче-нарушителю и обратному переходу к метке no_context, если это произошло в режиме ядра.


    Содержание раздела