克隆后如何访问errno(或:如何设置errno位置) [英] How to access errno after clone (or: How to set errno location)

查看:123
本文介绍了克隆后如何访问errno(或:如何设置errno位置)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于每个传统的POSIX, errno 只是一个整数左值,与 fork 完美配合,但显然不能在线程上几乎不能很好地工作。根据pthreads, errno 是线程局部整数左值。在Linux / NTPL下,作为实现细节,errno是扩展为返回整数左值的函数的宏。

Per traditional POSIX, errno is simply an integer lvalue, which works perfectly well with fork, but oviously doesn't work nearly as well with threads. As per pthreads, errno is a thread-local integer lvalue. Under Linux/NTPL, as an implementation detail, errno is some "macro that expands to a function returning an integer lvalue".

在我的Debian系统上,这似乎是 * __ errno_location(),在其他一些系统上,我看到过类似&((gettib()-> errnum

On my Debian system, this seems to be *__errno_location (), on some other systems I've seen things like &(gettib()->errnum.

TL; DR

假设我使用过克隆创建一个线程,我可以只调用 errno 并期望它能工作,还是我必须做一些特殊的雨舞?例如,我需要读取线程信息块中的某些特殊字段或某些特殊的TLS值,还是我要设置glibc以某种方式存储错误值的线程局部变量的地址,例如 __ set_errno_location ()也许?

TL;DR
Assuming I've used clone to create a thread, can I just call errno and expect that it will work, or do I have to do some special rain dance? For example, do I need to read some special field in the thread information block, or some special TLS value, or, do I get to set the address of the thread-local variable where the glibc stores the error values somehow? Something like __set_errno_location() maybe?

或者,它是否正常工作?

Or, will it "just work" as it is?

不可避免地,有人会倾向于回答仅使用phtreads-请不要。我不想使用pthreads。我想要 clone 。我不想使用任何不建议使用的pthread功能,也不想处理其任何怪癖,也不想实现这些怪癖的开销。我认识到,pthread的许多缺点来自这样一个事实,即对于已经使用了近三十年的某些完全崩溃的系统,它必须能够工作(而且令人惊讶地,它成功地工作)。并不意味着对所有人和每种情况都一定是一件好事。在这种情况下,可移植性没有任何问题。

在这种特殊情况下,我想要的是启动另一个进程,该进程在与父进程相同的地址空间中运行,通过简单的锁(例如futex)进行同步,并且 write 正常工作(这意味着我必须能够读取 errno 正确)。

Inevitably, someone will be tempted to reply "simply use phtreads" -- please don't. I do not want to use pthreads. I want clone. I do not want any of the ill-advised functionality of pthreads, and I do not want to deal with any of its quirks, nor do I want the overhead to implement those quirks. I recognize that much of the crud in pthreads comes from the fact that it has to work (and, surprisingly, it successfully works) amongst others for some completely broken systems that are nearly three decades old, but that doesn't mean that it is necessarily a good thing for everyone and every situation. Portability is not of any concern in this case.
All I want in this particular situation is fire up another process running in the same address space as the parent, synchronization via a simple lock (say, a futex), and write working properly (which means I also have to be able to read errno correctly). As little overhead as possible, no other functionality or special behavior needed or even desired.

推荐答案

根据 glibc源代码 errno 被定义为线程局部变量。不幸的是,这需要大量的C库支持。使用 pthread_create()创建的所有线程都将知道线程局部变量。我什至不愿意尝试让glibc接受您的外来线程。

According to the glibc source code, errno is defined as a thread-local variable. Unfortunately, this requires significant C library support. Any threads created using pthread_create() will be made aware of thread-local variables. I would not even bother trying to get glibc to accept your foreign threads.

另一种选择是使用不同的libc实现,该实现可能允许您提取其内部的某些内容。结构并手动设置线程控制块(如果 errno 是其中的一部分)。这将是令人难以置信的hacky和不可靠。我怀疑您会发现类似 __ set_errno_location()的东西,而是类似 __ set_tcb()的东西。

An alternative would be to use a different libc implementation that may allow you to extract some of its internal structures and manually set the thread control block if errno is part of it. This would be incredibly hacky and unreliable. I doubt you'll find anything like __set_errno_location(), but rather something like __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())-> errno

This assumes that the errno macro expands to something like: ((struct __tcb*)__read_fs())->errno.

当然,总是可以选择自己实现一个很小的libc子集。或者,您可以使用自定义存根来编写自己的 write()系统调用实现,以处理 errno 并拥有它与所选的libc实现共存。

Of course, there's always the option of implementing an extremely small subset of libc yourself. Or you could write your own implementation of the write() system call with a custom stub to handle errno and have it co-exist with the chosen libc implementation.

#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内联汇编和系统调用的确切细节了

I don't remember the exact details of GCC inline assembly and the system call invocation details vary depending on platform.

就个人而言,我只是实现了一个很小的libc子集,它只包含一个小汇编器和一些常量。这很简单,尽管有太多参考代码,但可能有些过分夸大了。

Personally, I'd just implement a very small subset of libc, which would just consist of a little assembler and a few constants. This is remarkably simple with so much reference code available out there, although it may be overambitious.

这篇关于克隆后如何访问errno(或:如何设置errno位置)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆