355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Олег Цилюрик » QNX/UNIX: Анатомия параллелизма » Текст книги (страница 9)
QNX/UNIX: Анатомия параллелизма
  • Текст добавлен: 8 октября 2016, 23:59

Текст книги "QNX/UNIX: Анатомия параллелизма"


Автор книги: Олег Цилюрик


Соавторы: Владимир Зайцев,Егор Горошко
сообщить о нарушении

Текущая страница: 9 (всего у книги 23 страниц) [доступный отрывок для чтения: 9 страниц]

Пример: синхронное выполнение кода

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

Простейшая реализация могла бы выглядеть так:

...

while(true) {

 delay(T);

 func();

}

Но это очень «слабое» решение:

• Задержка, обеспечиваемая функцией пассивной задержки delay(), согласно требованиям POSIX не может быть меньшеуказанного параметра T, но... может быть сколь угодно больше! (В [4] мы писали, что при T = 1 реальная величина задержки будет составлять не 1 мсек., как можно было бы ожидать, а с большой степенью вероятности 3 мсек., и там же мы подробно показывали, как это происходит.)

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

• Здесь мы обеспечиваем только одну синхронизированную последовательность вызовов функции func(). А если бы нам потребовалось несколько (много) синхросерий, в каждой из которых выполняется своя функция, а периоды серий не кратны друг другу?

• Наконец, время выполнения целевой функции func()включается в период одного «кругового пробега» цикла, то есть период T отсчитывается от концапредыдущего выполнения функции до началатекущего, а это не совсем то, что мы подразумевали при использовании термина «синхронное».

• Более того, если время выполнения функции func()достаточно флуктуирует от одного вызова до другого (например, из-за изменений данных, с которыми работает функция), то периоды вызовов начинают «гулять», а дисперсия периода результирующей последовательности вызовов func()становится просто непомерно большой.

Ниже показано решение, свободное от многих из этих недостатков ( файл t3.cc). Приложение представляет собой тестовую программу, осуществляющую 3 цепочки выполнения различных целевых функций ( mon1, mon2, mon3) с разными периодами для каждой цепочки (массив period[]):

Синхронизация выполнения участка кода

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

static void out(char s) {

 int policy;

 sched_param param;

 pthread_getschedparam(pthread_self(), &policy, ¶m);

 cout << s << param.sched_curpriority << flush;

}

// целевые функции каждой из последовательностей только

// выводят свой символ-идентификатор и следующий за ним

// приоритет, на котором выполняется целевая функция

static void mon1(void) { out('.'); }

static void mon2(void) { out('*'); }

static void mon3(void) { out('+'); }

// это всего лишь перерасчет временных интервалов,

// измеренных в тактах процессора (в наносекундах)

inline uint64_t cycles2nsec(uint64_t с) {

 const static uint64_t cps =

  // частота процессора

  SYSPAGE_ENTRY(qtime)->cycles_per_sec;

 return (с * 1000000000) / cps;

}

// структура, необходимая только для накопления статистики параметров

// ряда временных отметок: среднего, среднеквадратичного отклонения,

// минимального и максимального значений

struct timestat {

private:

 uint64_t prev;

public:

 uint64_t num;

 double mean, disp, tmin, tmax;

 timestat(void) {

  mean = disp = tmin = tmax = 0.0;

  num = 0;

 }

 // новая временная отметка в ряду:

 void operator++(void) {

  uint64_t next = ClockCycles(), delta;

  if (num i= 0) {

   double delta = cycles2nsec(next – prev);

   if (num == 1) tmin = tmax = delta;

   else tmin = min(tmin, delta), tmax = max(tmax, delta);

   mean += delta;

   disp += delta * delta;

  }

  prev = next;

  num++;

 }

 // подвести итог ряда;

 void operator !(void) {

  mean /= (num – 1);

  disp = sqrt(disp / (num – 1) – mean * mean);

 }

}

// предварительное описание функции потока объекта

void* syncthread(void*);

class thrblock {

private:

 static int code;

 bool ok, st;

public:

 pthread_t tid;

 struct sigevent event;

 timer_t timer;

 int chid;

 void* (*func)(void*);

 sched_param param;

 // структура только для статистики:

 timestat sync;

 // конструктор класса – он не только инициализирует структуру данных

 // создаваемого объекта, но и запускает отдельный поток для его исполнения

 thrblock(

  // параметры конструктора

  // – целевая функция последовательности

  void (*dofunc)(void);

  // – период ее синхронизации

  unsigned long millisec;

  // – приоритет возбуждения синхросерии

  unsigned short priority;

  // – копить ли статистику временных интервалов?

  bool statist = false

 ) {

  // создание канала для получения уведомлений от таймера

  if (!(ok = ((chid = ChannelCreate(0)) >= 0))) return;

  // создать соединение по каналу, которое будет использовать таймер

  event.sigev_coid =

   ConnectAttach(ND_LOCAL_NODE, 0, chid, NTO_SIDE_CHANNEL, 0);

  if (!(ok = (event.sigev_coid >= 0))) return;

  // занести целевую функцию, заодно выполнив

  // трюк преобразования над ее типом

  func = (void*(*)(void*))dofunc;

  int policy;

  // запомнить приоритет вызывающей программы

  // под этим приоритетом и вызывать целевую функцию

  pthread_getschedparam(pthread_self(), &policy, ¶m);

  st = statist;

  event.sigev_code = code++;

  event.sigev_notify = SIGEV_PULSE;

  // а вот это приоритет, с которым нужно будет пробуждаться от таймера!

  event.sigev_priority = priority;

  // создание таймера

  if (!(ok = (timer_create(CLOCK_REALTIME, &event, &timer) == 0))) return;

  // запуск отдельного потока, который по сигналу

  // таймера будет выполнять целевую функцию

  if (!(ok = (pthread_create(&tid, NULL, &syncthread, (void*)this) == EOK)))

   return;

  // и только после этого можно установить период срабатывания

  // таймера, после чего он фактически и запускается

  struct itimerspec itime;

  nsec2timespec(&itime.it_value, millisec * 1000000ull);

  itime it_interval = itime.it_value;

  if (!(ok = (timer_settime(timer, 0, &itime, NULL) == 0))) return;

 }

 // признак того, что объект создан успешно и его поток запущен:

 bool OK(void) { return ok; }

 bool statistic(void) { return st; }

};

int thrblock.code = _PULSE_CODE_MINAVAIL;

// функция потока объекта

void* syncthread(void *block) {

 thrblock *p = (thrblock*)block;

 struct _pulse buf;

 pthread_attr_t attr;

 while(true) {

  // ожидание пульса от периодического таймера объекта

  MsgReceivePulse(p->chid, &buf, sizeof(struct _pulse), NULL);

  pthread_attr_init(&attr);

  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

  // восстановить приоритет целевой функции до уровня того,

  // кто ее устанавливал, вызывая конструктор

  pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

  pthread_attr_setschedparam(&attr, &p->param);

  // запуск целевой функции в отдельном «отсоединенном» потоке

  pthread_create(NULL, &attr, p->func, NULL);

  if (p->statistic()) ++p->sync;

 }

}

// 'пустой' обработчик сигнала SIGINT (реакция на ^С)

inline static void empty(int signo) {}

int main(int argc, char **argv) {

 // с этой точки стандартная реакция на ^С отменяется...

 signal(SIGINT, empty);

 // массив целевых функций

 void(*funcs[])(void) = { &mon1, &mon2, &mon3 };

 // периоды их синхросерий запуска

 int period[] = { 317, 171, 77 };

 // приоритеты, на которых отрабатывается реакция

 // синхросерий на каждый из таймеров синхросерий

 int priority[] = { 15, 5, 25 };

 int num = sizeof(funcs) / sizeof(*funcs);

 // запуск 3-х синхронизированных последовательностей

 // выполнения (созданием объектов)

 thrblock** tb = new (thrblock*)[num];

 for (int i = 0; i < num; i++) {

  tb[i] = new thrblock(funcs[i], period[i],

  priority[i], true);

  if (!tb[i]->OK())

  perror(«synchro thread create»), exit(EXIT_FAILURE);

 }

 // ... а теперь ожидаем ^С.

 pause();

 // подсчет статистики и завершение программы

 cout << endl << «Monitoring finalisation!» << endl;

 // вывод временных интервалов будем делать в миллисекундах:

 const double n2m = 1000000.;

 for (int i = 0; i < num, i++) {

  timestat *p = &tb[i]->sync;

  !(*p); // подсчет статистики по объекту

  cout << i << 't' << p->num << "t=> " << p->mean / n2m << « [» <<

   p->tmin / n2m << «...» << p->tmax / n2m << «]t~» << p->disp / n2m <<

   " (" << p->disp / p->mean * 100 << «%)» << endl;

 }

 return EXIT_SUCCESS;

}

Вся функциональность программы сосредоточена в одном классе – thrblock, который может в неизменном виде использоваться для разных приложений. Необычной особенностью объекта этого класса является то, что он выполнен в технике «активных объектов», навеянной поверхностным знакомством с языками программирования школы Н. Вирта – ActiveOberon и Zormon. В ней говорится, что конструктор такого объекта не только создает объект данных, но и запускает (как вариант) отдельный поток выполнения для каждого создаваемого объекта. В нашем случае задача потоковой функции состоит в вызове целевой функции, адрес которой был передан конструктору объекта в качестве одного из параметров. [26]26
  Здесь применена только простейшая форма «активного объекта»: гораздо сложнее, к примеру, определить деструктор такого объекта. Но именно в своем простейшем виде это многообещающая техника активных объектов.


[Закрыть]

Ниже представлены отличия нашей реализации от простого цикла с задержкой, обсуждавшейся выше (помимо исправлений очевидных недостатков):

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

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

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

Запустим наше тестовое приложение:

# t3

+10+10*10+10+10.10*10+10+10*10+10+10.10*10+10+10+10*10+10.10+10*10+10+10*10+10.10+10*10+10+10*10+10.10+10+10*10+10+10+10.10+10+10*10+10+10.10*10+10+10+10*10+10.10+10*10+10+10*10+10+10.10*10+10+10*10+10+10.10+10*10+10+10*10+10.10+10*10+10+10*10+10.10+10+10*10+10+10*10^C

Monitoring finalisation!

0 32  => 316.919 [316.867...317.895] ~0.178511 (0.056327%)

1 59  => 170.955 [168.583...173.296] ~0.92472 (0.540914%)

2 132 => 76.9796 [76.942...77.9524]  ~0.085977 (0.111688%)

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

# t3

+25+25*5+25+25.15*5+25+25*5+25+25.15*5+25+25+25*5+25.15+25*5+25+25*5+25.15+25*5+25+25*5*5+25.15+25+25*5+25+25*5.15+25+25*5+25+25.15*5+25+25+25*5+25.15+25*5+25+25*5+25+25.15*5+25+25*5+25+25^C

Monitoring finalisation!

0 32 => 316.919 [316.797...317.915] ~0.185331 (0.0584792%)

1 60 => 170.955 [168.964...173.925] ~0.47915 (0.280279%)

2 34 => 76.9796 [76.8895...77.9694] ~0.0937379 (0.12177%)

В этом варианте (и диагностический вывод это подтверждает) мы искусственно ликвидировали наследование приоритета по цепочке порождения: сработавший таймер – функция потока – целевая функция объекта. Это не совсем соответствует цели, намеченной в начале этого раздела, но все же этот вариант иллюстрирует, что именно наш предыдущий вариант удовлетворял всем поставленным целям.

3. Сигналы

Сигналы инициируются некоторыми событиями в системе и посылаются процессу для его уведомления о том, что произошло нечто неординарное, требующее определенной реакции. Порождающее сигнал событие может быть действием пользователя или может быть вызвано другим процессом или ядром операционной системы. Сигналы являются одним из самых старых и традиционных механизмов UNIX. [27]27
  Именно поэтому, наверное, стандарту POSIX так сложно органично согласовать их с нововведениями, например с парадигмой потоков.


[Закрыть]

Уже из этого краткого описания можно заключить, что:

• действия, вызываемые для обработки сигнала, являются принципиально асинхронными;

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

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

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

•  Стандартное действие– выполнение действия, предписанного для обработки этого сигнала по умолчанию. Для многих сигналов действием по умолчанию является завершение, но это необязательно; есть сигналы, которые по умолчанию игнорируются. Для большей части сигналов, чьим действием по умолчанию является принудительное завершение процесса, предписано действие создания дампа памяти при завершении (core dump), но для некоторых, например SYSINT(завершение по [Ctrl+C]), определено такой дамп при завершении не создавать.

•  Игнорирование сигнала– сигнал не оказывает никакого воздействия на ход выполнения потока получателя.

•  Вызов обработчика– по поступлению сигнала вызывается функция реакции, определенная пользователем. Если для сигнала устанавливается функция-обработчик, то говорят, что сигнал перехватывается (относительно стандартного действия).

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

Для большинства сигналов их воздействие можно перехватить из программного кода или игнорировать, но не для всех. Например, сигналы SIGKILLи SIGSTOPне могут быть ни перехвачены, ни проигнорированы. Другой пример – описываемые далее специальные сигналы QNX, начиная с SIGSELECTи далее.

В QNX определены 64 сигнала в трех диапазонах:

• 1…40 – 40 POSIX-сигналов общего назначения;

• 41…56 – 16 POSIX-сигналов реального времени, введенных в стандарт позже (от SIGRTMINдо SIGRTMAX);

• 57…64 – 8 сигналов, используемых в QNX Neutrino для специальных целей.

Начнем со специальных сигналов. Эти сигналы не могут быть проигнорированы или перехвачены: попытка вызвать signal()или sigaction()(или вызов ядра SignalAction()native API) завершится для них с ошибкой EINVAL. Более того, эти сигналы всегда блокированы в пользовательском приложении, и для них установлено разрешение очереди обслуживания (очереди сигналов будут подробно рассмотрены далее). Попытка разблокировать эти сигналы, используя sigprocmask()( SignalProcmask()– native API), будет проигнорирована. Эти сигнальные механизмы используются, в частности, графической системой Photon для ожидания событий GUI и функцией select()для ожидания ввода/вывода на множестве дескрипторов (один из фундаментальных механизмов UNIX). Вот определения некоторых из этих сигналов:

#define SIGSELECT (SIGRTMAX + 1)

#define SIGPHOTON (SIGRTMAX + 2)

В силу своей недоступности эта группа сигналов представляет небольшой интерес для разработчика приложений.

Далее перейдем к POSIX-сигналам общего назначения (1…40), из которых назовем только самые употребляемые [28]28
  Наличие круглых скобок после имени сигнала – признак того, что для этих сигналов реакция по умолчанию – принудительное завершение процесса; наличие (+) означает, что для этих сигналов предусмотрено создание дампа завершения, а наличие (-) – для этих сигналов дамп не создается. В квадратных скобках после имени сигнала указано численное значение, соответствующее данному сигналу, как оно определено в QNX.


[Закрыть]
(пока мы описываем все сигналы в классической UNIX-нотации, не углубляясь в нюансы, например такие, как особенности реакции на них в многопоточном окружении):

•  SIGABRT (+) [6]– аварийное завершение процесса (process abort signal). Посылается процессу при вызове им функции abort(). В результате обработки сигнала SIGABRTпроизойдет то, что спецификация XSI описывает как аварийное завершение.

•  SIGALRM [14]– наступление тайм-аута таймера сигналов (real-time alarm clock). Посылается при срабатывании ранее установленного пользовательского таймера (таймер устанавливается заданием первого параметра setitimer(), равным ITIMER_REAL), например при помощи системного вызова alarm().

•  SIGBUS (+) [10]– сигнал ошибки на шине (bus error). Этот сигнал посылается при возникновении некоторых аппаратных ошибок (зависит от платформы); обычно он генерируется при попытке обращения к допустимому виртуальному адресу, для которого нет физической страницы.

•  SIGCHLD [18]– сигнал завершения или остановка дочернего процесса (child process terminated or stopped). По умолчанию родительский процесс игнорирует этот сигнал, поэтому если нужно получать уведомление о завершении порожденного процесса, этот сигнал нужно перехватывать. Для этого сигнала определен синоним:

#define SIGCLD SIGCHLD

•  SIGCONT [25]– продолжение работы остановленного процесса (continue executing if stopped). Это сигнал управления выполнением процесса, который возобновляет его выполнение, если ранее он был остановлен сигналом SIGSTOP; если процесс не остановлен, он игнорирует этот сигнал.

•  SIGDEADLK [7]– сигнал, говорящий о возникновении «мертвой» блокировки потока на мьютексе (mutex deadlock occurred). Эта ситуация будет подробно рассмотрена далее, когда речь пойдет о примитивах синхронизации (глава 4).

•  SIGEMT [7]– ЕМТ-инструкция (emulator trap). Как видно из сравнения с предыдущим сигналом, у них один код, то есть в QNX SIGDEADLKи SIGEMT– это один сигнал.

•  SIGFPE (+) [8]– недопустимая арифметическая операция с плавающей точкой (floating point exception).

•  SIGHUP [1]– сигнал освобождения линии, разрыв связи с управляющим терминалом (hangup signal). Посылается всем процессам, подключенным к управляющему терминалу при отключении терминала (обычно управляющий терминал группы процесса является терминалом пользователя, но это не всегда так). Этот сигнал также посылается всем членам сеанса, если завершает работу лидер сеанса (обычно процесс командного интерпретатора), связанного с управляющим терминалом (это гарантирует, что, если не были приняты специальные меры, при выходе пользователя из системы будут завершены все запущенные им фоновые процессы).

•  SIGILL [4]– попытка выполнения недопустимой инструкции процессора (illegal instruction); обработка сигнала не сбрасывается, когда он перехватывается.

•  SIGINT (-) [2]– принудительное прерывание процесса (interrupt); это тот сигнал, который генерируется при нажатии [Ctrl+C]. Это обычный способ завершения выполняющегося процесса.

•  SIGKILL (+) [9]– уничтожение процесса (kill); этот сигнал может активизироваться командой kill -9 . Это сигнал безусловного завершения, посылаемый процессу другим процессом или системой (при завершении работы системы). Этот сигнал не может быть проигнорирован или перехвачен.

•  SIGPIPE [13]– попытка выполнить недопустимую запись в канал или сокет, для которых принимающий процесс уже завершил работу (write on pipe or socket when recipient is terminated, write on pipe with no reader).

•  SIGPOLL [22]– сигнал уведомления об одном из опрашиваемых событий (pollable event). Этот сигнал генерируется системой, когда некоторый открытый дескриптор файла готов для ввода или вывода. Этот сигнал имеет синоним (наименование SIGPOLLболее известно из UNIX System V):

#define SIGIO SIGPOLL

•  SIGPROF [29]– сигнал профилирующего таймера (profiling timer expired). Как и сигнал SIGALRM, этот сигнал возбуждается по истечении времени таймера (но это другой таймер), который используется для измерения времени выполнения процесса в пользовательском и системном режимах (таймер устанавливается заданием первого параметра setitimer(), равным ITIMER_PROF).

•  SIGQUIT (-) [3]– выход из процесса (quit).

•  SIGSEGV (+) [11]– обращение к некорректному адресу памяти, ошибка защиты памяти, нарушение границ сегмента памяти (invalid memory reference, segmentation violation).

•  SIGSTOP [23]– временная остановка процесса (sendable stop signal not from tty). Если пользователь вводит с терминала [Ctrl+Z], то активному процессу посылается этот сигнал и процесс приостанавливается. Позже процесс может быть возобновлен с точки остановки при получении сигнала SIGCONT. Этот сигнал нельзя проигнорировать или перехватить.

•  SIGSYS (+) [12]– некорректный системный вызов (invalid system call, bad argument to system call).

•  SIGTERM [15]– программный сигнал завершения (software termination signal from kill). Программист может использовать этот сигнал для того, чтобы дать процессу время для «наведения порядка», прежде чем посылать ему сигнал SIGKILL. Именно этот сигнал посылается по умолчанию командой killбез параметра, указывающего сигнал.

•  SIGTRAP [5]– сигнал трассировочного прерывания (trace trap). Это особый сигнал, который в сочетании с системным вызовом ptrace()используется отладчиками: sdb, adb, gdb. По умолчанию сигнал приводит к аварийному завершению. Обработка сигнала не сбрасывается, когда он перехватывается.

•  SIGTSTP [24]– терминальный сигнал остановки (terminal stop signal). Генерируется при нажатии специальной комбинации остановки [Ctrl+Z]. Аналогичен сигналу SIGSTOP, но, в отличие от последнего, может быть перехвачен или проигнорирован.

•  SIGTTIN [26]– остановка фонового процесса, если он пытается прочитать данные со своего управляющего терминала (background process attempting read).

•  SIGTTOU [27]– остановка фонового процесса, если он пытается писать данные на свой управляющий терминал (background process attempting write).

•  SIGURG [21]– сигнал о поступлении в буфер сокета срочных (приоритетных) данных (high bandwidth data is available at a socket, urgent condition on I/O channel) уведомляет процесс, что по открытому им сетевому соединению получены внеочередные данные.

•  SIGUSR1 [16], SIGUSR2 [17]– зарезервированные сигналы пользователя. Для этих сигналов предопределенной реакцией в QNX является завершение процесса (хотя естественнее ожидать, и так это предлагает POSIX, реакцию «игнорировать сигнал»), и реакцию на них должен определять пользователь. Так же как и сигнал SIGTERM, эти сигналы никогда не посылаются системой.

•  SIGVTALRM [28]– сигнал виртуального таймера (virtual timer expired). Подобно SIGPROFи SIGALRM, этот сигнал возбуждается по истечении времени таймера (это третий из доступных таймеров), который измеряет время процессора только в пользовательском режиме (таймер устанавливается заданием первого параметра setitimer(), равным ITIMER_VIRTUAL).

•  SIGXCPU [30]– сигнал о превышении лимита процессорного времени (CPU time limit exceeded). Посылается процессу при исчерпании им ранее установленного лимита процессорного времени. Действие по умолчанию – аварийное завершение.

•  SIGXFSZ [31]– сигнал о превышении предела, установленного на размер файла (file size limit exceeded). Действие по умолчанию – аварийное завершение.

•  SIGWINCH [20]– сигнал, который генерируется (в консольном режиме ptermи xtermэмулируют его вручную при изменении их размеров) при изменении размера окна (window size change) для запущенного в окне приложения (mc, mqc…), чтобы оно перерисовало свой экран вывода.

Примечание

В QNX определено еще два специфических сигнала, которые вряд ли должны представлять для нас интерес:

SIGIOT [6]– IOT-инструкция; никогда не генерируется для платформы x86.

SIGPWR [19]– сигнал power-fail restart о котором в технической документации QNX ничего не говорится, но в преамбуле, описывающей нововведения версии 6.2.1, сказано: «corrected SIGPWR to SIGTERM», то есть этот сигнал, очевидно, – рудимент прежних версий системы.

Примечание

POSIX допускает, что не все сигналы могут быть реализованы. Более того, допускается ситуация, когда некоторое символическое имя сигнала определено, но сам сигнал отсутствует в системе (изменения такого рода вполне могут наблюдаться при переходе от одной версии QNX к другой). Для диагностики реального наличия сигнала можно воспользоваться рекомендацией, приведенной в информативной части стандарта POSIX 1003.1: наличие поддержки сигнала сообщает вызов функции sigaction()с аргументами actи oact, установленными в NULL. Приведем простейший тест ( файл s1.cc), реализующий рекомендацию POSIX в QNX 6.2.1:

#include

#include

#include

#include

int main(int argc, char *argv[]) {

 cout << «SIGNO»;

 for (int i = _SIGMIN; i <= _SIGMAX; i++) {

  if (i % 8 == 1) cout << endl << i << ':';

  int res = sigaction(i, NULL, NULL);

  cout << 't' << ((res != 0 && errno == EINVAL) ? '-' : '+');

 }

 cout << endl;

 return EXIT_SUCCESS;

}

И результат его выполнения:

SIGNO

1:  + + + + + + + +

9:  + + + + + + + +

17: + + + + + + + +

25: + + + + + + + +

33: + + + + + + + +

41: + + + + + + + +

49: + + + + + + + +

57: – – – – – – – -

Система «считает» все сигналы 1…56 реализуемыми, а последние 8 специфических сигналов QNX, как упоминалось выше, не допускают применения к ним sigaction(). Здесь с учетом цитировавшейся выше раскладкой сигналов QNX есть небольшая загадка: максимальным номером POSIX-сигнала, определенного в , является 31 ( SIGXFSZ – 31); там же в комментарии есть фраза: «допустимый диапазон пользовательских сигналов – от 1 до 56, используемых ядром – от 57 до 64». Непонятно, в каком качестве используются сигналы 32–40, непосредственно предшествующие сигналам реального времени (41–56) и диагностируемые sigaction()как действительные (valid)? Позже мы увидим, что они обслуживаются системой наравне с документированными сигналами.


    Ваша оценка произведения:

Популярные книги за неделю