Linux fork system call

In Linux, the fork system call can be used to create a child process. The child process will inherit most of the properties of the parent process, and then the child process will call the exec cluster function to run other programs.
Let's take a concrete example to illustrate the relationship between parent-child processes

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void)
{
	
	pid_t pid = -1; 
	char *const argv[] = {"Linux","Hello",NULL};
	int status;
	pid = fork();  // Call fork system to create subprocess
	
	if(pid == 0) // Subprocess
	{
		if(execv("hello.out",argv) < 0)
		{
			perror("exec");
		}
	}
	else if(pid > 0)  // Parent process
	{
		sleep(5);  // The parent process sleeps for 5 seconds and waits for the child process to exit
		if(wait(&status) < -1)      // If the return value of wait is - 1, the recovery fails
		{
			perror("wait");
		}
		if(WIFEXITED(status))
		{
			printf("child exit\r\n");
		}

	}
	else  if(pid < 0) // Process creation failed
	{
		printf("fork error\r\n");
	}
	return 0;
}

The fork system call returns three values
PID > 0 indicates that the process is the parent process
pid == 0 indicates that the process is a child process
PID < 0 means fork failed
When fork is successful, the kernel will copy some attributes of the parent process to the child process, such as file descriptor, signal and other attributes. The specific copy can be found in the kernel source code. copy_process is the function used by the kernel to copy the process, and it is also the main function of fork system call.

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;
	struct task_struct *p;
	/*******************Omit before************************************/
	/* Perform scheduler related setup. Assign this task to a CPU. */
	retval = sched_fork(clone_flags, p);
	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);
	if (retval)
		goto bad_fork_cleanup_namespaces;
	retval = copy_thread(clone_flags, stack_start, stack_size, p);
	if (retval)
		goto bad_fork_cleanup_io;

	if (pid != &init_struct_pid) {
		pid = alloc_pid(p->nsproxy->pid_ns_for_children);
		if (IS_ERR(pid)) {
			retval = PTR_ERR(pid);
			goto bad_fork_cleanup_io;
		}
	}

	p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
	/*
	 * Clear TID on mm_release()?
	 */
	p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL;
#ifdef CONFIG_BLOCK
	p->plug = NULL;
#endif
#ifdef CONFIG_FUTEX
	p->robust_list = NULL;
#ifdef CONFIG_COMPAT
	p->compat_robust_list = NULL;
#endif
	INIT_LIST_HEAD(&p->pi_state_list);
	p->pi_state_cache = NULL;
#endif
	/*
	 * sigaltstack should be cleared when sharing the same VM
	 */
	if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
		p->sas_ss_sp = p->sas_ss_size = 0;

	/*
	 * Syscall tracing and stepping should be turned off in the
	 * child regardless of CLONE_PTRACE.
	 */
	user_disable_single_step(p);
	clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
#ifdef TIF_SYSCALL_EMU
	clear_tsk_thread_flag(p, TIF_SYSCALL_EMU);
#endif
	clear_all_latency_tracing(p);

	/* ok, now we should be set up.. */
	p->pid = pid_nr(pid);
	if (clone_flags & CLONE_THREAD) {
		p->exit_signal = -1;
		p->group_leader = current->group_leader;
		p->tgid = current->tgid;
	} else {
		if (clone_flags & CLONE_PARENT)
			p->exit_signal = current->group_leader->exit_signal;
		else
			p->exit_signal = (clone_flags & CSIGNAL);
		p->group_leader = p;
		p->tgid = p->pid;
	}

	p->nr_dirtied = 0;
	p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10);
	p->dirty_paused_when = 0;

	p->pdeath_signal = 0;
	INIT_LIST_HEAD(&p->thread_group);
	p->task_works = NULL;

	/*
	 * Make it visible to the rest of the system, but dont wake it up yet.
	 * Need tasklist lock for parent etc handling!
	 */
	write_lock_irq(&tasklist_lock);

	/* CLONE_PARENT re-uses the old parent */
	if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
		p->real_parent = current->real_parent;
		p->parent_exec_id = current->parent_exec_id;
	} else {
		p->real_parent = current;
		p->parent_exec_id = current->self_exec_id;
	}

	spin_lock(&current->sighand->siglock);

	/*
	 * Copy seccomp details explicitly here, in case they were changed
	 * before holding sighand lock.
	 */
	copy_seccomp(p);

	/***********************Omit*************************************/
}

Continue to look back at the application. After the parent process successfully creates the child process, the child process will call the execv function to execute the new program. In the example, the program executed is hello out. hello.out by Hello C compiled.
hello.c. The procedure is as follows:

#include <stdio.h>
#include <unistd.h>
int main(int argc ,char *argv[])
{
	
	printf("hello world\r\n");
	
	printf("argv[0] = %s argv[1] = %s\r\n",argv[0],argv[1]);
	
	return 0;
}

The program is very simple. Print some information and exit.
The exec cluster function of Linux is used for sub processes to run new programs. The cluster function has the following functions

int execl(const char *path, const char *arg, ...)
int execv(const char *path, char *const argv[])
int execle(const char *path, const char *arg, ..., char *const envp[])
int execve(const char *path, char *const argv[], char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, char *const argv[])

The usage methods of these functions are similar. You can check the specific usage of these functions in the man Manual of Linux.

In the application, the parent process will sleep for 5 seconds first, and then wait for the child process to exit and recover the resources of the child process. Because when the child process exits, how the parent process does not recycle the resources of the child process will lead the child process to enter the zombie state. The so-called zombie state means that the process has ended, but the resources have not been recycled and still occupy the operating system resources. In the above example, the parent process calls wait and the system calls to collect the resources of the child process back and forth. If the wait system call returns - 1, it indicates that the recycling sub process failed. The application program can call the macro definition provided by the operating system to judge the exit state of the child process.
For example, the wired macro determines whether the child process exits normally
The wifsignled macro determines whether the child process is killed by the signal

Summary:
Applications can use fork system calls to create child processes.
Child processes can use the exec function cluster to run other applications
When the parent process calls the child process, the resource of the child process should be recycled in time.
Finally, let's take a look at the running results of the above application
When running on the command line/ When the fork program, the parent process will create a child process, and the child process will run hello Out program, print out "hello world" and the passed parameters on the command line, and then the sub process exits. At this point, the parent process first waits for 5 seconds to wait for the child process to exit, then calls the wait system call to retrieve the child process resources, and successfully reprints the "child exit" information at the same time, and finally the parent process exits.

Keywords: C Linux

Added by BioBob on Sun, 20 Feb 2022 19:25:49 +0200