2011-10-20 86 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