/* * sys_fork.c - Kernel function implementing the "fork" system call. * * Authors Alain Greiner (2016,2017,2018,2019) * * 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 //////////////////// int sys_fork( void ) { process_t * parent_process_ptr; // pointer on local parent process descriptor xptr_t parent_thread_xp; // extended pointer on parent thread descriptor pid_t parent_pid; // parent process identifier thread_t * parent_thread_ptr; // local pointer on local parent thread descriptor cxy_t parent_cxy; // parent thread cluster pid_t child_pid; // child process identifier thread_t * child_thread_ptr; // local pointer on remote child thread descriptor cxy_t child_cxy; // target cluster for forked child process xptr_t ref_process_xp; // extended pointer on reference parent process cxy_t ref_process_cxy; // cluster of reference parent process process_t * ref_process_ptr; // local pointer on reference parent process error_t error; // get pointers on local parent process and thread parent_thread_ptr = CURRENT_THREAD; parent_thread_xp = XPTR( local_cxy , parent_thread_ptr ); parent_process_ptr = parent_thread_ptr->process; parent_pid = parent_process_ptr->pid; parent_cxy = local_cxy; #if (DEBUG_SYS_FORK || CONFIG_INSTRUMENTATION_SYSCALLS) uint64_t tm_start = hal_get_cycles(); #endif #if DEBUG_SYS_FORK if( DEBUG_SYS_FORK < (uint32_t)tm_start ) printk("\n[%s] thread[%x,%x] enter / cycle = %d\n", __FUNCTION__, parent_pid, parent_thread_ptr->trdid, (uint32_t)tm_start ); #endif // get infos on reference parent process ref_process_xp = parent_process_ptr->ref_xp; ref_process_cxy = GET_CXY( ref_process_xp ); ref_process_ptr = GET_PTR( ref_process_xp ); // check parent process children number from reference xptr_t children_xp = XPTR( ref_process_cxy , &ref_process_ptr->children_nr ); if( hal_remote_atomic_add( children_xp , 1 ) >= CONFIG_PROCESS_MAX_CHILDREN ) { #if DEBUG_SYSCALLS_ERROR printk("\n[ERROR] in %s : thread[%x,%x] cannot fork : too much children\n", __FUNCTION__, parent_pid, parent_thread_ptr->trdid ); #endif hal_remote_atomic_add ( children_xp , -1 ); parent_thread_ptr->errno = EAGAIN; return -1; } // Select target cluster for child process and main thread. // If placement is not user-defined, it is defined by the DQDT. if( parent_thread_ptr->fork_user ) { child_cxy = parent_thread_ptr->fork_cxy; parent_thread_ptr->fork_user = false; } else // DQDT placement { child_cxy = dqdt_get_cluster_for_process(); } #if (DEBUG_SYS_FORK & 1 ) if( DEBUG_SYS_FORK < (uint32_t)tm_start ) printk("\n[%s] thread[%x,%x] selected cluster %x\n", __FUNCTION__, parent_pid, parent_thread_ptr->trdid, child_cxy ); #endif // call process_make_fork in target cluster if( child_cxy == local_cxy ) { error = process_make_fork( ref_process_xp, parent_thread_xp, &child_pid, &child_thread_ptr ); } else { rpc_process_make_fork_client( child_cxy, ref_process_xp, parent_thread_xp, &child_pid, &child_thread_ptr, &error ); } if( error ) { #if DEBUG_SYSCALLS_ERROR printk("\n[ERROR] in %s : thread[%x,%x] cannot fork\n", __FUNCTION__, parent_pid, parent_thread_ptr->trdid ); #endif parent_thread_ptr->errno = EAGAIN; return -1; } // set remote child FPU_context from parent_thread register values // only when the parent thread is the FPU owner if( CURRENT_THREAD->core->fpu_owner == parent_thread_ptr ) { hal_fpu_context_save( XPTR( child_cxy , child_thread_ptr ) ); } // set the remote child CPU context from parent register values, // set the remote child uzone from // replicates the parent thread kernel stack to the child thread descriptor, // and finally unblock the child thread. hal_cpu_context_fork( XPTR( child_cxy , child_thread_ptr ) ); // From this point, both parent and child threads execute the following code, // but child thread will only execute it after being unblocked by parent thread. // They can be distinguished by the (CURRENT_THREAD,local_cxy) values. // - parent return child PID to user application. // - child return 0 to user application thread_t * current = CURRENT_THREAD; #if (DEBUG_SYS_FORK || CONFIG_INSTRUMENTATION_SYSCALLS) uint64_t tm_end = hal_get_cycles(); #endif if( (current == parent_thread_ptr) && (local_cxy == parent_cxy) ) // parent thread { #if DEBUG_SYS_FORK if( DEBUG_SYS_FORK < (uint32_t)tm_end ) printk("\n[%s] parent thread[%x,%x] exit / child_pid %x / cycle %d\n", __FUNCTION__, current->process->pid, current->trdid, child_pid, (uint32_t)tm_end ); #endif // only parent display the parent and child VMM #if (DEBUG_SYS_FORK & 1 ) if( DEBUG_SYS_FORK < (uint32_t)tm_end ) { process_t * child_process_ptr = hal_remote_lpt( XPTR( child_cxy , &child_thread_ptr->process ) ); xptr_t child_process_xp = XPTR( child_cxy , child_process_ptr ); hal_vmm_display( ref_process_xp , true ); hal_vmm_display( child_process_xp , true ); } #endif // only parent contribute to syscalls instrumentation #if CONFIG_INSTRUMENTATION_SYSCALLS hal_atomic_add( &syscalls_cumul_cost[SYS_FORK] , tm_end - tm_start ); hal_atomic_add( &syscalls_occurences[SYS_FORK] , 1 ); #endif return child_pid; } else // child_thread { #if DEBUG_SYS_FORK if( DEBUG_SYS_FORK < (uint32_t)tm_end ) printk("\n[%s] child thread[%x,%x] exit / child_pid %x / cycle %d\n", __FUNCTION__, current->process->pid, current->trdid, child_pid, (uint32_t)tm_end ); #endif return 0; } } // end sys_fork()