2011-10-20 51 views
0

我们在生产系统中存在一个错误,其中一个进程在保持共享内存互斥的同时发生段错误。死亡时,我们希望它释放锁。我们使用sem_wait()/ sem_post(),但这样做我的功课,我发现这个API不允许这样的行为:如何(正确)使用健壮的pthreads进行进程同步?

http://www.usenetmessages.com/view.php?c=computer&g=1074&id=78029&p=0

答案,文章说,利用强大的pthreads API。我发现这个话题下面的文章:

http://www.embedded-linux.co.uk/tutorial/mutex_mutandis

但是,已经实施了下面的代码,我有一个不可靠的行为,也就是说,我应该告诉进程3,例如,对段错误,代码工作得很好。其他进程醒来,认识到一个进程在持有互斥体时死亡并恢复。但是,如果我告诉进程0死掉,或者我应该删除第63行的睡眠呼叫,其他进程不会在令人怀疑的进程自杀时唤醒。难道我做错了什么?

#include <stdio.h> 
#include <stdlib.h> 
#include <features.h> 
#define __USE_POSIX 
#include <signal.h> 
#include <sys/types.h> 
#include <unistd.h> 
#define __USE_MISC 
#include <sys/mman.h> 
#include <fcntl.h> 
#include <errno.h> 
#define __USE_GNU /* Necessario para usar a API PTHREAD_MUTEX_ROBUST_NP */ 
#define __USE_UNIX98 /* Necessario para usar a funcao pthread_mutexattr_settype */ 
#include <pthread.h> 
#include <sys/wait.h> 
static void *shrd; 

static int child_main(int slot, int segfault) { 
    pthread_mutex_t *lock = (pthread_mutex_t *) shrd; 
    int    err; 

    if (0 != (err=pthread_mutex_lock(lock))) { 
     switch(err) { 
     case EINVAL: 
      printf("Lock invalido no filho [%d]\n", slot); 
      goto excecao; 

     case EDEADLK: 
      printf("O filho [%d] tentou travar um lock que jah possui.\n", slot); 
      break; 

     case EOWNERDEAD: 
      printf("Filho [%d] foi informado que o processo que estava com o lock morreu.\n", slot); 
      if (0 == pthread_mutex_consistent_np(lock)) { 
       printf("Filho [%d] retornou o lock para um estado consistente.\n", slot); 
      } else { 
       fprintf(stderr, "Nao foi possivel retornar o lock a um estado consistente.\n"); 
       goto desistir; 
      } 

      if (0 != (err=pthread_mutex_lock(lock))) { 
       fprintf(stderr, "Apos recuperar o estado do lock, nao foi possivel trava-lo: %d\n", err); 
       goto desistir; 
      } 


     case ENOTRECOVERABLE: 
      printf("O filho [%d] foi informado de que o lock estah permanentemente em estado inconsistente.\n", slot); 
      goto desistir; 

     default: 
      printf("Erro desconhecido ao tentar travar o lock no filho [%d]: [%d]\n", slot, err); 
      goto excecao; 
     } 
    } 

    printf("Filho [%d] adquiriu o lock.\n", slot); 

    if (segfault == slot) { 
     printf("Matando o PID [%d] com SIGSEGV.\n", getpid()); 
     kill(getpid(), SIGSEGV); 
    } else { 
     sleep(1); 
    } 

    if (0 != (err = pthread_mutex_unlock(lock))) { 
     switch (err) { 
     case EPERM: 
      printf("O filho [%d] tentou liberar o lock, mas nao o possui.\n", slot); 
      break; 

     default: 
      fprintf(stderr, "Erro inesperado ao liberar o lock do filho [%d]: [%d]\n", slot, err); 
     } 
    } else { 
     printf("Filho [%d] retornou o lock.\n", slot); 
    } 

    return 0; 

excecao: 
    fprintf(stderr, "Programa terminado devido excecao.\n"); 
    return 1; 

desistir: 
    fprintf(stderr, "A execucao do sistema nao deve prosseguir. Abortando todos os processos.\n"); 
    kill(0, SIGTERM); 

    /* unreachable */ 
    return 1; 
} 

int main(int argc, const char * const argv[]) { 
    pid_t    filhos[10]; 
    int     status; 
    pid_t    p; 
    int     segfault = -1; 
    pthread_mutexattr_t attrs; 

    if (argc > 1) { 
     segfault = atoi(argv[1]); 
     if (segfault < 0 || segfault > 9) 
      segfault = -1; 
    } 

    if ((shrd = mmap(NULL, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)) == MAP_FAILED) { 
     perror("Erro ao criar shrd mem:\n"); 
     exit(1); 
    } 

    pthread_mutexattr_init   (&attrs); 
    pthread_mutexattr_settype  (&attrs, PTHREAD_MUTEX_RECURSIVE_NP); 
    pthread_mutexattr_setrobust_np (&attrs, PTHREAD_MUTEX_ROBUST_NP); 
    pthread_mutexattr_setpshared (&attrs, PTHREAD_PROCESS_SHARED); 
    /* 
     Devido a um BUG na glibc 2.5 (que eh a usada pelo CentOS 5, 
     a unica forma de fazer os mutexes robustos funcionarem eh 
     setando o protocolo para PTHREAD_PRIO_INHERIT: 
     http://sourceware.org/ml/libc-help/2010-04/msg00028.html 
    */ 
    pthread_mutexattr_setprotocol (&attrs, PTHREAD_PRIO_INHERIT); 
    pthread_mutex_init    ((pthread_mutex_t*) shrd, &attrs); 
    pthread_mutexattr_destroy  (&attrs); 

    for (size_t i=0; i<sizeof(filhos)/sizeof(pid_t); ++i) { 
     if ((filhos[i]=fork()) == 0) { 
      return child_main((int) i, segfault); 
     } else { 
      if (filhos[i] < 0) { 
       fprintf(stderr, "Erro ao criar o filho [%zu]. Abortando.\n", i); 
       exit(1); 
      } 
     } 
    } 

    for (size_t i=0; i<sizeof(filhos)/sizeof(pid_t); ++i) { 
     do { 
      p = waitpid(filhos[i], &status, 0); 
     } while (p != -1); 
    } 

    printf("Pai encerrou a sua execucao.\n"); 

    return 0; 
} 

BTW:我编译在CentOS 5,64位:

$ uname -rm 
2.6.18-194.el5 x86_64 
glibc-2.5-49 
gcc-4.1.2-48.el5 

(对不起,在代码中的句子和意见是在葡萄牙,我的母语)

回答

0

您的EOWNERDEAD区块在ENOTRECOVERABLE区块之前未输入break。 另外,根据pthread_mutex_lock联机帮助页,在第一次拨打pthread_mutex_lock()后,即使返回EOWNERDEAD,锁也由主叫方保持。因此,你不应该在EOWNERDEAD的区块内再次调用它。

+0

谢谢你回复@caruccio先生。实际上,ENOTRECOVERABLE之前的中断是在代码中。在复制到网站之前,我可能会删除它,同时删除夹板标记。正如你所指出的那样,在pthread_mutex_consistent_np()之后移除锁似乎在原始代码所处的相同环境下工作。我在那里,因为mutex_mutandis文章说这是必需的。但是,在将互斥量返回到一致状态后,无论是否使用锁,事实是,如果没有第63行的睡眠,或将0作为参数传递,程序将在段错误后挂起。 –

0

我试过一些其他的方法,即: 1.使用POSIX障碍 2.让父母在forking()时保持锁定并在每个孩子递增一个计数器后释放它。

第一种方法没有在所有的工作,但我出版我使用的源代码,因为我可能会使用API​​已经取得了一些错误:

在child_main:

pthread_barrier_t *barr = (pthread_barrier_t *) ((char *) shrd + sizeof(pthread_mutex_t)); 
... 
int rc = pthread_barrier_wait(barr); 
if(rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) 
{ 
    printf("Nao foi possivel esperar na barreira.\n"); 
    exit(-1); 
} 

在主:

pthread_barrierattr_t barr_attrs; 
pthread_barrier_t  *barr; 
... 
initialize(pthread_barrierattr_init,  &barr_attrs); 
initialize(pthread_barrierattr_setpshared, &barr_attrs, PTHREAD_PROCESS_SHARED); 
barr = (pthread_barrier_t *) ((char *) shrd + sizeof(pthread_mutex_t)); 

if ((init_result = pthread_barrier_init(barr, &barr_attrs, 10)) != 0) { 
    printf("Nao foi possivel iniciar a barreira.\n"); 
    exit(EXIT_FAILURE); 
} 

初始化是一个宏,定义为:

#define initialize(func, ...) \ 
do { \ 
    init_result = func(__VA_ARGS__); \ 
    if (0 != init_result) { \ 
     stored_errno = errno; \ 
     func_name = #func; \ 
     goto erro_criacao_semaforo; \ 
    } \ 
} while(0); 

第二种方法似乎工作:

在child_main:

int    *contador = (int *) ((char *) shrd + sizeof(pthread_mutex_t) + sizeof(int)); 
... 
int *n = (int *)(lock+1); 
... 
if (0 != (err=pthread_mutex_lock(lock))) { 
... 

在主:

volatile int   *n; // Cada filho iniciado incrementa esta variavel. 
          // Qdo ela chega em 10, liberamos o lock. 
... 
n   = (int *) ((char *) shrd + sizeof(pthread_mutex_t)); 
... 
pthread_mutex_lock(mutex); 
for (i=0; i<sizeof(filhos)/sizeof(pid_t); ++i) { 
... // the fork goes here. 
} 

while (*n != 10); // Isto garante que todos os filhos cheguem ao lock. 
pthread_mutex_unlock(mutex); 

但是,一旦我添加一个随机的睡眠时间,使他们获得不同步,再次我有一个死锁:

On child_main:

int    num_sorteado; 
struct timespec dessincronizador = { 1, 0 }; 

int *n = (int *)(lock+1); 

num_sorteado = 1 + (int) (999999.0 * (rand()/(RAND_MAX + 1.0))); 
dessincronizador.tv_nsec = num_sorteado; 
nanosleep(&dessincronizador, NULL); 

if (0 != (err=pthread_mutex_lock(lock))) { 
... 

可悲的是,似乎是学习,一个进程死亡,而持有锁没有可靠的方法,所以各地对我们的问题的最佳方式是捕获到死亡过程中的信号,提高一杀(0,SIGTERM )来让其他进程也死掉。

+0

这是一个答案,或试图扩大你的问题? – Marcin