summaryrefslogtreecommitdiff
path: root/linux-user
diff options
context:
space:
mode:
authorNathan Froyd <froydnj@codesourcery.com>2010-10-29 07:48:57 -0700
committerRiku Voipio <riku.voipio@nokia.com>2010-12-03 15:09:38 +0200
commit48e15fc2de29276f0c93482f5175b95e50557fbf (patch)
tree51cab296a8ce2f75f797830478e3d6c41e6d75c7 /linux-user
parentc65ffe6d6ca8b156e729e81054ca7597864354a9 (diff)
linux-user: fix memory leaks with NPTL emulation
Running programs that create large numbers of threads, such as this snippet from libstdc++'s pthread7-rope.cc: const int max_thread_count = 4; const int max_loop_count = 10000; ... for (int j = 0; j < max_loop_count; j++) { ... for (int i = 0; i < max_thread_count; i++) pthread_create (&tid[i], NULL, thread_main, 0); for (int i = 0; i < max_thread_count; i++) pthread_join (tid[i], NULL); } in user-mode emulation will quickly run out of memory. This is caused by a failure to free memory in do_syscall prior to thread exit: /* TODO: Free CPU state. */ pthread_exit(NULL); The first step in fixing this is to make all TaskStates used by QEMU dynamically allocated. The TaskState used by the initial thread was not, as it was allocated on main's stack. So fix that, free the cpu_env, free the TaskState, and we're home free, right? Not exactly. When we create a thread, we do: ts = qemu_mallocz(sizeof(TaskState) + NEW_STACK_SIZE); ... new_stack = ts->stack; ... ret = pthread_attr_setstack(&attr, new_stack, NEW_STACK_SIZE); If we blindly free the TaskState, then, we yank the current (host) thread's stack out from underneath it while it still has things to do, like calling pthread_exit. That causes problems, as you might expect. The solution adopted here is to let the C library allocate the thread's stack (so the C library can properly clean it up at pthread_exit) and provide a hint that we want NEW_STACK_SIZE bytes of stack. With those two changes, we're done, right? Well, almost. You see, we're creating all these host threads and their parent threads never bother to check that their children are finished. There's no good place for the parent threads to do so. Therefore, we need to create the threads in a detached state so the parent thread doesn't have to call pthread_join on the child to release the child's resources; the child does so automatically. With those three major changes, we can comfortably run programs like the above without exhausting memory. We do need to delete 'stack' from the TaskState structure. Signed-off-by: Nathan Froyd <froydnj@codesourcery.com> Signed-off-by: Riku Voipio <riku.voipio@nokia.com>
Diffstat (limited to 'linux-user')
-rw-r--r--linux-user/main.c4
-rw-r--r--linux-user/qemu.h2
-rw-r--r--linux-user/syscall.c11
3 files changed, 9 insertions, 8 deletions
diff --git a/linux-user/main.c b/linux-user/main.c
index dbba8befe..7d41d4ab8 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -2711,7 +2711,7 @@ int main(int argc, char **argv, char **envp)
struct target_pt_regs regs1, *regs = &regs1;
struct image_info info1, *info = &info1;
struct linux_binprm bprm;
- TaskState ts1, *ts = &ts1;
+ TaskState *ts;
CPUState *env;
int optind;
const char *r;
@@ -3038,7 +3038,7 @@ int main(int argc, char **argv, char **envp)
}
target_argv[target_argc] = NULL;
- memset(ts, 0, sizeof(TaskState));
+ ts = qemu_mallocz (sizeof(TaskState));
init_task_state(ts);
/* build Task State */
ts->info = info;
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index 708021e00..00c6549d8 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -126,8 +126,6 @@ typedef struct TaskState {
struct sigqueue sigqueue_table[MAX_SIGQUEUE_SIZE]; /* siginfo queue */
struct sigqueue *first_free; /* first free siginfo queue entry */
int signal_pending; /* non zero if a signal may be pending */
-
- uint8_t stack[0];
} __attribute__((aligned(16))) TaskState;
extern char *exec_path;
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index d44f512ed..5761106ce 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -3601,9 +3601,8 @@ static int do_fork(CPUState *env, unsigned int flags, abi_ulong newsp,
new_thread_info info;
pthread_attr_t attr;
#endif
- ts = qemu_mallocz(sizeof(TaskState) + NEW_STACK_SIZE);
+ ts = qemu_mallocz(sizeof(TaskState));
init_task_state(ts);
- new_stack = ts->stack;
/* we create a new CPU instance. */
new_env = cpu_copy(env);
#if defined(TARGET_I386) || defined(TARGET_SPARC) || defined(TARGET_PPC)
@@ -3639,7 +3638,8 @@ static int do_fork(CPUState *env, unsigned int flags, abi_ulong newsp,
info.parent_tidptr = parent_tidptr;
ret = pthread_attr_init(&attr);
- ret = pthread_attr_setstack(&attr, new_stack, NEW_STACK_SIZE);
+ ret = pthread_attr_setstacksize(&attr, NEW_STACK_SIZE);
+ ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
/* It is not safe to deliver signals until the child has finished
initializing, so temporarily block all signals. */
sigfillset(&sigmask);
@@ -3667,6 +3667,7 @@ static int do_fork(CPUState *env, unsigned int flags, abi_ulong newsp,
if (flags & CLONE_NPTL_FLAGS2)
return -EINVAL;
/* This is probably going to die very quickly, but do it anyway. */
+ new_stack = qemu_mallocz (NEW_STACK_SIZE);
#ifdef __ia64__
ret = __clone2(clone_func, new_stack, NEW_STACK_SIZE, flags, new_env);
#else
@@ -4240,7 +4241,9 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
sys_futex(g2h(ts->child_tidptr), FUTEX_WAKE, INT_MAX,
NULL, NULL, 0);
}
- /* TODO: Free CPU state. */
+ thread_env = NULL;
+ qemu_free(cpu_env);
+ qemu_free(ts);
pthread_exit(NULL);
}
#endif