为什么不能在克隆创建的线程中正确调用malloc / free API?

时间:2017-07-24 13:52:16

标签: c linux multithreading malloc

为什么在syscall clone创建的线程中无法正确调用某些glibc的API(例如函数<select ng-model="activeProfile.awardyear" ng-options="ay.display for ay in awardYears"></select> malloc()realloc())?

以下是我的测试代码:

free()

测试结果:线程int thread_func( void *arg ) { void *ptr = malloc( 4096 ); printf( "tid=%d, ptr=%x\n", gettid(), ptr ); sleep(1); if( ptr ) free( ptr ); return 0; } int main( int argc, char **argv ) { int i, m; void *stk; int stksz = 1024 * 128; int flag = CLONE_VM | CLONE _FILES | CLONE_FS | CLONE_SIGHAND; for( i=m=0; i < 100; i++ ) { stk = malloc( stksz ); if( !stk ) break; if( clone( thread_func, stk+stksz, flags, NULL, NULL, NULL, NULL ) != -1 ) m++; } printf( "create %d thread\n", m ); sleep(10); return 0; } 或主线程thread_func将在mainmalloc()函数中随机阻止。或者有时会导致free()malloc()崩溃。

我认为可能free()malloc()需要某些TLS数据来区分每个帖子。

有谁知道原因,可以使用什么解决方案来解决这个问题?

2 个答案:

答案 0 :(得分:1)

  

我认为可能是malloc()和free()需要某些TLS数据来区分每个线程。

Glibc的malloc()free()不依赖于TLS。它们使用互斥锁来保护共享内存分配数据结构。为了减少对这些争用的争用,他们采用了一种策略,即使用独立的元数据和互斥体来维护单独的内存分配领域。这在their manual page上有记录。

在纠正代码中的语法错误并忽略对不存在的函数gettid()的调用后(请参阅问题评论),我能够产生分段错误,但不能阻塞。也许你把程序的10秒睡眠造成的退出延迟与堵塞混淆了。

除了可能与您未公开的gettid()实现相关的任何问题之外,您的程序还包含两个语义错误,每个错误都会产生未定义的行为:

  1. 正如我在评论中已经提到的,它传递了错误的子堆栈指针值。 *

  2. 它使用printf()中的错误thread_func()指令来打印指针。指针值的指令是%p; %x适用于unsigned int类型的参数。

  3. 在我纠正了这些错误之后,程序也一直为我完成。修改后的代码:

    int thread_func(void *arg) {
        void *ptr = malloc(4096);
    
        // printf( "tid=%d, ptr=%x\n", gettid(), ptr );
        printf("tid=%d, ptr=%p\n", 1, ptr);
        sleep(1);
    
        if (ptr) {
            free(ptr);
        }
    
        return 0;
    }
    
    int main(int argc, char **argv) {
        int i, m;
        char *stk;  // Note: char * instead of void * to afford arithmetic
        int stksz = 1024 * 128;
        int flags = CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND;
    
        for (i = m = 0; i < 100; i++) {
            stk = malloc( stksz );
    
            if( !stk ) break;
    
            if (clone(thread_func, stk + stksz - 1, flags, NULL, NULL, NULL, NULL ) != -1) {
                m++;
            }
        }
    
        printf("create %d thread\n", m);
        sleep(10);
    
        return 0;
    }
    

    然而,即使如此,一切都不是很完美:我看到程序输出中出现了各种异常,特别是在开头附近。

    最重要的是,与你的断言相反, 你没有创建任何线程 ,至少在C库识别的意义上是这样。您只是创建具有类似于线程的行为的进程。对于某些目的而言,这可能就足够了,但您不能依赖系统来处理与线程相同的进程。

    在Linux上,系统和标准库将识别的真正的线程是通过pthread_create()启动的POSIX线程。 (我在此注意,修改程序以使用pthread_create()代替clone()解决了我的输出异常。)您可以为clone()调用添加标记和参数得到的进程就像Linux实现的pthreads实际上是完全相同的,但为什么你会做这样的事情而不是仅仅使用真正的pthreads呢?

    * 该程序还对void *执行指针算术,C不允许。然而,GCC接受这个作为扩展,并且因为你的代码无论如何都是特定于Linux的,所以我只用这个笔记来放这个幻灯片。

答案 1 :(得分:0)

正确,mallocfree至少需要TLS以下内容:

  1. 附加到当前线程的malloc竞技场(用于分配操作)。
  2. errno TLS变量(写入系统调用失败时)。
  3. 堆栈保护器金丝雀(如果启用,架构将金丝雀存储在TCB中)。
  4. malloc thread cache(在即将发布的glibc 2.26版本中默认启用)。
  5. 所有这些项目都需要一个正确初始化的线程控制块(TCB),但奇怪的是,直到最近,就malloc / free而言,如果创建一个线程几乎没有关系clone与另一个TCB共享(以便数据不再是线程本地的):

    线程基本上永远不会重新连接到不同的竞技场,因此竞技场TLS变量在初始化后实际上是只读的 - 多个线程可以共享一个竞技场。只要系统调用仅在其中一个正在进行共享的线程中失败,就可以共享errno。堆栈保护器canary在进程启动后是只读的,无论如何它的值在整个线程中是相同的。

    但所有这些都是一个实现细节,并且glibc 2.26中的东西在其malloc线程缓存中发生了根本性的变化:缓存在没有同步的情况下被读取和写入,因此您尝试执行的操作很可能会导致内存损坏。

    这不是glibc 2.26中的重大变化,它总是如此:从clone创建的线程调用任何 glibc函数是未定义的。 As John Bollinger pointed out,这大部分都是偶然发生的,但我可以向你保证,它一直都是完全未定义的。