0

我正在尝试使用 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:我的主要问题是如何实现“加入”原语?我知道它也应该基于 futexes。我现在很难想出一些东西。

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

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

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

提前致谢!

4

2 回答 2

2

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

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

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

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

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

于 2013-06-10T09:16:53.977 回答
0

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

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

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

4)见getrusage。

于 2013-06-10T09:15:07.630 回答