【问题标题】:Why realloc deadlock after clone syscall?为什么克隆系统调用后重新分配死锁?
【发布时间】:2012-12-06 02:38:00
【问题描述】:

我遇到了一个问题,即在 clone() 系统调用之后的某个时间 realloc() 死锁。

我的代码是:

#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <linux/types.h>
#define CHILD_STACK_SIZE 4096*4
#define gettid() syscall(SYS_gettid)
#define log(str) fprintf(stderr, "[pid:%d tid:%d] "str, getpid(),gettid())

int clone_func(void *arg){
    int *ptr=(int*)malloc(10);
    int i;
    for (i=1; i<200000; i++)
        ptr = realloc(ptr, sizeof(int)*i);
    free(ptr);
    return 0;
}

int main(){
    int flags = 0;
    flags = CLONE_VM;
    log("Program started.\n");
    int *ptr=NULL;
    ptr = malloc(16);
    void *child_stack_start = malloc(CHILD_STACK_SIZE);
    int ret = clone(clone_func, child_stack_start +CHILD_STACK_SIZE, flags, NULL, NULL, NULL, NULL);
    int i;
    for (i=1; i<200000; i++)
        ptr = realloc(ptr, sizeof(int)*i);

    free(ptr);
    return 0;
}

gdb 中的调用栈是:

[pid:13268 tid:13268] Program started.
^Z[New LWP 13269]

Program received signal SIGTSTP, Stopped (user).
0x000000000040ba0e in __lll_lock_wait_private ()
(gdb) bt
#0  0x000000000040ba0e in __lll_lock_wait_private ()
#1  0x0000000000408630 in _L_lock_11249 ()
#2  0x000000000040797f in realloc ()
#3  0x0000000000400515 in main () at test-realloc.c:36
(gdb) i thr
  2 LWP 13269  0x000000000040ba0e in __lll_lock_wait_private ()
* 1 LWP 13268  0x000000000040ba0e in __lll_lock_wait_private ()
(gdb) thr 2
[Switching to thread 2 (LWP 13269)]#0  0x000000000040ba0e in __lll_lock_wait_private ()
(gdb) bt
#0  0x000000000040ba0e in __lll_lock_wait_private ()
#1  0x0000000000408630 in _L_lock_11249 ()
#2  0x000000000040797f in realloc ()
#3  0x0000000000400413 in clone_func (arg=0x7fffffffe53c) at test-realloc.c:20
#4  0x000000000040b889 in clone ()
#5  0x0000000000000000 in ?? ()

我的操作系统是 debian linux-2.6.32-5-amd64,带有 GNU C 库 (Debian EGLIBC 2.11.3-4) 稳定版本 2.11.3。我深深怀疑 eglibc 是这个 bug 的罪魁祸首。 在 clone() 系统调用上,在使用 realloc() 之前还不够吗?

【问题讨论】:

  • 你确定CLONE_VM是你想要的吗?
  • 致 Michael Foukarakis,是的,我想创建一个新线程,而不是进程。我找到了这个问题的原因。看我的回答。

标签: c clone deadlock realloc


【解决方案1】:

您自己不能将cloneCLONE_VM 一起使用——或者,如果您这样做了,您至少必须确保限制自己在调用父级或父级中的clone 之后调用标准库中的任何函数。孩子。为了让多个线程或进程共享相同的内存,任何访问共享资源(如堆)的函数的实现都需要

  1. 请注意,多个控制流可能会访问它,以便他们可以安排执行适当的同步,并且
  2. 能够通过线程指针获取有关自己身份的信息,通常存储在一个特殊的机器寄存器中。这完全是实现内部的,因此您无法安排您自己通过clone 创建的新“线程”来正确设置线程指针。

正确的解决方案是使用pthread_create,而不是clone

【讨论】:

  • 谢谢。你的意思是标准库不是线程安全的?还是克隆不安全?
  • 线程安全的唯一方法是知道它的线程,并让线程知道它们自己的存在。如果你绕过它,这些都不可能。 pthread_create 是将cloneCLONE_VM 一起使用的正确方法。如果您不提供自己的堆栈,除了促进自动堆栈分配之外,pthread_create 所做的主要工作是管理同步访问共享资源所需的内部簿记以正常工作。
  • 是的,我认为您使用的短语“非克隆安全”是一个很好的描述方式。
【解决方案2】:

你不能这样做:

for (i=0; i<200000; i++)
        ptr = realloc(ptr, sizeof(int)*i);
free(ptr);

第一次循环时,i 为零。 realloc( ptr, 0 ) 等同于free( ptr ),你不能free 两次。

【讨论】:

  • 感谢您的回忆。我用非零 i 修改代码。并且 realloc() 死锁仍然存在。
  • 我不认为这是 OP 的问题。
【解决方案3】:

我在 clone() 系统调用中添加了一个标志 CLONE_SETTLS。然后僵局就消失了。 所以我认为 eglibc 的 realloc() 使用了一些 TLS 数据。当新线程在没有新 TLS 的情况下创建时,一些锁(在 TLS 中)在该线程和他的父亲之间共享,并且 realloc() 使用这些锁被卡住了。因此,如果有人想直接使用 clone(),最好的方法是为新线程分配一个新的 TLS。

代码 sn-p 喜欢这样:

flags = CLONE_VM | CLONE_SETTLS;
struct user_desc* p_tls_desc = malloc(sizeof(struct user_desc));
clone(clone_func, child_stack_start +CHILD_STACK_SIZE, flags, NULL, NULL, p_tls_desc, NULL);

【讨论】:

  • 将指针传递给未初始化的struct user_desc 绝对不会为新线程提供有效的 TLS 块。你很幸运(或不幸)这没有崩溃。如果它有效,那是由于发生了未定义的行为来使“正确的事情”发生;这不是一个稳定/可重复的结果。
  • 另外,除非您使用的是 32 位 x86,否则甚至不会使用 user_desc。相反,该指针被视为指向 TLS 基的直接指针。这可能就是为什么它没有那么可怕地崩溃,但它仍然没有做正确的事情。从您的问题来看,您似乎使用的是 x86_64。
猜你喜欢
  • 2014-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-05
  • 1970-01-01
  • 1970-01-01
  • 2015-10-24
相关资源
最近更新 更多