Process description
Process Descriptor (TASK_STRUCT)
The data structure used to describe the process, which can be understood as the properties of a process. such as the state of the process, the identity of the process (PID), etc., are encapsulated in the process descriptor of the data structure, the data structure is defined as Task_struct
Process Control block (PCB)
is a data structure in the core of the operating system that mainly represents process state.
Process status
Fork ()
Fork () returns once on each parent and child process. Returns the PID of the child process in the parent process, returning 0 in the child process.
Fork the code of a child process
#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main (int argc, char * argv[]) { int PID; /* Fork Another process * /pid = fork (); if (PID < 0) {/ * error occurred * /fprintf (stderr, "Fork failed!"); Exit ( -1); } else if (PID = = 0) { /* child process * /printf ("This is a child process!\n"); } else { /* parent Process * /printf ("This is parent process!\n"); /* Parent'll wait for the complete*/ Wait (NULL); printf ("Child complete!\n");} }
Process creation Approximate process
Fork is trapped in the kernel through 0x80 interrupts (system calls), and the system-provided system calls are made to complete the process creation.
Fork.c
Fork#ifdef __arch_want_sys_forksyscall_define0 (fork) {#ifdef Config_mmureturn do_fork (SIGCHLD, 0, 0, NULL, NULL); # Else/* can not be in Nommu mode */return-einval; #endif} #endif//vfork#ifdef __arch_want_sys_vforksyscall_define0 ( Vfork) {return do_fork (Clone_vfork | CLONE_VM | SIGCHLD, 0,0, NULL, NULL);} #endif//clone#ifdef __arch_want_sys_clone#ifdef config_clone_backwardssyscall_define5 (clone, unsigned long, clone_ Flags, unsigned long, newsp, int __user *, parent_tidptr, int, tls_val, int __user *, child_tidptr) #elif defined (CONFIG_CL ONE_BACKWARDS2) Syscall_define5 (clone, unsigned long, newsp, unsigned long, clone_flags, int __user *, parent_tidptr, int _ _user *, child_tidptr, int, tls_val) #elif defined (CONFIG_CLONE_BACKWARDS3) syscall_define6 (CLONE, unsigned long, clone_ Flags, unsigned long, newsp,int, Stack_size,int __user *, Parent_tidptr,int __user *, Child_tidptr,int, Tls_val) #elseSYSC All_define5 (clone, unsigned long, clone_flags, unsigned long, newsp, int __user *, parent_tidptr, int __user *, child_tidptr, int, tls_val) #endif {return do_fork (clone_flags, newsp, 0, Parent_tidptr, child_tidptr);} #endif
By looking at the top of the code, we can clearly see whether the use of fork or vfork to create the process, and ultimately through the Do_fork () method to achieve. Next we can trace to Do_fork () code (part of the code, after the author's refinement):
Long do_fork (unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *pa rent_tidptr, int __user *child_tidptr) {//Create process descriptor pointer struct task_struct *p;//......//copy process descriptor, copy_process () return value is a task_str UCT pointer. p = copy_process (Clone_flags, Stack_start, Stack_size, Child_tidptr, NULL, Trace); if (!is_err (p)) {struct completion vfor K;struct pid *pid;trace_sched_process_fork (current, p);//Get the newly created process descriptor pidpid = Get_task_pid (P, pidtype_pid); nr = Pid_ VNR (PID), if (Clone_flags & Clone_parent_settid) Put_user (NR, parent_tidptr);//If the Vfork () method is called, initialization vfork complete processing information. if (Clone_flags & clone_vfork) {P->vfork_done = &vfork;init_completion (&vfork); get_task_struct (P);} Add the child process to the scheduler, allocate the CPU for it, prepare to execute Wake_up_new_task (p),//fork complete, the child process will start to run if (unlikely (trace)) Ptrace_event_pid (Trace, PID) ;//If it is vfork, join the parent process to the wait queue, waiting for the child process to complete if (Clone_flags & clone_vfork) {if (!wait_for_vfork_done (p, &vfork)) Ptrace_ Event_pid (Ptrace_event_vfork_done, PID);} Put_pid (PID);} else {nr = Ptr_err (p);} return nr;}
Do_fork process
- Call copy_process to copy a process information for a child process
- If it is vfork initialization completes processing information
- Call Wake_up_new_task to add the child process to the scheduler and allocate the CPU
- If it is vfork, the parent process waits for the child process to complete exec to replace its own address space
Copy_process process
Tracking copy_process Code (partial)
static struct task_struct *copy_process (unsigned long clone_flags,unsigned long stack_start,unsigned long stack_size, int __user *child_tidptr,struct pid *pid,int trace) {int retval;//create process descriptor pointer struct task_struct *p;//......//copy current Task_ STRUCTP = dup_task_struct (current),//......//initializes the mutex variable Rt_mutex_init_task (p);//checks whether the number of processes exceeds the limit, defined by the operating system if (Atomic_read (& p->real_cred->user->processes) >=task_rlimit (P, Rlimit_nproc)) {if (P->real_cred->user! = INIT_ USER &&!capable (cap_sys_resource) &&!capable (cap_sys_admin)) goto Bad_fork_free;} ...//Check whether the number of processes exceeds max_threads determined by memory size if (nr_threads >= max_threads) goto bad_fork_cleanup_count;//......//Initialize spin lock Spin_ Lock_init (&p->alloc_lock);//Initialize the suspend signal init_sigpending (&p->pending);//Initialize the CPU timer posix_cpu_timers_init (p);//......//initializes the process data structure and sets the process state to Task_runningretval = Sched_fork (Clone_flags, p);//Copy all process information, including file system, signal processing function, signal, memory management, etc. if ( retval) Goto bad_fork_cleanup_policy;retval = Perf_event_init_task (P); if (retval) goto BAD_FORK_CLEANUP_POLicy;retval = Audit_alloc (P); if (retval) goto bad_fork_cleanup_perf;/* Copy all the process information */shm_init_task (p ); retval = Copy_semundo (Clone_flags, p), if (retval) goto bad_fork_cleanup_audit;retval = Copy_files (Clone_flags, p); if ( retval) Goto Bad_fork_cleanup_semundo;retval = Copy_fs (Clone_flags, p); if (retval) goto Bad_fork_cleanup_files;retval = Copy_sighand (Clone_flags, p), if (retval) goto bad_fork_cleanup_fs;retval = copy_signal (Clone_flags, p), if (retval) goto Bad_fork_cleanup_sighand;retval = copy_mm (Clone_flags, p); if (retval) goto bad_fork_cleanup_signal;retval = Copy_ Namespaces (Clone_flags, p), if (retval) goto bad_fork_cleanup_mm;retval = Copy_io (Clone_flags, p);//Initialize sub-process kernel stack retval = Copy_thread (Clone_flags, Stack_start, Stack_size, p);//assigns new pidif to new process (pid! = &init_struct_pid) {retval =-enomem; PID = Alloc_pid (P->nsproxy->pid_ns_for_children); if (!pid) goto Bad_fork_cleanup_io;} Set child Process Pidp->pid = PID_NR (PID);//......//returns structure Preturn p;
- Call Dup_task_struct to copy the current task_struct
- Check whether the number of processes exceeds the limit
- Initialize spin lock, suspend signal, CPU timer, etc.
- Call Sched_fork to initialize the process data structure and set the process state to task_running
- Copy all process information, including file system, signal processing function, signal, memory management, etc.
- Call Copy_thread to initialize the kernel stack of the child process
- Assigning and setting a new PID for a new process
Dup_task_struct process
static struct task_struct *dup_task_struct (struct task_struct *orig) {struct task_struct *tsk;struct thread_info *ti;int node = Tsk_fork_get_node (orig); int err;//assigns a task_struct node tsk = alloc_task_struct_node (node); if (!tsk) return null;// Assign a Thread_info node that contains the kernel stack of the process, TI is the stack bottom ti = alloc_thread_info_node (tsk, node), and if (!ti) goto free_tsk;//Assigns the value of the bottom of the stack to the stack of the new node tsk-> stack = Ti;//......return tsk;}
Call Alloc_task_struct_node to assign a task_struct node
Call Alloc_thread_info_node Assign a thread_info node, in fact, is assigned a thread_union consortium, the bottom of the stack back to Ti
Union thread_union { struct thread_info thread_info; unsigned long stack[thread_size/sizeof (long)];};
Finally, the value of TI at the bottom of the stack is assigned to the stack of the new node
After the final execution of Dup_task_struct, the child processes are all the same except for the Tsk->stack pointers!
Sched_fork process
Core.c
int sched_fork (unsigned long clone_flags, struct task_struct *p) {unsigned long flags;int CPU = GET_CPU (); __sched_fork (CLO Ne_flags, p);//Set child process state to Task_runningp->state = task_running;//......//assign Cpuset_task_cpu (p, CPU) to child process;p ut_cpu (); return 0;}
We can see that Sched_fork has roughly completed two important tasks, one is to set the child process state to task_running, and the other is to allocate CPU to it
Copy_thread process
int Copy_thread (unsigned long clone_flags, unsigned long sp,unsigned long arg, struct task_struct *p) {//Get register information struct PT_R EGS *childregs = Task_pt_regs (p); struct task_struct *tsk;int err;p->thread.sp = (unsigned long) childregs;p-> Thread.sp0 = (unsigned long) (childregs+1); memset (p->thread.ptrace_bps, 0, sizeof (p->thread.ptrace_bps)); if ( Unlikely (P->flags & Pf_kthread)) {//Kernel thread memset (childregs, 0, sizeof (struct pt_regs));p->thread.ip = ( unsigned long) ret_from_kernel_thread;task_user_gs (p) = __kernel_stack_canary;childregs->ds = __user_ds; Childregs->es = __user_ds;childregs->fs = __KERNEL_PERCPU;CHILDREGS->BX = sp;/* function */childregs->bp = Arg;childregs->orig_ax = -1;childregs->cs = __kernel_cs | GET_KERNEL_RPL (); childregs->flags = X86_eflags_if | X86_eflags_fixed;p->thread.io_bitmap_ptr = Null;return 0;} Copy the current register information to the subprocess *childregs = *current_pt_regs ();//Sub-process EAX 0, so fork returns 0childregs->ax = 0;if (sp) in the child process childregs-> SP = sp;//Child processThe IP is set to Ret_from_fork, so the child process starts from ret_from_fork P->thread.ip = (unsigned long) Ret_from_fork;//......return err;}
Copy_thread This code explains two very important questions for us!
One is why fork returns 0 in a child process because childregs->ax = 0;
this code assigns the eax of the subprocess to 0
The second is to set the IP of the p->thread.ip = (unsigned long) ret_from_fork;
child process to the first address of ret_form_fork, so the child process is executed from ret_from_fork
Summarize
The implementation of the new process stems from the following premises:
- A new stack is assigned to it in the dup_task_struct
- The sched_fork is called, and it is set to task_running
- Copy_thread copies the register context of the parent process to the child process, ensuring that the stack information for the father-child process is consistent
- Set the address of the ret_from_fork to the value of the EIP register
The final child process starts from ret_from_fork
How does the Linux kernel create a new process?