The process of linux system programming

A process's defining program becomes a process when it runs

Program: Static, executable stored on disk
Process: is dynamic, is the execution instance of a program running in memory.

A program is an ordered collection of instructions, and a process is a process of program execution, a process of program execution.
The state of a process is changing, including its creation, scheduling, and extinction
As long as the program runs, this is the process. Every time the program runs, a process is created

In linux systems, processes are the basic unit for managing transactions
Processes have their own independent processing environment and system resources (processors, memory, I/O devices, data, programs). The exec function can be used to read a program into memory by the kernel and make it execute called a process

Status and Transition of Processes

Ready:
The process has everything it needs to execute and is waiting to allocate processing time to the CPU
Execution state:
The process is running on CPU
Waiting state:
The state in which a process is temporarily unable to continue because it does not have some execution conditions

Scheduling mechanisms for processes

Time slice rotation, context switching

The general rule would be that the first process runs for 10 milliseconds, then the second process runs for 10 milliseconds, then the third process runs for 10 milliseconds, then the first process runs for 10 milliseconds, and so on.
Multiprocessing does not mean that one process finishes executing another process, but rather executes alternately, one process executes for a period of time, the next process executes for a period of time, and so on, all processes execute and then continue execution back to the first process

Process Control Block PCB

Role: A process control block is a structure used to hold information about a process, also known as a PCB

os controls and manages concurrently executed processes based on PCB. When a process is created, the system opens up a memory space to store PCB data structures related to the process.
PCB is the most important recorded data structure in the operating system. PCB records all the information needed to describe the progress of a process and control its operation.
PCB is the only sign that a process exists, and PCB is stored in task_on Linux Struct structure

Partial data in PCB structure

Scheduling data

Status, flags, priority, scheduling policy, etc. of the process

Time data

The time the process was created, the time it was run in the user state, the time it was run in the kernel state, etc.

File System Data

umask mask, file descriptor table, etc.
Memory data, process context, process identification (process number)

Process Control

Each process is identified by a process number of type pid_t, process number range 0~32767
Each process number is always unique, but it can be reused, and when a process terminates, its process number can be used again
The process number starts with 0 in the linux system
Processes with process numbers 0 and 1 are created by the kernel
A process with a process number of 0 is usually a dispatch process and is often referred to as a swapper.
A process with process number 1 is usually an init process, which is the ancestor of all processes
Except for scheduling processes, all processes under linux are created directly or indirectly by the process init process

Process number (PID)

A non-negative integer identifying the process
The process number is randomly assigned to the current process by the operating system and cannot be controlled by itself

Parent Process Number (PPID)

Any process (except the init process) is created by another process, which is called the parent of the created process and the corresponding process number is called the parent process number (PPID).

Process Number Group (PGID)

A process group is a collection of one or more processes that are related to each other. A process group can accept signals from the same terminal. Associated processes have a process group number (PGID)

How to view all processes in the current system

View all open processes ps ajx in the current system in ubuntu
ps View Current Process

PPID: Process number of the parent of the current process
PID: Process number of current process
PGID: Process group ID of the group in which the current process is located
COMMAND: Name of the current process

The linux operating system provides three functions to get the process number, getpid(), getppid(), and getpgid();

Need to include header file

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void)
Function: Get the process number (PID)
pid_t getppid(void)
Function: Gets the parent process number (PPID) of the process calling this function
pid_t getpgid(pid_t pid)
Function: Gets the process group number (PGID), returns the current PGID when the parameter is 0, otherwise returns the PGID of the process specified by the parameter
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
        //Get the pid number of the current process
        printf("pid = %d\n", getpid());

        //Gets the pid number of the parent process of the current process
        printf("ppid = %d\n", getppid());

        //Gets the pid number of the current process group 
        printf("pgid = %d\n", getpgid(getpid()));

        while(1){}
        return 0;
}

The results of using ps ajx are as follows

Create fork function for process

In a linux environment, the main way to create a process is to call the following functions

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

Create a new process

pid_t fork(void)
function

The fork() function creates a new process from an existing process, which is called a child process and the original process is called a parent process.

Return value

Success: Return 0 in child process and ID in parent process
Return-1
A child process using the fork function is a copy of the parent process, which inherits the address space of the entire process from the parent process
Address space:
Includes process context, process stack, open file descriptor, signal control settings, process priority, process group number, etc.
A subprocess is unique in that it only has a process number, a timer, and so on. Therefore, using the fork function is expensive.

Spatial diagram of parent-child process after fork function execution

Create Subprocess

(1) Parent-child process code area is indistinguishable

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

int main()
{
        fork();
	adopt fork Function creates a subprocess
	Be careful: Primary Execution fork,A new subprocess is created based on the original process
	And if fork Code area that does not distinguish between parent and child processes, all subsequent code executes
        printf("hello world\n");
        while(1);
        return 0;
}
~    


(2) Distinguish parent-child process code area
Distinguish the separate code areas of parent-child processes by the return value of the fork function
It is uncertain who will run the parent-child process first and who will run it later.
Never execute a child process until the parent process has finished executing
Time slice rotation, context switching

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{

        pid_t pid;
        pid = fork();

        if(pid < 0)
        {
                perror("fail to fork");
                return -1;
        }
        else if(pid > 0 )//This is the code area of the parent process
        {
                while(1)
                {
                        printf("parent: pid = %d ppid = %d\n", getpid(), getppid());
                        printf("pid = %d\n", pid);
                        printf("this is parent progect\n");
                        sleep(1);
                        printf("- - - - - -- - - - - - -- - - -- \n");
                }
        }
	 else  //This is the code area of the child process
        {
                while(1)
                {
                        printf("son: pid = %d ppid = %d\n", getpid(), getppid());
                        printf("this is son progect\n");
                        sleep(1);
                        printf("* * * * * * * * * * * * * * * * * *\n");
                }
        }
        return 0;
}

Parent-child processes have separate address spaces

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int a = 666;
int main()
{
        static int  b = 777;
        int c = 888;
        pid_t pid;
        pid = fork();
        if(pid < 0)
        {
                perror("fail to pid");
                return -1;
        }
        else if(pid > 0)//Code area of parent process
        {
                printf("This is a parent process\n");
                a++;
                b++;
                c++;
                printf("a = %d b = %d c = %d\n", a, b, c);
        }
         else //Code area of child process
        {
                sleep(1);
                printf("This is a son process\n");
                printf("a = %d b = %d c = %d\n", a, b, c);
        }
        while(1)
        {}
        return 0;
}


Summary: The child process copies everything before the parent process fork
But after fork, the parent and child processes are completely independent, so no matter how they change (heap, stack, data area) they will not be affected by each other

Child process inherits parent process space

A child process inherits some common areas of the parent process, such as disk space and kernel space.
The offset of the file descriptor is stored in kernel space, so if the parent process offset, the offset obtained by the child process is
After change
Example: For example, if the parent process reads the contents of a file, then the child process reads what the parent process reads (before the parent process executes first)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
        int fd;
        if((fd = open("test.txt", O_RDONLY))== -1)
        {
                perror("fail to open");
        }
        pid_t pid;
        pid = fork();
        if(pid < 0)
        {
                perror("fail to pid");
                return -1;
        }
        else if(pid > 0) //This is the code area of the parent process
        {
                printf("This is parent process\n");
                char buf[50] = "";
                if(read(fd, buf, 30) == -1)
                {
                        perror("fail to read");
                        return -1;
                }
                printf("buf = %s\n", buf);
        }
        else//This is the code area of the child process
        {
                printf("This is son process\n");
                sleep(1);
                char buf[50] = "";
                if(read(fd, buf, 30) == -1)
                {
                        perror("fail to read");
                        return -1;
                }

                printf("buf = %s\n", buf);
        }
        while(1)
        {}
        return 0;
}

Suspended sleep of process

#include<unistd.h>

unsigned int sleep(unsigned int sec);
Functions:
A process suspends for a specified number of seconds until the specified time has elapsed or a signal has been received.
Return value
Returns 0 if the process hangs up to the time specified by sec and the remaining seconds if there is a signal interrupt
Be careful:
The program does not execute immediately after a process has been suspended for a specified number of seconds; the system simply switches the process to ready state
Sleep for 5 seconds using the sleep(4) process
When running to the sleep function, the program waits for a set number of seconds at this location, and when the number of seconds is large, the code executes next
The sleep runtime process is in a wait state, which switches to ready state after time arrives, and to run state if code continues to execute.

Waiting for process

Parent-child processes sometimes require simple inter-process synchronization.
If the parent process waits for the child process to finish
Two waiting functions, wait(), waitpid(), are provided under linux.
Need to include header file,

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

wait function

pid_t wait(int * status);

Functions:

Block waiting for child process to exit
Waiting for the child process to terminate, if the child process terminates, this function reclaims the child process's resources
A process that calls the wait function hangs until one of its subprocesses exits or receives a process that cannot
The function returns immediately if the calling process has no child or if its child process has ended

Parameters:

When the function returns, the parameter
status
Include status information on child process exit
The exit information of the child process contains multiple fields in an int.
Each of these fields can be removed by using a macro definition
Sub-processes can pass either exit or _ Exit function sends exit status

Return value

Returns the process number of the child process if the execution is successful
Error returns -1, the cause of failure is in errno

Remove exit information for child process

WIFEXITED(status)
If the child process terminates normally, the field value is not zero
WEXITSTATUS(status)
Returns the exit status of the child process, which is stored in 8-16 bits of the status variable. The macro WIFEXITED should be used to determine whether the child process exited normally before using this macro. This macro can only be used after normal exit.

Be careful

This status is an integer variable pointed to by a wait parameter

Do not accept child process exit status

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
        pid_t pid;
        pid = fork();
        if(pid < 0)
        {
                perror("fail to pid");
                return -1;
        }
        else if(pid == 0)
        {
                int i = 0;
                for(i=0; i<5; i++)
                {
                        printf("This is son process\n");
                        sleep(1);
                }
        }
        else
        {
        //Using wait to block a child process in the parent process and wait for the child process to exit,
        //NULL denotes that child process exit status is not accepted
                wait(NULL);
                printf("This is father process\n");
        }
		return 0;
}


Receive the exit status of the child process

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
        pid_t pid;
        pid = fork();
        if(pid < 0)
        {
                perror("fail to pid");
                return -1;
        }
        else if(pid == 0)
        {
                int i = 0;
                for(i=0; i<5; i++)
                {
                        printf("This is son process\n");
                        sleep(1);
                }
                //Subprocess uses exit to issue exit status
                exit(2);
        }
		else
        {
        //Receives the exit status of a child process, which must either use exit or _ Exit function exits a process by sending an exit state
                int status;
                wait(&status);
                //Use WIFEXITED to determine if termination is normal
                //Then use WEXITSTATUS to fetch the returned status
                if((WIFEXITED(status)) != 0)
                {
                        printf("The son process return status %d\n",   WEXITSTATUS(status));
                }
                printf("This is father process\n");
        }
        return 0;
}

waitpid function

pid_t waitpid(pid_t pid, int* status, int options);
Functions:

Waiting for the child process to terminate, if the child process terminates, this function reclaims the child process's resources

Return value:

Return child process ID if execution is successful
Error returns -1, the cause of failure is in errno

There are several types of values for the parameter pid

pid > 0
Waiting for a child process whose process ID is equal to pid
pid = 0
Wait for any child process in the same process group. If the child process has joined another process group, waitpid will not wait for it
pid = -1
Wait for any subprocess, waitpid works the same as wait
pid < -1
Wait for any child process in the specified process group whose ID is equal to the absolute value of pid

Status parameter contains status information when a child process exits
options parameter further controls the operation of waitpid

0:
Like wait, block parent process, wait for child process to exit

WNOHANG:
Return immediately if there are no child processes that have ended

WUNTRACED
If the child process pauses, this function returns immediately, ignoring the end state of the child process (tracking debugging, rarely used)

Return value:

Success:

Returns the process number of the child process whose state has changed, or 0 if the option WNOHANG is set and the process specified by pid exists

Error:

Returns -1, when the subprocess indicated by pid does not exist, or this process exists, but is not a subprocess of the calling process, waitpid will return incorrectly, which is errno set to ECHILD.

Wait (status) <=> wait TPID (-1, status, 0) This is equal

Case:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
        pid_t pid;

        pid = fork();
        if(pid < 0)
        {
                perror("fail to fork");
                return -1;
        }
        if(pid == 0)
        {
                int i = 0;
                for(i=0; i<5; i++)
                {
                        printf("This is son process\n");
                        sleep(1);
                }
                exit(0);
        }
        else
        {
                waitpid(pid, NULL, 0);
                printf("This is father process\n");
        }
        return 0;
}

The results are as follows;

Special Processes

Zombie

A process that has finished running but whose resources have not been reclaimed is called a zombie process
The child process has finished running and the parent process has not called the wait or waitpid function to recycle the resources of the child process is the reason the child process becomes a zombie process

orphan

A child process whose parent process ended but whose child process did not run ended

Daemon (Daemon)

A daemon is a special orphan process that leaves the terminal and runs in the background

Termination of process

Under linux, you can end a running process by

void exit(int value);

void _exit(int value);

exit function

Function: End process execution

#include <stdlib.h>
void exit(int value)
Parameters:

status: Exit state, which is received by the parent process through the wait function
General Failure Exit Set to Non-0
General Successful Exit Set to 0

_ exit function

Function: End process execution

#include <stdlib.h>
void exit(int value)
Parameters:

status: Exit state, which is received by the parent process through the wait function
General Failure Exit Set to Non-0
General Successful Exit Set to 0

Exit and _ The difference between exit functions:
exit is a library function, and _ exit is a system call
exit refreshes the buffer, but _ exit will not refresh the buffer
exit is generally used

Example: Using return

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void myfun()
{
        printf("nihao beijing\n");
        //return
        return;
        printf("zi han shu de hello world\n");
}
int main()
{
        printf("hello world\n");
        myfun();
        printf("hello kitty\n");
        return 0;
}
~      


CONCLUSION: With return, in addition to returnable values, the process can be quit using the main function, but only the current function can be quit using the subfunction.

Use exit

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void myfun()
{
        printf("nihao beijing\n");
        exit(0);
        //_exit
        printf("hello world\n");
}
int main()
{
        printf("hello world\n");
        myfun();
        printf("hello kitty\n");
        return 0;
}


You can see that hello kitty is not printed
Conclusion Exit can exit a process and refresh the buffer

Use _ exit

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void myfun()
{
        printf("nihao beijing\n");
        _exit(0);
        printf("hello world\n");
}
int main()
{

        printf("hello world\n");
        myfun();
        printf("hello kitty\n");
        return 0;
}
~                


We can see _ exit also ended the process, but _ exit will not refresh the buffer

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void myfun()
{
        printf("nihao beijing");
        _exit(0);
        printf("hello world\n");
}
int main()
{

        printf("hello world\n");
        myfun();
        printf("hello kitty\n");
        return 0;
}

Let's remove the \n behind hello beijing above


You can see that hello beijng is not printed, so _ Exit does not refresh the buffer, but exit refreshes the buffer
Conclusion_ Exit can also exit a process, but does not refresh the buffer because there is no buffer

Process Exit Cleanup

A process can register an exit handler with the atexit function before exiting

#include<stdlib.h>

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

function

Functions called before the normal end of the registration process, process exits to execute the registration function

parameter

Function: Entry address to call a function before the process ends
A process can call the atexit function multiple times to register the cleanup function and call the function before it ends normally
Reverse order when registering

Return value

Success: 0
Failure: Non-0

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void clear_fun1(void)
{
        printf("perform clear fun1 \n");
}
void clear_fun2(void)
{
        printf("perform clear fun2 \n");
}
void clear_fun3(void)
{
        printf("perform clear fun3 \n");
}
int main()
{
        atexit(clear_fun1);
        atexit(clear_fun2);
        atexit(clear_fun3);
        printf("process exit 3 sec later !!! \n");
        sleep(3);
        return 0;
}


The atexit function does not execute the callback function for the parameter until the end of the process
After atexit is called more than once, the execution order is opposite to the calling order

vfork function for process creation

pid_t vfork(void)

Functions:
The vfork function, like the fork function, creates a new process in an existing process, but they create different sub-processes
Return value:
If the child process is created successfully, 0 is returned in the child process and the ID of the child process is returned in the parent process.
Error returns -1

The difference between fork and vfork

Child process runs before parent process

After the child process is created using the vfork function
The child process executes before the parent process executes until the child process executes exit or exec

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
        pid_t pid;
        pid = vfork();

        if(pid < 0)
        {
                perror("fail to vfrok");
                exit(1);
        }
        if(pid == 0)
        {
                int i = 0;
                for(i=0; i<5; i++)
                {
                        printf("this is son process\n");
                        sleep(1);
                }
                exit(0);
        }
     else
        {
                while(1)
                {
                        printf("this is father process\n");
                        sleep(1);
                }
        }
        return 0;
}


We can see that the child process runs and the parent process runs after exit(0)

Child and parent processes share the same block of space

Finish child process creation with vfork
Parent-child processes share the same address space before a child process executes an exit or exec

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int a = 10;
int main()
{
        pid_t pid;
        int b = 9;

        pid = vfork();
        if(pid < 0)
        {
                perror("fail to vfork");
                exit(1);
        }
        if(pid == 0)
        {
                a++;
                b++;
                printf("in son process a = %d b = %d\n",a, b);
                exit(0);
        }
        else
        {
                printf("in father process a=%d, b=%d\n", a, b);
        }
        return 0;
}

Replacement exec family functions for processes

Exec family functions, consisting of six exec functions

  1. The exec family functions provide six ways to start another program in a process
  2. exec family of functions can find executable files based on specified file or directory names
  3. The process calling the exec function does not create a new process, so the process number of the process does not change before and after the exec call
    The program it executes is completely replaced by the new program, which starts with the main function.
    The exec family replaces the data, code, and stack segments of the calling process

    Of the six exec functions, only execve is the real system call (the interface provided by the kernel), and the others are encapsulated library functions based on it



The function of e is rarely used
Be careful:
Unlike normal functions, functions in the exec family do not return after execution. They only return -1 if the call fails, and then execute from the call point of the original program and down after the failure.
In normal programming, if you use the family of exec functions, you must remember to add error judgment statements
whereis View Path Command whereis ls View ls Path

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

int main()
{
        pid_t pid;
        if((pid = fork()) < 0)
        {
                perror("fail to fork");
                exit(1);
        }
        else if(pid > 0)
        {
                printf("This is parent process\n");
                wait(NULL);
                printf("The child process has quited\n");
        }
        else
        {
                printf("This is child process\n");
     //Call functions in the exec family to execute other commands or programs
     //View the path of the command: whereis command or which command
     //***************exec family of functions calls shell command**********************
     //Function without p, command path must use absolute path

#if 0
if(execl("/bin/ls", "ls", "-l", NULL) == -1)
{
       				perror("fail to execl");
       				exit(1); 			
}
#end of
 	//The first parameter of a function with p can be either a relative path or an absolute path
#if 0
        if(execlp("ls", "ls", "-l", NULL) == -1)
        {
                perror("fail to execl");
                exit(1);
        }
#endif
//Functions with v need to be passed using pointer arrays
//Use absolute path because there is no p behind execv
#if 0
        char* str[] = {"ls", "-l", NULL};
        if(execv("/bin/ls", str) == -1)
        {
                perror("fail to execv");
                exit(1);
        }
#endif
#if 1
		//exec family calls executable
                if(execlp("./hello", "./hello", NULL) == -1)
                //First Path Second Executable
                {
                        perror("fail to execl");
                        exit(1);
                }

#endif

                printf("hello world\n");
        }
        return 0;
}
~                                        

The result of the first subprocess code execution

The exec family replaces the data, code, and stack segments of the calling process
So when the exec function is executed, the current process ends, so the code in the original process does not execute anymore
So helloworld above is not executing

The result of the second subprocess code execution

The result of execution of the third subprocess code

The result of execution of the fourth process code

Keywords: Linux

Added by TimUSA on Thu, 16 Dec 2021 20:57:20 +0200