Linux kernel page fault

how is page fault triggered in linux kernel

I understand linux kernel implements demand paging — the page is not allocated until it is first access. This is all handled in the page fault handler. But what I don’t understand is how is the page fault triggered? More precisely, what triggers the call of the page fault handler? Is it from hardware?

2 Answers 2

The page fault is caused by the CPU (more specifically, the MMU) whenever the application tries to access a virtual memory address which isn’t mapped to a physical address. The page fault handler (in the kernel) then checks whether the page is currently swapped to disk (swaps it back in) or has been reserved but not committed (commits it), then sends control back to the application to retry the memory access instruction. If the application doesn’t have that virtual address allocated, on the other hand, then it sends the segfault instruction back to the kernel.

So it’s most accurate to say that the hardware triggers the call.

when mapping onwards into memory that does not exist at all.( virtual to physical memory ).In this case, the MMU will say there is no corresponding physical memory and inform operating system which is known as a «page fault». The operating system tells it is a less used virtual memory and pls check it in disc .Then the page that the MMU was trying to find will be reloaded in place table. The memory map will be updated accordingly, then control will be given back user application at the exact point the page fault occured and perform that instruction again, only this time the MMU will output the correct address to the memory system, and all will continue.

Since page fault triggered by MMU which is part of hardware is responsible for it.

Hot Network Questions

Subscribe to RSS

To subscribe to this RSS feed, copy and paste this URL into your RSS reader.

Site design / logo © 2023 Stack Exchange Inc; user contributions licensed under CC BY-SA . rev 2023.7.14.43533

By clicking “Accept all cookies”, you agree Stack Exchange can store cookies on your device and disclose information in accordance with our Cookie Policy.

Источник

Управляемый PageFault в ядре Linux

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

Читайте также:  Kali linux изменить mac адрес

Далее, на примере PageFault будут рассмотрены некоторые особенности процесса обработки исключений, а также дано описание метода, позволяющего использовать данную возможность при разработке модулей ядра Linux для архитектуры x86.

Исключения в ядре

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

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

Очевидно, что выполнение команды, осуществляющей обращение по адресу, соответствующему отсутствующей странице, вызывает исключение, а именно — исключение отказа страницы, или Page Fault ( #PF ). В этот момент, ядро сохраняет контекст текущей задачи и выполняет код соответствующего обработчика — do_page_fault. Так или иначе, устранив проблему, ядро восстанавливает контекст прерванной задачи. Однако, в зависимости от результата обработки исключения, адрес возврата может отличаться от адреса той инструкции, которая и была причиной исключения. Другими словами, благодаря предусмотренному в ядре механизму, существует возможность задать для потенциально «опасной» инструкции адрес, с которого будет продолжена работа в случае исключения, генерируемого при её выполнении.

Интерфейс обработки исключений

Чтобы понять как реализован обозначенный механизм, стоит рассмотреть реализацию примитива копирования 4 байтов из ядра пользователю — функцию __put_user_4:

62 ENTRY(__put_user_4) 63 ENTER 64 mov TI_addr_limit(%_ASM_BX),%_ASM_BX 65 sub $3,%_ASM_BX 66 cmp %_ASM_BX,%_ASM_CX 67 jae bad_put_user 68 ASM_STAC 69 3: movl %eax,(%_ASM_CX)  

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

43 # define _ASM_EXTABLE(from,to) \ 44 .pushsection "__ex_table","a" ; \ 45 .balign 8 ; \ 46 .long (from) - . ; \ 47 .long (to) - . ; \ 48 .popsection 

Действие этого макроса сводится к тому, чтобы добавить в специальную секцию __ex_table два значения — from и to , которые, как не трудно заметить, соотносятся с адресами «подозрительной» инструкции в строке 69 и инструкции, с которой будет продолжено выполнение, после обработки исключения, а именно — bad_put_user . Добавление записи в таблицу __ex_table делает точку отказа управляемой, т.к. данная таблица используется ядром при обработке исключений.

Таблицы исключений и порядок их обработки

Итак, как было отмечено, таблица исключений представляет собой центральное место, где хранится информация о тех инструкциях, ошибку в ходе выполнения которых нужно обрабатывать отдельно. Забегая вперёд, стоит отметить, что помимо таблицы самого ядра, для каждого модуля также предусмотрена индивидуальная таблица. Однако, сейчас стоит рассмотреть строение её элемента, описываемого структурой exception_table_entry:

97 struct exception_table_entry < 98 int insn, fixup; 99 >; 

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

Что касается индивидуальных таблиц исключений модулей ядра, то формат элементов этих таблиц стандартный и соответствует оному для ядра. Ссылка на такую таблицу для каждого модуля доступна по указателю THIS_MODULE->extable , тогда как число элементов таблицы содержится в переменной THIS_MODULE->num_exentries . Сам же макрос THIS_MODULE даёт ссылку на структуру-описатель модуля:

Далее представлена ключевая функция ядра, которая выполняет поиск обработчика, соответствующего вызвавшей исключение инструкции. Вот её код:

 50 /* Given an address, look for it in the exception tables. */ 51 const struct exception_table_entry *search_exception_tables(unsigned long addr) 52

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

Обработка исключений в модуле ядра

Итак, если с процедурой обработки исключений в общих чертах понятно, то для тренировки можно создать модуль, целью работы которого будет создание исключений и их обработка. Код мной уже написан доступен на github. Далее я приведу краткое описание кода и дам некоторые комментарии.

Итак, пусть генерацией PageFault исключения занимается функция, которая делает обычный NULL pointer dereference:

static void raise_page_fault(void)

Очевидно, что попытка записи по нулевому указателю приведёт к падению. А это именно то, что нужно. Для того, чтобы правильно отреагировать необходимо:

  • определить адрес инструкции, вызывающей исключение
  • создать валидный элемент exception_table_entry
  • добавить созданный элемент в таблицу extable модуля

Ниже приведена функция, которая выполняет перечисленные выше действия используя дизассемблирование с применением udis86:

static int fixup_page_fault(struct exception_table_entry * entry) < ud_t ud; ud_initialize(&ud, BITS_PER_LONG, \ UD_VENDOR_ANY, (void *)raise_page_fault, 128); while (ud_disassemble(&ud) && ud.mnemonic != UD_Iret) < if (ud.mnemonic == UD_Imov && \ ud.operand[0].type == UD_OP_MEM && ud.operand[1].type == UD_OP_IMM) < unsigned long address = \ (unsigned long)raise_page_fault + ud_insn_off(&ud); extable_make_insn(entry, address); extable_make_fixup(entry, address + ud_insn_len(&ud)); return 0; >> return -EINVAL; > 

Как видно, первым делом настраивается дизассемблер (начало анализа — raise_page_fault ). Далее, с заданной глубиной поиска осуществляется перебор команд. Искомая команда (то, во что транслируется операция ((int *)0)[0] = 0xdeadbeef; ) представляет собой обычный movl $0xdeadbeef, 0 с первым операндом типа UD_OP_MEM и вторым — типа UD_OP_IMM . Как только адрес команды найден, происходит формирование элемента таблицы. При этом, выполняются функции:

static void extable_make_insn(struct exception_table_entry * entry, unsigned long addr) < #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0) entry->insn = (unsigned int)((addr - (unsigned long)&entry->insn)); #else entry->insn = addr; #endif > static void extable_make_fixup(struct exception_table_entry * entry, unsigned long addr) < #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0) entry->fixup = (unsigned int)((addr - (unsigned long)&entry->fixup)); #else entry->fixup = addr; #endif > 

Первая из них, формирует адрес инструкции в структуре. Вторая — адрес фиксапа, т.е. команды, на которую будет передаваться управление. Важно отметить, что начиная с ядра 3.5 в структуре exception_table_entry произошли небольшие изменения, а именно была уменьшена размерность её полей — insn и fixup для 64-битных архитектур. Это позволило сократить требуемый для хранения адресов объём памяти, однако немного поменялась логика расчёта. Так, после ядра 3.5, поля insn и fixup хранят 32-битные значения, соответствующие смещениям адресов относительно данных элементов. Для тех, кому интересно привожу коммит, который всё попортил 706276543b699d80f546e45f8b12574e7b18d952.

Заключение

Приведённый пример демонстрирует возможность управляемой обработки исключений в ядре Linux с использованием модуля ядра. В тестовом примере исключительная ситуация (PageFault) вызывалась в предварительно подготовленном окружении, а именно настроенной таблице exables модуля. Последнее обстоятельство позволило исключить аварийное завершение и продолжить выполнение программы со следующей за аварийной инструкцией команды.

Кроме того, подготовленный тестовый пример, позволяет оценить возможность обработки и некоторых других исключений, таких как division error (#DE) и undefined opcode (#UD):

Источник

Understanding the Linux Kernel, 3rd Edition by

Get full access to Understanding the Linux Kernel, 3rd Edition and 60K+ other titles, with a free 10-day trial of O'Reilly.

There are also live events, courses curated by job role, and more.

Page Fault Exception Handler

As stated previously, the Linux Page Fault exception handler must distinguish exceptions caused by programming errors from those caused by a reference to a page that legitimately belongs to the process address space but simply hasn’t been allocated yet.

The memory region descriptors allow the exception handler to perform its job quite efficiently. The do_page_fault( ) function, which is the Page Fault interrupt service routine for the 80 × 86 architecture, compares the linear address that caused the Page Fault against the memory regions of the current process; it can thus determine the proper way to handle the exception according to the scheme that is illustrated in Figure 9-4.

Overall scheme for the Page Fault handler

Figure 9-4. Overall scheme for the Page Fault handler

In practice, things are a lot more complex because the Page Fault handler must recognize several particular subcases that fit awkwardly into the overall scheme, and it must distinguish several kinds of legal access. A detailed flow diagram of the handler is illustrated in Figure 9-5.

The identifiers vmalloc_fault , good_area , bad_area , and no_context are labels appearing in do_page_fault( ) that should help you to relate the blocks of the flow diagram to specific lines of code.

The do_ page_fault( ) function accepts the following input parameters:

Get Understanding the Linux Kernel, 3rd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.

Источник

Оцените статью
Adblock
detector