In this part of the tutorial we will be creating and using apipe, wish me luck 😅, don’t worry we can do it.
Now how do we create apipe with code? Mmm 🤔, here’s a snippet.
#include <unistd.h>int main(void) { //file descriptors int pipefd[2]; int pipe(pipefd);}
Let’s see part of the documentation of thepipe() on the manpage:
pipe() creates apipe, aunidirectional data channel that can be used for interprocess communication. The array
pipefd
is used to return two file descriptors referring to the ends of the pipe.pipefd[0]
refers to the read end of the pipe.pipefd[1]
refers to the write end of the pipe.
This argument that takes contains the file descriptors needed, one for theread end of the pipe and another for thewrite end of the pipe. So ideally with one you push the data on thepipe and with the other you access that data. Very ingenious, right?
Let’s see if we could do something with this, I will tested on a very simple program the will behave like this:
I recommend you to play around with this by yourself. I mean you could try exactly as I put it here but you would understand better if you create a simple example by yourself. This steps will help you also:
Let’s see the code and break it in pieces, it would be better that way, because you know, it isC 🙄 .
#include <stdio.h>#include <string.h>#include <sys/types.h>#include <unistd.h>int main(int argc, char *argv[]) { // file descriptors to use on the write end // and read end of the pipe int filedes[2]; if (pipe(filedes) == -1) { perror("pipe couldn't be created"); return -1; } // after this both file descriptors are open // creating a child process pid_t process = fork(); switch (process) { case -1: perror("fork failed"); return -1; // we are in the child process case 0: // closing write end pipe in child if (close(filedes[1]) == -1) { perror("closing in child"); return -1; } break; // we are in the parent process default: if (close(filedes[0]) == -1) { perror("closing in parent"); return -1; } // closing read end pipe in parent } if (passingMsg(process, filedes) == -1) { perror("passingMsg"); return -1; } // cleaning if (process == 0) close(filedes[0]); else close(filedes[1]); return 0;}
In this first snippet we can see that I made use offork() to create a new process, go to its manpage, withman fork
and you will see its documentation. If the documentation is too heavy don’t worry I got your back, this 👉tutorial from Geeks for Geeks, is precisely aboutfork(), take a look at it is very concise.
If you read the code you notice that I created apipe, a new child process withfork() and then I made some magic there on the switch statement, right? What just happened there? Let me draw you the situation:
After the creation of the pipe
+-[Calling process]<-+ f0: read end of the pipe| f1 f0 | f1: write end of the pipe| |+--->| Pipe |-------+
This is with a single process because we haven’t usedfork() yet both file descriptors are on the calling process, we will call itparent process.
After the creation of the new process
+-[Calling process]<-+ f0: read end of the pipe| f1 f0 | f1: write end of the pipe| _______ | \+--->| |-----+/ | Pipe |/+--->|_______|-----+\| f1 f0 || |+---[Child process]<-+
Now here is the trick, after the creation of thechild process, both processes can write and read from the pipe. Which may be the behavior needed by your application but in our case, I want to write from theparent or calling process and read from thechild process. That’s the reason why I closed on the switch statement first thewrite end file descriptor of thechild process, and later also closed theread end file descriptor on theparent process. It seems like a tongue twister, but my next draw will help you understand:
After the switch statement
+-[Calling process] f0: read end of the pipe| f1 f1: write end of the pipe| _______ \+--->| | | Pipe | |_______|-----+\ f0 | | [Child process]<-+
After the switch statement I’m ready to pass messages from theparent to thechild, here is the part of the code corresponding to the functionpassingMsg.
int passingMsg(pid_t process, int *filedes) { char buff[3]; int BUF_SIZE = 3; int bRead; char msg[3]; switch (process) { //child process case 0: printf("MSG From Parent:\n"); for (;;) { bRead = read(filedes[0], buff, BUF_SIZE); //reading from the pipe if (bRead == -1) { perror("read on child"); return -1; } if (bRead == 0) // end of steam data break; // writing msg to stdout if (write(STDOUT_FILENO, buff, bRead) != bRead) { //writing to the console perror("write to stdout"); return -1; } } break; //parent process default: for (int i = 0; i < 10; i++) { sleep(1); snprintf(msg, 3, "%d\n", i); if (write(filedes[1], msg, strlen(msg)) == -1) { //writing on the pipe perror("write from parrent"); return -1; } } } return 0;}
I will leave you two links ofpipes inGo andPython.
Python:
Go:
No idea how is this for other programming languages go and find how are pipes on other programming language that you like :smiley:.
Well it is a pretty long article I admit it, but if you read it with calm you will learn a cool stuff. For me this is also new so don’t worry if you don’t understand it at first. There’s a lot of information about pipe.
That’s all folks :wave:.