/* * thread.c - implementation of thread operations (user & kernel) * * Author Ghassan Almaless (2008,2009,2010,2011,2012) * Mohamed Lamine Karaoui (2015) * Alain Greiner (2016) * * 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; ////////////////////////////////////////////////////////////////////////////////////// // global variables for display / must be consistant with enum in "thread.h" ////////////////////////////////////////////////////////////////////////////////////// const char* thread_type_name[THREAD_TYPES_NR] = { "USER", "RPC" "KERNEL", "IDLE", }; ////////////////////////////////////////////////////////////////////////////////////// // This static function returns a printable string for the thread type. ////////////////////////////////////////////////////////////////////////////////////// char * thread_type_str( uint32_t type ) { if ( type == THREAD_USER ) return "THREAD_USER"; else if( type == THREAD_RPC ) return "THREAD_RPC"; else if( type == THREAD_DEV ) return "THREAD_DEV"; else if( type == THREAD_KERNEL ) return "THREAD_KERNEL"; else if( type == THREAD_IDLE ) return "THREAD_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 ) { printk("\n[ERROR] in %s : no memory for thread descriptor\n", __FUNCTION__ ); return NULL; } else { return (thread_t *)ppm_page2base( page ); } } // end thread_alloc() ///////////////////////////////////////////////////////////////////////////////////// // 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; } // end thread_init() ///////////////////////////////////////////////////////// error_t thread_user_create( thread_t ** new_thread, pthread_attr_t * attr, intptr_t u_stack_base, uint32_t u_stack_size ) { 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 kmem_req_t req; // kmem request (for release) thread_dmsg("\n[INFO] %s : enters\n", __FUNCTION__ ); cluster_t * local_cluster = LOCAL_CLUSTER; // select a target core in local cluster if( attr->flags & PT_FLAG_CORE_DEFINED ) core_lid = attr->lid; else core_lid = cluster_select_local_core(); // check core local index if( core_lid >= local_cluster->cores_nr ) return EINVAL; // get process descriptor local copy process = process_get_local_copy( attr->pid ); if( process == NULL ) return ENOMEM; // allocates memory tor thread descriptor thread = thread_alloc(); if( thread == NULL ) return ENOMEM; // initializes thread descriptor error = thread_init( thread, process, THREAD_USER, attr->entry_func, attr->entry_args, core_lid, u_stack_base, u_stack_size ); if( error ) // release allocated memory for thread descriptor { req.type = KMEM_PAGE; req.ptr = ppm_base2page( thread ); kmem_free( &req ); return EINVAL; } // set LOADABLE flag thread->flags = THREAD_FLAG_LOADABLE; // set DETACHED flag if required if( attr->flags & PT_FLAG_DETACH ) thread->flags |= THREAD_FLAG_DETACHED; // allocate & initialise CPU context error = hal_cpu_context_create( thread ); if( error ) return ENOMEM; // allocate & initialise FPU context error = hal_fpu_context_create( thread ); if( error ) 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; } // end thread_user_create() ///////////////////////////////////////////////// error_t thread_user_fork( thread_t ** new_thread, process_t * process, intptr_t u_stack_base, uint32_t u_stack_size ) { error_t error; thread_t * thread; // pointer on new thread descriptor lid_t core_lid; // selected core local index kmem_req_t req; // kmem request (for release) thread_dmsg("\n[INFO] %s : enters\n", __FUNCTION__ ); // select a target core in local cluster core_lid = cluster_select_local_core(); // get pointer on calling thread descriptor thread_t * this = CURRENT_THREAD; // allocated memory for new thread descriptor thread = thread_alloc(); if( thread == NULL ) return ENOMEM; // initializes thread descriptor error = thread_init( thread, process, THREAD_USER, this->entry_func, this->entry_args, core_lid, u_stack_base, u_stack_size ); if( error ) // release allocated memory for thread descriptor { req.type = KMEM_PAGE; req.ptr = ppm_base2page( thread ); kmem_free( &req ); return EINVAL; } // set ATTACHED flag if set in this thread if( this->flags & THREAD_FLAG_DETACHED ) thread->flags = THREAD_FLAG_DETACHED; // allocate & initialise CPU context from calling thread error = hal_cpu_context_copy( thread , this ); if( error ) return ENOMEM; // allocate & initialise FPU context from calling thread error = hal_fpu_context_copy( thread , this ); if( error ) return ENOMEM; thread_dmsg("INFO : %s 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; } // end thread_user_fork() ///////////////////////////////////////////////////////// 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 kmem_req_t req; // kmem request (for release) 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" ); // allocated memory for new thread descriptor thread = thread_alloc(); if( thread == NULL ) return ENOMEM; // initializes 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 { req.type = KMEM_PAGE; req.ptr = ppm_base2page( thread ); kmem_free( &req ); return EINVAL; } // allocate & initialise 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; } // end thread_kernel_create() /////////////////////////////////////////////////// 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; } // end thread_kernel_init() /////////////////////////////////////////////////////////////////////////////////////// // 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; uint32_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_time_stamp(); // 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 ); // invalidate thread descriptor thread->signature = 0; // release memory for thread descriptor kmem_req_t req; req.type = KMEM_PAGE; req.ptr = ppm_base2page( thread ); kmem_free(&req); tm_end = hal_time_stamp(); thread_dmsg("\n[INFO] %s : exit for thread %x in process %x / duration = %d\n", __FUNCTION__, thread->trdid , process->pid , tm_end - tm_start ); } // end thread_destroy() ///////////////////////////////////////////////// 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_time_stamp(); 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() { uint32_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; } // end thread_exit() ///////////////////////////////////// void thread_block( thread_t * thread, uint32_t cause ) { // set blocking cause hal_atomic_or( &thread->blocked , cause ); } // end thread_block() //////////////////////////////////// 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 ); } // end thread_unblock() ///////////////////////////////////// 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 reschedule the target thread core. dev_icu_send_ipi( local_cxy , target->core->lid ); } // end thread_kill() /////////////////////// void thread_idle_func() { lid_t lid = CURRENT_CORE->lid; while( 1 ) { thread_dmsg("\n[INFO] %s : core[%x][%d] goes to sleep at cycle %d\n", __FUNCTION__ , local_cxy , lid , hal_time_stamp() ); // force core to sleeping state hal_core_sleep(); thread_dmsg("\n[INFO] %s : core[%x][%d] wake up at cycle %d\n", __FUNCTION__ , local_cxy , lid , hal_time_stamp() ); // acknowledge IRQ dev_icu_irq_handler(); // force scheduling sched_yield(); } } // end thread_idle()