[linux interprocess communication] 2 The Conduit

Posted by phu on Sun, 02 Jan 2022 19:36:57 +0100

1, What is pipeline

Pipeline, also known as anonymous pipeline, is a special type of file, which is embodied as two open file descriptors in the application layer

Anonymous pipeline is created in kernel space. Multiple processes know the space of the same anonymous pipeline and can use it to communicate

The anonymous pipeline will give the current process two file descriptors, one for read operation and one for write operation

Characteristics of pipeline:

  1. Half duplex, data can only flow in one direction at the same time.
  2. Data can only be written from one end of the pipe and read from the other end.
  3. Data written to the pipeline follows the first in, first out rule.
  4. The data transmitted by the pipeline is unformatted, which requires that the reader and writer of the pipeline must agree on the data format in advance, such as how many bytes count as a message.
  5. The pipeline is not an ordinary file and does not belong to a file system. It only exists in memory.
  6. The pipeline corresponds to a buffer in memory. Different systems may not have the same size.
  7. Reading data from the pipeline is a one-time operation. Once the data is read, it will be discarded from the pipeline to free up space for reading more data
  8. A pipe has no name and can only be used between processes with a common ancestor

II. Creation of pipeline

#include <unistd.h>
int pipe(int filedes[2]);

Function: the parameter filedes returns two file descriptors

Parameter: an array of int type, which holds two elements. filedes[0] is opened for reading and filedes[1] is opened for writing

Return value: 0 returned successfully, - 1 returned failed

Operation mode: the nameless pipeline can be operated through the read and write functions in the file IO

Example:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(){	
	int fds[2];
	if(pipe(fds)!=0){
		perror("fail to pepe!");
		return -1;	
	}
	printf("fds[0] = %d,fds[1] = %d\n",fds[0],fds[1]);

	if(write(fds[1],"hello world!",strlen("hello world!"))==-1)
	{                             //strlen only determines the length of string without terminator
		perror("fail to write!");
		exit(1);
	}
    /***************************************************
    *The content of the pipeline is not read out. Writing the content into it again is equivalent to adding content, and the previous content will not be deleted
    *****************************************************/
	write(fds[1],"nihao axuezm!",sizeof("nihao axuezm!"));
                        //sizeof determines that the length is added with a terminator
	char buf[32];
    /*********************************************
    *When reading, the content of the specified size will be read, no matter how many times it is written
    ************************************************/
	if(read(fds[0],buf,sizeof(buf))==-1){
		perror("fail to read!");
		exit(2);	
	}
	printf("%s\n",buf);
    /************************************************
    *If there is no content in the pipeline, it will be blocked when reading until it returns after reading the content
    *************************************************/
	if(read(fds[0],buf,sizeof(buf))==-1){
		perror("fail to read!");
		exit(2);	
	}
	printf("%s\n",buf);
    close(fds[0]);
    close(fds[1]);
	return 0;
}

III. application of pipeline

Because the pipeline can only communicate with related processes, the process communication using the pipeline is generally to create an unknown pipeline with the parent process first, and then the parent process creates a child process. The child process will inherit the file descriptor of the unknown pipeline created by the parent process, so that the communication between the parent and child processes can be carried out. (as mentioned earlier, the data in the kernel space is unique, so the pipeline created by the parent process inherited by the child process is the same as that of the parent process)

When data exchange is required, one end of sending data writes data to filedes[1], and one end of receiving data reads data from filedes[0]. This process is a data interaction

Application example:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){	
	int fds[2];
	if(pipe(fds)!=0){
		perror("fail to pepe!");
		return -1;	
	}

	pid_t pid;
	if((pid=fork())==-1)
	{
		perror("fail to fork!");
		return -1;
	}
	if(pid > 0){
		char buf[108];
		while(1){
			printf("-***write here***-\n");
			fgets(buf,sizeof(buf),stdin);
			if(write(fds[1],buf,strlen(buf))==-1){
				perror("fail to write");
				exit(2);
			}
			sleep(1);
		}
	
		wait(NULL);
	}else{
		char buf[108] = "";
		while(1){
			memset(buf,0,sizeof(buf));
			if(read(fds[0],buf,sizeof(buf))==-1){
					perror("fail to read");
					exit(1);
			}
			printf("-***read here***-\n");
			printf("%s",buf);
			printf("-***************-\n\n");
		}

	}
	
	close(fds[0]);
	close(fds[1]);
	return 0;
}

IV. characteristics of unknown pipeline reading data

1. By default, reading data from the pipeline with the read function is blocked.

2. Call the write function to write data to the pipeline. When the buffer is full (64K), write will also block.

3. During communication, when the write process writes data to the pipeline after all the read ports are closed, the write process will exit (receiving the SIGPIPE break signal). = " There are only write ports and no read ports

4. When the read end exists and the write end does not exist (that is, it is close d), if the pipeline has data, it will be read normally. If there is no data, read will immediately return 0 and will not be blocked

5, Set the blocking property of the file through the fcntl function

Set to block: fcntl(fd,F_SETFL,0);

Set to non blocking: fcntl(fd,F_SETFL,O_NONBLOCK);

What is blocking and non blocking?

If it is blocked and there is no data in the pipeline, read will wait until there is data, otherwise it will wait

If it is non blocking, when the read function runs, it will first check whether there is data in the pipeline. If there is data, it will run normally to read the data. If there is no data in the pipeline, the read function will return immediately and continue to execute the following code

6, Copy of file descriptor

DUP and dup2 are system calls that can be used to copy file descriptors so that the new file descriptor also identifies the file identified by the old file descriptor.

int dup(int oldfd);
/**********************************************************
*Copy an old file descriptor (oldfd) and assign a new file descriptor,
*The new file descriptor is the smallest available file descriptor in the calling process file descriptor table
*Return value: the new file descriptor is returned successfully, and - 1 is returned for failure
**********************************************************/
int dup2(int oldfd,int newfd);
/**********************************************************
*Copy an old file descriptor (oldfd) and assign a new file descriptor (newfd),
*The new file descriptor must be a value within the allowable range (0 ~ 1023),
*If newfd is already open, the file will be closed before copying
*Return value: newfd for success and - 1 for failure
**********************************************************/

DUP and dup2 are often used to redirect stdin, stdout, and stderr of a process

Example: use dup to copy the file descriptor of standard output

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

int main(){

	int fd = dup(1);
	if(fd == -1){
		perror("fail to dup!");
		exit(1);
	}
	printf("fd = %d\n",fd);
	write(fd,"hello world!\n",sizeof("hello world!\n"));

	close(fd);
	return 0;
}

Example: how to use output redirection = = "you can copy the original standard output file descriptor first, then replace it, and then copy it back. It can be used flexibly.

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

int main(){

	int fd_file = open("test.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
	if(fd_file == -1){
		perror("fail to open!");
		exit(1);
	}
    /********When using the dup function**********/
	close(1);
	int fd = dup(fd_file);
    /******************************/

    /********When using the dup2 function*********
	*int fd = dup2(fd_file,1);   
    *fd,fd_file,1 All points to test Txt file, fd==1, about to fd_file copy 1
    ******************************/
	if(fd == -1){
		perror("fail to dup!");
		exit(1);
	}
	printf("fd = %d\n",fd);
	printf("hello world!\n");
	close(fd_file);
		//close(fd);// If this file descriptor is closed here, writing will fail for unknown reasons
	return 0;
	
}

Note: when using dup2, specify the value of newfd. If a variable is only defined without an initial value, the minimum file descriptor value will be given by default, that is, 0

 

Topics: Linux