[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]

Программирование: Разработка и отладка программ


Глава 6. Исключительные ситуации в операциях с плавающей точкой

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

Институт инженеров по электротехнике и электронике (IEEE) разработал стандарт на исключительные ситуации операций с плавающей точкой, называемый Стандартом IEEE на двоичную арифметику с плавающей точкой (IEEE 754). Стандарт определяет пять типов исключительных ситуаций, которые должны отслеживаться:

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

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


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

Функции для работы с исключительными ситуациями в операциях с плавающей точкой позволяют выполнять следующие действия:

Для выполнения этих операция применяются следующие функции:

fp_any_xcp и fp_divbyzero Проверить флаги привязки
fp_enable и fp_enable_all Разрешить отправку сигнала при возникновении ошибки
fp_inexact, fp_invalid_op, fp_iop_convert, fp_iop_infdinf, fp_iop_infmzr, fp_iop_infsinf, fp_iop_invcmp, fp_iop_snan, fp_iop_sqrt, fp_iop_vxsoft, fp_iop_zrdzr и fp_overflow Проверить флаги привязки
fp_sh_info Определить, какая исключительная ситуация была причиной сигнала
fp_sh_set_stat Отключить исключительные ситуации или очистить флаги
fp_trap Изменить состояние выполнения процесса
fp_underflow Проверить флаги привязки
sigaction Установить обработчик сигнала

Обработчик прерываний при ошибках в операциях с плавающей точкой

Для вызова прерываний программа должна изменить состояние выполнения процесса с помощью процедуры fp_trap и разрешить отправку сигнала с помощью функции fp_enable или fp_enable_all.

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

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

Исключительные ситуации: сравнение режима включенной и выключенной обработки

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

Модель с отключенной обработкой исключительных ситуаций

Следующие функции позволяют проверить флаги исключительных ситуаций в режиме с отключенной обработкой:

Модель со включенной обработкой исключительных ситуаций

Следующие функции применяются в режиме со включенной обработкой исключительных ситуаций:

fp_enable и fp_enable_all Разрешить отправку сигнала при возникновении ошибки
fp_sh_info Определить, какая исключительная ситуация была причиной сигнала
fp_sh_set_stat Отключить исключительные ситуации или очистить флаги
fp_trap Изменить состояние выполнения процесса
sigaction Установить обработчик сигнала

Режим неточных прерываний

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

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

Для работы с неточными исключительными ситуациями обработчик сигналов должен определять, было ли прерывание точным или неточным.

Точные прерывания

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

Неточные прерывания

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

Для устранения неоднозначности в структуре trap_mode предусмотрено поле fp_sh_info. В этом поле указывается режим прерываний для пользовательского процесса на момент входа в обработчик сигнала. Эта информация может быть также определена по регистру состояния компьютера (MSR) из структуры mstsave.

Функция fp_sh_info позволяет обработчику сигнала определить вид исключительной ситуации (точная или неточная).

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

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

Функции, зависящие от аппаратного обеспечения

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

Связанная информация

Глава 1, Инструменты и утилиты

Пример обработчика прерывания в операциях с плавающей точкой

Обзор процессора с плавающей точкой в книге POWERstation and POWERserver Hardware Technical Information-General Architectures

Функции fp_clr_flag, fp_set_flag, fp_read_flag и fp_swap_flag

Функция fp_raise_xcp

Функции fp_sh_trap_info и fp_sh_set_stat

Функция fp_trap

Функции sigaction, sigvec и signal

Пример обработчика прерывания в операциях с плавающей точкой

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

#include <signal.h>
#include <setjmp.h>
#include <fpxcp.h>
#include <fptrap.h>
#include <stdlib.h>
#include <stdio.h>

#define EXIT_BAD  -1
#define EXIT_GOOD  0

/*
 * Переменная согласования с обработчиком сигнала.
 * Если она равна нулю, применяется стандартный механизм
 * возврата из обработчика. При ненулевом значении
 * используется функция longjmp.
 */
static int fpsigexit;
#define SIGRETURN_EXIT 0
#define LONGJUMP_EXIT  1

static jmp_buf jump_buffer;      /* буфер для перехода */
#define JMP_DEFINED 0            /* код возврата setjmp при первом вызове */
#define JMP_FPE     2            /* код возврата setjmp при возврате */
                                 /* из обработчика сигнала */

/*
 * Структура fp_list позволяет связать
 * текстовое описание прерываний каждого типа
 * с маской, идентифицирующей тип.
 */

typedef struct
  {
  fpflag_t mask;
  char     *text;
  } fp_list_t;

/* типы прерываний, определенные IEEE */

fp_list_t
trap_list[] =
  {
      { FP_INVALID,      "FP_INVALID"},
      { FP_OVERFLOW,     "FP_OVERFLOW"},
      { FP_UNDERFLOW,    "FP_UNDERFLOW"},
      { FP_DIV_BY_ZERO,  "FP_DIV_BY_ZERO"},
      { FP_INEXACT,      "FP_INEXACT"}
  };

/* список вариантов INEXACT -- системное расширение стандартной обработки */

fp_list_t
detail_list[] =
  {
      { FP_INV_SNAN,   "FP_INV_SNAN" } ,
      { FP_INV_ISI,    "FP_INV_ISI" } ,
      { FP_INV_IDI,    "FP_INV_IDI" } ,
      { FP_INV_ZDZ,    "FP_INV_ZDZ" } ,
      { FP_INV_IMZ,    "FP_INV_IMZ" } ,
      { FP_INV_CMP,    "FP_INV_CMP" } ,
      { FP_INV_SQRT,   "FP_INV_SQRT" } ,
      { FP_INV_CVI,    "FP_INV_CVI" } ,
      { FP_INV_VXSOFT, "FP_INV_VXSOFT" }
  };

/*
 * Макроопределение TEST_IT применяется в функции main()
 * для вызова исключительной ситуации.
 */

#define TEST_IT(WHAT, RAISE_ARG)        \
  {                                     \
  puts(strcat("тест: ", WHAT));      \
  fp_clr_flag(FP_ALL_XCP);              \
  fp_raise_xcp(RAISE_ARG);              \
  }

/*
 * ИМЯ: my_div
 *
 * ФУНКЦИЯ:  Деление чисел с плавающей запятой.
 *
 */

double
my_div(double x, double y)
  {
  return x / y;
  }

/*
 * ИМЯ:      sigfpe_handler
 *
 * ФУНКЦИЯ:    Обработчик прерывания, которому передается управление при
 *             возникновении исключительной ситуации операции с плавающей
 *             точкой. Функция определяет, какой тип исключительной ситуации
 *             вызвал прерывание, выводит его в stdout и возвращает
 *             управление процессу, в котором произошло прерывание.
 *
 * ПРИМЕЧАНИЯ: Обработчик прерывания может завершаться,
 *             используя механизм по умолчанию, или с помощью longjmp().
 *             Метод определяется глобальной переменной fpsigexit.
 *
 *             При входе в функцию исключительные ситуации операций
 *             с плавающей точкой отключаются.
 *
 *             В примере применяется функция printf().
 *             Ее нужно использовать аккуратно, так как вывод
 *             этой функцией числа с плавающей точкой может вызвать ошибку.
 *             После этого вызов другого printf() для числа
 *             в обработчике исказит содержимое буфера,
 *             используемого при преобразовании.
 *
 * ВЫВОД:      Тип исключительной ситуации, вызвавшей
 *             прерывание.
 */

static void
sigfpe_handler(int sig,
               int code,
               struct sigcontext *SCP)
  {
  struct mstsave * state = &SCP->sc_jmpbuf.jmp_context;
  fp_sh_info_t flt_context;     /* структура для вызова
                                /* fp_sh_info() */
  int i;                        /* счетчик цикла */
  extern int fpsigexit;         /* глобальная переменная согласования */
  extern jmp_buf jump_buffer    /* буфер перехода */
 
  /*
   * Определить, какая исключительная ситуация вызвала прерывание.
   * Функция fp_sh_info() применяется для получения структуры с
   * информацией о сигнале. После этого проверяется элемент структуры
   * flt_context.trap. Сначала тип прерывания сравнивается
   * со стандартными типами IEEE, и если прерывание вызвано
   * недопустимой операцией, проверяются дополнительные разряды.
   */
 
  fp_sh_info(SCP, &flt_context, FP_SH_INFO_SIZE);

static void
sigfpe_handler(int sig,
               int code,
               struct sigcontext *SCP)
  {
  struct mstsave * state = &SCP->sc_jmpbuf.jmp_context;
  fp_sh_info_t flt_context;     /* структура для вызова
                                /* fp_sh_info() */
  int i;                        /* счетчик цикла */
  extern int fpsigexit;         /* глобальная переменная согласования */
  extern jmp_buf jump_buffer    /* буфер перехода */
 
  /*
   * Определить, какая исключительная ситуация вызвала прерывание.
   * Функция fp_sh_info() применяется для получения структуры с
   * информацией о сигнале. После этого проверяется элемент структуры
   * flt_context.trap. Сначала тип прерывания сравнивается
   * со стандартными типами IEEE, и если прерывание вызвано
   * недопустимой операцией, проверяются дополнительные разряды.
   */
 
  fp_sh_info(SCP, &flt_context, FP_SH_INFO_SIZE);

  for (i = 0; i < (sizeof(trap_list) / sizeof(fp_list_t)); i++)
      {
      if (flt_context.trap & trap_list[i].mask)
        (void) printf("Прерывание вызвано ошибкой %s \n", trap_list[i].text);
      }

  if (flt_context.trap & FP_INVALID)
      {
      for (i = 0; i < (sizeof(detail_list) / sizeof(fp_list_t)); i++)
          {
          if (flt_context.trap & detail_list[i].mask)
            (void) printf("Тип недопустимой операции - %s\n", detail_list[i].text);
          }
      }

  /* сообщить о текущем режиме прерываний */

  switch (flt_context.trap_mode)
      {
    case FP_TRAP_OFF:
      puts("Режим прерываний: OFF");
      break;
 
    case FP_TRAP_SYNC:
      puts("Режим прерываний: SYNC");
      break;
 
    case FP_TRAP_IMP:
      puts("Режим прерываний: IMP");
      break;
 
    case FP_TRAP_IMP_REC:
      puts("Режим прерываний: IMP_REC");
      break;
 
    default:
      puts("ОШИБКА:  Недопустимый режим прерываний");
      }

  if (fpsigexit == LONGJUMP_EXIT)
      {
      /*
       * Возврат по longjmp. В этой версии не требуется
       * очищать флаги исключительных ситуаций или отключать прерывания
       * для предотвращения зацикливания, так как при возврате из
       * обработчика процесс получит информацию о состоянии операций с
       * плавающей точкой обработчика сигнала.
       */
      longjmp(jump_buffer, JMP_FPE);
      }
  else
      {
      /*
       * Возврат с помощью стандартного механизма возврата из
       * обработчика сигнала. В этом случае требуется предупредить
       * зацикливание прерывания, или с помощью очистки разряда
       * исключительной ситуации в fpscr, или с помощью отключения прерываний.
       * В данном случае очищается разряд исключительной ситуации.
       * Для очистки применяется функция fp_sh_set_stat.
       */

      fp_sh_set_stat(SCP, (flt_context.fpscr & ((fpstat_t) ~flt_context.trap)));
      

      /*
       * Увеличить iar процесса, вызвавшего прерывание
       * для предотвращения повторного выполнения инструкции.
       * Разряд FP_IAR_STAT в flt_context.flags определяет,
       * указывает ли state->iar на инструкцию, которая логически
       * запущена. Если этот бит равен 1, state->iar указывает на
       * операцию, которая была запущена и вызовет исключительную
       * ситуацию при повторном запуске. В данном случае требуется
       * продолжить выполнение, проигнорировав результаты операции
       * поэтому iar увеличивается таким образом, чтобы он указывал
       * на следующую инструкцию. Если этот разряд - нулевой, iar
       * уже указывает на следующую инструкцию.
       */
 
      if ( flt_context.flags & FP_IAR_STAT )
          {
          puts("Increment IAR");
          state->iar += 4;
          }
      }
  return;
  }
 
/*
 * ИМЯ:      main
 *
 * ФУНКЦИЯ:  Продемонстрировать работу обработчика sigfpe_handler.
 *
 */

int
main(void)
  {
  struct sigaction response;
  struct sigaction old_response;
  extern int fpsigexit;
  extern jmp_buf jump_buffer;
  int jump_rc;
  int trap_mode;
  double arg1, arg2, r;
 
  /*
   * Установить обработку прерываний операций с плавающей точкой:
   *  1.  Очистить существующие флаги исключительных ситуаций.
   *  2.  Установить обработчик сигнала SIGFPE.
   *  3.  Перевести процесс в режим синхронного выполнения.
   *  4.  Разрешить все прерывания операций с плавающей точкой.
   */

  fp_clr_flag(FP_ALL_XCP);
  (void) sigaction(SIGFPE, NULL, &old_response);
  (void) sigemptyset(&response.sa_mask);
  response.sa_flags = FALSE;
  response.sa_handler = (void (*)(int)) sigfpe_handler;
  (void) sigaction(SIGFPE, &response, NULL);
  fp_enable_all();

  /*
   * Продемонстрировать обработчик со стандартным механизмом возврата.
   * Макроопределение TEST_IT вызывает исключительную ситуацию
   * указанного во втором аргументе типа. Проверка выполняется
   * в режиме точных прерываний, который поддерживается
   * всеми существующими платформами.
   */

  trap_mode = fp_trap(FP_TRAP_SYNC);
  if ((trap_mode == FP_TRAP_ERROR) ||
      (trap_mode == FP_TRAP_UNIMPL))
      {
      printf("ОШИБКА: fp_trap вернул код %d\n",
             trap_mode);
      exit(-1);
      }
 
  (void) printf("Вызов обработчика по умолчанию: \n");
  fpsigexit = SIGRETURN_EXIT;

  TEST_IT("деление на ноль", FP_DIV_BY_ZERO);
  TEST_IT("переполнение",    FP_OVERFLOW);
  TEST_IT("потеря значимости",   FP_UNDERFLOW);
  TEST_IT("неточное",     FP_INEXACT);

  TEST_IT("сигнал NAN",      FP_INV_SNAN);
  TEST_IT("INF - INF",          FP_INV_ISI);
  TEST_IT("INF / INF",          FP_INV_IDI);
  TEST_IT("0 / 0",        FP_INV_ZDZ);
  TEST_IT("INF * 0",         FP_INV_IMZ);
  TEST_IT("ошибка сравнения",    FP_INV_CMP);
  TEST_IT("ошибка извлечения квадратного корня",       FP_INV_SQRT);
  TEST_IT("ошибка преобразования",  FP_INV_CVI);
  TEST_IT("программный запрос",   FP_INV_VXSOFT);

  /*
   * Вызывать fp_trap() для определения
   * самого быстрого режима обработки прерываний
   * для текущей платформы.
   */

  trap_mode = fp_trap(FP_TRAP_FASTMODE);
  switch (trap_mode)
      {
    case FP_TRAP_SYNC:
      puts("Быстрый режимом для текущей платформы: PRECISE");
      break;
 
    case FP_TRAP_OFF:
      puts("Платформа не поддерживает прерывания");
      break;

    case FP_TRAP_IMP:
      puts("Быстрый режим для текущей платформы: IMPRECISE");
      break;

    case FP_TRAP_IMP_REC:
      puts("Быстрый режим для текущей платформы: IMPRECISE RECOVERABLE");
      break;

    default:
      printf("Непредвиденный код возврата fp_trap(FP_TRAP_FASTMODE): %d\n",
             trap_mode);
      exit(-2);
      }

  /*
   * Если платформа поддерживает неточные прерывания, продемонстрировать режим.
   */
 
  trap_mode = fp_trap(FP_TRAP_IMP);
  if (trap_mode != FP_TRAP_UNIMPL)
      {
      puts("Демонстрация неточных исключительных ситуаций в операциях с плавающей точкой");
      arg1 = 1.2;
      arg2 = 0.0;
      r = my_div(arg1, arg2);
      fp_flush_imprecise();
      }

  /* Продемонстрировать обработчик с возвратом по longjmp().
   */
 
  (void) printf("возврат по longjump: \n");
  fpsigexit = LONGJUMP_EXIT;
  jump_rc = setjmp(jump_buffer);
 
  switch (jump_rc)
      {
    case JMP_DEFINED:
      (void) printf("точка возврата установлена setjmp; идет проверка ...\n");
      TEST_IT("деление на ноль", FP_DIV_BY_ZERO);
      break;
 
    case JMP_FPE:
      (void) printf("Возврат из обработчика сигнала\n");
      /*
       * Учтите, что на текущем этапе состояние операций с плавающей точкой
       * процесса было получено от обработчика сигнала. Если в обработчике
       * не были включены прерывания (в этом примере они не включались),
       * то на этом этапе для процесса прерывания будут также отключены.
       * Мы создадим исключительную ситуацию, чтобы показать,
       * что прерывание не будет вызвано, а затем включим
       * прерывания.
       */

      (void) printf("Создается переполнение, прерывания не происходит\n");
      TEST_IT("Переполнение", FP_OVERFLOW);
      fp_enable_all();
      break;
 
    default:
      (void) printf("непредвиденный код возврата setjmp: %d\n", jump_rc);
      exit(EXIT_BAD);
      }
  exit(EXIT_GOOD);
  }

Связанная информация

Глава 1, Инструменты и утилиты

Глава 6, Исключительные ситуации в операциях с плавающей точкой

Функции sigaction, sigvec и signal

Функции fp_sh_trap_info и fp_sh_set_stat

Функция fp_trap

Функция fp_raise_xcp

Функции fp_clr_flag, fp_set_flag, fp_read_flag и fp_swap_flag


[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]