| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <assert.h> |
| #include <sched.h> // sched_param |
| #include <semaphore.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #include <sys/mman.h> |
| #include <stdlib.h> |
| |
| #include "port_malloc.h" |
| #include "port_thread.h" |
| #include "port_thread_internal.h" |
| |
| // Linux/FreeBSD defines |
| #if defined(FREEBSD) |
| #define STACK_MMAP_ATTRS \ |
| (MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_STACK) |
| #else |
| #ifdef _IPF_ |
| #define STACK_MMAP_ATTRS \ |
| (MAP_PRIVATE | MAP_ANONYMOUS) |
| #else /* !_IPF_ */ |
| #define STACK_MMAP_ATTRS \ |
| (MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN) |
| #endif /* _IPF_ */ |
| #endif |
| |
| #ifdef _IPF_ |
| #define STACK_MAPPING_ACCESS (PROT_READ | PROT_WRITE) |
| #else /* !_IPF_ */ |
| #define STACK_MAPPING_ACCESS (PROT_READ | PROT_WRITE | PROT_EXEC) |
| #endif /* _IPF_ */ |
| |
| |
| /* Forward declarations */ |
| static int suspend_init(); |
| static int suspend_init_lock(); |
| static port_thread_info_t* init_susres_list_item(); |
| static port_thread_info_t* suspend_add_thread(osthread_t thread); |
| static void suspend_remove_thread(osthread_t thread); |
| static port_thread_info_t* suspend_find_thread(osthread_t thread); |
| static void sigusr2_handler(int signum, siginfo_t* info, void* context); |
| |
| |
| /** |
| * Calculates absolute time in future for sem_timedwait timeout. |
| * @param ptime The pointer to time structure to fill |
| * @param delay Desired timeout in ns; not greater than 10^9 (1s) |
| */ |
| static inline __attribute__((always_inline)) |
| void get_exceed_time(struct timespec* ptime, long delay) |
| { |
| clock_gettime(CLOCK_REALTIME, ptime); |
| |
| ptime->tv_nsec += delay; |
| if (ptime->tv_nsec >= 1000000000L) // overflow |
| { |
| ptime->tv_nsec -= 1000000000L; |
| ++ptime->tv_sec; |
| } |
| } |
| |
| typedef void* (PORT_CDECL *pthread_func_t)(void*); |
| |
| typedef struct |
| { |
| port_threadfunc_t fun; |
| void* arg; |
| size_t stack_size; |
| } thread_start_struct_t; |
| |
| static PORT_CDECL int thread_start_func(void* arg) |
| { |
| int err, result; |
| port_tls_data_t* tlsdata = NULL; |
| thread_start_struct_t* ptr = (thread_start_struct_t*)arg; |
| port_threadfunc_t fun = ptr->fun; |
| size_t stack_size = ptr->stack_size; |
| arg = ptr->arg; |
| STD_FREE(ptr); |
| |
| if (port_shared_data) |
| { |
| tlsdata = (port_tls_data_t*)STD_ALLOCA(sizeof(port_tls_data_t)); |
| err = port_thread_attach_local(tlsdata, FALSE, FALSE, stack_size); |
| assert(err == 0); |
| } |
| |
| result = fun(arg); |
| |
| if (tlsdata) |
| port_thread_detach(); |
| |
| return result; |
| } |
| |
| int port_thread_create(/* out */osthread_t* phandle, size_t stacksize, int priority, |
| port_threadfunc_t func, void *data) |
| { |
| pthread_t thread; |
| pthread_attr_t attr; |
| pthread_attr_t attr_nosched; |
| struct sched_param param; |
| thread_start_struct_t* startstr; |
| int res; |
| |
| if (!port_shared_data) |
| { |
| res = init_port_shared_data(); |
| /* assert(res); */ |
| /* It's OK to have an error here when Port shared library |
| is not available yet; only signals/crash handling will |
| not be available for the thread */ |
| /* return res; */ |
| } |
| |
| if (!func) |
| return EINVAL; |
| |
| startstr = |
| (thread_start_struct_t*)STD_MALLOC(sizeof(thread_start_struct_t)); |
| |
| if (!startstr) |
| return ENOMEM; |
| |
| pthread_attr_init(&attr); |
| pthread_attr_init(&attr_nosched); |
| pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
| pthread_attr_setdetachstate(&attr_nosched, PTHREAD_CREATE_DETACHED); |
| |
| if (stacksize != 0) |
| { |
| if (stacksize < MINSIGSTKSZ) |
| stacksize = MINSIGSTKSZ; |
| |
| if (port_shared_data) |
| { |
| size_t min_stacksize = |
| /* Let's get alt stack size for normal stack and add guard page size */ |
| ((2*port_shared_data->guard_stack_size + port_shared_data->guard_page_size) |
| /* Roung up to alt stack size */ |
| + port_shared_data->guard_stack_size - 1) & ~(port_shared_data->guard_stack_size - 1); |
| |
| if (stacksize < min_stacksize) |
| stacksize = min_stacksize; |
| } |
| |
| res = pthread_attr_setstacksize(&attr, stacksize); |
| if (res == 0) |
| res = pthread_attr_setstacksize(&attr_nosched, stacksize); |
| if (res) |
| { |
| pthread_attr_destroy(&attr); |
| pthread_attr_destroy(&attr_nosched); |
| STD_FREE(startstr); |
| return res; |
| } |
| } |
| |
| if (priority) |
| { |
| res = pthread_attr_setschedpolicy(&attr, SCHED_FIFO); |
| if (res == 0) |
| { |
| param.sched_priority = priority; |
| res = pthread_attr_setschedparam(&attr, ¶m); |
| } |
| /* This does not work anyway on some Linuses |
| if (res != 0) |
| { |
| pthread_attr_destroy(&attr); |
| pthread_attr_destroy(&attr_nosched); |
| STD_FREE(startstr); |
| return res; |
| }*/ |
| } |
| |
| startstr->fun = func; |
| startstr->arg = data; |
| startstr->stack_size = stacksize; |
| |
| res = pthread_create(&thread, &attr, (pthread_func_t)thread_start_func, startstr); |
| |
| if (res == EPERM) // EPERM relates to scheduling only |
| res = pthread_create(&thread, &attr_nosched, (pthread_func_t)thread_start_func, startstr); |
| |
| pthread_attr_destroy(&attr); |
| pthread_attr_destroy(&attr_nosched); |
| |
| if (res == 0) |
| { |
| *phandle = thread; |
| return 0; |
| } |
| |
| STD_FREE(startstr); |
| return res; |
| } |
| |
| int set_alt_stack(port_tls_data_t* tlsdata, Boolean set) |
| { |
| stack_t sigalt; |
| |
| // sets alternative stack |
| sigalt.ss_sp = tlsdata->guard_stack_addr; |
| sigalt.ss_size = tlsdata->guard_stack_size; |
| //#if defined(FREEBSD) |
| sigalt.ss_flags = set ? 0 : SS_DISABLE; |
| //#else |
| // sigalt.ss_flags = set ? SS_ONSTACK : SS_DISABLE; |
| //#endif |
| if (sigaltstack(&sigalt, NULL) != 0) |
| return errno; |
| |
| return 0; |
| } |
| |
| static int set_guard_page(port_tls_data_t* tlsdata, Boolean set) |
| { |
| int res; |
| |
| if (!tlsdata) |
| tlsdata = get_private_tls_data(); |
| |
| if (!tlsdata) |
| return -1; |
| |
| if (!tlsdata->guard_page_addr) |
| return 0; |
| |
| if ((set && tlsdata->guard_page_set) || |
| (!set && !tlsdata->guard_page_set)) |
| return 0; // Already in needed state |
| |
| res = mprotect(tlsdata->guard_page_addr, tlsdata->guard_page_size, |
| set ? PROT_NONE : (PROT_READ | PROT_WRITE | PROT_EXEC)); |
| |
| if (res != 0) |
| return errno; |
| |
| if (set) |
| { |
| res = set_alt_stack(tlsdata, TRUE); |
| |
| if (res != 0) |
| return res; |
| } |
| |
| tlsdata->guard_page_set = set; |
| return 0; |
| } |
| |
| int port_thread_restore_guard_page() |
| { |
| return set_guard_page(NULL, TRUE); |
| } |
| |
| int port_thread_clear_guard_page() |
| { |
| return set_guard_page(NULL, FALSE); |
| } |
| |
| void port_thread_postpone_guard_page() |
| { |
| port_tls_data_t* tlsdata = get_private_tls_data(); |
| |
| if (!tlsdata || !tlsdata->guard_page_addr) |
| return; |
| |
| tlsdata->restore_guard_page = FALSE; |
| } |
| |
| void* port_thread_get_stack_address() |
| { |
| port_tls_data_t* tlsdata = get_private_tls_data(); |
| return tlsdata ? tlsdata->stack_addr : NULL; |
| } |
| |
| size_t port_thread_get_stack_size() |
| { |
| port_tls_data_t* tlsdata = get_private_tls_data(); |
| return tlsdata ? tlsdata->stack_size : 0; |
| } |
| |
| size_t port_thread_get_effective_stack_size() |
| { |
| port_tls_data_t* tlsdata = get_private_tls_data(); |
| |
| if (!tlsdata) |
| return 0; |
| |
| if (!tlsdata->guard_page_addr || !tlsdata->guard_page_set) |
| return tlsdata->stack_size |
| - PSD->guard_page_size |
| - PSD->mem_protect_size; |
| |
| return tlsdata->stack_size - 2*PSD->guard_page_size |
| - PSD->guard_stack_size - PSD->mem_protect_size; |
| } |
| |
| static int setup_stack(port_tls_data_t* tlsdata) |
| { |
| int res; |
| void* ptr; |
| stack_t sigalt; |
| size_t /*current_page_addr,*/ mapping_addr, mapping_size; |
| |
| if (!port_shared_data) |
| return -1; |
| |
| // current_page_addr = ((size_t)&res) & ~(PSD->guard_page_size - 1); |
| // leave place for mmap work |
| // mapping_addr = current_page_addr - PSD->guard_page_size; |
| // found size of the stack area which should be maped |
| // mapping_size = tlsdata->stack_size |
| // - ((size_t)tlsdata->stack_addr - mapping_addr); |
| |
| if ((size_t)(&res) - PSD->mem_protect_size |
| < (size_t)tlsdata->guard_page_addr + tlsdata->guard_page_size) |
| return EINVAL; |
| |
| // maps unmapped part of the stack |
| mapping_addr = (size_t)tlsdata->stack_addr - tlsdata->stack_size; |
| mapping_size = |
| (tlsdata->guard_stack_size + tlsdata->mem_protect_size + 2*PSD->guard_page_size - 1) & |
| ~(PSD->guard_page_size - 1); |
| ptr = (char*)mmap((void*)mapping_addr, mapping_size, |
| STACK_MAPPING_ACCESS, STACK_MMAP_ATTRS, -1, 0); |
| |
| if (ptr == MAP_FAILED) |
| return errno; |
| |
| res = set_guard_page(tlsdata, TRUE); |
| |
| if (res != 0) |
| return res; |
| |
| return 0; |
| } |
| |
| inline int find_stack_addr_size(void** paddr, size_t* psize) |
| { |
| int err; |
| pthread_attr_t pthread_attr; |
| void* stack_addr; |
| size_t stack_size; |
| pthread_t thread = pthread_self(); |
| |
| if (!paddr) return EINVAL; |
| |
| err = pthread_attr_init(&pthread_attr); |
| if (err != 0) return err; |
| |
| #if defined(FREEBSD) |
| err = pthread_attr_get_np(thread, &pthread_attr); |
| #else |
| err = pthread_getattr_np(thread, &pthread_attr); |
| #endif |
| if (err != 0) return err; |
| |
| err = pthread_attr_getstack(&pthread_attr, &stack_addr, &stack_size); |
| if (err != 0) return err; |
| |
| pthread_attr_destroy(&pthread_attr); |
| *paddr = (void*)((size_t)stack_addr + stack_size); |
| *psize = stack_size; |
| return 0; |
| } |
| |
| static int init_stack(port_tls_data_t* tlsdata, size_t stack_size, Boolean temp) |
| { |
| int err; |
| size_t stack_begin; |
| |
| if (!port_shared_data) |
| return -1; |
| |
| err = find_stack_addr_size(&tlsdata->stack_addr, &tlsdata->stack_size); |
| if (err != 0) {fprintf(stderr, "init_stack:CP#1\n");return err;} |
| |
| // Workaround for incorrect stack_size returned by pthread_attr_getstack |
| // for main thread, while stack_addr + stack_size gives correct stack top |
| if (tlsdata->foreign) |
| tlsdata->stack_size = PSD->foreign_stack_size; |
| |
| if (stack_size != 0 && !tlsdata->foreign) |
| tlsdata->stack_size = stack_size; |
| |
| tlsdata->guard_page_size = PSD->guard_page_size; |
| tlsdata->guard_stack_size = PSD->guard_stack_size; |
| tlsdata->mem_protect_size = PSD->mem_protect_size; |
| |
| if (temp) |
| return 0; |
| |
| stack_begin = (size_t)tlsdata->stack_addr - tlsdata->stack_size; |
| tlsdata->guard_stack_addr = (void*)(stack_begin + tlsdata->guard_page_size); |
| tlsdata->guard_page_addr = |
| (void*)((size_t)tlsdata->guard_stack_addr + tlsdata->guard_stack_size); |
| |
| return setup_stack(tlsdata); |
| } |
| |
| int port_thread_attach_local(port_tls_data_t* tlsdata, Boolean temp, |
| Boolean foreign, size_t stack_size) |
| { |
| int res; |
| |
| memset(tlsdata, 0, sizeof(port_tls_data_t)); |
| |
| tlsdata->foreign = foreign; |
| res = init_stack(tlsdata, stack_size, temp); |
| if (res != 0) return res; |
| /* They are already zeroed |
| tlsdata->violation_flag = 0; |
| tlsdata->debugger = FALSE; |
| tlsdata->produce_core = FALSE;*/ |
| |
| res = set_private_tls_data(tlsdata); |
| |
| if (res != 0) |
| set_guard_page(tlsdata, FALSE); |
| |
| return res; |
| } |
| |
| int port_thread_attach() |
| { |
| int res; |
| port_tls_data_t* tlsdata; |
| |
| if (!port_shared_data && (res = init_port_shared_data()) != 0) |
| return res; |
| |
| if (get_private_tls_data()) |
| return 0; |
| |
| tlsdata = (port_tls_data_t*)STD_MALLOC(sizeof(port_tls_data_t)); |
| |
| if (!tlsdata) |
| return ENOMEM; |
| |
| res = port_thread_attach_local(tlsdata, FALSE, TRUE, 0); |
| |
| if (res != 0) |
| STD_FREE(tlsdata); |
| |
| return res; |
| } |
| |
| int port_thread_detach_temporary() |
| { |
| port_tls_data_t* tlsdata = get_private_tls_data(); |
| |
| if (!tlsdata || tlsdata->guard_page_addr) |
| return -1; |
| |
| return set_private_tls_data(NULL); |
| } |
| |
| int port_thread_detach() |
| { |
| port_tls_data_t* tlsdata; |
| int res; |
| |
| if (!port_shared_data && (res = init_port_shared_data()) != 0) |
| return res; |
| |
| tlsdata = get_private_tls_data(); |
| |
| if (!tlsdata) |
| return 0; |
| |
| if (port_thread_detach_temporary() == 0) |
| return 0; |
| |
| res = set_guard_page(tlsdata, FALSE); |
| |
| if (res != 0) |
| return res; |
| |
| size_t mapping_addr = (size_t)tlsdata->stack_addr - tlsdata->stack_size; |
| size_t mapping_size = |
| (tlsdata->guard_stack_size + tlsdata->mem_protect_size + 2*PSD->guard_page_size - 1) & |
| ~(PSD->guard_page_size - 1); |
| munmap((void*)mapping_addr, mapping_size); |
| |
| if (tlsdata->foreign) |
| STD_FREE(tlsdata); |
| |
| return set_private_tls_data(NULL); |
| } |
| |
| int port_thread_set_priority(osthread_t os_thread, int priority) |
| { |
| #if defined(FREEBSD) |
| /* Not sure why we don't just use this on linux? - MRH */ |
| struct sched_param param; |
| int policy; |
| int r = pthread_getschedparam(os_thread, &policy, ¶m); |
| if (r == 0) { |
| param.sched_priority = priority; |
| r = pthread_setschedparam(os_thread, policy, ¶m); |
| } |
| return r; |
| #else |
| // setting thread priority on linux is only supported for current thread |
| if (os_thread == pthread_self()) { |
| int r; |
| struct sched_param param; |
| pid_t self = gettid(); |
| param.sched_priority = priority; |
| r = sched_setparam(self, ¶m); |
| return r ? errno : 0; |
| } else { |
| // setting other thread priority not supported on linux |
| return 0; |
| } |
| #endif |
| } |
| |
| osthread_t port_thread_current() |
| { |
| return pthread_self(); |
| } |
| |
| int port_thread_free_handle(osthread_t os_thread) |
| { |
| return 0; |
| } |
| |
| int port_thread_join(osthread_t os_thread) |
| { |
| int error; |
| |
| do { |
| // FIXME - somehow pthread_join returns before thread is terminated |
| error = pthread_join(os_thread, NULL); |
| } while (error != ESRCH && error != EINVAL && error != EDEADLK); |
| return 0; |
| } |
| |
| int port_thread_cancel(osthread_t os_thread) |
| { |
| int status; |
| port_thread_info_t* pinfo; |
| |
| if (!suspend_init_lock()) |
| return -1; |
| |
| pinfo = suspend_find_thread(os_thread); |
| |
| if (os_thread == pthread_self()) |
| { |
| if (pinfo && status == 0) |
| suspend_remove_thread(os_thread); |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return pthread_cancel(os_thread); |
| } |
| |
| status = pthread_cancel(os_thread); |
| |
| if (pinfo && status == 0) |
| suspend_remove_thread(os_thread); |
| |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return status; |
| } |
| |
| void port_thread_exit(int status) |
| { |
| pthread_exit((void*)(size_t)status); |
| } |
| |
| int port_get_thread_times(osthread_t os_thread, int64* pkernel, int64* puser) |
| { |
| clockid_t clock_id; |
| struct timespec tp; |
| int r; |
| #ifdef FREEBSD |
| return EINVAL; /* TOFIX: Implement */ |
| #else |
| |
| r = pthread_getcpuclockid(os_thread, &clock_id); |
| if (r) return r; |
| |
| r = clock_gettime(clock_id, &tp); |
| if (r) return r; |
| |
| *puser = tp.tv_sec * 1000000000ULL + tp.tv_nsec; |
| return 0; |
| #endif |
| } |
| |
| void port_thread_yield_other(osthread_t os_thread) { |
| struct timespec timeout; |
| port_thread_info_t* pinfo; |
| int err = -1; |
| |
| if (!suspend_init_lock()) |
| return; |
| |
| pinfo = suspend_find_thread(os_thread); |
| |
| if (pinfo && pinfo->suspend_count > 0) { |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return; |
| } |
| |
| PSD->req_type = THREADREQ_YIELD; |
| |
| assert(os_thread); |
| if (pthread_kill(os_thread, SIGUSR2) == 0) { |
| // signal sent, let's do timed wait to make sure the signal |
| // was actually delivered |
| get_exceed_time(&timeout, 10000000L); |
| err = sem_timedwait(&PSD->yield_sem, &timeout); |
| // sem_wait(&PSD->yield_sem); |
| } else { |
| if (pinfo) |
| suspend_remove_thread(os_thread); |
| } |
| |
| if (err != 0) |
| PSD->req_type = THREADREQ_NONE; |
| |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| } |
| |
| |
| int port_thread_suspend(osthread_t thread) |
| { |
| int status; |
| port_thread_info_t* pinfo; |
| |
| if (!thread) |
| return -1; |
| |
| if (!suspend_init_lock()) |
| return -1; |
| |
| pinfo = suspend_find_thread(thread); |
| |
| if (!pinfo) |
| pinfo = suspend_add_thread(thread); |
| |
| if (!pinfo) |
| { |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return -1; |
| } |
| |
| if (pinfo->suspend_count > 0) |
| { |
| ++pinfo->suspend_count; |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return 0; |
| } |
| |
| PSD->suspendee = thread; |
| PSD->req_type = THREADREQ_SUS; |
| |
| if (pthread_kill(thread, SIGUSR2) != 0) |
| { |
| suspend_remove_thread(thread); |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return -1; |
| } |
| |
| /* Waiting for suspendee response */ |
| sem_wait(&pinfo->wake_sem); |
| /* Check result */ |
| status = (pinfo->suspend_count > 0) ? 0 : -1; |
| |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return status; |
| } |
| |
| int port_thread_resume(osthread_t thread) |
| { |
| int status; |
| port_thread_info_t* pinfo; |
| |
| if (!thread) |
| return -1; |
| |
| if (!suspend_init_lock()) |
| return -1; |
| |
| pinfo = suspend_find_thread(thread); |
| |
| if (!pinfo) |
| { |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return -1; |
| } |
| |
| if (pinfo->suspend_count > 1) |
| { |
| --pinfo->suspend_count; |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return 0; |
| } |
| |
| PSD->suspendee = thread; |
| PSD->req_type = THREADREQ_RES; |
| |
| if ((status = pthread_kill(thread, SIGUSR2)) != 0) |
| { |
| suspend_remove_thread(thread); |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return status; |
| } |
| |
| /* Waiting for resume notification */ |
| sem_wait(&pinfo->wake_sem); |
| |
| suspend_remove_thread(thread); |
| |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return 0; |
| } |
| |
| int port_thread_get_suspend_count(osthread_t thread) |
| { |
| port_thread_info_t* pinfo; |
| int suspend_count; |
| |
| if (!thread) |
| return -1; |
| |
| if (!suspend_init_lock()) |
| return -1; |
| |
| pinfo = suspend_find_thread(thread); |
| |
| suspend_count = pinfo ? pinfo->suspend_count : 0; |
| |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return suspend_count; |
| } |
| |
| int port_thread_get_context(osthread_t thread, thread_context_t *context) |
| { |
| int status = -1; |
| port_thread_info_t* pinfo; |
| |
| if (!thread || !context) |
| return -1; |
| |
| if (!suspend_init_lock()) |
| return -1; |
| |
| pinfo = suspend_find_thread(thread); |
| |
| if (!pinfo) |
| { |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return status; |
| } |
| |
| if (pinfo->suspend_count > 0) |
| { |
| *context = pinfo->context; |
| status = -1; |
| } |
| |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return status; |
| } |
| |
| int port_thread_set_context(osthread_t thread, thread_context_t *context) |
| { |
| int status = -1; |
| port_thread_info_t* pinfo; |
| |
| if (!thread || !context) |
| return -1; |
| |
| if (!suspend_init_lock()) |
| return -1; |
| |
| pinfo = suspend_find_thread(thread); |
| |
| if (!pinfo) |
| { |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return status; |
| } |
| |
| if (pinfo->suspend_count > 0) |
| { |
| pinfo->context = *context; |
| status = 0; |
| } |
| |
| pthread_mutex_unlock(&PSD->suspend_mutex); |
| return status; |
| } |
| |
| |
| static int suspend_init() |
| { |
| struct sigaction sa; |
| int result = 1; |
| |
| if (port_shared_data && PSD->signal_set) |
| return 1; |
| |
| if (!port_shared_data && init_port_shared_data() != 0) |
| return 0; |
| |
| pthread_mutex_lock(&PSD->suspend_init_mutex); |
| |
| if (!PSD->signal_set) |
| { |
| /* set signal handler */ |
| sigemptyset(&sa.sa_mask); |
| sa.sa_flags = SA_SIGINFO | SA_RESTART; |
| sa.sa_sigaction = sigusr2_handler; |
| |
| if (sigaction(SIGUSR2, &sa, NULL) != 0) |
| result = 0; |
| else |
| PSD->signal_set = TRUE; |
| } |
| |
| pthread_mutex_unlock(&PSD->suspend_init_mutex); |
| return result; |
| } |
| |
| static int suspend_init_lock() |
| { |
| if (!suspend_init()) |
| return 0; |
| |
| if (pthread_mutex_lock(&PSD->suspend_mutex) != 0) |
| return 0; |
| |
| return 1; |
| } |
| |
| static port_thread_info_t* init_susres_list_item() |
| { |
| port_thread_info_t* pinfo = |
| (port_thread_info_t*)malloc(sizeof(port_thread_info_t)); |
| |
| if (pinfo == NULL) |
| return NULL; |
| |
| pinfo->suspend_count = 0; |
| |
| int status = sem_init(&pinfo->wake_sem, 0, 0); |
| |
| if (status != 0) |
| { |
| free(pinfo); |
| return NULL; |
| } |
| |
| return pinfo; |
| } |
| |
| static port_thread_info_t* suspend_add_thread(osthread_t thread) |
| { |
| port_thread_info_t* pinfo = init_susres_list_item(); |
| |
| if (pinfo == NULL) |
| return NULL; |
| |
| pinfo->thread = thread; |
| pinfo->next = PSD->suspended_list; |
| PSD->suspended_list = pinfo; |
| return pinfo; |
| } |
| |
| static void suspend_remove_thread(osthread_t thread) |
| { |
| port_thread_info_t** pprev = &PSD->suspended_list; |
| port_thread_info_t* pinfo; |
| |
| for (pinfo = PSD->suspended_list; pinfo; pinfo = pinfo->next) |
| { |
| if (pinfo->thread == thread) |
| break; |
| |
| pprev = &pinfo->next; |
| } |
| |
| if (pinfo != NULL) |
| { |
| sem_destroy(&pinfo->wake_sem); |
| *pprev = pinfo->next; |
| free(pinfo); |
| } |
| } |
| |
| static port_thread_info_t* suspend_find_thread(osthread_t thread) |
| { |
| port_thread_info_t* pinfo; |
| int status; |
| |
| for (pinfo = PSD->suspended_list; pinfo; pinfo = pinfo->next) |
| { |
| if (pinfo->thread == thread) |
| break; |
| } |
| |
| return pinfo; |
| } |
| |
| |
| static void sigusr2_handler(int signum, siginfo_t* info, void* context) |
| { |
| int status; |
| port_thread_info_t* pinfo; |
| |
| if (!PSD) |
| return; |
| |
| /* We have suspend_mutex locked already */ |
| |
| if (PSD->req_type == THREADREQ_YIELD) |
| { |
| PSD->req_type = THREADREQ_NONE; |
| /* Inform requester */ |
| sem_post(&PSD->yield_sem); |
| return; |
| } |
| |
| if (PSD->req_type == THREADREQ_NONE) |
| return; |
| |
| if (!suspend_init() || |
| (pinfo = suspend_find_thread(PSD->suspendee)) == NULL) |
| { |
| return; /* Return to interrupted THREADREQ_SUS handler */ |
| } |
| |
| if (PSD->req_type == THREADREQ_SUS) |
| { |
| pinfo->suspend_count++; |
| PSD->req_type = THREADREQ_NONE; |
| memcpy(&pinfo->context, context, sizeof(ucontext_t)); |
| /* Inform suspender */ |
| sem_post(&pinfo->wake_sem); |
| |
| do |
| { |
| sigset_t sig_set; |
| sigemptyset(&sig_set); |
| sigsuspend(&sig_set); |
| |
| } while (pinfo->suspend_count > 0); |
| |
| /* We have returned from THREADREQ_RES handler */ |
| memcpy(context, &pinfo->context, sizeof(ucontext_t)); |
| /* Inform suspender */ |
| sem_post(&pinfo->wake_sem); |
| return; |
| } |
| else if (PSD->req_type == THREADREQ_RES) |
| { |
| pinfo->suspend_count--; |
| PSD->req_type = THREADREQ_NONE; |
| return; /* Return to interrupted THREADREQ_SUS handler */ |
| } |
| } |