【问题标题】:What's a good way to implement simple clone() based multithread library?实现简单的基于 clone() 的多线程库的好方法是什么?
【发布时间】:2013-06-10 17:16:53
【问题描述】:

我正在尝试使用 clone() 和其他内核实用程序构建基于 linux 的简单多线程库。我已经到了一个我不确定什么是做事的正确方法的地步。我尝试通过原始 NPTL 代码,但它有点太多了。

这就是我想象的 create 方法的方式:

typedef int sk_thr_id;
typedef void *sk_thr_arg;
typedef int (*sk_thr_func)(sk_thr_arg);


sk_thr_id sk_thr_create(sk_thr_func f, sk_thr_arg a){

  void* stack;

  stack = malloc( 1024*64 );
  if ( stack == 0 ){
         perror( "malloc: could not allocate stack" );
         exit( 1 );
  }

  return ( clone(f, (char*) stack + FIBER_STACK, SIGCHLD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_VM, a ) );


}

1:我不太确定正确的 clone() 标志应该是什么。我刚刚发现这些在一个简单的例子中被使用。欢迎在这里提供任何一般性说明。

以下是使用 futexes 创建的部分互斥体原语(暂时不是我自己的代码):

#define cmpxchg(P, O, N) __sync_val_compare_and_swap((P), (O), (N))

#define cpu_relax() asm volatile("pause\n": : :"memory")

#define barrier() asm volatile("": : :"memory")


static inline unsigned xchg_32(void *ptr, unsigned x)
{
    __asm__ __volatile__("xchgl %0,%1"
                :"=r" ((unsigned) x)
                :"m" (*(volatile unsigned *)ptr), "0" (x)
                :"memory");

    return x;
}


static inline unsigned short xchg_8(void *ptr, char x)
{
    __asm__ __volatile__("xchgb %0,%1"
                :"=r" ((char) x)
                :"m" (*(volatile char *)ptr), "0" (x)
                :"memory");

    return x;
}



int sys_futex(void *addr1, int op, int val1, struct timespec *timeout, void *addr2, int val3)
{
    return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3);
}




typedef union mutex mutex;

union mutex
{
    unsigned u;
    struct
    {
        unsigned char locked;
        unsigned char contended;
    } b;
};


int mutex_init(mutex *m, const pthread_mutexattr_t *a)
{
    (void) a;
    m->u = 0;
    return 0;
}

int mutex_lock(mutex *m)
{
    int i;

    /* Try to grab lock */
    for (i = 0; i < 100; i++)
    {
        if (!xchg_8(&m->b.locked, 1)) return 0;

        cpu_relax();
    }

    /* Have to sleep */
    while (xchg_32(&m->u, 257) & 1)
    {
        sys_futex(m, FUTEX_WAIT_PRIVATE, 257, NULL, NULL, 0);
    }

    return 0;
}

int mutex_unlock(mutex *m)
{
    int i;

    /* Locked and not contended */
    if ((m->u == 1) && (cmpxchg(&m->u, 1, 0) == 1)) return 0;

    /* Unlock */
    m->b.locked = 0;

    barrier();

    /* Spin and hope someone takes the lock */
    for (i = 0; i < 200; i++)
    {
        if (m->b.locked) return 0;

        cpu_relax();
    }

    /* We need to wake someone up */
    m->b.contended = 0;

    sys_futex(m, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0);

    return 0;
}

2:我的主要问题是如何实现“join”原语?我知道它也应该基于 futexes。我现在很难想出一些东西。

3:我需要一些方法来在线程完成后清理一些东西(比如分配的堆栈)。我也没有什么好的方法来做到这一点。

可能对于这些,我需要在用户空间中为每个线程添加额外的结构,并在其中保存一些信息。有人可以为我指出解决这些问题的好方向吗?

4:我想知道一个线程已经运行了多少时间,距离上次调度它已经过去了多长时间以及其他类似的东西。是否有一些内核调用提供此类信息?

提前致谢!

【问题讨论】:

  • 这个练习的目的是什么?
  • 这纯粹是教育性的。它不应该在任何“真实”应用程序中使用。关键是要证明我可以做出有效的东西。我也在做完整的用户空间“基于ucontext.h”的变体。最终目标是在执行此操作的不同方式之间进行一些实验比较。

标签: c linux multithreading


【解决方案1】:

可以将“多线程库”作为与标准库的其余部分分开的第三方库存在的想法是一个过时且有缺陷的概念。如果你想这样做,你必须首先放弃对标准库的所有使用;特别是,如果你自己打电话给clone,你打电话给malloc 是完全不安全的,因为:

  1. malloc 将不知道存在多个线程,因此可能无法执行正确的同步。

  2. 即使它知道它们的存在,malloc 也需要访问位于线程指针给定地址的未指定的、特定于实现的结构。由于此结构是特定于实现的,因此您无法创建这样一个结构,该结构将被系统 libc 的当前版本和所有未来版本正确解释。

这些问题不仅适用于malloc,还适用于大多数标准库;即使是异步信号安全函数也可能不安全,因为它们可能会取消引用线程指针以实现与取消相关的目的、执行最佳系统调用机制等。

如果您真的坚持自己实现线程,您将不得不放弃使用 glibc 或任何与线程集成的现代 libc,而是选择像 klibc 这样更幼稚的东西。这可能是一个教育实验,但不适用于已部署的应用程序。

【讨论】:

  • 我从来没有想过这一点。我正在处理的是大学项目,所以它并不需要 100% 可靠。我只需要让它与我现在拥有的 libc 一起工作。很可能我需要让使用该库的用户程序能够使用标准 libc。现在对我来说真的很有趣,当完全使用 libc(pthreads/nptl 和 malloc 之类的其他东西)时,这些东西是如何安全工作的。除了未来 libc 版本的一些最终变化之外,我有点错过了很大的不同。
  • 实际上 glibc 允许overriding memory allocation,因此可以特别注意这一点。这并不能解决标准库必须注意诸如stdin/stdout/stderr 之类的线程的其他地方(其他文件流不需要是线程安全的,但这三个是)。
  • @JanHudec:确实,malloc 只是最明显的例子。正如我在第 2 项之后的段落中暗示的那样,libc 中还有很多其他地方也会中断。
【解决方案2】:

1) 您正在使用 LinuxThreads 的示例。我不会重写好的参考资料,但我建议你 Michael Kerrisk 的“Linux 编程接口”,第 28 章。它用 25 页解释了你需要什么。

2) 如果设置了 CLONE_CHILD_CLEARID 标志,当子进程终止时,clone 的 ctid 参数被清除。如果将该指针视为 futex,则可以实现连接原语。祝你好运 :-) 如果你不想使用 futex,也可以看看 wait3 和 wait4。

3) 我不知道你想清理什么,但你可以使用 clone tls 参数。这是一个线程本地存储缓冲区。如果线程完成,您可以清理该缓冲区。

4) 参见 getrusage。

【讨论】:

  • 3) 我需要一种方法来清理我试图动态分配的堆栈,例如在线程完成后。这就是我所说的那种东西,而不是最终用户要自己分配的东西。看来我有一些关于 clone() 的功课。
猜你喜欢
  • 2022-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多