Почему ядро ​​linux использует trap gate для обработки исключения div_error? - PullRequest
9 голосов
/ 16 декабря 2011

В ядре 2.6.11.5 обработчик исключения деления на ноль установлен как:

set_trap_gate(0,&divide_error);

В соответствии с «Пониманием ядра Linux», доступ к ловушке Intel не может быть выполнен процессом пользовательского режима.Но вполне возможно, что процесс пользовательского режима также генерирует divide_error.Так почему же Linux реализует это таким образом?

[Редактировать] Я думаю, что вопрос все еще открыт, так как set_trap_gate() устанавливает значение DPL для записи IDT равным 0, что означает, что только CPL = 0 (чтение ядра) код может выполнить его, поэтому неясно,Мне, как этот обработчик может быть вызван из пользовательского режима:

#include<stdio.h>

int main(void)
{
    int a = 0;
    int b = 1;

    b = b/a;

    return b;
}

, который был скомпилирован с gcc div0.c.И вывод ./a.out:

Исключение с плавающей запятой (ядро сброшено)

Так что не похоже, что это было обработано делением на 0 ловушкукод.

Ответы [ 5 ]

5 голосов
/ 19 марта 2013

У меня на руках источники Linux kernel 3.7.1 , и поэтому я постараюсь дать ответ на ваш вопрос на основе этих источников.Что мы имеем в коде.В arch\x86\kernel\traps.c у нас есть функция early_trap_init(), где можно найти следующую строку кода:

set_intr_gate(X86_TRAP_DE, &divide_error);

Как мы видим, set_trap_gate() был заменен на set_intr_gate().Если в следующем ходу мы расширим этот вызов, мы достигнем:

_set_gate(X86_TRAP_DE, GATE_INTERRUPT, &divide_error, 0, 0, __KERNEL_CS);

_set_gate - это подпрограмма, отвечающая за две вещи:

  1. Построение дескриптора IDT
  2. Установка созданного дескриптора в целевую ячейку в массиве дескрипторов IDT.Второй - просто копирование памяти и нам не интересно.Но если мы посмотрим, как он создает дескриптор из предоставленных параметров, мы увидим:

    struct desc_struct{
        unsigned int a;
        unsigned int b;
    };
    
    desc_struct gate;
    
    gate->a = (__KERNEL_CS << 16) | (&divide_error & 0xffff);
    gate->b = (&divide_error & 0xffff0000) | (((0x80 | GATE_INTERRUPT | (0 << 5)) & 0xff) << 8); 
    

Или, наконец,

gate->a = (__KERNEL_CS << 16) | (&divide_error & 0xffff);
gate->b = (&divide_error & 0xffff0000) | (((0x80 | 0xE | (0 << 5)) & 0xff) << 8);

Как мы можем видеть наВ конце конструкции дескриптора мы будем иметь следующую 8-байтовую структуру данных в памяти

[0xXXXXYYYY][0xYYYY8E00], where X denotes digits of kernel code segment selector number, and Y denotes digits of address of the divide_error routine.

. Эта 8-байтовая структура данных является дескриптором прерывания, определяемым процессором.Он используется процессором для определения того, какие действия необходимо предпринять в ответ на принятие прерывания с конкретным вектором.Давайте теперь посмотрим на формат дескриптора прерывания, определенного Intel для x86 семейства процессоров:

                              80386 INTERRUPT GATE
31                23                15                7                0
+-----------------+-----------------+---+---+---------+-----+-----------+
|           OFFSET 31..16           | P |DPL|  TYPE   |0 0 0|(NOT USED) |4
|-----------------------------------+---+---+---------+-----+-----------|
|             SELECTOR              |           OFFSET 15..0            |0
+-----------------+-----------------+-----------------+-----------------+

В этом формате пара SELECTOR: OFFSET определяетадрес функции (в длинном формате), которая получит управление в ответ на принятие прерывания.В нашем случае это __KERNEL_CS:divide_error, где divide_error() является фактическим обработчиком исключения деления на ноль.Флаг P указывает на то, что дескриптор должен рассматриваться как действительный дескриптор, правильно настроенный ОС, и в нашем случае он в поднятом состоянии.DPL - укажите защитные кольца, на которых функция divide_error() может быть активирована с помощью программных прерываний.Чтобы понять роль этой области, нужно было немного предыстории.

Обычно существует три вида источников прерываний:

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

Последний случай имеет специальную поддержку со стороны процессора в виде специальной инструкции int XX.Каждый раз, когда программе требуется услуга ОС, она устанавливает параметры, которые описывают запрос и выдает команду int, с параметром, который описывает вектор прерывания, который используется ОС для предоставления услуги.Прерывания, генерируемые с помощью команды int, называемой мягкими прерываниями.Таким образом, здесь процессор учитывает поле DPL только при обработке мягких прерываний, а полностью игнорирует их в случае прерываний, генерируемых самим процессором или внешними устройствами.DPL является очень важной функцией, потому что она запрещает приложениям моделировать устройства, и это подразумевает поведение системы.

Представьте себе, например, что какое-то приложение создаст что-то вроде этого:

for(;;){    
    __asm int 0xFF; 

  //where 0xFF is vector used by system timer, to notify the kernel that the 
   another one timer tick was occurred
}

В этом случае время на вашем компьютере будет идти намного быстрее, чем в реальной жизни, тогда вы ожидаете, и ваша система ожидает.В результате ваша система будет очень плохо себя вести.Как видите, процессор и внешние устройства считаются доверенными, но это не относится к приложениям пользовательского режима.В нашем случае исключения деления на ноль Linux указывает, что это исключение может быть вызвано программным прерыванием только из кольца 0 или, другими словами, только из ядра.В результате, если инструкция int 0 будет выполнена в пространстве ядра, процессор передаст управление подпрограмме divide_error().Если та же самая инструкция будет выполнена в пользовательском пространстве, ядро ​​будет считать это нарушением защиты и передаст управление обработчику исключений общего сбоя защиты (это действие по умолчанию для всех недопустимых программных прерываний).Но если исключение «Деление на ноль» будет сгенерировано самим процессором, который попытался разделить некоторое значение на ноль, управление будет передано подпрограмме divide error() независимо от места, где произошло неправильное деление.В целом, похоже, что не будет большим вредом позволить приложению инициировать исключение Division By Zero с помощью мягкого прерывания.Но для первого это будет некрасивый дизайн, а для второго некоторая логика может быть за кадром, что связано с тем фактом, что исключение Division By Zero может быть сгенерировано только при действительной некорректной операции деления.Поле

TYPE определяет вспомогательные действия, которые должен предпринять процессор в ответ на принятие прерывания.В реальной жизни используются только два типа дескрипторов исключений: дескриптор прерывания и дескриптор прерывания.Они отличаются только в одном аспекте.Дескриптор прерывания заставляет процессор отключить прием будущих прерываний, а дескриптор прерываний не делает этого.Если честно, я не знал, почему ядро ​​Linux использует дескриптор прерывания для обработки исключений Division By Zero.Как по мне, дескриптор ловушки там больше подходит.

И последнее замечание по поводу сбивающего с толку вывода тестовой программы

Floating point exception (core dumped)  

По историческим причинам ядро ​​Linux отвечает на исключение «Деление на ноль», отправляя SIGFPE (прочитайте SIGnal Float Point Exception) сигнал процесса попытался разделить на ноль.Да, не SIGDBZ (читайте разделение SIGnal по нулям).Я знаю, что это достаточно запутанно.Причина такого поведения заключается в том, что Linux имитирует оригинальное UNIX поведение (я думаю, что это поведение было заморожено в POSIX), а оригинальное UNIX некоторые почему-то рассматривают исключение «Деление на ноль» как«Исключение с плавающей точкой».Я не знаю почему.

5 голосов
/ 13 июня 2012

Бит DPL в IDT просматривается только при вызове программного прерывания с помощью команды int. Деление на ноль - это программное прерывание, запускаемое ЦП и, следовательно, DPL не действует в этом случае

3 голосов
/ 16 декабря 2011

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

2 голосов
/ 16 декабря 2011

Ядро не работает в пользовательском режиме. Он должен обрабатывать ловушку, генерируемую программами пользовательского режима (например, процессы Linux в пользовательской среде). Код ядра не должен делиться на ноль.

Я не очень хорошо понимаю ваш вопрос. Как бы вы реализовали это иначе?

1 голос
/ 02 августа 2012

Ответ на часть вашего вопроса можно найти в разделе 6.12.1.1 «Руководства разработчика по архитектуре и программному обеспечению Intel (R) 64 и IA-32, том 3A»

Процессор проверяет DPL шлюза прерывания или прерывания только в случае исключения или прерывание генерируется инструкцией INT n, INT 3 или INTO. Здесь, CPL должен быть меньше или равен DPL шлюза. Это ограничение мешает прикладные программы или процедуры, работающие на уровне привилегий 3 от использования программное прерывание для доступа к критическим обработчикам исключений, таким как сбой страницы обработчик, при условии, что эти обработчики помещены в более привилегированный код сегменты (численно более низкий уровень привилегий). Для аппаратных прерываний и обнаруженные процессором исключения, процессор игнорирует DPL прерывания и ловушки ворот.

На это ответил Алекс Креймер

По поводу сообщения. Я не совсем уверен, но, похоже, ОС посылает процессу SIGFPE сигнал.

...