Регистрация событий, ошибок и статусов.

Версия 1.0.1

© Copyright 2006
Грибов Игорь,
e-mail: gribov@depni.sinp.msu.ru

Оглавление.

Изменения в версиях.
Свойства регистратора.
Программная реализация регистратора.

Изменения в версиях.

Полная версия раздела задается в виде: версия.подверсия.выпуск.

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


Свойства регистратора.

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




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

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

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




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


Программная реализация регистратора.

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

Программа регистратора использует функции стандартных библиотек С. Из <string.h> вызывается функция копирования строк, <stdio.h> поставляет функции ввода-вывода на терминал и в файл, <stdlib.h> содержит функции выделения динамической памяти, а для работы с функциями времени подключается <time.h>.

#define CACHE_SIZE  8   // Число буферов в кэше (не менее 2)

#define PMC_STATUSBUF_MIN   10      // Min 5
#define PMC_STATUSBUF_MAX   60000   // Max 64883

#define ERROR_CONFIG       -20   // Ошибка конфигурации

#define ERROR_STATUSCACHE  -12   // Ошибка переполнения кэша
#define ERROR_STATUSFIFO   -11   // Ошибка переполнения FIFO
#define ERROR_MALLOC       -10   // Ошибка выделения памяти для FIFO

#define ERROR  -1
#define OK     1

#define STR_FILE_NAME_SIZE   256   // Максимальная длина имени файла (255 и '\0')
#define STR_STATMES_SIZE     60    // Максимальная длина сообщения (59 и '\0')
#define STR_TS_SIZE          20    // Длина строки временной метки (19 и '\0')

typedef char            int8;
typedef unsigned char   unsigned8;
typedef short           int16;
typedef unsigned short  unsigned16;
typedef int             int32;
typedef unsigned int    unsigned32;

typedef struct {
        time_t ts;                      
        int16 status;
        char message[STR_STATMES_SIZE];
} status;   // Структура сообщения

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

typedef struct {
        short busy; 
        status st;
} statuscache;   // Структура данных буфера, дополнена семафором busy

statuscache status_cache[CACHE_SIZE];   // Кэш-буфер

status *statbase;                 // Указатель начала кольцевого буфера (FIFO)
unsigned16 bufsize_status;        // Конфигурируемый размер FIFO
int16 sem_statsen, sem_cachefl;   // Семафоры отправки данных из FIFO и записи из кэша в FIFO
unsigned16 head_st, tail_st;      // Голова и хвост кольцевого буфера

int logs_hour;      // Время в часах. Используется для управления файлами журнала

FILE *filestatus;   // Файл журнала для записи сообщений из FIFO         

char argv_file_name[STR_FILE_NAME_SIZE];
     // Имя файла программы, полученное от операционной системы.
     // Используется при формировании имени файла журнала.


void write_status(int16 status, char *mess);

void send_status(status *st)   // sn_0
{
   char ts[STR_TS_SIZE];
   char stat[STR_STATMES_SIZE];

   strftime(ts, STR_TS_SIZE, "%d-%m-%Y %H:%M:%S", localtime(&st->ts));   // sn_5
   if (st->status == ERROR_MALLOC) {                                     // sn_6
      sprintf(stat, "Memory allocation error");
   } else if (st->status == ERROR_STATUSFIFO) {
      sprintf(stat, "Status FIFO overflow");
   } else if (st->status == ERROR_STATUSCACHE) {
      sprintf(stat, "Status cache overflow");
   } else if (st->status == ERROR_CONFIG) {
      sprintf(stat, "Configuration error");
   } else sprintf(stat, "Error/Status %4i", st->status);               // sn_14
   if (filestatus != NULL) {                                           // sn_15
      fprintf(filestatus, "%s   %s   %s\n",  ts, stat, st->message);   // sn_16
   } else {
      printf("%s   %s   %s\n", ts, stat, st->message);                 // sn_18
   }
}

Функция send_status(st) выполняет отправку сообщения регистратора. Поскольку окончательным местом назначения является файл журнала, функция преобразует сообщение в текстовый вид. Его временная метка записывается в формате DD-MM-YYYY HH:MM:SS [sn_5]. В строках [sn_6...sn_14] раскрывается код сообщения. И, наконец, сообщение заносится в файл журнала [sn_16], если таковой был открыт [sn_15], либо – при отсутствии файла – выводится на терминал [sn_18].

void flush_status_cache(void)   // fs_0
{
   unsigned16 head, cnt;

   sem_cachefl++;            // fs_4
   if (sem_cachefl != 0) {   // fs_5
      sem_cachefl--;         // fs_6
      return;
   }
   for (cnt = 0; cnt < CACHE_SIZE; cnt++) {       // fs_9  
      if (status_cache[cnt].busy < 0) continue;   // fs_10  
      head = head_st+1;                           // fs_11
      if (head == bufsize_status) head = 0;       // fs_12   
      sem_statsen++;                              // fs_13
      if (head == tail_st) {                      // fs_14
         write_status(ERROR_STATUSFIFO, "flush_status_cache()");      // fs_15
         if (sem_statsen != 0) {                  // fs_16
            sem_statsen--;                        // fs_17
            sem_cachefl--;                        // fs_18
            return;                               // fs_19
         }
         tail_st += 4 + bufsize_status/100;                           // fs_21
         if (tail_st >= bufsize_status) tail_st -= bufsize_status;    // fs_22
      }
      sem_statsen--;                              // fs_24
      memcpy(statbase+head, &status_cache[cnt].st, sizeof(status));   // fs_25
      head_st = head;                             // fs_26
      status_cache[cnt].busy = -1;                // fs_27
   }
   sem_cachefl--;                                 // fs_29
}

Функция flush_status_cache() переписывает накопленные в кэше сообщения в кольцевой буфер. Она работает с разделяемыми ресурсами (голова [fs_11] и хвост [fs_21] кольцевого буфера), повторное обращение к которым не допустимо. Безусловная сигналобезопасность функции поддерживается блокирующим семафором sem_cachefl [fs_4, fs_6, fs_18, fs_29], который также используется для закрытия доступа к кэшу на время выполнения критической секции записи данных в write_status(status, mess). При выводе сообщений в FIFO производится линейный просмотр кэша (цикл [fs_9]) с пропуском не занятых буферов [fs_10]. Для заполненных кэш-буферов осуществляется продвижение головы FIFO [fs_11, fs_12] и, если имеется свободное место, производится копирование содержимого кэша по адресу головы кольцевого буфера [fs_25]. Затем устанавливается новое значение адреса головы head_st [fs_26] и открывается семафор занятости кэш-буфера [fs_27].

Операторы [fs_13...fs_24] осуществляют подчистку кольцевого буфера в случае, если он оказался полон, то есть когда голова буфера уперлась в его хвост [fs_14]. Для осуществления этой операции нужно прежде всего заблокировать доступ к хвосту буфера, для чего используется семафор извлечения данных из FIFO sem_statsen, который запрещает вывод накопленных в буфере сообщений [fs_13]. Сам факт подчистки регистрируется путем записи статуса [fs_15], который будет занесен в кэш функцией write_status(status, mess). Здесь и далее в качестве дополнительного текстового сообщения используется название функции, в которой регистрируется статус. Оператор [fs_16] осуществляет проверку того, что подчистка кольцевого буфера не была активирована во время вывода данных из FIFO, то есть при осуществлении манипуляций с хвостом буфера. Если такая ситуация имеет место, выполнение flush_status_cache() прекращается [fs_19], предоставляя возможность функции вывода show_status() завершить свою работу. Операторы [fs_21, fs_22] выполняют собственно подчистку FIFO. Величина продвижения хвоста определяется линейной формулой: один процент от размера кольцевого буфера плюс 4 записи. Именно такая формула обуславливает предельные значения минимального и максимального размеров буфера (5 – при подчистке теряются все сообщения FIFO, 64883 – значение в [fs_21] не превышает 65535 при использовании в качестве головы и хвоста буфера переменных типа unsigned16). Отметим еще раз, что при выполнении любых манипуляций с подчисткой FIFO сообщения не теряются, а продолжают регистрироваться в кэше.

void write_status(int16 status, char *mess)   // ws_0
{
   unsigned16 cnt;

   sem_cachefl++;                                // ws_4
   for (cnt = 1; cnt < CACHE_SIZE; cnt++) {      // ws_5
      status_cache[cnt].busy++;                  // ws_6
      if (status_cache[cnt].busy == 0) {         // ws_7
         status_cache[cnt].st.ts = time(NULL);   // ws_8
         status_cache[cnt].st.status = status;   // ws_9
         strncpy(status_cache[cnt].st.message, mess, STR_STATMES_SIZE);   // ws_10
         status_cache[cnt].st.message[STR_STATMES_SIZE-1] = '\0';         // ws_11
         sem_cachefl--;                          // ws_12
         if (statbase == NULL) head_st++;        // ws_13
         else flush_status_cache();              // ws_14
         return;
      }
      status_cache[cnt].busy--;                  // ws_17
   }
   status_cache[0].st.ts = time(NULL);           // ws_18
   status_cache[0].busy = 0;                     // ws_19
   sem_cachefl--;                                // ws_20
   if (statbase == NULL) head_st++;              // ws_21
   else flush_status_cache();                    // ws_22
}

Повторно-входимая функция записи сообщений (статусов) write_status(status, *mess) заносит код события и текстовое сообщение в кэш-буфер, дополняя их временной меткой [ws_8]. При этом полагается, что допустим повторно-входимый запрос текущего времени time(NULL). Эта функция выполняет роль прикладного интерфейса (API) асинхронного регистратора.

Доступ к каждому буферу осуществляется сигналобезопасно с использованием семафора [ws_6, ws_7, ws_17]. Функция просматривает кэш, начиная с первого буфера [ws_5], поскольку нулевой используется для индикации переполнения самого кэша. При обнаружении свободного буфера [ws_7] он заполняется данными [ws_8...ws_11], причем текстовое сообщение дополняется завершающим нулем [ws_11], поскольку длина mess может превышать STR_STATMES_SIZE. После записи данных в кэш выполняемая операция зависит от того, существует ли кольцевой буфер или нет. Если он еще не определен (указатель начала FIFO равен NULL), то инкрементируется значение головы буфера [ws_13], которое в данном случае используется лишь для определения числа зарегистрированных сообщений. Этим обеспечивается возможность регистрации событий, возникающих до либо в процессе выделения памяти для FIFO. Так, если при запросе памяти для кольцевого буфера возникает ошибка, она также будет зафиксирована в кэше - см. далее функцию allocate_status_buffer() [ab_3, ab_4]. Если же FIFO уже существует, осуществляется попытка немедленного вывода данных из кэша [ws_14]. В строках [ws_18...ws_22] обрабатывается ситуация переполнения самого кэша. Для этого в нулевой буфер при его инициализации заносится на постоянное хранение статус ошибки переполнения. А при ее возникновении осуществляется лишь инициализация нулевого кэша: установ временной метки [ws_18] и семафора буфера [ws_19]. Затем, аналогично операторам [ws_13, ws_14] осуществляется либо инкремент головы буфера [ws_21], либо вывод данных из кэша [ws_22].

Обратите внимание на использование семафора блокирования вывода данных sem_cachefl [ws_4, ws_12, ws_20]. Поскольку вызов функции переноса данных из кэша в FIFO может осуществляться асинхронно по отношению к функции записи сообщений, нужно предотвратить некорректное использование разделяемых ресурсов. Так, после выполнения попытки захвата буфера [ws_6] и до фактической записи данных [ws_11] этот буфер не может считаться готовым для вывода, не взирая на значение флага busy, соответствующее заполненному буферу. Кроме того, если вызов flush_status_cache() произойдет непосредственно до семафорной операции [ws_17], значение семафора станет меньше минус 1 и данный буфер будет постоянно блокирован.

unsigned16 nof_status(void)   // ns_0
{
   int32 ht;

   ht = head_st - tail_st;             // ns_4
   if (ht < 0) ht += bufsize_status;   // ns_5
   return ht;
}

Функция nof_status() возвращает число заполненных элементов кольцевого буфера (зарегистрированных сообщений). Для обеспечения сигналобезопасности относительно записи и чтения FIFO разность головы и хвоста буфера присваивается локальной переменной со знаком [ns_4]. Если же использовать в операторе [ns_5] переменные head_st и tail_st непосредственно, можно получить совершенно неверный результат, если запись или чтение буфера произойдут после проверки условия в [ns_5] и совпадут с закольцовыванием FIFO.

void transform_file_name(char *filename, char *initfn)   // tn_0
{
   unsigned16 fnp, cnt;

   strncpy(filename, argv_file_name, STR_FILE_NAME_SIZE);
   fnp = STR_FILE_NAME_SIZE-1;          // tn_5
   while (fnp > 0) {                    // tn_6
      fnp--;
      if (filename[fnp] == '\\') {   
         fnp++;
         break;
      }
   }
   cnt = 0;
   while (fnp < STR_FILE_NAME_SIZE) {   // tn_14
      filename[fnp] = initfn[cnt];
      if (filename[fnp] == '\0') break;
      fnp++; cnt++;
   }
   filename[STR_FILE_NAME_SIZE-1] = '\0';
}

Вспомогательная функция transform_file_name(filename, initfn) преобразует имя файла initfn так, чтобы учесть размещение программы регистратора. Полное имя запускаемой программы – включая путь ее размещения – передается операционной системой в нулевом аргументе вектора параметров, который сохраняется в argv_file_name (см. ниже [mn_2]). Из путевого имени удаляется название самой программы (цикл [tn_6]) и дописывается имя файла initfn (цикл [tn_14]). Таким образом, полученное в результате имя filename оказывается фиксированным относительно места расположения самой программы не зависимо от директории и метода ее запуска.

void log_status_file(void)   // lf_0
{
   time_t ts;
   struct tm tp;
   char fn[STR_TS_SIZE+10];
   char file_name[STR_FILE_NAME_SIZE];

   ts = time(NULL);
   tp = *localtime(&ts);
   if (logs_hour != tp.tm_hour || filestatus == NULL) {   // lf_9
      if (filestatus != NULL) fclose(filestatus);
      sprintf(fn, "Log\\status");                         // lf_11
      strftime(fn+10, STR_TS_SIZE, "_%Y%m%d_%H", &tp);    // lf_12
      transform_file_name(file_name, fn);                 // lf_13
      filestatus = fopen(file_name, "w");
      logs_hour = tp.tm_hour;
   }
}

Вспомогательная функция log_status_file() осуществляет начальное, а затем ежечасное формирование новых файлов журнала регистратора [lf_9]. Файлы с именами status_YYYYMMDD_HH размещаются в поддиректории Log [lf_11, lf_12] относительно директории размещения программы регистратора [lf_13].

void show_cache(void)   // sc_0
{
   unsigned16 cnt;

   for (cnt = 0; cnt < CACHE_SIZE; cnt++) {
      if (status_cache[cnt].busy >= 0) {
         send_status(&status_cache[cnt].st);   // sc_6
         status_cache[cnt].busy = -1;          // sc_7
         head_st--;                            // sc_8
      }
   }
}

Функция show_cache() выполняет отправку сообщений [sc_6], накопленных в кэше регистратора. После отправки сообщения открывается семафор занятости буфера [sc_7] и декрементируется значение головы FIFO [sc_8], используемое в данном случае только для определения числа сообщений в кэше.

void show_status(void)   // ss_0
{
   unsigned16 tail;

   log_status_file();                  // ss_4
   if (nof_status() == 0) return;      // ss_5
   if (statbase == NULL) {             // ss_6
      show_cache();                    // ss_7
      return;
   }
   flush_status_cache();               // ss_10
   sem_statsen++;                      // ss_11
   if (sem_statsen == 0) {             // ss_12
      while (tail_st != head_st) {     // ss_13
         tail = tail_st+1;
         if (tail == bufsize_status) tail = 0;
         send_status(statbase+tail);   // ss_16
         tail_st = tail;
      }
   }
   sem_statsen--;                      // ss_20
}

Функция show_status() занимается отправкой сообщений из кэша и кольцевого буфера, если последний существует. Асинхронное обращение к этой функции не допускается, поскольку она взаимодействует с операционной системой – осуществляет открытие, закрытие и запись в файл. В нашем примере периодический вызов show_status() включен в мониторный цикл программы [mr_2]. Функция обращается с запросом формирования файлов журнала [ss_4] даже при отсутствии зарегистрированных сообщений [ss_5]. При этом появляются файлы нулевого размера, если в течение часа не фиксируется ни одного сообщения. Если операторы [ss_4] и [ss_5] поменять местами, новые файлы журнала будут формироваться лишь при наличии сообщений в FIFO. При этом, однако, следует внести изменения и в функцию log_status_file(), чтобы учитывать не только смену часа, но и дня, дабы сообщения, разделенные сутками, не попали в один и тот же файл.

Когда кольцевой буфер еще не определен и указатель начала FIFO равен NULL [ss_6], производится отправка сообщений из кэша регистратора [ss_7]. Если же FIFO существует, то, переписав в кольцевой буфер данные из кэша [ss_10], функция осуществляет вывод всех накопленных сообщений [ss_13]. Семафор извлечения данных из FIFO sem_statsen [ss_11, ss_12, ss_20] используется не для поддержки сигналобезопасности доступа к хвосту буфера, но для его запрета в момент подчистки FIFO функцией flush_status_cache() [fs_13, fs_24]. Однако, этот семафор оказался бы полезен и в случае реализации асинхронной отправки данных - доступ к хвосту буфера также должен осуществляться сигналобезопасно.

void allocate_status_buffer(void)   // ab_0
{
   statbase = malloc(bufsize_status*sizeof(status));            // ab_2
   if (statbase == NULL) {                                      // ab_3
      write_status(ERROR_MALLOC, "allocate_status_buffer()");   // ab_4
      return;
   }
   head_st = 0;        // ab_7
   tail_st = 0;        // ab_8
   sem_statsen = -1;   // ab_9
   sem_cachefl = -1;   // ab_10
}

Функция allocate_status_buffer() обеспечивает выделение памяти для размещения кольцевого буфера. Сегмент памяти нужного размера запрашивается у операционной системы [ab_2], причем сам размер буфера определяется переменной bufsize_status и может быть задан конфигурационными средствами. В случае успешного создания FIFO осуществляется его инициализация [ab_7], ([ab_8] - не обязательно) и открываются семафоры отправки данных из FIFO [ab_9] и записи из кэша в FIFO [ab_10].

void init_statusproc(void)   // is_0
{
   unsigned16 cnt;

   logs_hour = 0;
   sem_statsen = 0;   // is_5
   sem_cachefl = 0;   // is_6
   head_st = 0;
   tail_st = 0;
   statbase = NULL;   // is_9
   for (cnt = 0; cnt < CACHE_SIZE; cnt++) {                 // is_10
      memset(&status_cache[cnt], 0, sizeof(statuscache));
      status_cache[cnt].busy = -1;
   }
   status_cache[0].st.status = ERROR_STATUSCACHE;           // is_14
   sprintf(status_cache[0].st.message, "write_status()");   // is_15
}

Функция init_statusproс() производит начальную инициализацию данных регистратора. Семафоры, используемые при работе с FIFO, устанавливаются в закрытое состояние [is_5, is_6], инициализируется указатель начала кольцевого буфера [is_9]. Таким образом, до фактического выделения памяти для FIFO возможность обращения к нему в функции flush_status_cache() [fs_25] будет невозможна. Затем осуществляется инициализация кэша (цикл [is_10]). Операторы [is_14, is_15] заносят в нулевой кэш-буфер статус ошибки переполнения. При ее возникновении будет открыт семафор нулевого кэша [ws_19], позволяя зарегистрировать саму ситуацию переполнения.

void periodic(void)   // pr_0
{
   flush_status_cache();
}       

Функция periodic() активируется периодическим таймером, инициируя пересылку накопленных в кэше сообщений в кольцевой буфер. В данном примере ее использование не обязательно, поскольку эта операция регулярно выполняется функцией отправки сообщений show_status(), вызываемой из монитора регистратора [mr_2]. Однако, следует подчеркнуть саму необходимость такого обращения к функции пересылки, причем с гарантированной периодичностью. Ведь асинхронный вызов flush_status_cache() может завершиться безуспешно, а повторное сканирование кэша в ней не предусмотрено. В результате сообщения, вновь записанные в начальные элементы кэш-буфера, не будут переданы в FIFO. Поскольку flush_status_cache() является безусловно сигналобезопасной, допустим ее асинхронный вызов, в том числе по сигналу или прерыванию таймера.

void monitor(void)   // mr_0
{
   show_status();   // mr_2
}

Функция show_status(), отправляющая сообщения из кэша и кольцевого буфера, взаимодействует с операционной системой – осуществляет открытие, закрытие и запись в файл. Поэтому ее периодический вызов осуществляется из монитора программы [mr_2].

void read_config(void)
{
   bufsize_status = 1234;
   if (bufsize_status < PMC_STATUSBUF_MIN || bufsize_status > PMC_STATUSBUF_MAX) {
      write_status(ERROR_CONFIG, "read_config()");
   }
}

Функция read_config() считывает конфигурационную информацию. Здесь задается размер кольцевого буфера bufsize_status. Конфигурационная информация обычно хранится в файлах соответствующего формата.

void init_all(void)
{
   init_statusproc();
   read_config();
   if (nof_status() != 0) return;
   allocate_status_buffer();
}

Функция init_all() осуществляет общую инициализацию приложения регистратора. Обратите внимание на вызов nof_status(), возвращающей число зарегистрированных сообщений, в качестве индикатора возникновения ошибок.

int main(int argc, char **argv)   // mn_0
{
   strncpy(argv_file_name, argv[0], STR_FILE_NAME_SIZE);   // mn_2
   filestatus = NULL;
   init_all();                // mn_4
   if (nof_status() == 0) {   // mn_5
      while (1) monitor();    // mn_6
   }
   show_status();
   if (filestatus != NULL) fclose(filestatus);
   return 1;
}

В функции main() сохраняется полное имя запускаемой программы [mn_2], включающее путь ее фактического размещения. Это дает возможность формирования имен файлов относительно директории расположения программы не зависимо от способа ее запуска. Вызов init_all() [mn_4] производит общую инициализацию приложения регистратора. В качестве индикатора ошибок здесь также используется функция nof_status() [mn_5]. При наличии таковых программа прекращает работу – вход в ее монитор [mn_6] не осуществляется.