process-exec/zombies and orphans/wait/pipes

exec function family

        After fork creates a child process, it executes the same program as the parent process (but it may execute a different code branch). The child process often calls an exec function to execute another program. When a process calls an exec function, the process's user-space code and data are completely replaced by the new program, starting from the core program's startup routine. Calling exec does not create a new process, so the process id does not change before and after calling exec

execlp function

Load a process with the PATH environment variable
        int execlp(cosnt char* file, const char* arg,...); Success: no return Failure: -1
        Parameter 1: The name of the program to be loaded. This function needs to be used with the PATH environment variable. When there is no parameter 1 after searching all directories in PATH, an error will be returned.
        This function is usually used to call system programs. Such as: ls, date, cp, cat and other commands

Execlp implements ls -l -a in the child process

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

int main(void){
    pid_t pid;
    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(1);
    }else if(pid > 0){
        sleep(1);
        printf("parent\n");
    }else{
        execlp("ls","ls","-l","-a",NULL);
    }

    return 0;
}

execl function

Load a process by path + program name
        int execl(const char* path, const char* arg,...); success: no return failure: -1
vs. execlp such as loading "ls" command with -l -F parameters
execl("/bin/ls","ls","-l","-F",NULL);

Use execl to achieve

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

int main(void){
    pid_t pid;
    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(1);
    }else if(pid > 0){
        sleep(1);
        printf("parent\n");
    }else{
        execl("bin/ls","ls","-l","-a",NULL);
    }

    return 0;
}

The execvp function
loads a process using a custom environment variable env
        int execvp(const char* file, const char* argv[]);

dup2

Print process information to a file

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

int main(void){
    int fd;

    fd = open("ps.out",O_WRONLY|O_CREAT|O_TRUNC,0644);
    if(fd<0){
        perror("open ps.out error");
        exit(1);
    }
    dup2(fd, STDOUT_FILENO);

    execlp("ps","ps","ax",NULL);

    return 0;
}

Zombie and orphan processes

Orphan process: When the parent process ends before the child process, the child process becomes an orphan process, and the parent process of the child process becomes the init process, which is called the init process adopts the orphan process

Zombie process: The process is terminated, the parent process has not been recycled, and the residual resources (PCB) of the child process are stored in the kernel and become a zombie process

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

int main(void){
    pid_t pid;

    pid = fork();
    if(pid == -1){
        perror("fork");
        exit(1);
    }else if(pid > 0){
        sleep(1);
        printf("parent pid = %d, parentID = %d\n",getpid(),getppid());
    }else if(pid == 0){
        printf("child pid = %d, parentID = %d\n",getpid(),getppid());
        sleep(3);
        printf("child pid = %d, parentID = %d\n",getpid(),getppid());
    }
    return 0;
}

 Here we can see that the first output of the parent process and the child process is no problem, but after sleeping for 3 seconds, the parent process has ended, and the second output of the child process will enter the orphanage, so the second child process The output parentID is 1,
and the corresponding directory is /sbin/init. The init process here finally recycles the orphan process.

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

int main(void){
    pid_t pid;
    pid = fork();

    if(pid == 0){
        printf("i am child, my parent = %d,going to sleep 10s\n",getppid());
        sleep(10);
        printf("-------child die-------");
    }else if(pid > 0){
        while(1){
            printf("i am parent, pid= %d, myson = %d\n",getppid(),pid);
            sleep(1);
        }
        
    }else{
        perror("fork");
        return 1;
    }
    return 0;
}

After compiling and running, you can find that there is one more [ ] file in ps aux, which is the zombie file

wait function

When a process terminates, it will close all file descriptors and release the memory allocated in user space, but its PCB is still reserved, and the kernel saves some information in it; if it terminates normally, it saves the exit status, and if it terminates abnormally It stores which signal caused the process to terminate. The parent process of this process can call wait or waitpid to obtain this information, and then completely clear the process.

The parent process calls the wait function to recycle the child process termination information. This function has three functions:
1. Block and wait for the sub-process to exit
2. Recycle the residual resources of the sub-process
3. Obtain the end status of the sub-process (exit reason)

pid_t wait(int *status); Success: Cleaned up child process id Failure: -1 (no child process)

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

int main(void){
    pid_t pid,wpid;
    pid = fork();

    if(pid == 0){
        printf("i am child, my parent = %d,going to sleep 10s\n",getppid());
        sleep(10);
        printf("-------child die-------");
    }else if(pid > 0){
        wpid = wait(NULL);
        if(wpid == -1){
            perror("wait error");
            exit(1);
        }
        while(1){
            printf("i am parent, pid= %d, myson = %d\n",getppid(),pid);
            sleep(1);
        }
        
    }else{
        perror("fork");
        return 1;
    }
    return 0;
}

After compiling and running, check through ps aux, there is no [ ] file process, indicating that the zombie process has been recycled by wait

waitpid function

A wait function call can only recycle one child process. If there are 5 child processes, the waitpid function needs to be used.

The function is the same as wait but you can specify the pid process to clean up, and you can not block
pid_t waitpid(pid_t pid, int* status, in options); Success: return the cleaned child process ID Failure: -1 (no child process)
parameter pid:
        >0 Recycle the child process with the specified ID
        -1 Recycle any child process (equivalent to wait)
        0 Recycle all child processes in the same group as the current call to waitpid
        <-1 Recycle any child process in the specified process group

pipeline

IPC method:
        In the Linux environment, the process address spaces are independent of each other, and each process has a different user address space. The global variables of any process cannot be seen in another process, so processes cannot access each other. To exchange data, a buffer must be created in the kernel through the kernel. Process 1 copies the data from user space to The kernel buffer, and process 2 reads the data from the kernel buffer. This mechanism provided by the kernel is called inter-process communication.

        The completion of data transfer between processes requires special methods provided by the operating system, such as: files, pipes, signals, shared content, message queues, sockets, named pipes, etc. With the vigorous development of computers, some methods have been eliminated or abandoned due to their own defects. The commonly used inter-process communication methods today are:
1. Pipeline (easiest to use)
2. Signal (minimum overhead)
3. Shared mapping area (no blood relationship)
4. Local socket (most stable)

The concept of the pipeline:
        The pipeline is the most basic IPC mechanism, which acts between blood-related processes to complete data transmission. A pipe can be created by calling the pipe system function. It has the following characteristics:
1. Its essence is a pseudo-file (actually a kernel buffer)
2. It is referenced by two file descriptors, one indicates the read end and the other indicates the write end
3. It stipulates that data flow into the pipeline from the write end of the pipeline, from the read side.

The principle of the pipeline: the pipeline actually uses the ring queue mechanism for the kernel, and is implemented with the help of the kernel buffer (4k)

Limitations of the pipeline:
1. The data cannot be written by itself
. 2. Once the data is read, it does not exist in the pipeline and cannot be read repeatedly.
3. Because the pipeline adopts half-duplex communication mode. Therefore, data can only flow in one direction
4. Pipes can only be used between processes that have a common ancestor

Create an unnamed pipe:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 
  5 int main(void){
  6     int fds[2];
  7     int ret = -1;
  8 
  9     //create a pipe
 10     ret = pipe(fds);
 11     if(-1 == ret){
 12         perror("pipe");
 13         return 1;
 14     }
 15 
 16     //fds[0] is for reading fds[1] is for writing
 17     printf("fds[0]: %d  fds[1]: %d\n",fds[0],fds[1]);
 18 
 19     close(fds[0]);
 20     close(fds[1]);
 21 
 22     return 0;
 23 }

Output result:

fds[0]: 3  fds[1]: 4

The output here is 3 and 4 because 0 1 2 is occupied by standard input, standard output and standard error
 

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

#define SIZE 64

//父进程使用无名管道进行通信
//父进程写管道 子进程读管道
int main(void){
    pid_t pid = -1;
    int fds[2];
    char buf[SIZE];
    int ret = -1;
    
    //创建无名管道
    ret = pipe(fds);
    if(-1 == ret){
        perror("pipe");
        return 1;
    }
    
    //创建进程
    pid = fork();
    if(-1 == pid){
        perror("fork");
        return 1;
    }

    //子进程
    if(0 == pid){
        //关闭写端
        close(fds[1]);

        memset(buf, 0, SIZE);
        //读管道的内容
        ret = read(fds[0], buf, SIZE);
        if(ret < 0){
            perror("read");
            exit(-1);
        }

        printf("child process buf: %s\n",buf);

        //关闭读端
        close(fds[0]);
        exit(0);
    }
    
    //父进程
    //关闭读端
    close(fds[0]);

    ret = write(fds[1],"ABCDEGHIJK",10);
    if(ret < 0){
        perror("write");
        return 1;
    }

    printf("parent process write len: %d\n",ret);

    //关闭写端
    close(fds[1]);

    return 0;
}

Output result: 

parent process write len: 10
child process buf: ABCDEGHIJK

Pipeline read and write characteristics

 

 Summarize:

Read pipeline:
there is data in the pipeline, read returns the number of bytes actually read.
No data in the pipeline:
        all write ends of the pipeline are closed, and read returns 0 (equivalent to reading to the end of the file). If
        all write ends are not closed, read does not block Wait (there may be data delivery in the near future, and the cpu will be released at this time)

Write pipe:
all the read ends of the pipe are closed, and the process terminates abnormally (the process can also be terminated by capturing the SIGPIPE signal). The read ends of the pipe are
not all closed:
        the pipe is full, the write block
        pipe is not full, write writes the data, and returns the actual Bytes written

 The pipe size in ulimit -a    is the size of the pipeline buffer

famous pipeline

Pipes, since they have no names, can only be used for interprocess communication with affinity. In order to overcome this shortcoming, a named pipe (FIFO), also called a named pipe, FIFO file is proposed.

FIFO and pipe have some characteristics in common, the difference is:
1. FIFO exists as a special file in the file system, but the content in FIFO is stored in memory
2. When the process using FIFO exits , FIFO files will continue to be saved in the file system for future use
3. FIFO has a name, and unrelated processes can communicate by opening named pipes

mkfifo fifo  creates a well-known pipe through the command and finds that the size is 0 because its content is placed in memory

Note:
1. A process that opens a pipe for read-only will block until another process opens the pipe for write-only
2. A process that opens a pipe for write-only will block until another process opens the pipe for read-only open the pipe

Guess you like

Origin blog.csdn.net/weixin_43754049/article/details/126083999