/* * thread.c - implementation of thread operations (user & kernel) * * Author Ghassan Almaless (2008,2009,2010,2011,2012) * Alain Greiner (2016,2017) * * Copyright (c) UPMC Sorbonne Universites * * This file is part of ALMOS-MKH. * * ALMOS-MKH is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2.0 of the License. * * ALMOS-MKH is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ALMOS-MKH; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////////////// // Extern global variables ////////////////////////////////////////////////////////////////////////////////////// extern process_t process_zero; ////////////////////////////////////////////////////////////////////////////////////// // This function returns a printable string for the thread type. ////////////////////////////////////////////////////////////////////////////////////// char * thread_type_str( uint32_t type ) { if ( type == THREAD_USER ) return "USER"; else if( type == THREAD_RPC ) return "RPC"; else if( type == THREAD_DEV ) return "DEV"; else if( type == THREAD_KERNEL ) return "KERNEL"; else if( type == THREAD_IDLE ) return "IDLE"; else return "undefined"; } ///////////////////////////////////////////////////////////////////////////////////// // This static function allocates physical memory for a thread descriptor. // It can be called by the three functions: // - thread_user_create() // - thread_user_fork() // - thread_kernel_create() ///////////////////////////////////////////////////////////////////////////////////// // @ return pointer on thread descriptor if success / return NULL if failure. ///////////////////////////////////////////////////////////////////////////////////// static thread_t * thread_alloc() { page_t * page; // pointer on page descriptor containing thread descriptor kmem_req_t req; // kmem request // allocates memory for thread descriptor + kernel stack req.type = KMEM_PAGE; req.size = CONFIG_THREAD_DESC_ORDER; req.flags = AF_KERNEL | AF_ZERO; page = kmem_alloc( &req ); // return pointer on new thread descriptor if( page == NULL ) return NULL; else return (thread_t *)ppm_page2vaddr( page ); } ///////////////////////////////////////////////////////////////////////////////////// // This static function releases the physical memory for a thread descriptor. // It is called by the three functions: // - thread_user_create() // - thread_user_fork() // - thread_kernel_create() ///////////////////////////////////////////////////////////////////////////////////// // @ thread : pointer on thread descriptor. ///////////////////////////////////////////////////////////////////////////////////// static void thread_release( thread_t * thread ) { kmem_req_t req; req.type = KMEM_PAGE; req.ptr = ppm_vaddr2page( thread ); kmem_free( &req ); } ///////////////////////////////////////////////////////////////////////////////////// // This static function initializes a thread descriptor (kernel or user). // It can be called by the four functions: // - thread_user_create() // - thread_user_fork() // - thread_kernel_create() // - thread_user_init() ///////////////////////////////////////////////////////////////////////////////////// // @ thread : pointer on thread descriptor // @ process : pointer on process descriptor. // @ type : thread type. // @ func : pointer on thread entry function. // @ args : pointer on thread entry function arguments. // @ core_lid : target core local index. // @ u_stack_base : stack base (user thread only) // @ u_stack_size : stack base (user thread only) ///////////////////////////////////////////////////////////////////////////////////// static error_t thread_init( thread_t * thread, process_t * process, thread_type_t type, void * func, void * args, lid_t core_lid, intptr_t u_stack_base, uint32_t u_stack_size ) { error_t error; trdid_t trdid; // allocated thread identifier cluster_t * local_cluster = LOCAL_CLUSTER; // register new thread in process descriptor, and get a TRDID spinlock_lock( &process->th_lock ); error = process_register_thread( process, thread , &trdid ); spinlock_unlock( &process->th_lock ); if( error ) { printk("\n[ERROR] in %s : cannot get TRDID\n", __FUNCTION__ ); return EINVAL; } // Initialize new thread descriptor thread->trdid = trdid; thread->type = type; thread->quantum = 0; // TODO thread->ticks_nr = 0; // TODO thread->time_last_check = 0; thread->core = &local_cluster->core_tbl[core_lid]; thread->process = process; thread->local_locks = 0; list_root_init( &thread->locks_root ); thread->remote_locks = 0; xlist_root_init( XPTR( local_cxy , &thread->xlocks_root ) ); thread->u_stack_base = u_stack_base; thread->u_stack_size = u_stack_size; thread->k_stack_base = (intptr_t)thread; thread->k_stack_size = CONFIG_THREAD_DESC_SIZE; thread->entry_func = func; // thread entry point thread->entry_args = args; // thread function arguments thread->flags = 0; // all flags reset thread->signals = 0; // no pending signal thread->errno = 0; // no error detected thread->fork_user = 0; // no fork required thread->fork_cxy = 0; // thread blocked thread->blocked = THREAD_BLOCKED_GLOBAL; // reset children list xlist_root_init( XPTR( local_cxy , &thread->children_root ) ); thread->children_nr = 0; // reset sched list and brothers list list_entry_init( &thread->sched_list ); xlist_entry_init( XPTR( local_cxy , &thread->brothers_list ) ); // reset thread info memset( &thread->info , 0 , sizeof(thread_info_t) ); // initialise signature thread->signature = THREAD_SIGNATURE; // update local DQDT dqdt_local_update_threads( 1 ); // register new thread in core scheduler sched_register_thread( thread->core , thread ); return 0; } ///////////////////////////////////////////////////////// error_t thread_user_create( pid_t pid, void * start_func, void * start_arg, pthread_attr_t * attr, thread_t ** new_thread ) { error_t error; thread_t * thread; // pointer on created thread descriptor process_t * process; // pointer to local process descriptor lid_t core_lid; // selected core local index vseg_t * vseg; // stack vseg thread_dmsg("\n[INFO] %s : enters for process %x\n", __FUNCTION__ , pid ); // get process descriptor local copy process = process_get_local_copy( pid ); if( process == NULL ) { printk("\n[ERROR] in %s : cannot get process descriptor %x\n", __FUNCTION__ , pid ); return ENOMEM; } // select a target core in local cluster if( attr->attributes & PT_ATTR_CORE_DEFINED ) core_lid = attr->lid; else core_lid = cluster_select_local_core(); // check core local index if( core_lid >= LOCAL_CLUSTER->cores_nr ) { printk("\n[ERROR] in %s : illegal core index attribute = %d\n", __FUNCTION__ , core_lid ); return EINVAL; } // allocate a stack from local VMM vseg = vmm_create_vseg( process, 0 , 0 , VSEG_TYPE_STACK ); if( vseg == NULL ) { printk("\n[ERROR] in %s : cannot create stack vseg\n", __FUNCTION__ ); return ENOMEM; } // allocate memory for thread descriptor thread = thread_alloc(); if( thread == NULL ) { printk("\n[ERROR] in %s : cannot create new thread\n", __FUNCTION__ ); vmm_remove_vseg( vseg ); return ENOMEM; } // initialize thread descriptor error = thread_init( thread, process, THREAD_USER, start_func, start_arg, core_lid, vseg->min, vseg->max - vseg->min ); if( error ) { printk("\n[ERROR] in %s : cannot initialize new thread\n", __FUNCTION__ ); vmm_remove_vseg( vseg ); thread_release( thread ); return EINVAL; } // set LOADABLE flag thread->flags = THREAD_FLAG_LOADABLE; // set DETACHED flag if required if( attr->attributes & PT_ATTR_DETACH ) thread->flags |= THREAD_FLAG_DETACHED; // allocate & initialize CPU context error = hal_cpu_context_create( thread ); if( error ) { printk("\n[ERROR] in %s : cannot create CPU context\n", __FUNCTION__ ); vmm_remove_vseg( vseg ); thread_release( thread ); return ENOMEM; } // allocate & initialize FPU context error = hal_fpu_context_create( thread ); if( error ) { printk("\n[ERROR] in %s : cannot create FPU context\n", __FUNCTION__ ); vmm_remove_vseg( vseg ); thread_release( thread ); return ENOMEM; } thread_dmsg("\n[INFO] %s : exit / trdid = %x / process %x / core = %d\n", __FUNCTION__ , thread->trdid , process->pid , core_lid ); *new_thread = thread; return 0; } ////////////////////////////////////////////// error_t thread_user_fork( process_t * process, thread_t ** new_thread ) { error_t error; thread_t * thread; // pointer on new thread descriptor lid_t core_lid; // selected core local index vseg_t * vseg; // stack vseg thread_dmsg("\n[INFO] %s : enters\n", __FUNCTION__ ); // allocate a stack from local VMM vseg = vmm_create_vseg( process, 0 , 0 , VSEG_TYPE_STACK ); if( vseg == NULL ); { printk("\n[ERROR] in %s : cannot create stack vseg\n", __FUNCTION__ ); return ENOMEM; } // select a target core in local cluster core_lid = cluster_select_local_core(); // get pointer on calling thread descriptor thread_t * this = CURRENT_THREAD; // allocate memory for new thread descriptor thread = thread_alloc(); if( thread == NULL ) { printk("\n[ERROR] in %s : cannot allocate new thread\n", __FUNCTION__ ); vmm_remove_vseg( vseg ); return ENOMEM; } // initialize thread descriptor error = thread_init( thread, process, THREAD_USER, this->entry_func, this->entry_args, core_lid, vseg->min, vseg->max - vseg->min ); if( error ) { printk("\n[ERROR] in %s : cannot initialize new thread\n", __FUNCTION__ ); vmm_remove_vseg( vseg ); thread_release( thread ); return EINVAL; } // set ATTACHED flag if set in this thread if( this->flags & THREAD_FLAG_DETACHED ) thread->flags = THREAD_FLAG_DETACHED; // allocate & initialize CPU context from calling thread error = hal_cpu_context_copy( thread , this ); if( error ) { printk("\n[ERROR] in %s : cannot create CPU context\n", __FUNCTION__ ); vmm_remove_vseg( vseg ); thread_release( thread ); return ENOMEM; } // allocate & initialize FPU context from calling thread error = hal_fpu_context_copy( thread , this ); if( error ) { printk("\n[ERROR] in %s : cannot create CPU context\n", __FUNCTION__ ); vmm_remove_vseg( vseg ); thread_release( thread ); return ENOMEM; } thread_dmsg("\n[INFO] %s : exit / thread %x for process %x on core %d in cluster %x\n", __FUNCTION__, thread->trdid, process->pid, core_lid, local_cxy ); *new_thread = thread; return 0; } ///////////////////////////////////////////////////////// error_t thread_kernel_create( thread_t ** new_thread, thread_type_t type, void * func, void * args, lid_t core_lid ) { error_t error; thread_t * thread; // pointer on new thread descriptor thread_dmsg("\n[INFO] %s : enters for type %s in cluster %x\n", __FUNCTION__ , thread_type_str( type ) , local_cxy ); assert( ( (type == THREAD_KERNEL) || (type == THREAD_RPC) || (type == THREAD_IDLE) || (type == THREAD_DEV) ) , __FUNCTION__ , "illegal thread type" ); assert( (core_lid < LOCAL_CLUSTER->cores_nr) , __FUNCTION__ , "illegal core_lid" ); // allocate memory for new thread descriptor thread = thread_alloc(); if( thread == NULL ) return ENOMEM; // initialize thread descriptor error = thread_init( thread, &process_zero, type, func, args, core_lid, 0 , 0 ); // no user stack for a kernel thread if( error ) // release allocated memory for thread descriptor { thread_release( thread ); return EINVAL; } // allocate & initialize CPU context hal_cpu_context_create( thread ); thread_dmsg("\n[INFO] %s : exit in cluster %x / trdid = %x / core_lid = %d\n", __FUNCTION__ , local_cxy , thread->trdid , core_lid ); *new_thread = thread; return 0; } /////////////////////////////////////////////////// error_t thread_kernel_init( thread_t * thread, thread_type_t type, void * func, void * args, lid_t core_lid ) { assert( ( (type == THREAD_KERNEL) || (type == THREAD_RPC) || (type == THREAD_IDLE) || (type == THREAD_DEV) ) , __FUNCTION__ , "illegal thread type" ); if( core_lid >= LOCAL_CLUSTER->cores_nr ) { printk("\n[PANIC] in %s : illegal core_lid / cores = %d / lid = %d / cxy = %x\n", __FUNCTION__ , LOCAL_CLUSTER->cores_nr , core_lid , local_cxy ); hal_core_sleep(); } error_t error = thread_init( thread, &process_zero, type, func, args, core_lid, 0 , 0 ); // no user stack for a kernel thread // allocate & initialize CPU context if success if( error == 0 ) hal_cpu_context_create( thread ); return error; } /////////////////////////////////////////////////////////////////////////////////////// // TODO: check that all memory dynamically allocated during thread execution // has been released, using a cache of mmap and malloc requests. [AG] /////////////////////////////////////////////////////////////////////////////////////// void thread_destroy( thread_t * thread ) { uint32_t tm_start; uint32_t tm_end; reg_t state; process_t * process = thread->process; core_t * core = thread->core; thread_dmsg("\n[INFO] %s : enters for thread %x in process %x / type = %s\n", __FUNCTION__ , thread->trdid , process->pid , thread_type_str( thread->type ) ); assert( (thread->children_nr == 0) , __FUNCTION__ , "still attached children" ); assert( (thread->local_locks == 0) , __FUNCTION__ , "all local locks not released" ); assert( (thread->remote_locks == 0) , __FUNCTION__ , "all remote locks not released" ); tm_start = hal_get_cycles(); // update intrumentation values uint32_t pgfaults = thread->info.pgfault_nr; uint32_t u_errors = thread->info.u_err_nr; uint32_t m_errors = thread->info.m_err_nr; process->vmm.pgfault_nr += pgfaults; process->vmm.u_err_nr += u_errors; process->vmm.m_err_nr += m_errors; // release memory allocated for CPU context and FPU context hal_cpu_context_destroy( thread ); hal_fpu_context_destroy( thread ); // release FPU if required // TODO This should be done before calling thread_destroy() hal_disable_irq( &state ); if( core->fpu_owner == thread ) { core->fpu_owner = NULL; hal_fpu_disable(); } hal_restore_irq( state ); // remove thread from process th_tbl[] // TODO This should be done before calling thread_destroy() ltid_t ltid = LTID_FROM_TRDID( thread->trdid ); spinlock_lock( &process->th_lock ); process->th_tbl[ltid] = XPTR_NULL; process->th_nr--; spinlock_unlock( &process->th_lock ); // update local DQDT dqdt_local_update_threads( -1 ); // invalidate thread descriptor thread->signature = 0; // release memory for thread descriptor thread_release( thread ); tm_end = hal_get_cycles(); thread_dmsg("\n[INFO] %s : exit for thread %x in process %x / duration = %d\n", __FUNCTION__, thread->trdid , process->pid , tm_end - tm_start ); } ///////////////////////////////////////////////// void thread_child_parent_link( xptr_t xp_parent, xptr_t xp_child ) { // get extended pointers on children list root cxy_t parent_cxy = GET_CXY( xp_parent ); thread_t * parent_ptr = (thread_t *)GET_PTR( xp_parent ); xptr_t root = XPTR( parent_cxy , &parent_ptr->children_root ); // get extended pointer on children list entry cxy_t child_cxy = GET_CXY( xp_child ); thread_t * child_ptr = (thread_t *)GET_PTR( xp_child ); xptr_t entry = XPTR( child_cxy , &child_ptr->brothers_list ); // set the link xlist_add_first( root , entry ); hal_remote_atomic_add( XPTR( parent_cxy , &parent_ptr->children_nr ) , 1 ); } /////////////////////////////////////////////////// void thread_child_parent_unlink( xptr_t xp_parent, xptr_t xp_child ) { // get extended pointer on children list lock cxy_t parent_cxy = GET_CXY( xp_parent ); thread_t * parent_ptr = (thread_t *)GET_PTR( xp_parent ); xptr_t lock = XPTR( parent_cxy , &parent_ptr->children_lock ); // get extended pointer on children list entry cxy_t child_cxy = GET_CXY( xp_child ); thread_t * child_ptr = (thread_t *)GET_PTR( xp_child ); xptr_t entry = XPTR( child_cxy , &child_ptr->brothers_list ); // get the lock remote_spinlock_lock( lock ); // remove the link xlist_unlink( entry ); hal_remote_atomic_add( XPTR( parent_cxy , &parent_ptr->children_nr ) , -1 ); // release the lock remote_spinlock_unlock( lock ); } ///////////////////////////////////////////////// inline void thread_set_signal( thread_t * thread, uint32_t mask ) { hal_atomic_or( &thread->signals , mask ); } /////////////////////////////////////////////////// inline void thread_reset_signal( thread_t * thread, uint32_t mask ) { hal_atomic_and( &thread->signals , ~mask ); } ////////////////////////////////// inline bool_t thread_is_joinable() { thread_t * this = CURRENT_THREAD; return( (this->brothers_list.next != XPTR_NULL) && (this->brothers_list.pred != XPTR_NULL) ); } ////////////////////////////////// inline bool_t thread_is_runnable() { thread_t * this = CURRENT_THREAD; return( this->blocked == 0 ); } //////////////////////////////// inline bool_t thread_can_yield() { thread_t * this = CURRENT_THREAD; return ( (this->local_locks == 0) && (this->remote_locks == 0) ); } /////////////////////////// bool_t thread_check_sched() { thread_t * this = CURRENT_THREAD; // check locks count if( (this->local_locks != 0) || (this->remote_locks != 0) ) return false; // compute elapsed time, taking into account 32 bits register wrap uint32_t elapsed; uint32_t time_now = hal_get_cycles(); uint32_t time_last = this->time_last_check; if( time_now < time_last ) elapsed = (0xFFFFFFFF - time_last) + time_now; else elapsed = time_now - time_last; // update thread time this->time_last_check = time_now; // check elapsed time if( elapsed < CONFIG_CORE_CHECK_EVERY ) return false; else return true; } ///////////////////// error_t thread_exit() { reg_t sr_save; thread_t * this = CURRENT_THREAD; // test if this thread can be descheduled if( !thread_can_yield() ) { printk("ERROR in %s : thread %x in process %x on core %d in cluster %x\n" " did not released all locks\n", __FUNCTION__ , this->trdid , this->process->pid , CURRENT_CORE->lid , local_cxy ); return EINVAL; } if( this->flags & THREAD_FLAG_DETACHED ) { // if detached set signal and set blocking cause atomically hal_disable_irq( &sr_save ); thread_set_signal( this , THREAD_SIG_KILL ); thread_block( this , THREAD_BLOCKED_EXIT ); hal_restore_irq( sr_save ); } else { // if attached, set blocking cause thread_block( this , THREAD_BLOCKED_EXIT ); } // deschedule sched_yield(); return 0; } ///////////////////////////////////// void thread_block( thread_t * thread, uint32_t cause ) { // set blocking cause hal_atomic_or( &thread->blocked , cause ); } //////////////////////////////////// void thread_unblock( xptr_t thread, uint32_t cause ) { // get thread cluster and local pointer cxy_t cxy = GET_CXY( thread ); thread_t * ptr = (thread_t *)GET_PTR( thread ); // reset blocking cause hal_remote_atomic_and( XPTR( cxy , &ptr->blocked ) , ~cause ); } ///////////////////////////////////// void thread_kill( thread_t * target ) { // set SIG_KILL signal in target thread descriptor thread_set_signal( target , THREAD_SIG_KILL ); // set the global blocked bit in target thread descriptor. thread_block( target , THREAD_BLOCKED_GLOBAL ); // send an IPI to schedule the target thread core. dev_pic_send_ipi( local_cxy , target->core->lid ); } /////////////////////// void thread_idle_func() { #if CONFIG_IDLE_DEBUG lid_t lid = CURRENT_CORE->lid; #endif while( 1 ) { idle_dmsg("\n[INFO] %s : core[%x][%d] goes to sleep at cycle %d\n", __FUNCTION__ , local_cxy , lid , hal_get_cycles() ); // force core to sleeping state hal_core_sleep(); idle_dmsg("\n[INFO] %s : core[%x][%d] wake up at cycle %d\n", __FUNCTION__ , local_cxy , lid , hal_get_cycles() ); // force scheduling sched_yield(); } } ///////////////////////////////////////////////// void thread_user_time_update( thread_t * thread ) { // TODO printk("\n[WARNING] function %s not implemented\n", __FUNCTION__ ); } /////////////////////////////////////////////////// void thread_kernel_time_update( thread_t * thread ) { // TODO printk("\n[WARNING] function %s not implemented\n", __FUNCTION__ ); } //////////////////////////////////////////////// void thread_signals_handle( thread_t * thread ) { // TODO printk("\n[WARNING] function %s not implemented\n", __FUNCTION__ ); } ///////////////////////////////////// xptr_t thread_get_xptr( pid_t pid, trdid_t trdid ) { cxy_t target_cxy; // target thread cluster identifier ltid_t target_thread_ltid; // target thread local index thread_t * target_thread_ptr; // target thread local pointer xptr_t target_process_xp; // extended pointer on target process descriptor process_t * target_process_ptr; // local pointer on target process descriptor pid_t target_process_pid; // target process identifier xlist_entry_t root; // root of list of process in target cluster xptr_t lock_xp; // extended pointer on lock protecting this list // get target cluster identifier and local thread identifier target_cxy = CXY_FROM_TRDID( trdid ); target_thread_ltid = LTID_FROM_TRDID( trdid ); // get root of list of process descriptors in target cluster hal_remote_memcpy( XPTR( local_cxy , &root ), XPTR( target_cxy , &LOCAL_CLUSTER->pmgr.local_root ), sizeof(xlist_entry_t) ); // get extended pointer on lock protecting the list of processes lock_xp = XPTR( target_cxy , &LOCAL_CLUSTER->pmgr.local_lock ); // take the lock protecting the list of processes in target cluster remote_spinlock_lock( lock_xp ); // loop on list of process in target cluster to find the PID process xptr_t iter; bool_t found = false; XLIST_FOREACH( XPTR( target_cxy , &LOCAL_CLUSTER->pmgr.local_root ) , iter ) { target_process_xp = XLIST_ELEMENT( iter , process_t , local_list ); target_process_ptr = (process_t *)GET_PTR( target_process_xp ); target_process_pid = hal_remote_lw( XPTR( target_cxy , &target_process_ptr->pid ) ); if( target_process_pid == pid ) { found = true; break; } } // release the lock protecting the list of processes in target cluster remote_spinlock_unlock( lock_xp ); // check target thread found if( found == false ) { return XPTR_NULL; } // get target thread local pointer xptr_t xp = XPTR( target_cxy , &target_process_ptr->th_tbl[target_thread_ltid] ); target_thread_ptr = (thread_t *)hal_remote_lpt( xp ); if( target_thread_ptr == NULL ) { return XPTR_NULL; } return XPTR( target_cxy , target_thread_ptr ); }