web-dev-qa-db-ja.com

mallocセグメンテーションエラー

セグメンテーション違反が発生するコードの一部を次に示します(perrorは呼び出されていません)。

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

より正確に言えば、gdbはsegfault__int_malloc呼び出しの内部で発生すると言っています。これはmallocによって行われるサブルーチン呼び出しです。

Malloc関数は他のスレッドと並行して呼び出されるので、最初は問題があるのではないかと思いました。私はglibcのバージョン2.19を使用していました。

データ構造:

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;
};

今、将来の実装:

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));
}

スレッドプール自体:

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);
}

そして最後に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 ;
}

キューは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);               \
  }

この問題は、非同期呼び出しの数が非常に多い場合に発生します。

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

私は問題が何であるかを理解しました:スタックオーバーフロー。

最初に、スタックオーバーフローがmalloc内で発生する理由を説明します(これがおそらくこれを読んでいる理由です)。プログラムを実行すると、別のタスクの実行を(再帰的に)開始するたびに、スタックサイズが増加し続けました(プログラムした方法のため)。しかし、そのたびに、mallocを使用して新しいタスクを割り当てる必要がありました。ただし、mallocは他のサブルーチン呼び出しを行うため、別のタスクを実行するための単純な呼び出しよりもスタックのサイズが大きくなります。つまり、mallocがなくても、スタックオーバーフローが発生するということでした。ただし、私はmallocを使用していたため、スタックがオーバーフローした瞬間はmalloc内にあり、別の再帰呼び出しを行うことでオーバーフローしました。下の図は、何が起こっていたかを示しています。

初期スタック状態:

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

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.
-------------------------

その後、スタックは再び縮小し、私のコードは新しい再帰呼び出しに入りました:

-------------------------
| 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.
-------------------------

次に、この新しい再帰呼び出し内でmallocを再度呼び出しました。ただし、今回はオーバーフローしました。

-------------------------
| 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.
-------------------------

[残りの回答は、特にコードでこの問題が発生した理由に重点を置いています。]

通常、たとえば特定の数nのフィボナッチを再帰的に計算すると、スタックサイズはその数とともに直線的に増加します。ただし、この場合は、タスクを作成し、キューを使用してタスクを格納し、(fib)タスクをキューから取り出して実行します。これを紙に描くと、タスクの数が直線的にではなく、nとともに指数関数的に増加することがわかります(作成時にタスクを格納するためにスタックを使用していた場合、タスクの数はスタックサイズはnに比例して直線的に増加するだけです。したがって、スタックはnとともに指数的に増加し、スタックオーバーフローにつながります。mallocの呼び出し内でこのオーバーフローが発生する理由がここにあります。基本的に、上記で説明したように、スタックオーバーフローは、スタックが最大の場所であったため、malloc呼び出しの内部で発生しました。スタックがほとんど爆発していて、mallocがその内部で関数を呼び出すため、スタックはmywaitの呼び出しだけでなく、フィブ。

皆さん、ありがとうございました!それがあなたの助けでなければ、私はそれを理解することができません!

12
guilhermemtr

SIGSEGV(セグメンテーション違反)がmallocで発生しているのは、通常ヒープの破損が原因です。ヒープの破損はセグメンテーション違反を引き起こさないので、mallocがそこにアクセスしようとしたときにのみ、それがわかります。問題は、ヒープの破損を引き起こすコードが、mallocが呼び出された場所から遠く離れた任意の場所にある可能性があることです。通常、ヒープの破損によって無効なアドレスに変更されるのは、malloc内の次のブロックポインターです。そのため、mallocを呼び出すと、無効なポインターが逆参照され、セグメンテーションエラーが発生します。

バグの可視性を減らすために、プログラムの残りの部分から分離されたコードの一部を試すことができると思います。

さらに、ここでメモリを解放することは決してなく、メモリリークの可能性もあります。

メモリリークをチェックするには、トップコマンドtop -b -n 1を実行して次のことをチェックします。

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