process and subprocess

Table of contents

  process concept

orphan process

zombie process

1. fork and pid

verify

2. Parent process and child process file sharing

 verify​

3. Competition after fork()

verify

 code show as below

4. Monitor child processes

code writing wait

verify

code writing waitpid


  process concept

        When a process creates a child process, the two of them become a parent-child process relationship. The life cycles of the parent process and the child process are often different, and there are two problems here:

⚫ The parent process ends before the child process
⚫ The child process ends before the parent process

orphan process

        The parent process ends before the child process, which means that the child process becomes an "orphan" at this time, and we call this process an orphan process. In the Linux system, all orphan processes automatically become subprocesses of the init process (process number 1). In other words, after the parent process of a subprocess ends, the subprocess call Became the "foster father" of an orphaned process

zombie process

        The process ends before the parent process. After the process ends, the parent process usually needs to "collect the corpse" for it and reclaim some memory resources occupied by the child process. The parent process calls wait() (or its variants waitpid(), waitid() etc.) function reclaims subprocess resources and returns them to the system.

        If the child process ends before the parent process, and the parent process has not yet had time to "collect the corpse" of the child process, then the child process becomes a zombie process at this time. After the child process ends, its parent process does not have time to "collect the corpse" for it immediately, and the child process is in the state of "exposed corpse wilderness". In such a state, we make the child process a zombie process and the parent process calls wait()
        ( or its variants (waitpid(), waitid(), etc.) function "collects" the child process, the zombie process will be completely deleted by the kernel. In another case, if the parent process does not call the wait() function and then exits, then the init process will take over its child process and call wait() automatically, so removing the zombie process from the system does not "collect the corpse
        " "Such programming is problematic. If there are a large number of zombie processes in the system, they will inevitably fill up the kernel process table, thereby preventing the creation of new processes. It should be noted that the zombie process cannot be killed by a signal, even the "one-hit kill" signal SIGKILL cannot kill it, so in this case, only the parent process of the zombie process can be killed ( or wait for their parent process to terminate), the init process will take over these zombie processes, cleaning them from the system

1. fork and pid

        After the fork() call is successful, the PID of the child process will be returned in the parent process, and the return value in the child process is 0; if the call fails, the parent process will return the value -1, the child process will not be created, and errno will be set.
        After the fork() call is successful, the child process and the parent process will continue to execute the instructions after the fork() call, and the child process and the parent process run in their own process spaces. In fact, the child process is a copy of the parent process. For example, the child process copies the data segment, heap, and stack of the parent process and inherits the file descriptors opened by the parent process. The parent process and the child process do not share these storage spaces. It is a complete copy of the corresponding part of the storage space of the parent process by the child process. After executing fork(), each process can modify its own stack data and variables in the heap segment without affecting another process.
        Although the child process is a copy of the parent process, for the program code segment (text segment), the two processes execute the same code segment, because the code segment is read-only, that is, the parent-child process shares the code segment, in memory There is only one piece of code segment data

 Use fork to create a child process, and use the parent process and child process to print information

verify

code show as below 

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

    pid = fork();
    /*此时有两个进程在同时运行*/
    if (pid < 0)
    {
        printf("error fork\n");
        exit(-1);
    }
    else if (pid == 0)
    {
        printf("child process id:%d,father id:%d\n", getpid(),getppid());
        _exit(0);/*子进程退出*/
    }
    else
    {
        printf("father process id:%d, child id:%d\n", getpid(),pid);
        exit(0);
    }
}

2. Parent process and child process file sharing

        After calling the fork() function, the child process will obtain a copy of all file descriptors of the parent process, which also means that the file descriptors corresponding to the parent and child processes point to the same file table, so these files are implemented between the parent and child processes In addition to sharing, if the child process updates the file offset, then this change will also affect the position offset of the corresponding file descriptor in the parent process.

        After the parent process opens the file, then fork() creates a child process. At this time, the child process inherits the file descriptor opened by the parent process (a copy of the parent process file descriptor), and then the parent and child processes write to the file at the same time.

        After the parent process opens the file, it calls fork() to create a child process. In this case, the parent and child processes respectively write to the same file, and the result is continuous writing, regardless of whether it is the parent process or the child process , each write is written from the end of the file

 verify

code show as below 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    pid_t pid;
    int fd, i;
    if ((fd = open("./test.txt", O_RDWR | O_CREAT | O_TRUNC, S_IRWXU)) < 0)
    {
        perror("fork error");
        exit(-1);
    }
    pid = fork();
    switch (pid)
    {
    case -1:
        perror("fork error");
        close(fd);
        exit(-1);
    case 0: /*子进程*/
        for (i = 0; i < 4; i++)
            write(fd, "12", 2);
        close(fd);
        _exit(0);
    default: /*父进程*/
        for (i = 0; i < 4; i++)
            write(fd, "AB", 2);
        close(fd);
        exit(0);
    }
}

        Let’s test another situation. After the parent process calls fork(), both the parent process and the child process open the same file, and then write to the file.

         Open and write files in the parent and child processes respectively, and view the results, as follows

         This file sharing method realizes that two processes write to the file separately, because the two file descriptors of the parent and child processes point to different file tables, which means that they have their own files. Offset, the file offset modified by one process will not affect the file offset of another process, so the written data will be overwritten.

3. Competition after fork()

        After calling fork(), the child process becomes an independent process that can be scheduled to run by the system, while the parent process continues to be scheduled to run by the system. There is a problem here. After calling fork, it is impossible to determine which of the two processes is the parent or the child. It will be the first to access the CPU, that is to say, it is impossible to confirm who is called first by the system.

        For some specific applications, it has certain requirements on the order of execution, for example, it must require the parent process to
run first, or must require the child process to run first, the program produces correct results, it depends on the specific execution order, then it will be possible Failed to get correct result due to race condition. At this time, it can be realized by using some kind of synchronization technology, such as the signal introduced earlier. If you want the child process to run first, you can block the parent process and wait until the child process wakes it up.

         The child process first runs and prints the corresponding information, and then executes the parent process to print the information. In the parent process branch, directly calls sigsuspend() to make the parent process enter the suspended state, and the child process sends a signal through the kill command to wake up.

        The signal sent by the child process to wake up the parent process will not be sent to the parent process until the child process exits. Because after the child process calls kill(getppid(), SIGUSR1)to send a signal, it does not stay active and wait for the parent process to receive the signal, but exits immediately. At this time, the kernel will take over and send a signal to the parent process at an appropriate time.

        Therefore, when the parent process sigsuspend(&wait_mask)is blocked after the call, it will wait to receive the signal sent by the child process, but the child process has not exited at this time, so the parent process will remain blocked until it receives the signal sent by the child process and is blocked. until successfully awakened.

verify

 code show as below

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

static void sig_handler(int sig)
{
    printf("接收到信号\n");
}
int main()
{
    struct sigaction sig = {0};
    sigset_t wait_mask;
    sigemptyset(&wait_mask);
    sig.sa_handler = sig_handler;
    sig.sa_flags = 0;
    if (sigaction(SIGUSR1, &sig, NULL) == -1)
    {
        perror("sigaction error");
        exit(-1);
    }
    switch (fork())
    {
    case 0:
        /* 子进程 */
        printf("子进程开始执行\n");
        printf("子进程打印信息\n");
        printf("~~~~~~~~~~~~~~~\n");
        sleep(2);
        kill(getppid(), SIGUSR1); // 发送信号给父进程、唤醒它
        _exit(0);
    default:
        /* 父进程 */
        if (-1 != sigsuspend(&wait_mask)) // 挂起、阻塞
            exit(-1);
        printf("父进程开始执行\n");
        printf("父进程打印信息\n");
        exit(0);
    }
}

4. Monitor child processes

        For many processes that need to create child processes, sometimes the design needs to monitor the termination time of the child process and some status information at the time of termination, which is necessary under certain design requirements. The system call wait() can wait for any child process of the process to terminate, and obtain the termination status information of the child process at the same time.

code writing wait

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
int main()
{
    int status, ret, i;
    for (i = 0; i < 3; i++)
    {
        switch (fork())
        {
        case -1:
            perror("fork error");
            exit(-1);
        case 0:
            /* 子进程 */
            printf("子进程<%d>被创建\n", getpid());
            sleep(i);
            _exit(i);
        default:
            /* 父进程 */
            break;
        }
    }
    sleep(i);
    printf("~~~~~~~~~~~~~~\n");
    for (i = 1; i <= 3; i++)
    {
        ret = wait(&status);
        if (ret == -1)
        {
            if (ECHILD == errno)/*表示当前没有需要等待回收的子进程*/
            {
                printf("没有需要等待回收的子进程\n");
                exit(0);
            }
            else
            {
                perror("wait error");
                exit(-1);
            }
        }
        printf("回收子进程<%d>, 终止状态<%d>\n", ret, WEXITSTATUS(status));
    }
    exit(0);
}

         The parent process executes a for loop and creates 3 child processes. Each child process outputs its own pid first, and then sleeps for a period of time. After the sleep is over, the child process calls the _exit() function to exit and returns a different exit code. The exit code here is the sleep time of the child process. The parent process judges whether any child process exits according to the return value of the wait() function. If there are no child processes to recycle, exit the loop and end the program normally.

verify

There are some restrictions on using the wait() system call, including the following:
⚫ If the parent process creates multiple child processes, using wait() will not be able to wait for the completion of a specific child process, but can only wait for the next child process in order The termination of the process comes one by one, whoever terminates first will be processed first;
⚫ If the child process is not terminated and is running, then wait() will always keep blocking, sometimes we want to perform non-blocking waiting, whether there is a child process terminated, through Judgment can be known;
⚫ Use wait() to only find those sub-processes that have been terminated. For sub-processes that stop due to a signal (such as SIGSTOP signal) (note that stop here refers to suspending operation), or have stopped There is nothing you can do if the child process resumes execution after receiving the SIGCONT signal.

waitpid() can break through these limitations

code writing waitpid

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

int main()
{
    int status, ret, i;
    /* 循环创建 3 个子进程 */
    for (i = 1; i <= 3; i++)
    {
        switch (fork())
        {
        case -1:
            perror("fork error");
            exit(-1);
        case 0:
            /* 子进程 */
            printf("子进程<%d>被创建\n", getpid());
            sleep(i);
            _exit(i);
        default:
            /* 父进程 */
            break;
        }
    }
    sleep(1);
    printf("~~~~~~~~~~~~~~\n");
    for (;;)
    {
        ret = waitpid(-1, &status, WNOHANG);/*执行非阻塞等待,可以实现轮询*/
        if (ret <0)
        {
            if (ECHILD == errno)
                exit(0);
            else
            {
                perror("wait error");
                exit(-1);
            }
        }
        else if(ret == 0)
            continue;
        else
        printf("回收子进程<%d>, 终止状态<%d>\n", ret,WEXITSTATUS(status));
    }
    exit(0);
}

        Using waitpid()the function to perform non-blocking waiting, polling can be implemented, and it will constantly check whether all child processes have exited. If a child process exits, the child process is recycled and its exit status is printed. If no child processes have exited, polling continues. When the return value of waitpid is less than 0, it means that an error has occurred. At this time, it is necessary to judge whether it is ECHILD or other errors through errno. If it is ECHILD, it means that there is no child process to be recycled, and the program can be exited directly. Otherwise, use the perror function to print an error message and exit the program. The function of this if (ret == 0) continue;code is that if no child process currently exits, it will directly skip the code that continues to poll and enter the next cycle to reduce unnecessary system overhead

The verification effect is the same as that of wait() above

Guess you like

Origin blog.csdn.net/weixin_46829095/article/details/129642185