5

いくつかのスレッドを作成するコードを書きました。スレッドの 1 つが終了するたびに、新しいスレッドが作成されて置き換えられます。pthreads を使用して非常に多数のスレッド (>450) を作成できなかったため、代わりに clone システム コールを使用しました。(このように膨大な数のスレッドを持つことの意味を認識していることに注意してください。ただし、このプログラムはシステムに負荷をかけることのみを目的としています)
clone() は、子スレッドのスタック領域をパラメーターとして指定する必要があるため、スレッドごとに必要なスタック領域のチャンクを割り当て、スレッドが終了すると解放します。スレッドが終了したら、親にシグナルを送信して、同じことを通知します。
コードを以下に示します。

#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

#define NUM_THREADS 5

unsigned long long total_count=0;
int num_threads = NUM_THREADS;
static int thread_pids[NUM_THREADS];
static void *thread_stacks[NUM_THREADS];
int ppid;

int worker() {
 int i;
 union sigval s={0};
 for(i=0;i!=99999999;i++);
 if(sigqueue(ppid, SIGUSR1, s)!=0)
  fprintf(stderr, "ERROR sigqueue");
 fprintf(stderr, "Child [%d] done\n", getpid());
 return 0;
}

void sigint_handler(int signal) {
 char fname[35]="";
 FILE *fp;
 int ch;
 if(signal == SIGINT) {
  fprintf(stderr, "Caught SIGINT\n");
  sprintf(fname, "/proc/%d/status", getpid());
  fp = fopen(fname,"r");
  while((ch=fgetc(fp))!=EOF)
   fprintf(stderr, "%c", (char)ch);
  fclose(fp);
  fprintf(stderr, "No. of threads created so far = %llu\n", total_count);
  exit(0);
 } else
  fprintf(stderr, "Unhandled signal (%d) received\n", signal);
}


int main(int argc, char *argv[]) {
 int rc, i; long t;
 void *chld_stack, *chld_stack2;
 siginfo_t siginfo;
 sigset_t sigset, oldsigset;

 if(argc>1) {
  num_threads = atoi(argv[1]);
  if(num_threads<1) {
   fprintf(stderr, "Number of threads must be >0\n");
   return -1;
  }
 }
 signal(SIGINT, sigint_handler);

 /* Block SIGUSR1 */
 sigemptyset(&sigset);
 sigaddset(&sigset, SIGUSR1); 
 if(sigprocmask(SIG_BLOCK, &sigset, &oldsigset)==-1)
  fprintf(stderr, "ERROR: cannot block SIGUSR1 \"%s\"\n", strerror(errno));

 printf("Number of threads = %d\n", num_threads);
 ppid = getpid();
 for(t=0,i=0;t<num_threads;t++,i++) {
  chld_stack = (void *) malloc(148*512);
  chld_stack2 = ((char *)chld_stack + 148*512 - 1);
  if(chld_stack == NULL) {
   fprintf(stderr, "ERROR[%ld]: malloc for stack-space failed\n", t);
   break;
  }
  rc = clone(worker, chld_stack2, CLONE_VM|CLONE_FS|CLONE_FILES, NULL);
  if(rc == -1) {
   fprintf(stderr, "ERROR[%ld]: return code from pthread_create() is %d\n", t, errno);
   break;
  }
  thread_pids[i]=rc;
  thread_stacks[i]=chld_stack;
  fprintf(stderr, " [index:%d] = [pid:%d] ; [stack:0x%p]\n", i, thread_pids[i], thread_stacks[i]);
  total_count++;
 }
 sigemptyset(&sigset);
 sigaddset(&sigset, SIGUSR1); 
 while(1) {
  fprintf(stderr, "Waiting for signal from childs\n");
  if(sigwaitinfo(&sigset, &siginfo) == -1)
   fprintf(stderr, "- ERROR returned by sigwaitinfo : \"%s\"\n", strerror(errno));
  fprintf(stderr, "Got some signal from pid:%d\n", siginfo.si_pid);

  /* A child finished, free the stack area allocated for it */ 
  for(i=0;i<NUM_THREADS;i++) {
   fprintf(stderr, " [index:%d] = [pid:%d] ; [stack:%p]\n", i, thread_pids[i], thread_stacks[i]);
   if(thread_pids[i]==siginfo.si_pid) {
    free(thread_stacks[i]);
    thread_stacks[i]=NULL;
    break;
   }
  }
  fprintf(stderr, "Search for child ended with i=%d\n",i);
  if(i==NUM_THREADS) 
   continue;
  /* Create a new thread in its place */
  chld_stack = (void *) malloc(148*512);
  chld_stack2 = ((char *)chld_stack + 148*512 - 1);
  if(chld_stack == NULL) {
   fprintf(stderr, "ERROR[%ld]: malloc for stack-space failed\n", t);
   break;
  }
  rc = clone(worker, chld_stack2, CLONE_VM|CLONE_FS|CLONE_FILES, NULL);
  if(rc == -1) {
   fprintf(stderr, "ERROR[%ld]: return code from clone() is %d\n", t, errno);
   break;
  }
  thread_pids[i]=rc;
  thread_stacks[i]=chld_stack;
  total_count++;
 }
 fprintf(stderr, "Broke out of infinite loop. [total_count=%llu] [i=%d]\n",total_count, i);
 return 0;
}

いくつかの配列を使用して、子プロセスのpidとスタック領域のベースアドレスを追跡しました(解放するため)。
このプログラムを実行すると、しばらくすると終了します。gdb で実行すると、スレッドの 1 つが SIGSEGV (セグメンテーション違反) を取得したことがわかります。しかし、場所はわかりません。出力は次のようになります。

Program received signal SIGSEGV, Segmentation fault.
[Switching to LWP 15864]
0x00000000 in ?? ()

次のコマンドラインを使用して、valgrind で実行してみました。

valgrind --tool=memcheck --leak-check=yes --show-reachable=yes -v --num-callers=20 --track-fds=yes ./a.out

しかし、valgrind の下では問題なく実行され続けます。
このプログラムをデバッグする方法がわかりません。これは何らかのスタック オーバーフローか何かではないかと感じましたが、スタック サイズを増やしても (最大 74KB)、問題は解決しませんでした。
私の唯一の質問は、セグメンテーション違反の理由と場所、またはこのプログラムをデバッグする方法です。

4

2 に答える 2

4

実際の問題を見つけました。
ワーカー スレッドが sigqueue() を使用して親プロセスにシグナルを送ると、子プロセスが return ステートメントを実行する前に、親プロセスがすぐに制御を取得し、スタックを解放することがあります。同じ子スレッドが return ステートメントを使用すると、スタックが破損したため、セグメンテーション フォールトが発生します。
これを解決するために、交換しました

exit(0)

それ以外の

return 0;
于 2010-01-25T09:32:39.160 に答える
1

私は答えを見つけたと思う

ステップ1

これを置き換えます:

static int thread_pids[NUM_THREADS];
static void *thread_stacks[NUM_THREADS];

これで:

static int *thread_pids;
static void **thread_stacks;

ステップ2

これをメイン関数に追加します(引数を確認した後):

thread_pids = malloc(sizeof(int) * num_threads);
thread_stacks = malloc(sizeof(void *) * num_threads);

ステップ 3

これを置き換えます:

chld_stack2 = ((char *)chld_stack + 148*512 - 1);

これで:

chld_stack2 = ((char *)chld_stack + 148*512);

両方の場所で使用します。

それが本当にあなたの問題かどうかはわかりませんが、テストした後、セグメンテーション違反は発生しませんでした。ところで、5 つ以上のスレッドを使用している場合にのみ、セグメンテーション違反が発生しました。

私が助けてくれることを願っています!

編集: 1000 スレッドでテストされ、完全に実行されます

edit2: thread_pids と thread_stacks の静的割り当てがエラーを引き起こす理由の説明。

これを行う最善の方法は、例を使用することです。

num_threads = 10 と仮定します。

この問題は、次のコードで発生します。

for(t=0,i=0;t<num_threads;t++,i++) {
...

thread_pids[i]=rc; 
thread_stacks[i]=chld_stack;

...
}

ここでは、自分に属していないメモリにアクセスしようとしています (0 <= i <= 9、ただし両方の配列のサイズは 5 です)。これにより、セグメンテーション違反またはデータ破損が発生する可能性があります。両方の配列が次々に割り当てられると、データが破損する可能性があり、その結果、もう一方の配列への書き込みが発生します。(静的または動的に)割り当てていないメモリに書き込むと、セグメンテーションが発生する可能性があります。

幸運でエラーがまったくないかもしれませんが、コードは安全ではありません。

位置合わせされていないポインターについて: コメント以上に説明する必要はないと思います。

于 2010-01-24T18:22:41.357 に答える