Getting started with Linux Programming (15) - process creation and termination

Posted by teebo on Wed, 29 Dec 2021 17:11:07 +0100

The previous two articles learned the theoretical basis related to process, and this paper began to learn process programming.

Introduction to Linux Programming (13) - process (I)

Introduction to Linux Programming (14) - process (II)

Create process

System function fork()

Linux provides a system call fork() to create a new process. Its function prototype is:

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

If the call succeeds, the function fork() returns the ID of the child process in the parent process; Returns 0 in the child process.

If the call fails, - 1 is returned in the parent process, and the child process will not be created.

After the process calls fork(), the processing procedure of the kernel is as follows:

  • Allocate new memory blocks and kernel data structures.
  • Copy the original process to the new process.
  • Adds a new process to the collection of running processes.
  • Return control to both processes.

Thus, the child process obtains copies of the stack, data segment, heap and executable text segment of the parent process.

After the new process is created successfully, the parent-child process executes the same program text segment. The stack segments, several segments, and heap segments of the two processes are used by the process itself. Each process can modify its own segment without affecting the other process.

The child process will get the code of the parent process and the current running location. The new process runs from where fork returns, not from the beginning of the program. Note that it is unknown who will execute the parent-child process first after fork, which depends on the scheduling algorithm of the current kernel.

Common processing flow codes when fork() is called:

pid_t chid_pid;

child_pid = fork();
if(child_pid == 0)
{
	//Process subprocess code
}
else if(child_pid == -1)
{
	//Call error, handling exception	
}
//Continue executing parent process code

Get process ID

When using fork() to create a new process, you can distinguish the parent and child processes by the return value.

In the parent process, fork () will return the PID of the newly created child process. In the child process, fork() returns 0.

If the child process or other processes want to get the process ID, they can get it through the getpid() function, and call getppid() to get the parent process ID. Its function prototype is:

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);
pid_t getppid(void);

File sharing

After fork() is executed successfully, the child process will get copies of all the file descriptors of the parent process. This means that the descriptors corresponding to the parent and child processes point to the same open file handle.

Open file offsets and file flags are shared between parent and child processes.

If one process updates the file offset, the other process will be affected. In this way, when the parent and child processes write to a file at the same time, they will not overwrite each other's write contents. However, the content written by the two processes will be mixed randomly. To avoid this problem, you need to use inter process synchronization (which will be explained later).

If you do not need to share file descriptors, you can pay attention to two points after calling fork():

  • The parent and child processes use different file descriptions.
  • Close descriptors that are no longer in use.

Programming example

Through programming, demonstrate how to use fork() and its execution. The example code is as follows:

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

int main()
{
	pid_t fork_new;
	/* Print parent process ID */
	printf("fork before, my pid is %d\n", getpid());
	/* Create a new process */
	fork_new = fork();
	/* Judge the fork execution result */
	if(fork_new == -1)
	{
		perror("fork");
	}
	else if(fork_new == 0)
	{
		/* The child process executes and prints its own process ID */
		printf("I am the child. my pid is %d\n", getpid());
	}
	else
	{
		/* The parent process executes and prints the child process ID */
		printf("I am the parent. my child is %d\n", fork_new);
	}

	return 0;
}

Compilation and running results:

$ gcc forkdemo.c
$ ./a.out 

fork before, my pid is 2521
I am the parent. my child is 2522
I am the child. my pid is 2522

It can be seen from the execution results that process 2521 calls fork() to create a child process, and determines whether it is a parent process or a child process through the return value. The sub process PID is 2522.

From the printing situation, the parent process is executed first. fork() can not only create a new process, but also distinguish between the original process and the newly created process.

Terminate process

There are two ways to terminate a process:

  • Normal termination. If you call exit().
  • Abnormal termination. If the process termination signal is received.

This section mainly explains the contents related to calling system functions to terminate processes normally.

_ exit() system function

Process can call_ The exit() function terminates_ The exit() system function is a kernel operation. The actions performed include:

  • Process all memory allocated to this process.
  • Close all files open by this process.
  • Release all data structures used by the kernel to manage and maintain the process.

_ The prototype of the exit() function is:

#include <unistd.h>

void _exit(int status);

The parameter status indicates the termination status of the process. The parent process can obtain this status by calling wait(). Only the lower 8 bits of the status word are used by the parent process.

The termination status is 0, indicating that the process exits normally; Non zero indicates that the process exited abnormally.

exit() system function

exit() is the inverse of fork(). The process stops running by calling exit(). This is a standard library function.

exit() performs the following actions:

  • Flush stdio buffer.
  • Called by atexit() and on_exit() is registered (these two functions are described later). Exit the processing function, and the execution order is opposite to the registration order.
  • Perform other exit() related operations defined by the current system.
  • Then call exit() function.

Its function prototype is:

#include <stdlib.h>

void exit(int status);

Parameter status and_ The parameters of exit() are the same.

Action of process termination

The system calls exit to terminate the current process, which requires a series of cleanup. These works are somewhat different in different versions of Linux, but generally include the following operations:

  • Close all open file descriptors and directory descriptors.
  • Release any file locks held by the process.
  • Send SIGCHLD to parent process.
  • If the parent process calls wait() or waitpid() to wait for the child process to finish, the parent process is notified.

Register exit handler

If the application needs to perform some operations when the process terminates, the handler can be registered with the kernel and will be executed automatically when the process calls exit() to terminate normally.

If the program calls_ exit() or terminated by a signal, the exit handler is not called.

GNU C language functions provide two ways to register exit handlers: atexit() and on_exit(). The function prototypes are as follows:

atexit() function

#include <stdlib.h>

int atexit(void (*function)(void));     

The parameter function is a function pointer, and atexit() registers the function it points to in the kernel.

0 is returned if the atexit() call is successful. If a non-zero is returned, the registration fails.

The function pointed to by function does not accept any parameters and has no return value. Its general form is as follows:

void function(void)
{
}

on_exit() function

#include <stdlib.h>

int on_exit(void (*function)(int , void *), void *arg);

on_ The parameter function of exit() function is a function pointer to the following types of functions:

void function(int status, void *arg)
{
}

When calling, two parameters will be passed to function(), the status parameter provided to exit () and the status parameter provided to on during registration_ Copy of the arg parameter of exit().

The meaning of the arg parameter can be defined by the designer of the program. It can be used as a pointer or as an integer value (through cast).

on_ 0 is returned when the exit() call is successful. Returns a non-zero value on failure.

Using atexit() and on_exit() can register multiple exit handlers. When an application calls exit(), the execution order of these functions is opposite to the registration order.

Program example

Write experimental code to demonstrate how to register exit handler functions and the execution order of handler functions. The example code is as follows:

#include <stdio.h>
#include <stdlib.h>

void atexitFunc1(void)
{
	printf("atexit function 1 called\n");
}

void atexitFunc2(void)
{
	printf("atexit function 2 called\n");
}

void onexitFunc(int status, void *arg)
{
	printf("on_exit function called, status = %d, arg = %ld\n", status, (long)arg);
}

int main(void)
{
	if(on_exit(onexitFunc, (void *)10) != 0)
	{
		perror("on_exit 1");
	}
	
	if(atexit(atexitFunc1) != 0)
	{
		perror("aexit 1");
	}

	if(atexit(atexitFunc2) != 0)
	{
		perror("aexit 2");
	}
	
	if(on_exit(onexitFunc, (void *)20) != 0)
	{
		perror("on_exit 2");
	}
}

Compile and run, and the results are as follows:

$ gcc aexit_demo.c -o aexit_demo
$ ./aexit_demo

on_exit function called, status = 0, arg = 20
atexit function 2 called
atexit function 1 called
on_exit function called, status = 0, arg = 10

Compared with the program code, the execution order of the exit handler function is exactly the opposite of its registration order.

Summary

This article mainly learned how to create a process and how to exit the process. The main points are as follows:

  • Call the system function fork() to create a process and the process flow of the process.
  • The system function that gets the PID and PPID of the process.
  • File sharing between parent and child processes.
  • To terminate the processing flow of a process, the system provides a function to terminate the process normally.
  • How to register exit handlers and corresponding system functions.

Well, let's talk about this today and continue next time. come on.

The official account [learning embedded] is full dry cargo.

Topics: Linux server