就上述关联parent_tidptr
创建新进程child_tidptr
是什么意思?do_fork()
答案1
让我们首先查看原始系统调用接口。它因体系结构而略有不同,但在 x86-64 上是:
long clone(unsigned long flags, void *child_stack,
int *ptid, int *ctid,
unsigned long newtls);
ptid
是ctid
你的parent_tidptr
和child_tidptr
。现在让我们看看是什么clone(2)
手册页必须说:
CLONE_CHILD_CLEARTID (since Linux 2.5.49)
Erase the child thread ID at the location ctid in
child memory when the child exits, and do a wakeup on
the futex at that address.
CLONE_CHILD_SETTID (since Linux 2.5.49)
Store the child thread ID at the location ctid in the
child's memory.
CLONE_PARENT_SETTID (since Linux 2.5.49)
Store the child thread ID at the location ptid in the
parent's memory.
这些标志主要是为了实现线程库而设计的。如果我们看一下 NPTL 的实现pthread_create()
在 glibc 内部,我们最终找到了代码sysdeps/unix/sysv/linux/createthread.c
clone()
进行包含CLONE_PARENT_SETTID
and CLONE_CHILD_CLEARTID
in 的调用flags
。
在该clone()
调用中,我们还可以看到ptid
和ctid
参数指向同一个地址。 (请记住,POSIX 线程共享地址空间;这是通过标志完成的clone()
CLONE_VM
。)
那么,这里发生的事情如下。
CLONE_PARENT_SETTID
用于确保内核线程 ID 存储在用户空间的某个位置。线程实现的用户空间端需要知道该线程 ID。CLONE_CHILD_CLEARTID
当由创建的线程终止时,用于清除(即清零)同一位置clone()
。
让我们再进一步...
ptid
通过/返回的线程IDctid
是不是与 POSIX 线程 ID ( ) 相同pthread_t
,尽管在 NPTL 等 1:1 线程实现中,内核线程 ID 和 POSIX 线程 ID 之间存在一一对应的关系。内核线程 ID 与您使用 Linux 获得的 ID 相同gettid()
称呼。它也clone()
作为系统调用返回值返回,这就提出了一个问题:为什么我们需要ptid
/ ctid
?问题是从用户空间方面来看,事情看起来像这样:
tid = clone(...);
从用户空间线程实现的角度来看,这里存在竞争,因为对 的赋值tid
仅发生后 clone()
返回。这意味着如果用户空间线程库在新线程执行任何操作(例如终止)之前需要该信息,则可能会遇到某些问题。使用CLONE_PARENT_SETTID
确保新的线程ID被放置在指向的位置ptid
前
clone()
返回,从而允许线程库避免此类竞争条件。 (CLONE_CHILD_SETTID
也可以用于类似的效果。)
CLONE_CHILD_CLEARTID
使用清除ptid
/的原因ctid
是为了提供一种方法pthread_join()
调用另一个线程发现该线程已经终止。本质上,ptid
/ctid
位置被用作富泰克斯,以及futex()
系统调用用于阻塞,等待该位置的整数改变。 (细节有点复杂,但是对于glibc 源代码中和grep
的使用。最终,发生了一个操作。回想一下上面对目标地址进行 futex 唤醒。)lll_wait_tid
lll_futex_wait
FUTEX_WAIT
CLONE_CHILD_CLEARTID
答案2
tid
代表“线程 ID”。参数parent_tidptr
和child_tidptr
分别指向父进程地址空间和子进程地址空间中的用户空间内存。新创建的线程的 id 存储在指针指向的 int 变量中。
欲了解更多信息,请参阅clone(2)
联机帮助页。