NetBSD 5.0 における pthread と fork
再現
再現プログラムではfork前に一度3つのpthreadを作り、その後1つ殺して lwpid 1 と 3 が残っている。この状態で3からforkすると子では3が残り、これにlwpid 1が割り当てられるのだが、どこかに3の情報が残っていて、その後呼びに行ってしまう
22612 2 a.out CALL _lwp_park(0xbb7ffd94,3,0x8049198,0x8049198)
22612 2 a.out RET _lwp_park -1 errno 3 No such process
詳細
NetBSD 5.0 ではカーネルスレッドに当たる lwp (light weight process) とユーザレベルを司る pthread が 1:1 対応している。プロセス内に存在する lwp の情報は、以下のあたりに保存されている。
// /usr/include/sys/lwp.h struct lwp { ... /* Process level and global state, misc. */ LIST_ENTRY(lwp) l_list; /* a: entry on list of all LWPs */ void *l_ctxlink; /* p: uc_link {get,set}context */ struct proc *l_proc; /* p: parent process */ LIST_ENTRY(lwp) l_sibling; /* p: entry on proc's list of LWPs */ ... };
// /usr/include/sys/lwp.h struct proc { ... pid_t p_pid; /* :: Process identifier. */ LIST_ENTRY(proc) p_pglist; /* l: List of processes in pgrp. */ struct proc *p_pptr; /* l: Pointer to parent process. */ LIST_ENTRY(proc) p_sibling; /* l: List of sibling processes. */ LIST_HEAD(, proc) p_children; /* l: List of children. */ LIST_HEAD(, lwp) p_lwps; /* p: List of LWPs. */ struct ras *p_raslist; /* a: List of RAS entries */ /* The following fields are all zeroed upon creation in fork. */ #define p_startzero p_nlwps int p_nlwps; /* p: Number of LWPs */ int p_nzlwps; /* p: Number of zombie LWPs */ int p_nrlwps; /* p: Number running/sleeping LWPs */ int p_nlwpwait; /* p: Number of LWPs in lwp_wait1() */ int p_ndlwps; /* p: Number of detached LWPs */ int p_nlwpid; /* p: Next LWP ID */ ... };
fork は実際には /sys/kern/kern_fork.c の fork1 で行われていて、ここでプロセスの複製と initial thread の作成を行っている模様。initial thread は fork を実行した lwp をテンプレートにして作られる。
/* * Finish creating the child process. * It will return through a different path later. */ lwp_create(l1, p2, uaddr, inmem, (flags & FORK_PPWAIT) ? LWP_VFORK : 0, stack, stacksize, (func != NULL) ? func : child_return, arg, &l2, l1->l_class); /* * It's now safe for the scheduler and other processes to see the * child process. */ mutex_enter(proc_lock); if (p1->p_session->s_ttyvp != NULL && p1->p_lflag & PL_CONTROLT) p2->p_lflag |= PL_CONTROLT; LIST_INSERT_HEAD(&parent->p_children, p2, p_sibling); p2->p_exitsig = exitsig; /* signal for parent on exit */ LIST_INSERT_AFTER(p1, p2, p_pglist); LIST_INSERT_HEAD(&allproc, p2, p_list);
この lwp_create は /usr/src/sys/kern/kern_lwp.c にある。しかし、引用部の後半にある、プロセスの lwp リストに新しい lwp を追加するコードは、別のプロセスの lwp をテンプレートにした場合を考慮しているように見えない。
/* * Create a new LWP within process 'p2', using LWP 'l1' as a template. * The new LWP is created in state LSIDL and must be set running, * suspended, or stopped by the caller. */ int lwp_create(lwp_t *l1, proc_t *p2, vaddr_t uaddr, bool inmem, int flags, void *stack, size_t stacksize, void (*func)(void *), void *arg, lwp_t **rnewlwpp, int sclass) { ... if (isfree == NULL) { l2 = pool_cache_get(lwp_cache, PR_WAITOK); memset(l2, 0, sizeof(*l2)); l2->l_ts = pool_cache_get(turnstile_cache, PR_WAITOK); SLIST_INIT(&l2->l_pi_lenders); } else { l2 = isfree; ts = l2->l_ts; KASSERT(l2->l_inheritedprio == -1); KASSERT(SLIST_EMPTY(&l2->l_pi_lenders)); memset(l2, 0, sizeof(*l2)); l2->l_ts = ts; } ... p2->p_nlwpid++; if (p2->p_nlwpid == 0) p2->p_nlwpid++; l2->l_lid = p2->p_nlwpid; LIST_INSERT_HEAD(&p2->p_lwps, l2, l_sibling); p2->p_nlwps++; }
つまり推測するに、LIST_INSERT_HEAD(&p2->p_lwps, l2, l_sibling) で、l2 の next が 3 を指したまま insert してしまっているのではあるまいか。その結果、あとで pthread_cond_wait した時に存在しない lwpid 3 を見に行って困る、と。
ちなみに、回避法はあって、pthread_createを十分な数作って捨てることで、欠番になっている lwpid を使った上で消せばよい(ぉ
send-pr
http://www.netbsd.org/cgi-bin/query-pr-single.pl?number=42772 How to Repeat と Fix を使いそこねた
謝辞
この問題の解決には @_enami さんの助けがありました。