web-dev-qa-db-fra.com

Défaut de segmentation Malloc

Voici le morceau de code dans lequel la faute de segmentation se produit (la perror n'est pas appelée):

job = malloc(sizeof(task_t));
if(job == NULL)
    perror("malloc");

Pour être plus précis, gdb dit que le segfault se produit à l'intérieur d'un __int_malloc appel, qui est un appel de sous-routine effectué par malloc.

Étant donné que la fonction malloc est appelée en parallèle avec d'autres threads, j'ai d'abord pensé que cela pourrait être le problème. J'utilisais la version 2.19 de glibc.

Les structures de données:

typedef struct rv_thread thread_wrapper_t;

typedef struct future
{
  pthread_cond_t wait;
  pthread_mutex_t mutex;
  long completed;
} future_t;

typedef struct task
{
  future_t * f;
  void * data;
  void *
  (*fun)(thread_wrapper_t *, void *);
} task_t;

typedef struct
{
  queue_t * queue;
} pool_worker_t;

typedef struct
{
  task_t * t;
} sfuture_t;

struct rv_thread
{
  pool_worker_t * pool;
};

Maintenant, la future mise en œuvre:

future_t *
create_future()
{
  future_t * new_f = malloc(sizeof(future_t));
  if(new_f == NULL)
    perror("malloc");
  new_f->completed = 0;
  pthread_mutex_init(&(new_f->mutex), NULL);
  pthread_cond_init(&(new_f->wait), NULL);
  return new_f;
}

int
wait_future(future_t * f)
{
  pthread_mutex_lock(&(f->mutex));
  while (!f->completed)
    {
      pthread_cond_wait(&(f->wait),&(f->mutex));
    }
  pthread_mutex_unlock(&(f->mutex));
  return 0;
}

void
complete(future_t * f)
{
  pthread_mutex_lock(&(f->mutex));
  f->completed = 1;
  pthread_mutex_unlock(&(f->mutex));
  pthread_cond_broadcast(&(f->wait));
}

Le pool de threads lui-même:

pool_worker_t *
create_work_pool(int threads)
{
  pool_worker_t * new_p = malloc(sizeof(pool_worker_t));
  if(new_p == NULL)
    perror("malloc");
  threads = 1;
  new_p->queue = create_queue();
  int i;
  for (i = 0; i < threads; i++){
    thread_wrapper_t * w = malloc(sizeof(thread_wrapper_t));
    if(w == NULL)
      perror("malloc");
    w->pool = new_p;
    pthread_t n;
    pthread_create(&n, NULL, work, w);
  }
  return new_p;
}

task_t *
try_get_new_task(thread_wrapper_t * thr)
{
  task_t * t = NULL;
  try_dequeue(thr->pool->queue, t);
  return t;
}

void
submit_job(pool_worker_t * p, task_t * t)
{
  enqueue(p->queue, t);
}

void *
work(void * data)
{
  thread_wrapper_t * thr = (thread_wrapper_t *) data;
  while (1){
    task_t * t = NULL;
    while ((t = (task_t *) try_get_new_task(thr)) == NULL);
    future_t * f = t->f;
    (*(t->fun))(thr,t->data);
    complete(f);
  }
  pthread_exit(NULL);
}

Et enfin le task.c:

pool_worker_t *
create_tpool()
{
  return (create_work_pool(8));
}

sfuture_t *
async(pool_worker_t * p, thread_wrapper_t * thr, void *
(*fun)(thread_wrapper_t *, void *), void * data)
{
  task_t * job = NULL;
  job = malloc(sizeof(task_t));
  if(job == NULL)
    perror("malloc");
  job->data = data;
  job->fun = fun;
  job->f = create_future();
  submit_job(p, job);
  sfuture_t * new_t = malloc(sizeof(sfuture_t));
  if(new_t == NULL)
    perror("malloc");
  new_t->t = job;
  return (new_t);
}

void
mywait(thread_wrapper_t * thr, sfuture_t * sf)
{
  if (sf == NULL)
    return;
  if (thr != NULL)
    {
      while (!sf->t->f->completed)
        {
          task_t * t_n = try_get_new_task(thr);
          if (t_n != NULL)
            {
          future_t * f = t_n->f;
          (*(t_n->fun))(thr,t_n->data);
          complete(f);
            }
        }
      return;
    }
  wait_future(sf->t->f);
  return ;
}

La file d'attente est la file d'attente sans verrouillage lfds.

#define enqueue(q,t) {                                 \
    if(!lfds611_queue_enqueue(q->lq, t))             \
      {                                               \
        lfds611_queue_guaranteed_enqueue(q->lq, t);  \
      }                                               \
  }

#define try_dequeue(q,t) {                            \
    lfds611_queue_dequeue(q->lq, &t);               \
  }

Le problème se produit chaque fois que le nombre d'appels à async est très élevé.

Sortie Valgrind:

Process terminating with default action of signal 11 (SIGSEGV)
==12022==  Bad permissions for mapped region at address 0x5AF9FF8
==12022==    at 0x4C28737: malloc (in /usr/lib/valgrind/vgpreload_memcheck-AMD64-linux.so)
11
guilhermemtr

J'ai compris quel est le problème: un débordement de pile.

Tout d'abord, permettez-moi d'expliquer pourquoi le débordement de pile se produit à l'intérieur de malloc (c'est probablement la raison pour laquelle vous lisez ceci). Lorsque mon programme était exécuté, la taille de la pile augmentait chaque fois qu'il commençait à exécuter (récursivement) une autre tâche (à cause de la façon dont je l'avais programmée). Mais à chaque fois, j'ai dû allouer une nouvelle tâche à l'aide de malloc. Cependant, malloc effectue d'autres appels de sous-routine, ce qui fait que la pile augmente encore plus sa taille qu'un simple appel pour exécuter une autre tâche. Donc, ce qui se passait, c'est que, même s'il n'y avait pas de malloc, j'obtiendrais un débordement de pile. Cependant, parce que j'avais malloc, le moment où la pile a débordé était en malloc, avant de déborder en effectuant un autre appel récursif. L'illustration ci-dessous montre ce qui se passait:

État initial de la pile:

-------------------------
| recursive call n - 3  |
-------------------------
| recursive call n - 2  |
-------------------------
| recursive call n - 1  |
-------------------------
|        garbage        |
-------------------------
|        garbage        | <- If the stack passes this point, the stack overflows.
-------------------------

pile pendant l'appel malloc:

-------------------------
| recursive call n - 3  |
-------------------------
| recursive call n - 2  |
-------------------------
| recursive call n - 1  |
-------------------------
|        malloc         |
-------------------------
|     __int_malloc      | <- If the stack passes this point, the stack overflows.
-------------------------

Ensuite, la pile a rétréci à nouveau et mon code est entré dans un nouvel appel récursif:

-------------------------
| recursive call n - 3  |
-------------------------
| recursive call n - 2  |
-------------------------
| recursive call n - 1  |
-------------------------
| recursive call n      |
-------------------------
|        garbage        | <- If the stack passes this point, the stack overflows.
-------------------------

Ensuite, il a de nouveau invoqué malloc dans ce nouvel appel récursif. Cependant, cette fois, il a débordé:

-------------------------
| recursive call n - 3  |
-------------------------
| recursive call n - 2  |
-------------------------
| recursive call n - 1  |
-------------------------
| recursive call n      |
-------------------------
|        malloc         | <- If the stack passes this point, the stack overflows.
-------------------------
|     __int_malloc      | <- This is when the stack overflow occurs.
-------------------------

[Le reste de la réponse est plus concentré sur les raisons pour lesquelles j'ai eu ce problème dans mon code en particulier.]

Habituellement, lors du calcul récursif de Fibonacci, par exemple, d'un certain nombre n, la taille de la pile croît linéairement avec ce nombre. Cependant, dans ce cas, je crée des tâches, j'utilise une file d'attente pour les stocker et je retire une tâche (fib) pour exécution. Si vous dessinez ceci sur du papier, vous verrez que le nombre de tâches augmente de façon exponentielle avec le n, plutôt que linéairement (notez également que si j'avais utilisé une pile pour stocker les tâches telles qu'elles étaient créées, le nombre de tâches allouées comme ainsi que la taille de la pile ne croîtrait que linéairement avec n. Donc, ce qui se passe est que la pile croît de façon exponentielle avec n, conduisant à un débordement de pile ... Maintenant vient la partie pourquoi ce débordement se produit à l'intérieur de l'appel à malloc. Donc, fondamentalement, comme J'ai expliqué ci-dessus, le débordement de la pile s'est produit à l'intérieur de l'appel malloc car c'était là que la pile était la plus grande. Ce qui s'est passé, c'est que la pile a presque explosé, et puisque malloc appelle des fonctions à l'intérieur, la pile se développe plus que le simple appel de mywait et mensonge.

Merci à tous! Si ce n'était pas votre aide, je ne serais pas en mesure de le comprendre!

12
guilhermemtr

Un SIGSEGV (défaut de segmentation) qui se déclenche dans malloc est généralement causé par une corruption de tas. La corruption de tas ne provoque pas une erreur de segmentation, vous ne le verrez donc que lorsque malloc essaiera d'y accéder. Le problème est que le code qui crée la corruption de tas peut être en tout point, même loin de l'endroit où le malloc est appelé. C'est généralement le pointeur du bloc suivant à l'intérieur du malloc qui est modifié par votre corruption de tas en une adresse non valide, de sorte que lorsque vous appelez malloc, un pointeur non valide est déréférencé et vous obtenez une erreur de segmentation.

Je pense que vous pouvez essayer des parties de votre code isolées du reste du programme pour réduire la visibilité du bogue.

De plus, je vois que vous ne libérez jamais la mémoire ici et il peut y avoir une fuite de mémoire possible.

Afin de vérifier une fuite de mémoire, vous pouvez exécuter la commande supérieure top -b -n 1 et vérifie:

RPRVT - resident private address space size
RSHRD - resident shared address space size
RSIZE - resident memory size
VPRVT - private address space size
VSIZE - total memory size
14
Jekyll