首页 > 解决方案 > 为什么子线程中不能释放父进程的malloced内存空间?

问题描述

在这个程序中,我克隆了一个带有标志的子线程:CLONE_VM | 克隆_SETTLS。子进程的堆栈空间分配在前面的父进程中。

在子线程中,我想通过直接更改rsp寄存器来切换它的堆栈。并且新的栈空间也提前分配在了father中。

错误是:
当我尝试释放旧堆栈空间时,出现“分段错误”,如下所示 malloc.c:2981 的段错误的屏幕截图:没有这样的文件或目录

以下四个操作将使bug消失:

  1. 不要切换堆栈空间
  2. malloc旧堆栈空间之后和克隆方法之前的新堆栈空间
  3. malloc子线程中的新堆栈空间
  4. 不要使用 TLS

C程序:

#define _GNU_SOURCE

#include <sched.h>
#include <unistd.h>
#include <asm/prctl.h>
#include <string.h>
#include <malloc.h>

extern void gogo_switch_new_free_old_and_jmp(void *new_stack, void *old_stack, void *new_pc);

int arch_prctl(int code, unsigned long *addr);

typedef struct {
    void *stack_space;
    void *stack_top;
} stack_struct;

typedef struct {
    stack_struct stack;
    stack_struct stack2;
} T;

void get_tls(void *tls_addr) {
    arch_prctl(ARCH_GET_FS, tls_addr);
}

void task_done() {
    T *t;
    get_tls(&t);
    void *old_stack_space = t->stack.stack_space;
    t->stack.stack_space = t->stack2.stack_space;
    t->stack.stack_top = t->stack2.stack_top;
    gogo_switch_new_free_old_and_jmp(t->stack.stack_top, old_stack_space, NULL);
}

#define stack_size 128

int main() {
    stack_struct stack2;
    stack2.stack_space = malloc(stack_size);
    stack2.stack_top = stack2.stack_space + stack_size;

    T *t = malloc(sizeof(T));
    t->stack.stack_space = malloc(stack_size);
    t->stack.stack_top = t->stack.stack_space + stack_size;
    t->stack2 = stack2;

    clone((void *) task_done, t->stack.stack_top,
          CLONE_VM |
          CLONE_SETTLS,
          NULL, NULL, t, NULL);
    sleep(1000000000);
    return 0;
}

汇编器:

#void gogo_switch_new_free_old_and_jmp(void *new_stack, void *old_stack, void *new_pc);
    .text
    .globl  gogo_switch_new_free_old_and_jmp
    .type   gogo_switch_new_free_old_and_jmp, @function
gogo_switch_new_free_old_and_jmp:
    # switch stack
    movq    %rdi,%rsp
    pushq   %rdx
    # free old space
    movq    %rsi,%rdi
    call    free@PLT
    #JMP 
    popq    %rax
    movq    %rsp,   %rdi
    addq    $8,     %rdi
    pushq   %rax
    ret

标签: clinuxmultithreadingmalloc

解决方案


关于“.... in tcache_init () at malloc.c:2981”错误消息。

出于性能考虑,GLIBC 的动态内存分配器使用 pthread 的 TLS 来维护每线程竞技场(即每线程缓存)上的指针,线程可以使用专用互斥锁在其中进行内存分配以增加并行度(否则如果线程系统地使用分配器的全局互斥锁,则线程的所有动态分配都将被序列化)。

因此,当您通过 clone() 创建的“自制”线程调用任何 malloc()/free() 函数时,动态内存分配器会查找存储在当前线程的 TLS 中的 pthread 信息,以便检索线程的竞技场上的指针。但是这些信息并不一致,因为当前线程不是基于 pthread 的。因此,崩溃。

您应该使用不依赖于任何 pthread 特定信息的 mmap() 系统调用,而不是 GLIBC 的动态内存分配器


推荐阅读