Detailed explanation of linux pipeline

pipeline

The concept of pipeline:

Pipeline is the most basic IPC mechanism, which acts between blood-related processes to complete data transfer. A pipe is created by calling the pipe system function. Has the following characteristics:

1. Its essence is a pseudo file (actually a kernel buffer)

2. Referenced by two file descriptors, one for the read side and one for the write side.

3. Specify that data flows into the pipe from the write end of the pipe and flows out from the read end.

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

Pipeline limitations:

① Data can be read by itself and cannot be written by itself.

② Once the data is read, it does not exist in the pipeline and cannot be read repeatedly.

③ Because the pipeline adopts half-duplex communication mode. Therefore, data can only flow in one direction.

④ Pipes can only be used between processes with common ancestors.

Common communication methods include simplex communication, half-duplex communication, and full-duplex communication.

pipe function

Create pipeline

    int pipe(int pipefd[2]); success: 0; failure: -1, set errno

The function call successfully returns r/w two file descriptors. No need to open, but manual close is required. Provision: fd[0] → r; fd[1] → w, just like 0 corresponds to standard input and 1 corresponds to standard output. Reading and writing data to the pipe file is actually reading and writing to the kernel buffer.

After the pipeline is successfully created, the process (parent process) that created the pipeline controls both the read and write ends of the pipeline. How to realize the communication between parent and child processes? Usually the following steps can be taken:

 

1. The parent process calls the pipe function to create a pipe, and gets two file descriptors fd[0] and fd[1] that point to the read and write ends of the pipe.

2. The parent process calls fork to create a child process, then the child process also has two file descriptors pointing to the same pipe.

3. The parent process closes the read end of the pipe, and the child process closes the write end of the pipe. The parent process can write data to the pipe, and the child process can read the data from the pipe. Since the pipeline is implemented using a circular queue, data flows into the pipeline from the write end and flows out from the read end, thus realizing inter-process communication.

    Exercise: Parent and child processes communicate using pipes, the parent writes a string, the child process reads and prints to the screen. 【pipe.c】

Thinking: Why, the sleep function is not used in the program, but it is still guaranteed that the child process will read the data when it runs?

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

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

int main(void)
{
    pid_t pid;
    char buf[1024];
    int fd[2];
    char *p = "test for pipe\n";
    
   if (pipe(fd) == -1) 
       sys_err("pipe");

   pid = fork();
   if (pid < 0) {
       sys_err("fork err");
   } else if (pid == 0) {
        close(fd[1]);
        int len = read(fd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
        close(fd[0]);
   } else {
       close(fd[0]);
       write(fd[1], p, strlen(p));
       wait(NULL);
       close(fd[1]);
   }
    
    return 0;
}


Read and write behavior of pipes

    The following four special cases need to be paid attention to when using pipes (assuming they are all blocking I/O operations and the O_NONBLOCK flag is not set):

1. If all file descriptors pointing to the write end of the pipe are closed (the reference count of the write end of the pipe is 0), and there are still processes reading data from the read end of the pipe, then the remaining data in the pipe is read and read again. will return 0, as if reading to the end of the file.

2. If the file descriptor pointing to the write end of the pipe is not closed (the reference count of the write end of the pipe is greater than 0), and the process holding the write end of the pipe does not write data to the pipe, then a process reads data from the read end of the pipe, then After the remaining data in the pipeline has been read, the read will block again, and the data will be read and returned until the data in the pipeline is readable.

3. If all file descriptors pointing to the read end of the pipe are closed (the reference count of the read end of the pipe is 0), and a process writes to the write end of the pipe, the process will receive the signal SIGPIPE, which usually causes the process to terminate abnormally. . Of course, you can also capture the SIGPIPE signal without terminating the process. The specific method signal chapter is described in detail.

4. If the file descriptor pointing to the read end of the pipe is not closed (the reference count of the read end of the pipe is greater than 0), and the process holding the read end of the pipe does not read data from the pipe, then a process writes data to the write end of the pipe, then Writing again will block when the pipe is full, and will not write data and return until there is a free space in the pipe.

Summarize:

① Read pipe: 1. There is data in the pipe, read returns the actual number of bytes read.

2. No data in the pipeline:

(1) The write end of the pipe is all closed, and read returns 0 (as if the end of the file was read)

  (2) The write side is not all closed, and the read block waits (there may be data delivery in the near future, and the cpu will be released at this time)

    ② Write pipe: 1. All the read ends of the pipe are closed, and the process terminates abnormally (you can also use the capture SIGPIPE signal to make the process not terminate)

2. The read end of the pipe is not fully closed:

(1) The pipeline is full and write is blocked.

(2) If the pipe is not full, write writes the data and returns the actual number of bytes written.

    Exercise: Use pipes to communicate between parent and child processes, complete: ls | wc -l. Assume that the parent process implements ls and the child process implements wc.

ls命令正常会将结果集写出到stdout,但现在会写入管道的写端;wc –l 正常应该从stdin读取数据,但此时会从管道的读端读。      【pipe1.c】

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

int main(void)
{
	pid_t pid;
	int fd[2];

	pipe(fd);
	pid = fork();

	if (pid == 0) {  //child
		close(fd[1]);	                //子进程从管道中读数据,关闭写端
		dup2(fd[0], STDIN_FILENO);		//让wc从管道中读取数据
		execlp("wc", "wc", "-l", NULL);	//wc命令默认从标准读入取数据

	} else {

		close(fd[0]);	//父进程向管道中写数据,关闭读端
		dup2(fd[1], STDOUT_FILENO);		//将ls的结果写入管道中
		execlp("ls", "ls", NULL);		//ls输出结果默认对应屏幕
	}

	return 0;
}





/*
 *  程序不时的会出现先打印$提示符,再出程序运行结果的现象。
 *  这是因为:父进程执行ls命令,将输出结果给通过管道传递给
 *  子进程去执行wc命令,这时父进程若先于子进程打印wc运行结果
 *  之前被shell使用wait函数成功回收,shell就会先于子进程打印
 *  wc运行结果之前打印$提示符。
 *  解决方法:让子进程执行ls,父进程执行wc命令。或者在兄弟进程间完成。
 */



程序执行,发现程序执行结束,shell还在阻塞等待用户输入。这是因为,shell → fork → ./pipe1, 程序pipe1的子进程将stdin重定向给管道,父进程执行的ls会将结果集通过管道写给子进程。若父进程在子进程打印wc的结果到屏幕之前被shell调用wait回收,shell就会先输出$提示符。

    练习:使用管道实现兄弟进程间通信。 兄:ls  弟: wc -l  父:等待回收子进程。

要求,使用“循环创建N个子进程”模型创建兄弟进程,使用循环因子i标示。注意管道读写行为。 【pipe2.c】

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

int main(void)
{
	pid_t pid;
	int fd[2], i;
	
	pipe(fd);

	for (i = 0; i < 2; i++) {
		if((pid = fork()) == 0) {
			break;
        }
    }

	if (i == 0) {			//兄
		close(fd[0]);				//写,关闭读端
		dup2(fd[1], STDOUT_FILENO);		
		execlp("ls", "ls", NULL);	
	} else if (i == 1) {	//弟
		close(fd[1]);				//读,关闭写端
		dup2(fd[0], STDIN_FILENO);		
		execlp("wc", "wc", "-l", NULL);		
	} else {
        close(fd[0]);
        close(fd[1]);
		for(i = 0; i < 2; i++)		//两个儿子wait两次
			wait(NULL);
	}

	return 0;
}


    测试:是否允许,一个pipe有一个写端,多个读端呢?是否允许有一个读端多个写端呢? 【pipe3.c】

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

int main(void)
{
	pid_t pid;
	int fd[2], i, n;
	char buf[1024];

	int ret = pipe(fd);
	if(ret == -1){
		perror("pipe error");
		exit(1);
	}

	for(i = 0; i < 2; i++){
		if((pid = fork()) == 0)
			break;
		else if(pid == -1){
			perror("pipe error");
			exit(1);
		}
	}

	if (i == 0) {			
		close(fd[0]);				
		write(fd[1], "1.hello\n", strlen("1.hello\n"));
	} else if(i == 1) {	
		close(fd[0]);				
		write(fd[1], "2.world\n", strlen("2.world\n"));
	} else {
		close(fd[1]);       //父进程关闭写端,留读端读取数据    
//		sleep(1);
		n = read(fd[0], buf, 1024);     //从管道中读数据
		write(STDOUT_FILENO, buf, n);

		for(i = 0; i < 2; i++)		//两个儿子wait两次
			wait(NULL);
	}

	return 0;
}


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324228864&siteId=291194637