Примеры использования HAM

Примеры отказоустойчивых приложений с различными сценариями обеспечения высокой готовности

Оглавление:

Обычный перезапуск
Многоэтапный перезапуск
Уведомление о завершении объекта или наступлении условия
Механизм контрольных сигналов (определение доступности объектов)
Отправка контрольных сигналов в процессе
Блокировка процесса
Отправка контрольных сигналов в потоке 2

Обычный перезапуск

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

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

Рассмотрим код перезапуска демона inetd:

/* addinet.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/netmgr.h>
#include <fcntl.h>
#include "ha/ham.h"
int main( int argc, char *argv[] )
{
int status;
char *inetdpath;
ham_entity_t *ehdl;
ham_condition_t *chdl;
ham_action_t *ahdl;
int inetdpid;
if ( argc > 1 )
inetdpath = strdup( argv[1] );
else
inetdpath = strdup( "/usr/sbin/inetd -D") ;
if ( argc > 2 )
inetdpid = atoi( argv[2] );
else
inetdpid = -1;
ham_connect( 0 );
ehdl = ham_attach( "inetd", ND_LOCAL_NODE, inetdpid, inetdpath, 0 );
if ( ehdl != NULL )
{
chdl = ham_condition( ehdl, CONDDEATH, "death", HREARMAFTERRESTART );
if ( chdl != NULL )
{
ahdl = ham_action_restart( chdl, "restart", inetdpath, HREARMAFTERRESTART );
if ( ahdl == NULL )
printf( "add action failed\n" );
} else
printf( "add condition failed\n" );
} else
printf( "add entity failed\n" );
ham_disconnect( 0 );
exit( 0 );
}

В этом примере процесс inetd присоединяется к HAM, а затем создает в нем условие death (завершение) и действие restart (перезапуск).


Note: Если inetd не является самостоятельно присоединяемым объектом, следует передать ему ключ -D, чтобы «демонизировать» его посредством функции procmgr_daemon(), а не daemon(). HAM получает сообщения о завершении только тех объектов, которые самостоятельно присоединились к нему, завершились аварийно либо выполнялись в сеансе 1; функция daemon() не помещает вызывающий ее процесс в этот сеанс.

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


Когда процесс inetd завершается, HAM автоматически перезапускает его с помощью команды, которая указана в переменной inetdpath. Если процесс inetd уже выполняется в системе, можно записать его идентификатор (pid) в переменную inetdpid, и присоединение будет выполнено автоматически. Если inetd не работает, HAM запускает его и начинает наблюдать за ним.

С помощью указанного кода можно также контролировать процесс slogger2 (команда /usr/sbin/slogger2), mqueue (команда /sbin/mqueue) и т.д. — достаточно указать полный путь к исполняемому файлу и все командно-строковые параметры.

Многоэтапный перезапуск

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

HAM позволяет составлять списки действий, которые выполняются при наступлении заданного условия. Допустим, что мы наблюдаем за файловой системой fs-nfs3, в которой смонтированы и используются несколько каталогов. Если просто перезапустить процесс fs-nfs3 после завершения, эти каталоги не будут перемонтированы и доступны для работы! Необходимо сначала перезапустить процесс fs-nfs3, а затем явно указать команды монтирования требуемых каталогов.

Аналогичным образом, завершение процесса io-pkt-* приводит к завершению сетевых драйверов и стека TCP/IP, поэтому при перезапуске io-pkt-* также требуется повторно инициализировать сетевой драйвер. Кроме того, необходимо выполнять сброс всех остальных компонентов (таких как inetd), в которых используются сетевые подключения, чтобы они установили их заново.

В следующем примере показан механизм многоэтапного перезапуска.

/* addnfs.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/netmgr.h>
#include <fcntl.h>
#include <ha/ham.h>
int main( int argc, char *argv[] )
{
int status;
ham_entity_t *ehdl;
ham_condition_t *chdl;
ham_action_t *ahdl;
char *fsnfspath;
int fsnfs3pid;
if ( argc > 1 )
fsnfspath = strdup( argv[1] );
else
fsnfspath = strdup( "/usr/sbin/fs-nfs3" );
if ( argc > 2 )
fsnfs3pid = atoi( argv[2] );
else
fsnfs3pid = -1;
ham_connect( 0 );
ehdl = ham_attach( "Fs-nfs3", ND_LOCAL_NODE, fsnfs3pid, fsnfspath, 0 );
if ( ehdl != NULL )
{
chdl = ham_condition( ehdl, CONDDEATH, "Death", HREARMAFTERRESTART );
if ( chdl != NULL )
{
ahdl = ham_action_restart( chdl, "Restart", fsnfspath, HREARMAFTERRESTART );
if ( ahdl == NULL )
printf( "add action failed\n" );
/*
else {
ahdl = ham_action_waitfor( chdl, "Delay1", NULL, 2000, HREARMAFTERRESTART );
if ( ahdl == NULL )
printf( "add action failed\n" );
ahdl = ham_action_execute( chdl, "MountPPCBE", "/bin/mount -t nfs 10.12.1.115:/ppcbe /ppcbe",
HREARMAFTERRESTART | ((fsnfs3pid == -1) ? HACTIONDONOW : 0) );
if ( ahdl == NULL )
printf( "add action failed\n" );
ahdl = ham_action_waitfor( chdl, "Delay2", NULL, 2000, HREARMAFTERRESTART );
if ( ahdl == NULL )
printf( "add action failed\n" );
ahdl = ham_action_execute( chdl, "MountWeb", "/bin/mount -t nfs 10.12.1.115:/web /web",
HREARMAFTERRESTART | ((fsnfs3pid == -1) ? HACTIONDONOW : 0) );
if ( ahdl == NULL )
printf( "add action failed\n" );
}
*/
} else
printf( "add condition failed\n" );
} else
printf( "add entity failed\n" );
ham_disconnect( 0 );
exit( 0 );
}

В этом примере к менеджеру высокой готовности присоединяется объект fs-nfs3, а затем в условие death (завершение) добавляется последовательность действий execute (выполнение) и waitfor (ожидание). Когда процесс fs-nfs3 завершается, менеджер высокой готовности перезапускает его и последовательно перемонтирует необходимые удаленные каталоги. Следует иметь в виду, что с помощью действий можно указывать задержки и ожидать появления конкретных имен в пространстве имен.

Уведомление о завершении объекта или наступлении условия

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

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

/* regevent.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <sys/iomsg.h>
#include <sys/netmgr.h>
#include <signal.h>
#include <ha/ham.h>
#define PCODEINETDDEATH (_PULSE_CODE_MINAVAIL + 1)
#define PCODEINETDDETACH (_PULSE_CODE_MINAVAIL + 2)
#define PCODENFSDELAYED (_PULSE_CODE_MINAVAIL + 3)
#define PCODEINETDRESTART1 (_PULSE_CODE_MINAVAIL + 4)
#define PCODEINETDRESTART2 (_PULSE_CODE_MINAVAIL + 5)
#define MYSIG (SIGRTMIN + 1)
int fsnfs_value;
/* Обработчик сигналов для обработки уведомлений о завершении процесса fs-nfs3 */
void MySigHandler( int signo, siginfo_t *info, void *extra )
{
printf( "Received signal %d, with code = %d, value %d\n",
signo, info->si_code, info->si_value.sival_int );
if ( info->si_value.sival_int == fsnfs_value )
printf( "FS-nfs3 died, this is the notify signal\n" );
return;
}
int main( int argc, char *argv[] )
{
int chid, coid, rcvid;
struct _pulse pulse;
pid_t pid;
int status;
int value;
ham_entity_t *ehdl;
ham_condition_t *chdl;
ham_action_t *ahdl;
struct sigaction sa;
int scode;
int svalue;
/* для приема импульсов необходимо создать канал */
chid = ChannelCreate( 0 );
/* для доставки импульсов необходимо подключиться к этому каналу */
coid = ConnectAttach( 0, 0, chid, _NTO_SIDE_CHANNEL, 0 );
/* заполнение структуры события для импульса */
pid = getpid();
value = 13;
ham_connect( 0 );
/* считаем, что объект с именем "inetd" уже существует */
chdl = ham_condition_handle( ND_LOCAL_NODE, "inetd","death", 0 );
ahdl = ham_action_notify_pulse( chdl, "notifypulsedeath", ND_LOCAL_NODE, pid, chid,
PCODEINETDDEATH, value, HREARMAFTERRESTART );
ham_action_handle_free( ahdl );
ham_condition_handle_free( chdl );
ehdl = ham_entity_handle( ND_LOCAL_NODE, "inetd", 0 );
chdl = ham_condition( ehdl, CONDDETACH, "detach", HREARMAFTERRESTART );
ahdl = ham_action_notify_pulse( chdl, "notifypulsedetach", ND_LOCAL_NODE, pid, chid,
PCODEINETDDETACH, value, HREARMAFTERRESTART );
ham_action_handle_free( ahdl );
ham_condition_handle_free( chdl );
ham_entity_handle_free( ehdl );
fsnfs_value = 18; /* значение, которое будет использоваться как признак завершения fs-nfs */
scode = 0;
svalue = fsnfs_value;
sa.sa_sigaction = MySigHandler;
sigemptyset( &sa.sa_mask );
sa.sa_flags = SA_SIGINFO;
sigaction( MYSIG, &sa, NULL );
/* Считаем, что объект с именем "Fs-nfs3" уже существует. Имя "Fs-nfs3" используется в
* качестве символьного идентификатора объекта fs-nfs3. Имя объекта может быть любым,
* однако рекомендуется использовать легкочитаемые и осмысленные имена. */
ehdl = ham_entity_handle( ND_LOCAL_NODE, "Fs-nfs3", 0 );
/* Добавление нового "независимого" условия его уведомления/действия защищены от
* задержек "waitfor" в потоках, которые выполняют другие последовательности действий */
chdl = ham_condition( ehdl, CONDDEATH, "DeathSep", HCONDINDEPENDENT | HREARMAFTERRESTART );
ahdl = ham_action_notify_signal( chdl, "notifysignaldeath", ND_LOCAL_NODE, pid, MYSIG,
scode, svalue, HREARMAFTERRESTART );
ham_action_handle_free( ahdl );
ham_condition_handle_free( chdl );
ham_entity_handle_free( ehdl );
chdl = ham_condition_handle( ND_LOCAL_NODE, "Fs-nfs3","Death", 0 );
/* Это действие добавляется в условие, у которого не установлен флаг hcondnowait.
* Условие может включать в себя последовательность действий с произвольными
* задержками и ожидания, которые влияют на доставку уведомлений. */
ahdl = ham_action_notify_pulse( chdl, "delayednfsdeathpulse", ND_LOCAL_NODE, pid, chid,
PCODENFSDELAYED, value, HREARMAFTERRESTART );
ham_action_handle_free( ahdl );
ham_condition_handle_free( chdl );
ehdl = ham_entity_handle( ND_LOCAL_NODE, "inetd", 0 );
chdl = ham_condition( ehdl, CONDRESTART, "restart", HREARMAFTERRESTART | HCONDINDEPENDENT );
ahdl = ham_action_notify_pulse( chdl, "notifyrestart_imm", ND_LOCAL_NODE, pid, chid,
PCODEINETDRESTART1, value, HREARMAFTERRESTART );
ham_action_handle_free( ahdl );
ahdl = ham_action_waitfor( chdl, "delay", NULL, 6532, HREARMAFTERRESTART );
ham_action_handle_free( ahdl );
ahdl = ham_action_notify_pulse( chdl, "notifyrestart_delayed", ND_LOCAL_NODE, pid, chid,
PCODEINETDRESTART2, value, HREARMAFTERRESTART );
ham_action_handle_free( ahdl );
ham_condition_handle_free( chdl );
ham_entity_handle_free( ehdl );
while ( 1 )
{
rcvid = MsgReceivePulse( chid, &pulse, sizeof( pulse ), NULL );
if ( rcvid < 0 )
{
if ( errno != EINTR )
exit( -1 );
} else {
switch ( pulse.code )
{
case PCODEINETDDEATH:
printf( "Inetd Death Pulse\n" );
break;
case PCODENFSDELAYED:
printf( "Fs-nfs3 died: this is the possibly delayed pulse\n" );
break;
case PCODEINETDDETACH:
printf( "Inetd detached, so quitting\n" );
goto the_end;
case PCODEINETDRESTART1:
printf( "Inetd Restart Pulse: Immediate\n" );
break;
case PCODEINETDRESTART2:
printf( "Inetd Restart Pulse: Delayed\n" );
break;
}
}
}
/* Мы больше не ждем информацию о процессе inetd, поскольку знаем, что он завершен.
* Мы продолжаем получать информацию о завершении процесса fs-nfs3, поскольку не
* удалили эти действия. если мы завершим работу сейчас, при следующем выполнении
* этих действий произойдет ошибка (поскольку адресата уведомлений больше не
* существует), после чего они будут автоматически удалены и уничтожены. */
the_end:
ham_disconnect( 0 );
exit( 0 );
}

В приведенном выше примере клиент подписывается на различные виды уведомлений о важных событиях, связанных с процессами inetd и fs-nfs3. Отправка уведомлений может выполняться немедленно или с определенной задержкой.

Уведомления о каждом условии объекта (завершение — CONDDEATH, перезапуск — CONDRESTART и отсоединение — CONDDETACH) можно принимать независимо друг от друга.

HAM автоматически инициирует условие CONDRESTART после успешного перезапуска объекта.

Механизм контрольных сигналов (определение доступности объектов)

Иногда нарушение работоспособности компонента проявляется не в форме «аварии», а в отсутствии реакции на запросы и фактическом прекращении функционирования.

Такие ситуации могут происходить, когда несколько процессов/потоков входят в состояние блокировки или взаимной блокировки и не могут продолжать работу. Как правило, эти ситуации трудно обнаруживать, поскольку они происходят весьма редко.

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

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

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

Оба потока содержат код, в котором используются два мьютекса.

Поток 1 Поток 2
... ...
while true while true
do do
захват мьютекса a захват мьютекса b
(1ая секция вычислений) (1ая секция вычислений)
захват мьютекса b захват мьютекса a
(2ая секция вычислений) (2ая секция вычислений)
освобождение мьютекса b освобождение мьютекса a
освобождение мьютекса a освобождение мьютекса b
done done
... ...

Фрагменты кода обоих потоков представлены ниже. Единственное различие между ними заключается в порядке захвата мьютексов. Взаимная блокировка при выполнении этих потоков происходит весьма редко; момент ее наступления зависит от длины «фрагментов с вычислениями».

/* mutexdeadlock.c */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <process.h>
#include <sys/neutrino.h>
#include <sys/procfs.h>
#include <sys/procmgr.h>
#include <ha/ham.h>
pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER;
FILE *logfile;
pthread_t threadID;
int doheartbeat = 0;
#define COMPUTE_DELAY 100
void * func1( void *arg )
{
int id;
/* захват мьютексов в порядке a -> b
* выполнение вычислений с последующим
* освобождением мьютексов ...
* выполнение в непрерывном цикле */
id = pthread_self();
while ( 1 )
{
delay( 85 ); /* задержка, чтобы отпустить другой поток */
if ( doheartbeat )
ham_heartbeat();
pthread_mutex_lock( &mutex_a );
fprintf( logfile, "Thread 1: Obtained lock a\n" );
fprintf( logfile, "Thread 1: Waiting for lock b\n" );
pthread_mutex_lock( &mutex_b );
fprintf( logfile, "Thread 1: Obtained lock b\n" );
fprintf( logfile, "Thread 1: Performing computation\n" );
delay( rand() % COMPUTE_DELAY + 5 ); /* задержка для выполнения вычислительной работы */
fprintf( logfile, "Thread 1: Unlocking lock b\n" );
pthread_mutex_unlock( &mutex_b );
fprintf( logfile, "Thread 1: Unlocking lock a\n" );
pthread_mutex_unlock( &mutex_a );
}
return (NULL);
}
void * func2( void *arg )
{
int id;
/* захват мьютексов в порядке b -> a
* выполнение вычислений с последующим
* освобождением мьютексов ...
* выполнение в непрерывном цикле */
id = pthread_self();
while ( 1 )
{
delay(25);
if ( doheartbeat )
ham_heartbeat();
pthread_mutex_lock( &mutex_b );
fprintf( logfile, "\tThread 2: Obtained lock b\n" );
fprintf( logfile, "\tThread 2: Waiting for lock a\n" );
pthread_mutex_lock( &mutex_a );
fprintf( logfile, "\tThread 2: Obtained lock a\n" );
fprintf( logfile, "\tThread 2: Performing computation\n" );
delay( rand() % COMPUTE_DELAY + 5 ); /* задержка для выполнения вычислительной работы */
fprintf( logfile, "\tThread 2: Unlocking lock a\n" );
pthread_mutex_unlock( &mutex_a );
fprintf( logfile, "\tThread 2: Unlocking lock b\n" );
pthread_mutex_unlock( &mutex_b );
}
return (NULL);
}
int main( int argc, char *argv[] )
{
pthread_attr_t attrib;
struct sched_param param;
ham_entity_t *ehdl;
ham_condition_t *chdl;
ham_action_t *ahdl;
int i = 0;
char c;
logfile = stderr;
while ( (c = getopt( argc, argv, "f:l" )) != -1 )
{
switch ( c )
{
case 'f': /* файл журнала */
logfile = fopen( optarg, "w" );
break;
case 'l': /* отправка контрольного сигнала */
if ( access( "/proc/ham", F_OK ) == 0 )
doheartbeat = 1;
break;
}
}
setbuf( logfile, NULL );
srand( time( NULL ) );
fprintf( logfile, "Creating separate competing compute thread\n" );
pthread_attr_init( &attrib );
pthread_attr_setinheritsched( &attrib, PTHREAD_EXPLICIT_SCHED );
pthread_attr_setschedpolicy( &attrib, SCHED_RR );
param.sched_priority = getprio( 0 );
pthread_attr_setschedparam( &attrib, &param );
if ( doheartbeat )
{
/* присоединение к менеджеру высокой готовности */
ehdl = ham_attach_self( "mutex-deadlock", 1000000000UL, 5 ,5, 0 );
chdl = ham_condition( ehdl, CONDHBEATMISSEDHIGH, "heartbeat-missed-high", 0 );
ahdl = ham_action_execute( chdl, "terminate", "/proc/boot/mutex-deadlock-heartbeat.sh", 0 );
}
/* создание потока-конкурента */
pthread_create( &threadID, &attrib, func1, NULL );
pthread_detach( threadID );
func2( NULL );
exit( 0 );
}

При выполнении программы происходит следующее:

  1. Запуск процесса с двумя потоками.

    Потоки выполняются, как описано ранее, но в некоторый момент происходит их взаимная блокировка. Мы ждем ее возникновения в течение разумного интервала времени (несколько секунд). Потоки регистрируют свои действия в журнале /dev/shmem/mutex-deadlock.log.

  2. Ожидание взаимной блокировки.

    Текущее состояние потоков процесса 73746 выглядит следующим образом:

    pid tid name prio STATE Blocked 73746 1 oot/mutex-deadlock 10r MUTEX 73746-02 #-21474 73746 2 oot/mutex-deadlock 10r MUTEX 73746-01 #-21474

    Последние записи в файле журнала потоков:

    Thread 2: Obtained lock b Thread 2: Waiting for lock a Thread 2: Obtained lock a Thread 2: Performing computation Thread 2: Unlocking lock a Thread 2: Unlocking lock b Thread 2: Obtained lock b Thread 2: Waiting for lock a Thread 1: Obtained lock a Thread 1: Waiting for lock b

  3. Основная информация о текущем процессе, извлеченная из файла дампа:

    /tmp/mutex-deadlock.core: processor=PPC num_cpus=2 cpu 1 cpu=602370 name=604e speed=299 flags=0xc0000001 FPU MMU EAR cpu 2 cpu=602370 name=604e speed=299 flags=0xc0000001 FPU MMU EAR cyc/sec=16666666 tod_adj=999522656000000000 nsec=5190771360840 inc=999960 boot=999522656 epoch=1970 intr=-2147483648 rate=600000024 scale=-16 load=16666 MACHINE="mtx604-smp" HOSTNAME="localhost" hwflags=0x000004 pretend_cpu=0 init_msr=36866 pid=73746 parent=49169 child=0 pgrp=73746 sid=1 flags=0x000300 umask=0 base_addr=0x48040000 init_stack=0x4803fa20 ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ign=0000000006801000 queue=ff00000000000000 pending=0000000000000000 fds=4 threads=2 timers=0 chans=1 thread 1 REQUESTED ip=0xfe32f838 sp=0x4803f920 stkbase=0x47fbf000 stksize=528384 state=MUTEX flags=0 last_cpu=1 timeout=00000000 pri=10 realpri=10 policy=RR thread 2 ip=0xfe32f838 sp=0x47fbef80 stkbase=0x47f9e000 stksize=135168 state=MUTEX flags=4020000 last_cpu=2 timeout=00000000 pri=10 realpri=10 policy=RR

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

Отправка контрольных сигналов в процессе

Теперь рассмотрим пример, в котором клиент отправляет контрольные сигналы, а HAM автоматически обнаруживает их отсутствие и завершает клиента.

Поток 1 Поток 2
... ...
while true while true
do do
захват мьютекса a захват мьютекса b
(1ая секция вычислений) (1ая секция вычислений)
захват мьютекса b захват мьютекса a
отправка контрольного сигнала отправка контрольного сигнала
(2ая секция вычислений) (2ая секция вычислений)
освобождение мьютекса b освобождение мьютекса a
освобождение мьютекса a освобождение мьютекса b
done done
... ...

Здесь процесс должен отправлять контрольные сигналы HAM. Отправка контрольного сигнала выполняется во внутреннем цикле, что позволяет диагностировать взаимные блокировки. Когда HAM обнаруживает отсутствие контрольных сигналов, он восстанавливает объект.

При выполнении процесса происходит следующее:

  1. Запуск процесса с двумя потоками.

    Потоки выполняются, как описано ранее, но в некоторый момент происходит их взаимная блокировка. Мы ждем ее возникновения в течение разумного интервала времени (несколько секунд). Потоки регистрируют свои действия в журнале /dev/shmem/mutex-deadlock-heartbeat.log. HAM обнаруживает отсутствие контрольных сигналов потоков, завершает процесс и сохраняет его состояние для анализа сбоя.

  2. Ожидание взаимной блокировки.

    Текущее состояние потоков процесса 462866 и состояние объекта mutex-deadlock после пропуска контрольных сигналов:

    pid tid name prio STATE Blocked 462866 1 oot/mutex-deadlock 10r MUTEX 462866-03 #-2147 462866 2 oot/mutex-deadlock 63r RECEIVE 1 462866 3 oot/mutex-deadlock 10r MUTEX 462866-01 #-2147 Состояние объекта согласно HAM Path : mutex-deadlock Entity Pid : 462866 Num conditions : 1 Condition type : ATTACHEDSELF Stats: HeartBeat Period: 1000000000 HB Low Mark : 5 HB High Mark : 5 Last Heartbeat : 2001/09/03 14:40:41:406575120 HeartBeat State : MISSEDHIGH Created : 2001/09/03 14:40:40:391615720 Num Restarts : 0

    Последние записи в файле журнала потоков:

    Thread 2: Obtained lock b Thread 2: Waiting for lock a Thread 2: Obtained lock a Thread 2: Performing computation Thread 2: Unlocking lock a Thread 2: Unlocking lock b Thread 2: Obtained lock b Thread 2: Waiting for lock a Thread 1: Obtained lock a Thread 1: Waiting for lock b

  3. Основная информация о текущем процессе, извлеченная из файла дампа:

    /tmp/mutex-deadlock.core: processor=PPC num_cpus=2 cpu 1 cpu=602370 name=604e speed=299 flags=0xc0000001 FPU MMU EAR cpu 2 cpu=602370 name=604e speed=299 flags=0xc0000001 FPU MMU EAR cyc/sec=16666666 tod_adj=999522656000000000 nsec=5390696363520 inc=999960 boot=999522656 epoch=1970 intr=-2147483648 rate=600000024 scale=-16 load=16666 MACHINE="mtx604-smp" HOSTNAME="localhost" hwflags=0x000004 pretend_cpu=0 init_msr=36866 pid=462866 parent=434193 child=0 pgrp=462866 sid=1 flags=0x000300 umask=0 base_addr=0x48040000 init_stack=0x4803f9f0 ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ign=0000000006801000 queue=ff00000000000000 pending=0000000000000000 fds=5 threads=3 timers=1 chans=4 thread 1 REQUESTED ip=0xfe32f838 sp=0x4803f8f0 stkbase=0x47fbf000 stksize=528384 state=MUTEX flags=0 last_cpu=2 timeout=00000000 pri=10 realpri=10 policy=RR thread 2 ip=0xfe32f1a8 sp=0x47fbef50 stkbase=0x47f9e000 stksize=135168 state=RECEIVE flags=4000000 last_cpu=2 timeout=00000000 pri=63 realpri=63 policy=RR blocked_chid=1 thread 3 ip=0xfe32f838 sp=0x47f9df80 stkbase=0x47f7d000 stksize=135168 state=MUTEX flags=4020000 last_cpu=1 timeout=00000000 pri=10 realpri=10 policy=RR

Блокировка процесса

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

Поток 1 Поток 2
... ...
(выполнение с высоким приоритетом) (выполнение с высоким приоритетом)
while true while true
do do
захват мьютекса a захват мьютекса a
(1ая секция вычислений) (1ая секция вычислений)
освобождение мьютекса a освобождение мьютекса a
done done
... ...

Ниже представлен код каждого из потоков; их единственным различием является приоритет. При выполнении процесса поток 2 в некоторый момент времени блокируется.

/* mutexstarvation.c */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <process.h>
#include <sys/neutrino.h>
#include <sys/procfs.h>
#include <sys/procmgr.h>
#include <ha/ham.h>
pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER;
FILE *logfile;
int doheartbeat = 0;
#define COMPUTE_DELAY 900
void * func1( void *arg )
{
int id;
id = pthread_self();
while ( 1 )
{
pthread_mutex_lock( &mutex_a );
fprintf( logfile, "Thread 1: Locking lock a\n" );
delay( rand() % COMPUTE_DELAY + 50 ); /* задержка для имитации вычислительной работы */
fprintf( logfile, "Thread 1: Unlocking lock a\n" );
pthread_mutex_unlock( &mutex_a );
}
return (NULL);
}
void * func2( void *arg )
{
int id;
id = pthread_self();
while ( 1 )
{
pthread_mutex_lock( &mutex_a );
fprintf( logfile, "\tThread 2: Locking lock a\n" );
if ( doheartbeat )
ham_heartbeat();
delay( rand() % COMPUTE_DELAY + 50 ); /* задержка для имитации вычислительной работы */
fprintf( logfile, "\tThread 2: Unlocking lock a\n" );
pthread_mutex_unlock( &mutex_a );
}
return (NULL);
}
int main( int argc, char *argv[] )
{
pthread_attr_t attrib;
struct sched_param param;
ham_entity_t *ehdl;
ham_condition_t *chdl;
ham_action_t *ahdl;
int i = 0;
char c;
pthread_attr_t attrib2;
struct sched_param param2;
pthread_t threadID;
pthread_t threadID2;
logfile = stderr;
while ( (c = getopt( argc, argv, "f:l" )) != -1 )
{
switch ( c )
{
case 'f': /* файл журнала */
logfile = fopen( optarg, "w" );
break;
case 'l': /* отправка контрольного сигнала */
if ( access( "/proc/ham", F_OK ) == 0 )
doheartbeat = 1;
break;
}
}
setbuf( logfile, NULL );
srand( time( NULL ) );
fprintf( logfile, "Creating separate competing compute thread\n" );
if ( doheartbeat )
{
/* присоединение к менеджеру высокой готовности */
ehdl = ham_attach_self( "mutex-starvation", 1000000000UL, 5, 5, 0 );
chdl = ham_condition( ehdl, CONDHBEATMISSEDHIGH, "heartbeat-missed-high", 0 );
ahdl = ham_action_execute( chdl, "terminate", "/proc/boot/mutex-starvation-heartbeat.sh", 0 );
}
/* создание потока-конкурента */
pthread_attr_init( &attrib2 );
pthread_attr_setinheritsched( &attrib2, PTHREAD_EXPLICIT_SCHED );
pthread_attr_setschedpolicy( &attrib2, SCHED_RR );
param2.sched_priority = sched_get_priority_min( SCHED_RR );
pthread_attr_setschedparam( &attrib2, &param2 );
pthread_create( &threadID2, &attrib2, func2, NULL );
delay( 3000 ); /* другой поток получает возможность продолжить работу на некоторое время... */
pthread_attr_init( &attrib );
pthread_attr_setinheritsched( &attrib, PTHREAD_EXPLICIT_SCHED );
pthread_attr_setschedpolicy( &attrib, SCHED_RR );
param.sched_priority = sched_get_priority_max( SCHED_RR );
pthread_attr_setschedparam( &attrib, &param );
pthread_create( &threadID, &attrib, func1, NULL );
pthread_join( threadID, NULL );
pthread_join( threadID2, NULL );
exit( 0 );
}

При выполнении программы происходит следующее:

  1. Запуск процесса с двумя потоками.

    Потоки выполняются, как описано ранее, но в некоторый момент времени поток 2 блокируется. Мы ждем блокировки потока 2 в течение разумного интервала времени (несколько секунд). Потоки регистрируют свои действия в журнале /dev/shmem/mutex-starvation.log.

  2. Ожидание выполнения потоков в течение некоторого времени.

    Текущее состояние потоков процесса 622610 выглядит следующим образом:

    pid tid name prio STATE Blocked 622610 1 t/mutex-starvation 10r JOIN 3 622610 2 t/mutex-starvation 1r MUTEX 622610-03 #-2147 622610 3 t/mutex-starvation 63r NANOSLEEP

    Последние записи в файле журнала потоков:

    Thread 1: Unlocking lock a Thread 1: Locking lock a Thread 1: Unlocking lock a Thread 1: Locking lock a Thread 1: Unlocking lock a Thread 1: Locking lock a Thread 1: Unlocking lock a Thread 1: Locking lock a Thread 1: Unlocking lock a Thread 1: Locking lock a

  3. Основная информация о текущем процессе, извлеченная из файла дампа:

    /tmp/mutex-starvation.core: processor=PPC num_cpus=2 cpu 1 cpu=602370 name=604e speed=299 flags=0xc0000001 FPU MMU EAR cpu 2 cpu=602370 name=604e speed=299 flags=0xc0000001 FPU MMU EAR cyc/sec=16666666 tod_adj=999522656000000000 nsec=5561011550640 inc=999960 boot=999522656 epoch=1970 intr=-2147483648 rate=600000024 scale=-16 load=16666 MACHINE="mtx604-smp" HOSTNAME="localhost" hwflags=0x000004 pretend_cpu=0 init_msr=36866 pid=622610 parent=598033 child=0 pgrp=622610 sid=1 flags=0x000300 umask=0 base_addr=0x48040000 init_stack=0x4803fa10 ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ign=0000000006801000 queue=ff00000000000000 pending=0000000000000000 fds=4 threads=3 timers=0 chans=1 thread 1 REQUESTED ip=0xfe32f8c8 sp=0x4803f8a0 stkbase=0x47fbf000 stksize=528384 state=JOIN flags=0 last_cpu=1 timeout=00000000 pri=10 realpri=10 policy=RR thread 2 ip=0xfe32f838 sp=0x47fbef80 stkbase=0x47f9e000 stksize=135168 state=MUTEX flags=4000000 last_cpu=2 timeout=00000000 pri=1 realpri=1 policy=RR thread 3 ip=0xfe32f9a0 sp=0x47f9df20 stkbase=0x47f7d000 stksize=135168 state=NANOSLEEP flags=4000000 last_cpu=2 timeout=0x1001000 pri=63 realpri=63 policy=RR

Отправка контрольных сигналов в потоке 2

Теперь рассмотрим пример, в котором поток 2 отправляет контрольные сигналы. HAM автоматически обнаруживает отсутствие активности этого потока и может завершать и/или восстанавливать его.

Поток 1 Поток 2
... ...
(выполнение с высоким приоритетом) (выполнение с высоким приоритетом)
while true while true
do do
захват мьютекса a захват мьютекса a
отправка контрольного сигнала
(1ая секция вычислений) (1ая секция вычислений)
освобождение мьютекса a освобождение мьютекса a
done done
... ...

Здесь поток 2 должен отправлять контрольные сигналы HAM. Поскольку отправка контрольного сигнала выполняется во внутреннем цикле, HAM обнаруживает блокировку потока 2.

Потоки выполняются, как описано ранее, но в некоторый момент времени поток 2 блокируется. Мы ждем его блокировку в течение разумного интервала времени (несколько секунд). Потоки регистрируют свои действия в журнале /dev/shmem/mutex-starvation-heartbeat.log. HAM обнаруживает отсутствие контрольных сигналов потока, завершает процесс и сохраняет его состояние для анализа сбоя.

При выполнении процесса происходит следующее:

  1. Ожидание в течение некоторого времени.

    Текущее состояние потоков процесса 753682 и состояние объекта mutex-starvation после пропуска контрольных сигналов:

    pid tid name prio STATE Blocked 753682 1 t/mutex-starvation 10r JOIN 4 753682 2 t/mutex-starvation 63r RECEIVE 1 753682 3 t/mutex-starvation 1r MUTEX 753682-04 #-2147 753682 4 t/mutex-starvation 63r NANOSLEEP Состояние объекта согласно HAM Path : mutex-starvation Entity Pid : 753682 Num conditions : 1 Condition type : ATTACHEDSELF Stats: HeartBeat Period: 1000000000 HB Low Mark : 5 HB High Mark : 5 Last Heartbeat : 2001/09/03 14:44:37:796119160 HeartBeat State : MISSEDHIGH Created : 2001/09/03 14:44:34:780239800 Num Restarts : 0

    Последние записи в файле журнала потоков:

    Thread 1: Unlocking lock a Thread 1: Locking lock a Thread 1: Unlocking lock a Thread 1: Locking lock a Thread 1: Unlocking lock a Thread 1: Locking lock a Thread 1: Unlocking lock a Thread 1: Locking lock a Thread 1: Unlocking lock a Thread 1: Locking lock a

  2. Основная информация о текущем процессе, извлеченная из файла дампа:

    /tmp/mutex-starvation.core: processor=PPC num_cpus=2 cpu 1 cpu=602370 name=604e speed=299 flags=0xc0000001 FPU MMU EAR cpu 2 cpu=602370 name=604e speed=299 flags=0xc0000001 FPU MMU EAR cyc/sec=16666666 tod_adj=999522656000000000 nsec=5627098907040 inc=999960 boot=999522656 epoch=1970 intr=-2147483648 rate=600000024 scale=-16 load=16666 MACHINE="mtx604-smp" HOSTNAME="localhost" hwflags=0x000004 pretend_cpu=0 init_msr=36866 pid=753682 parent=729105 child=0 pgrp=753682 sid=1 flags=0x000300 umask=0 base_addr=0x48040000 init_stack=0x4803f9f0 ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ign=0000000006801000 queue=ff00000000000000 pending=0000000000000000 fds=5 threads=4 timers=1 chans=4 thread 1 REQUESTED ip=0xfe32f8c8 sp=0x4803f880 stkbase=0x47fbf000 stksize=528384 state=JOIN flags=0 last_cpu=2 timeout=00000000 pri=10 realpri=10 policy=RR thread 2 ip=0xfe32f1a8 sp=0x47fbef50 stkbase=0x47f9e000 stksize=135168 state=RECEIVE flags=4000000 last_cpu=2 timeout=00000000 pri=63 realpri=63 policy=RR blocked_chid=1 thread 3 ip=0xfe32f838 sp=0x47f9df80 stkbase=0x47f7d000 stksize=135168 state=MUTEX flags=4000000 last_cpu=2 timeout=00000000 pri=1 realpri=1 policy=RR thread 4 ip=0xfe32f9a0 sp=0x47f7cf20 stkbase=0x47f5c000 stksize=135168 state=NANOSLEEP flags=4000000 last_cpu=1 timeout=0x1001000 pri=63 realpri=63 policy=RR




Предыдущий раздел: Менеджер высокой готовности (HAM)