У меня на руках источники Linux kernel 3.7.1 , и поэтому я постараюсь дать ответ на ваш вопрос на основе этих источников.Что мы имеем в коде.В arch\x86\kernel\traps.c
у нас есть функция early_trap_init()
, где можно найти следующую строку кода:
set_intr_gate(X86_TRAP_DE, ÷_error);
Как мы видим, set_trap_gate()
был заменен на set_intr_gate()
.Если в следующем ходу мы расширим этот вызов, мы достигнем:
_set_gate(X86_TRAP_DE, GATE_INTERRUPT, ÷_error, 0, 0, __KERNEL_CS);
_set_gate
- это подпрограмма, отвечающая за две вещи:
- Построение дескриптора IDT
Установка созданного дескриптора в целевую ячейку в массиве дескрипторов IDT.Второй - просто копирование памяти и нам не интересно.Но если мы посмотрим, как он создает дескриптор из предоставленных параметров, мы увидим:
struct desc_struct{
unsigned int a;
unsigned int b;
};
desc_struct gate;
gate->a = (__KERNEL_CS << 16) | (÷_error & 0xffff);
gate->b = (÷_error & 0xffff0000) | (((0x80 | GATE_INTERRUPT | (0 << 5)) & 0xff) << 8);
Или, наконец,
gate->a = (__KERNEL_CS << 16) | (÷_error & 0xffff);
gate->b = (÷_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()
может быть активирована с помощью программных прерываний.Чтобы понять роль этой области, нужно было немного предыстории.
Обычно существует три вида источников прерываний:
- Внешнее устройство, которое запрашивает службу у ОС.
- Сам процессор, когда обнаруживается, что он переходит в ненормальное состояние, запрашивая ОС, чтобы помочь ему выйти из этого состояния.
- Программа, выполняющаяся на процессоре под управлением ОС, которая запрашивает какой-то специальный сервис у ОС.
Последний случай имеет специальную поддержку со стороны процессора в виде специальной инструкции 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 некоторые почему-то рассматривают исключение «Деление на ноль» как«Исключение с плавающей точкой».Я не знаю почему.