From d648afb8d7852cc7ca9819315356ec605e2e0ee7 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Sun, 11 May 2008 16:25:35 +0000 Subject: [PATCH] * For each userland team the kernel creates an area in the userland address space that is fully locked and marked B_KERNEL_AREA. It can thus be accessed by the kernel without additional checks. * For each userland thread we do create a user_thread structure in that area. The structure is accessible from userland via TLS, using the private get_user_thread() function. * Introduced private userland functions [un]defer_signals(). They can be used to cheaply disable/re-enable signal delivery. They use the user_thread::defer_signals/pending_signals fields which are checked/updated by the kernel. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25451 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/kernel/arch/x86/arch_kernel.h | 5 +- headers/private/kernel/team.h | 3 + headers/private/kernel/thread_types.h | 20 ++- headers/private/kernel/tls.h | 1 + headers/private/libroot/user_thread.h | 48 ++++++ src/system/kernel/Jamfile | 1 + src/system/kernel/arch/x86/arch_thread.cpp | 4 +- src/system/kernel/signal.cpp | 14 ++ src/system/kernel/team.cpp | 154 +++++++++++++++++- src/system/kernel/thread.cpp | 19 ++- 10 files changed, 256 insertions(+), 13 deletions(-) create mode 100644 headers/private/libroot/user_thread.h diff --git a/headers/private/kernel/arch/x86/arch_kernel.h b/headers/private/kernel/arch/x86/arch_kernel.h index eaa5e017c5..66bbfd0dbc 100644 --- a/headers/private/kernel/arch/x86/arch_kernel.h +++ b/headers/private/kernel/arch/x86/arch_kernel.h @@ -32,7 +32,8 @@ #define USER_SIZE (KERNEL_BASE - 0x10000) #define USER_TOP (USER_BASE + USER_SIZE) -#define USER_STACK_REGION 0x70000000 -#define USER_STACK_REGION_SIZE (USER_TOP - USER_STACK_REGION) +#define KERNEL_USER_DATA_BASE 0x6fff0000 +#define USER_STACK_REGION 0x70000000 +#define USER_STACK_REGION_SIZE (USER_TOP - USER_STACK_REGION) #endif /* _KERNEL_ARCH_x86_KERNEL_H */ diff --git a/headers/private/kernel/team.h b/headers/private/kernel/team.h index 2e8b28d38a..77b3a92994 100644 --- a/headers/private/kernel/team.h +++ b/headers/private/kernel/team.h @@ -47,6 +47,9 @@ status_t start_watching_team(team_id team, void (*hook)(team_id, void *), status_t stop_watching_team(team_id team, void (*hook)(team_id, void *), void *data); +struct user_thread* team_allocate_user_thread(struct team* team); +void team_free_user_thread(struct thread* thread); + // used in syscalls.c thread_id _user_load_image(int32 argCount, const char **args, int32 envCount, const char **env, int32 priority, uint32 flags, diff --git a/headers/private/kernel/thread_types.h b/headers/private/kernel/thread_types.h index ec88ba8fab..4b6b1456e4 100644 --- a/headers/private/kernel/thread_types.h +++ b/headers/private/kernel/thread_types.h @@ -71,6 +71,7 @@ enum { struct image; // defined in image.c struct realtime_sem_context; // defined in realtime_sem.cpp struct select_info; +struct user_thread; // defined in libroot/user_thread.h struct death_entry { struct list_link link; @@ -165,6 +166,11 @@ struct team_dead_children : team_job_control_children { #endif // __cplusplus +struct free_user_thread { + struct free_user_thread* next; + struct user_thread* thread; +}; + struct team { struct team *next; // next in hash struct team *siblings_next; @@ -199,6 +205,12 @@ struct team { struct list watcher_list; struct arch_team arch_info; + addr_t user_data; + area_id user_data_area; + size_t user_data_size; + size_t used_user_data; + struct free_user_thread* free_user_threads; + struct team_debug_info debug_info; bigtime_t dead_threads_kernel_time; @@ -239,13 +251,15 @@ struct thread { size_t signal_stack_size; bool signal_stack_enabled; + bool in_kernel; + bool was_yielded; + + struct user_thread* user_thread; + struct { uint8 parameters[32]; } syscall_restart; - bool in_kernel; - bool was_yielded; - struct { status_t status; // current wait status uint32 flags; // interrupable flags diff --git a/headers/private/kernel/tls.h b/headers/private/kernel/tls.h index 1a33abd168..bd44614d3e 100644 --- a/headers/private/kernel/tls.h +++ b/headers/private/kernel/tls.h @@ -17,6 +17,7 @@ enum { TLS_THREAD_ID_SLOT, TLS_ERRNO_SLOT, TLS_ON_EXIT_THREAD_SLOT, + TLS_USER_THREAD_SLOT, // Note: these entries can safely be changed between // releases; 3rd party code always calls tls_allocate() diff --git a/headers/private/libroot/user_thread.h b/headers/private/libroot/user_thread.h new file mode 100644 index 0000000000..568fe0962f --- /dev/null +++ b/headers/private/libroot/user_thread.h @@ -0,0 +1,48 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef _LIBROOT_USER_THREAD_H +#define _LIBROOT_USER_THREAD_H + +#include +#include + +#include /* kernel header */ + + +struct user_thread { + int32 defer_signals; // counter; 0 == signals allowed + uint32 pending_signals; // signals that are pending, when + // signals are deferred + status_t wait_status; +}; + + +static inline user_thread* +get_user_thread() +{ + return (user_thread*)tls_get(TLS_USER_THREAD_SLOT); +} + + +static void inline +defer_signals() +{ + get_user_thread()->defer_signals++; +} + + +static void inline +undefer_signals() +{ + user_thread* thread = get_user_thread(); + if (--thread->defer_signals == 0 && thread->pending_signals != 0) { + // signals shall no longer be deferred -- call a dummy syscall to handle + // the pending ones + is_computer_on(); + } +} + + +#endif /* _LIBROOT_USER_THREAD_H */ diff --git a/src/system/kernel/Jamfile b/src/system/kernel/Jamfile index 2765b23d71..353d59766f 100644 --- a/src/system/kernel/Jamfile +++ b/src/system/kernel/Jamfile @@ -10,6 +10,7 @@ SubDir HAIKU_TOP src system kernel ; SubDirC++Flags $(defines) ; } +UsePrivateHeaders libroot ; UsePrivateHeaders shared ; UsePrivateHeaders runtime_loader ; diff --git a/src/system/kernel/arch/x86/arch_thread.cpp b/src/system/kernel/arch/x86/arch_thread.cpp index 3b4f5c003f..9674117d3c 100644 --- a/src/system/kernel/arch/x86/arch_thread.cpp +++ b/src/system/kernel/arch/x86/arch_thread.cpp @@ -309,15 +309,17 @@ arch_thread_init_kthread_stack(struct thread *t, int (*start_func)(void), status_t arch_thread_init_tls(struct thread *thread) { - uint32 tls[TLS_THREAD_ID_SLOT + 1]; + uint32 tls[TLS_USER_THREAD_SLOT + 1]; int32 i; thread->user_local_storage = thread->user_stack_base + thread->user_stack_size; // initialize default TLS fields + memset(tls, 0, sizeof(tls)); tls[TLS_BASE_ADDRESS_SLOT] = thread->user_local_storage; tls[TLS_THREAD_ID_SLOT] = thread->id; + tls[TLS_USER_THREAD_SLOT] = (addr_t)thread->user_thread; return user_memcpy((void *)thread->user_local_storage, tls, sizeof(tls)); } diff --git a/src/system/kernel/signal.cpp b/src/system/kernel/signal.cpp index 3ac7272e74..25d487ec91 100644 --- a/src/system/kernel/signal.cpp +++ b/src/system/kernel/signal.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -42,6 +43,11 @@ #define DEFAULT_IGNORE_SIGNALS \ (SIGNAL_TO_MASK(SIGCHLD) | SIGNAL_TO_MASK(SIGWINCH) \ | SIGNAL_TO_MASK(SIGCONT)) +#define NON_DEFERRABLE_SIGNALS \ + (KILL_SIGNALS \ + | SIGNAL_TO_MASK(SIGILL) \ + | SIGNAL_TO_MASK(SIGFPE) \ + | SIGNAL_TO_MASK(SIGSEGV)) const char * const sigstr[NSIG] = { @@ -270,6 +276,14 @@ handle_signals(struct thread *thread) if (signalMask == 0) return 0; + if (thread->user_thread->defer_signals > 0 + && (signalMask & NON_DEFERRABLE_SIGNALS) == 0) { + thread->user_thread->pending_signals = signalMask; + return 0; + } + + thread->user_thread->pending_signals = 0; + bool restart = (atomic_and(&thread->flags, ~THREAD_FLAGS_DONT_RESTART_SYSCALL) & THREAD_FLAGS_DONT_RESTART_SYSCALL) == 0; diff --git a/src/system/kernel/team.cpp b/src/system/kernel/team.cpp index d02b5acc88..d52611891d 100644 --- a/src/system/kernel/team.cpp +++ b/src/system/kernel/team.cpp @@ -1,4 +1,5 @@ /* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2002-2008, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. * @@ -35,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +71,7 @@ struct fork_arg { size_t user_stack_size; addr_t user_local_storage; sigset_t sig_block_mask; + struct user_thread* user_thread; struct arch_fork_arg arch_info; }; @@ -830,6 +833,11 @@ create_team_struct(const char *name, bool kernel) team->state = TEAM_STATE_BIRTH; team->flags = 0; team->death_sem = -1; + team->user_data_area = -1; + team->user_data = 0; + team->used_user_data = 0; + team->user_data_size = 0; + team->free_user_threads = NULL; team->supplementary_groups = NULL; team->supplementary_group_count = 0; @@ -911,6 +919,11 @@ delete_team_struct(struct team *team) while (job_control_entry* entry = team->dead_children->entries.RemoveHead()) delete entry; + while (free_user_thread* entry = team->free_user_threads) { + team->free_user_threads = entry->next; + free(entry); + } + malloc_referenced_release(team->supplementary_groups); delete team->job_control_entry; @@ -935,6 +948,42 @@ get_arguments_data_size(char **args, int32 argc) } +static status_t +create_team_user_data(struct team* team) +{ + void* address = (void*)KERNEL_USER_DATA_BASE; + size_t size = 4 * B_PAGE_SIZE; + team->user_data_area = create_area_etc(team, "user area", &address, + B_BASE_ADDRESS, size, B_FULL_LOCK, B_READ_AREA | B_WRITE_AREA); + if (team->user_data_area < 0) + return team->user_data_area; + + team->user_data = (addr_t)address; + team->used_user_data = 0; + team->user_data_size = size; + team->free_user_threads = NULL; + + return B_OK; +} + + +static void +delete_team_user_data(struct team* team) +{ + if (team->user_data_area >= 0) { + delete_area_etc(team, team->user_data_area); + team->user_data = 0; + team->used_user_data = 0; + team->user_data_size = 0; + team->user_data_area = -1; + while (free_user_thread* entry = team->free_user_threads) { + team->free_user_threads = entry->next; + free(entry); + } + } +} + + static void free_team_arg(struct team_arg *teamArg) { @@ -1004,6 +1053,9 @@ team_create_thread_start(void *args) TRACE(("team_create_thread_start: entry thread %ld\n", t->id)); + // get a user thread for the main thread + t->user_thread = team_allocate_user_thread(team); + // create an initial primary stack area // Main stack area layout is currently as follows (starting from 0): @@ -1216,13 +1268,18 @@ load_image_etc(int32 argCount, char * const *args, int32 envCount, else threadName = args[0]; + // create the user data area + status = create_team_user_data(team); + if (status != B_OK) + goto err4; + // Create a kernel thread, but under the context of the new team // The new thread will take over ownership of teamArgs thread = spawn_kernel_thread_etc(team_create_thread_start, threadName, B_NORMAL_PRIORITY, teamArgs, team->id, team->id); if (thread < 0) { status = thread; - goto err4; + goto err5; } // wait for the loader of the new team to finish its work @@ -1264,6 +1321,8 @@ load_image_etc(int32 argCount, char * const *args, int32 envCount, return thread; +err5: + delete_team_user_data(team); err4: vm_put_address_space(team->address_space); err3: @@ -1362,6 +1421,7 @@ exec_team(const char *path, int32 argCount, char * const *args, user_debug_prepare_for_exec(); + delete_team_user_data(team); vm_delete_areas(team->address_space); delete_owned_ports(team->id); sem_delete_owned_sems(team->id); @@ -1370,6 +1430,14 @@ exec_team(const char *path, int32 argCount, char * const *args, delete_realtime_sem_context(team->realtime_sem_context); team->realtime_sem_context = NULL; + status = create_team_user_data(team); + if (status != B_OK) { + // creating the user data failed -- we're toast + // TODO: We should better keep the old user area in the first place. + exit_thread(status); + return status; + } + user_debug_finish_after_exec(); // rename the team @@ -1422,6 +1490,7 @@ fork_team_thread_start(void *_args) thread->user_stack_size = forkArgs->user_stack_size; thread->user_local_storage = forkArgs->user_local_storage; thread->sig_block_mask = forkArgs->sig_block_mask; + thread->user_thread = forkArgs->user_thread; arch_thread_init_tls(thread); @@ -1509,6 +1578,8 @@ fork_team(void) // ToDo: should be able to handle stack areas differently (ie. don't have them copy-on-write) // ToDo: all stacks of other threads than the current one could be left out + forkArgs->user_thread = NULL; + cookie = 0; while (get_next_area_info(B_CURRENT_TEAM, &cookie, &info) == B_OK) { void *address; @@ -1519,13 +1590,30 @@ fork_team(void) break; } - if (info.area == parentThread->user_stack_area) + if (info.area == parentThread->user_stack_area) { forkArgs->user_stack_area = area; + } else if (info.area == parentTeam->user_data_area) { + team->user_data = (addr_t)address; + team->used_user_data = 0; + team->user_data_size = info.size; + team->user_data_area = area; + team->free_user_threads = NULL; + forkArgs->user_thread = team_allocate_user_thread(team); + } } if (status < B_OK) goto err4; + if (forkArgs->user_thread == NULL) { +#if KDEBUG + panic("user data area not found, parent area is %ld", + parentTeam->user_data_area); +#endif + status = B_ERROR; + goto err4; + } + forkArgs->user_stack_base = parentThread->user_stack_base; forkArgs->user_stack_size = parentThread->user_stack_size; forkArgs->user_local_storage = parentThread->user_local_storage; @@ -2558,6 +2646,68 @@ stop_watching_team(team_id teamID, void (*hook)(team_id, void *), void *data) } +/*! The team lock must be held or the team must still be single threaded. +*/ +struct user_thread* +team_allocate_user_thread(struct team* team) +{ + if (team->user_data == 0) + return NULL; + + user_thread* thread = NULL; + + // take an entry from the free list, if any + if (struct free_user_thread* entry = team->free_user_threads) { + thread = entry->thread; + team->free_user_threads = entry->next; + deferred_free(entry); + return thread; + } else { + // enough space left? + size_t needed = _ALIGN(sizeof(user_thread)); + if (team->user_data_size - team->used_user_data < needed) + return NULL; + // TODO: This imposes a per team thread limit! We should resize the + // area, if necessary. That's problematic at this point, though, since + // we've got the team lock. + + thread = (user_thread*)(team->user_data + team->used_user_data); + team->used_user_data += needed; + } + + thread->defer_signals = 0; + thread->pending_signals = 0; + thread->wait_status = B_OK; + + return thread; +} + + +/*! The team lock must not be held. \a thread must be the current thread. +*/ +void +team_free_user_thread(struct thread* thread) +{ + user_thread* userThread = thread->user_thread; + if (userThread == NULL) + return; + + // create a free list entry + free_user_thread* entry + = (free_user_thread*)malloc(sizeof(free_user_thread)); + if (entry == NULL) { + // we have to leak the user thread :-/ + return; + } + + InterruptsSpinLocker _(team_spinlock); + + entry->thread = userThread; + entry->next = thread->team->free_user_threads; + thread->team->free_user_threads = entry; +} + + // #pragma mark - Public kernel API diff --git a/src/system/kernel/thread.cpp b/src/system/kernel/thread.cpp index fca78b9e29..148fcf78d6 100644 --- a/src/system/kernel/thread.cpp +++ b/src/system/kernel/thread.cpp @@ -444,7 +444,16 @@ create_thread(thread_creation_attributes& attributes, bool kernel) GRAB_TEAM_LOCK(); // look at the team, make sure it's not being deleted team = team_get_team_struct_locked(attributes.team); - if (team != NULL && team->state != TEAM_STATE_DEATH) { + + if (team == NULL || team->state == TEAM_STATE_DEATH) + abort = true; + + if (!abort && !kernel) { + thread->user_thread = team_allocate_user_thread(team); + abort = thread->user_thread == NULL; + } + + if (!abort) { // Debug the new thread, if the parent thread required that (see above), // or the respective global team debug flag is set. But only, if a // debugger is installed for the team. @@ -457,8 +466,7 @@ create_thread(thread_creation_attributes& attributes, bool kernel) } insert_thread_into_team(team, thread); - } else - abort = true; + } RELEASE_TEAM_LOCK(); if (abort) { @@ -1407,8 +1415,10 @@ thread_exit(void) if (team->main_thread == thread) { // this was the main thread in this team, so we will delete that as well deleteTeam = true; - } else + } else { threadDeathEntry = (death_entry*)malloc(sizeof(death_entry)); + team_free_user_thread(thread); + } // remove this thread from the current team and add it to the kernel // put the thread into the kernel team until it dies @@ -2937,4 +2947,3 @@ _user_setrlimit(int resource, const struct rlimit *userResourceLimit) return common_setrlimit(resource, &resourceLimit); } -