/* * sys_fork.c - fork the current process * * Authors 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 #if CONFIG_FORK_DEBUG #define fork_debug(...) printk(__VA_ARGS__) #else #define fork_debug(...) /**/ #endif /*********************************************************************************************** * This kernel function implement the "fork" system call. * The calling process descriptor (parent process), and the associated thread descriptor are * replicated in the same cluster as the calling thread, but the new process (child process) * is registered in another target cluster, that will become the process owner. * The child process and the associated main thread will be migrated to the target cluster * later, when the child process makes an "exec" or any other system call. * The target cluster depends on the "fork_user" flag and "fork_cxy" variable that can be * stored in the calling thread descriptor by the specific fork_place() system call. * If not, the sys_fork() function makes a query to the DQDT to select the target cluster. * @ returns child process PID if success / returns -1 if failure **********************************************************************************************/ int sys_fork(); { process_t * parent_process; // pointer on parent process descriptor pid_t parent_pid; // parent process identifier thread_t * parent_thread; // pointer on parent thread descriptor process_t * child_process; // pointer on child process descriptor pid_t child_pid; // child process identifier thread_t * child_thread; // pointer on child main thread descriptor trdid_t child_trdid; // child main thread identifier core_t * child_core; // pointer on core for child main thread lid_t child_core_lid; // core local index for the child main thread cxy_t target_cxy; // final target cluster for forked child process error_t error; cluster_t * parent_cluster = LOCAL_CLUSTER; // get pointers on parent process and thread parent_thread = CURRENT_THREAD; parent_process = parent_thread->process; // check parent process children number if( hal_atomic_add( &parent_process->childs_nr , 1 ) >= CONFIG_PROCESS_CHILDS_MAX_NR ) { printk("ERROR in %s : too much children processes\n", __FUNCTION__); hal_atomic_add ( &parent_process->childs_nr , -1 ); return EAGAIN; } fork_debug("INFO : %s enters for process %d at cycle [%d]\n", __FUNCTION__, parent_process->pid, hal_time_stamp()); // save FPU state in fpu_context if parent process is FPU owner // because we want the child process to share the FPU context if( CURRENT_CORE->fpu_owner == parent_thread ) { hal_fpu_context_save( parent_thread ); fork_debug("INFO : %s save FPU\n", __FUNCTION__); } // Select target cluster for future migration of child process and main thread. // The placement can be specified by user. If placement is not user-defined, // the placement is defined by the DQDT. // The two first processes ("init" and "sh") on boot cluster will not migrate. if( parent_threads->fork_user ) { // user defined placement target_cxy = parent->thread.fork_cxy; parent->thread.fork_cxy = false; } else if( (LPID_FROM_PID(parent_process->pid) < 2 ) && ( parent_cluster->cxy == parent_cluster->boot_cxy ) ) { // 2 first process stay in boot cluster target_cxy = parent_cluster->cxy; } else { // DQDT placement target_cxy = dqdt_get_cluster_for_process(); } fork_debug("INFO : %s select target_cluster = %x\n", __FUNCTION__ , target_cxy ); // allocates memory in local cluster for the child process descriptor child_process = process_alloc(); if( child_process == NULL ) { printk("ERROR in %s : cannot allocate memory for child process\n", __FUNCTION__ ); hal_atomic_add ( &parent_process->childs_nr , -1 ); return EAGAIN; } // get a new PID for child process // it requires an RPC if target cluster is remote xptr_t xp = XPTR( target_cxy , child_process ); if( target_cxy == parent_cluster->cxy ) // local cluster { error = process_pid_alloc( xp , &child_pid ); } else // remote cluster { rpc_process_pid_alloc_server( target_cxy , xp , &error , &child_pid ); } if( error ) { printk("ERROR in %s : cannot allocate PID\n", __FUNCTION__ ); atomic_add ( &parent_process->childs_nr , -1 ); process_destroy( child_process ); return EAGAIN; } // initialize and register the child process descriptor error = process_reference_init( child_process , child_pid , parent_pid ); if( error ) { printk("ERROR in %s : cannot initialise child process\n", __FUNCTION__ ); atomic_add ( &parent_process->childs_nr , -1 ); process_destroy( child_process ); return EAGAIN; } fork_debug("INFO : %s created child process : pid = %x / ppid = %x\n", __FUNCTION__, child_pid , parent_pid ); // set IS_REFERENCE flag in child process descriptor child_process->flags = PDF_IS_REFERENCE; // initialises child process standard files structures // ( root / cwd / bin ) from parent process descriptor spinlock_lock( &parent_process->cwd_lock ); vfs_file_count_up( &parent_process->vfs_root ); child_process->vfs_root = parent_process->vfs_root; vfs_file_count_up( &parent_process->vfs_cwd ); child_process->vfs_cwd = parent_process->vfs_cwd; vfs_file_count_up( &parent_process->vfs_bin ); child_process->vfs_bin = parent_process->vfs_bin; spinlock_unlock( &parent_process->cwd_lock ); // copy the parent process fd_array to the child process fd_array process_fd_fork( child_process , parent_process ); // initialise child process signal manager TODO ??? signal_manager_init( child_process ); fork_debug("INFO : %s duplicated child process from parent process\n", __FUNCTION__ ); // replicates virtual memory manager error = vmm_dup( &child_process->vmm , &parent_process->vmm ); if( error ) { printk("ERROR in %s : cannot duplicate VMM\n", __FUNCTION__ ); atomic_add ( &parent_process->childs_nr , -1 ); process_destroy( child_process ); return EAGAIN; } fork_debug("INFO : %s: parent vmm duplicated in child process\n", __FUNCTION__ ); // create child main thread descriptor in local cluster TODO stack ??? error = thread_user_fork( &child_thread , process , parent_thread ); if( error ) { printk("ERROR in %s : cannot duplicate thread\n", __FUNCTION__ ); atomic_add ( &parent_process->childs_nr , -1 ); process_destroy( child_process ); return EAGAIN; } // register child thread in child process, and get a TRDID spinlock_lock( &child->process->th_lock ); error = process_register_thread( child->process, child->thread , &child_trdid ); spinlock_unlock( &process->th_lock ); if( error ) { printk("ERROR in %s : cannot register thread\n", __FUNCTION__ ); atomic_add ( &parent_process->childs_nr , -1 ); thread_destroy( child_thread ); process_destroy( child_process ); return EAGAIN; } // get a local core to execute child thread child_core_lid = cluster_select_local_core(); // Update child thread descriptor child_thread->core = process->core_tbl[child_core_lid]; child_thread->process = child_process; child_thread->trdid = chid_trdid; fork_debug("INFO : %s initialised child main thread\n", __FUNCTION__ ); // register local child thread into local child process th_tbl[] // we don't use the th_lock because there is no concurrent access ltid_t ltid = LTID_FROM_TRDID( trdid ); child_process->th_tbl[ltid] = XPTR( local_cxy , child_thread ); child_process->threads_nr = 1; // register child thread in scheduler sched_register( child_thread->core , child_thread ); fork_debug("INFO : %s registered main thread in scheduler\n", __FUNCTION__); // update DQDT for the child thread dqdt_update_threads_number( 1 ); fork_debug("INFO :%s completed / parent pid = %x / child pid = %x / at cycle [%d]\n", __FUNCTION__, parent_process->pid, child_process->pid, hal_time_stamp() ); return child_process->pid; } // end sys_fork()