【问题标题】:How to access errno after clone (or: How to set errno location)克隆后如何访问errno(或:如何设置errno位置)
【发布时间】:2026-01-29 08:50:01
【问题描述】:

根据传统的 POSIX,errno 只是一个整数左值,它与 fork 完美配合,但显然不能与线程配合使用。根据 pthreads,errno 是线程局部整数左值。在 Linux/NTPL 下,作为实现细节,errno 是一些“扩展为返回整数左值的函数的宏”。

在我的 Debian 系统上,这似乎是 *__errno_location (),在其他一些系统上,我见过类似 &(gettib()->errnum 的东西。

TL;DR
假设我已经使用clone 创建了一个线程,我可以直接调用errno 并期望它会起作用,还是我必须做一些特殊的雨舞?例如,我是否需要读取线程信息块中的一些特殊字段,或者一些特殊的 TLS 值,或者,我是否需要设置 glibc 以某种方式存储错误值的线程局部变量的地址?可能是__set_errno_location() 之类的东西?

或者,它会“正常工作”吗?

不可避免地,有人会想回复“简单地使用 phtreads”——请不要。我不想使用 pthreads。我想要clone。我不想要 pthread 的任何不明智的功能,我不想处理它的任何怪癖,也不想要实现这些怪癖的开销。我认识到 pthreads 中的大部分杂乱无章来自于它必须工作(而且,令人惊讶的是,它成功工作)以及其他一些完全崩溃的系统,这些系统已经有近 30 年的历史了,但是并不意味着它对每个人和每种情况都一定是一件好事。在这种情况下,可移植性不是问题。
在这种特殊情况下,我想要的只是启动另一个与父进程在同一地址空间中运行的进程,通过一个简单的锁(例如,futex)进行同步,并且write 正常工作(这意味着我 em> 必须能够正确读取errno)。尽可能少的开销,不需要甚至不需要其他功能或特殊行为。

【问题讨论】:

  • 不要使用clone(或futex)系统调用;它本质上是为那些实现线程库保留的。只需使用pthread(不使用clonefutex ...)
  • 好的,谢谢您的提示。我熟悉 pthread,并且如前所述,我明确对使用 pthread 不感兴趣。我想使用clone,并且我有一个可靠工作的futex 包装器。我唯一的问题是我找不到关于如何处理 errno 的信息。
  • 这是一个系统特定的实现细节。研究 Linux x86-64 ABI 并查看内核和一些 libc 的源代码,例如 MUSL libc;你需要编写一些汇编代码。

标签: linux clone errno


【解决方案1】:

如果errno是线程局部变量,那么clone()会复制到新进程的地址空间吗?我在 2001 年左右覆盖了 errno_location() 函数,以使用基于 pid 的 errno。

http://tamtrajnana.blogspot.com/2012/03/thread-safety-of-errno-variable.html

因为 errno 现在被定义为“__thread int errno;” (见上面的评论)这解释了如何处理 __thread 类型:Linux's thread local storage implementation

【讨论】:

  • 这应该是评论而不是答案。
  • 既是评论也是答案。
  • 不幸的是,情况似乎并非如此。这是我所希望的,然而,在 clone-child 中设置errno 的小测试程序也将在主线程中设置它。 errno_location() 也从父节点和克隆子节点输出相同的地址。所以它不是“神奇的线程本地”,你显然需要做一些未指定/秘密/内部设置(这可能发生在pthread_create 内部)。你说你overrode errno_location,你是怎么做到的?它在应用程序端是微不足道的(简单的宏!),但是如何为 libc 覆盖它?
  • __erno_location() 在 libc 源代码中被定义为“弱”。所以它就像这里的第 104 行一样简单:// WARNING: you MUST compile with -DREENTRANT for this to work int *__errno_location (void) { long pid = (long) getpid(); //if ( pid == s_pid ) return &g_errno; if ( pid <= (long)MAX_PID ) return &s_errnos[pid]; s_bad++; s_badPid = pid; return &s_errno; } 但是后来他们更改了 libc,使它不再是一个弱函数,你不能再这样做了。然后他们添加了 __thread 线程局部变量类型。您使用的是旧版本的 libc 吗?
  • 你是对的。我对新的 glibc 有同样的问题。也许我们确实需要查看 pthread_create() 源代码。
【解决方案2】:

根据glibc source codeerrno被定义为线程局部变量。不幸的是,这需要大量的 C 库支持。使用pthread_create() 创建的任何线程都将知道线程局部变量。我什至不会费心让 glibc 接受你的外来线程。

另一种方法是使用不同的 libc 实现,如果 errno 是其中的一部分,您可以提取它的一些内部结构并手动设置线程控制块。这将是令人难以置信的hacky和不可靠的。我怀疑你会找到像 __set_errno_location() 这样的东西,而是像 __set_tcb() 这样的东西。

#include <bits/some_hidden_file.h>

void init_errno(void)
{
    struct __tcb* tcb;

    /* allocate a dummy thread control block (malloc may set errno
     * so might have to store the tcb on stack or allocate it in the
     * parent) */
    tcb = malloc(sizeof(struct __tcb));

    /* initialize errno */
    tcb->errno = 0;

    /* set pointer to thread control block (x86) */
    arch_prctl(ARCH_SET_FS, tcb);
}

这假设 errno 宏扩展为:((struct __tcb*)__read_fs())-&gt;errno

当然,您始终可以选择自己实现极小的 libc 子集。或者,您可以编写自己的 write() 系统调用实现,并使用自定义存根来处理 errno,并使其与所选的 libc 实现共存。

#define my_errno /* errno variable stored at some known location */

ssize_t my_write(int fd, const void* buf, size_t len)
{
    ssize_t ret;

    __asm__ (
        /* set system call number */
        /* set up parameters */
        /* make the call */
        /* retrieve return value in c variable */
    );

    if (ret >= -4096 && ret < 0) {
        my_errno = -ret;
        return -1;
    }

    return ret;
}

我不记得 GCC 内联汇编的确切细节,系统调用调用细节因平台而异。

就我个人而言,我只是实现了一个非常小的 libc 子集,它只包含一个小汇编程序和一些常量。这非常简单,有很多可用的参考代码,尽管它可能过于雄心勃勃。

【讨论】:

  • 我有点担心这并不容易。您可能是对的,围绕我需要的一个(或两个)系统调用实现我自己的包装器可能是最简单的。