実装依存なので注意。今回の話は Linux (glibc) についてです。

pthread_create 第一引数や、pthread_self戻り値で使用される pthread_t 型であるがその値の意味について考えたことは無かった。

pthread_t 型をマニュアルなどを確認すると「スレッドの識別子」と記載されているが、システムコール(gettid)の返す値とは異なり、無意味な値だと考えていた。ところが、前回の調査(Linux の pthread のデフォルトスタックサイズについて « 余談ですが……)を行なったときに、あるところのポインタ値であることが分かった。

まず、pthread_t の値(ポインタ値)を確認しみる。値自体は gdb で “info threads” としたときにも出力されます。

(gdb) info threads

2 Thread 0xb7fedb90 (LWP 8398) 0xb7fff424 in __kernel_vsyscall ()

* 1 Thread 0xb7fee6c0 (LWP 8395) 0xb7fff424 in __kernel_vsyscall ()
(gdb)

スレッドの1がメインスレッド(main関数)で、スレッドの2がpthread_createで作成したスレッドということになる。どちらも値がかなり大きく、あまり意味のあるように思えない。

pthread_createの実装を確認してみた。

nptl/pthread_create.c

int
__pthread_create_2_1 (newthread, attr, start_routine, arg)
     pthread_t *newthread;
     const pthread_attr_t *attr;
     void *(*start_routine) (void *);
     void *arg;
{
  STACK_VARIABLES;

  const struct pthread_attr *iattr = (struct pthread_attr *) attr;
  if (iattr == NULL)
    /* Is this the best idea?  On NUMA machines this could mean
       accessing far-away memory.  */
    iattr = &default_attr;
  struct pthread *pd = NULL;
  int err = ALLOCATE_STACK (iattr, &pd);
//(中略)
  /* Pass the descriptor to the caller.  */
  *newthread = (pthread_t) pd;
  /* Start the thread.  */
  return create_thread (pd, iattr, STACK_VARIABLES_ARGS);
}

ALLOCATE_STACK というマクロの第二引数のポインタが設定されるようだ。ALLOCATE_STACK マクロは allocate_stack に展開される。その関数の中で以下のとおり実施している。

static int
allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
        ALLOCATE_STACK_PARMS)
{
  struct pthread *pd;
  size_t size;
  size_t pagesize_m1 = __getpagesize () - 1;
  void *stacktop;

  assert (attr != NULL);
  assert (powerof2 (pagesize_m1 + 1));
  assert (TCB_ALIGNMENT >= STACK_ALIGN);

  /* Get the stack size from the attribute if it is set.  Otherwise we
     use the default we determined at start time.  */
  size = attr->stacksize ?: __default_stacksize;
  /* Get memory for the stack.  */
  if (__builtin_expect (attr->flags & ATTR_FLAG_STACKADDR, 0))
    {
//スタックアドレスが設定済の場合(中略)
    }
  else
    {
      /* Allocate some anonymous memory.  If possible use the cache.  */
      size_t guardsize;
      size_t reqsize;
      void *mem;
      const int prot = (PROT_READ | PROT_WRITE
            | ((GL(dl_stack_flags) & PF_X) ? PROT_EXEC : 0));

// (中略)
      mem = mmap (NULL, size, prot,
              MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);

      /* Place the thread descriptor at the end of the stack.  */
      pd = (struct pthread *) ((char *) mem + size - coloring) - 1;
// (中略)
    }
// (中略)
  /* We place the thread descriptor at the end of the stack.  */
  *pdp = pd;
// (中略)
  return 0;
}

(スタックアドレスを明示的に設定した場合の処理は中略とした。)
注目すべきは、mmapした領域(スタック領域)のmmap領域サイズの最後尾辺りに、pd というポインタを設定して、それを引数の *pdp に設定している、という点である。

つまり、pthread_t の値は、自身のスレッドのスタックの底(近辺)のアドレスを意味している。ソース内のコメントもそれを意味した内容が記載してある。

“pthread_t のポインタが示すもの(pthread_create 第一引数/pthread_self戻り値)” への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。