Comunicação entre processos - tubos

Pipe é um meio de comunicação entre dois ou mais processos relacionados ou inter-relacionados. Pode ser dentro de um processo ou uma comunicação entre os processos filho e pai. A comunicação também pode ser multinível, como comunicação entre os pais, o filho e o neto, etc. A comunicação é alcançada por um processo escrevendo no cachimbo e outro lendo no cachimbo. Para realizar a chamada do sistema de pipe, crie dois arquivos, um para gravar no arquivo e outro para ler o arquivo.

O mecanismo do tubo pode ser visualizado em um cenário em tempo real, como encher água com o tubo em algum recipiente, digamos um balde, e alguém recuperando-o, digamos com uma caneca. O processo de enchimento nada mais é do que escrever no tubo e o processo de leitura nada mais é do que recuperar do tubo. Isso implica que uma saída (água) é entrada para a outra (balde).

#include<unistd.h>

int pipe(int pipedes[2]);

Esta chamada de sistema criaria um pipe para comunicação unilateral, ou seja, ela cria dois descritores, o primeiro é conectado para ler do pipe e o outro é conectado para escrever no pipe.

O descritor pipedes [0] é para leitura e pipedes [1] é para escrita. O que quer que seja escrito em pipedes [1] pode ser lido em pipedes [0].

Esta chamada retornaria zero em caso de sucesso e -1 em caso de falha. Para saber a causa da falha, verifique com a variável errno ou função perror ().

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

Mesmo que as operações básicas do arquivo sejam de leitura e gravação, é essencial abrir o arquivo antes de realizar as operações e fechá-lo após a conclusão das operações necessárias. Normalmente, por padrão, 3 descritores abertos para cada processo, que são usados ​​para entrada (entrada padrão - stdin), saída (saída padrão - stdout) e erro (erro padrão - stderr) tendo descritores de arquivo 0, 1 e 2 respectivamente.

Esta chamada de sistema retornaria um descritor de arquivo usado para outras operações de arquivo de leitura / gravação / busca (lseek). Normalmente os descritores de arquivo começam em 3 e aumentam em um número conforme o número de arquivos abertos.

Os argumentos passados ​​para abrir a chamada do sistema são nome do caminho (caminho relativo ou absoluto), sinalizadores que mencionam o propósito de abrir o arquivo (digamos, abrir para leitura, O_RDONLY, para escrever, O_WRONLY, para ler e escrever, O_RDWR, para anexar ao arquivo existente O_APPEND, para criar arquivo, se não existir com O_CREAT e assim por diante) e o modo necessário fornecendo permissões de leitura / gravação / execução para o usuário ou proprietário / grupo / outros. O modo pode ser mencionado com símbolos.

Ler - 4, Gravar - 2 e Executar - 1.

Por exemplo: valor octal (começa com 0), 0764 significa que o proprietário tem permissões de leitura, gravação e execução, o grupo tem permissões de leitura e gravação, outro tem permissões de leitura. Isso também pode ser representado como S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, que implica ou operação de 0700 | 0040 | 0020 | 0004 → 0764.

Esta chamada de sistema, em caso de sucesso, retorna o novo id do descritor de arquivo e -1 em caso de erro. A causa do erro pode ser identificada com a variável errno ou função perror ().

#include<unistd.h>

int close(int fd)

O encerramento da chamada do sistema acima já abriu o descritor de arquivo. Isso significa que o arquivo não está mais em uso e os recursos associados podem ser reutilizados por qualquer outro processo. Esta chamada de sistema retorna zero em caso de sucesso e -1 em caso de erro. A causa do erro pode ser identificada com a variável errno ou função perror ().

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count)

A chamada de sistema acima é para ler o arquivo especificado com argumentos do descritor de arquivo fd, buffer adequado com memória alocada (estática ou dinâmica) e o tamanho do buffer.

O ID do descritor de arquivo é para identificar o respectivo arquivo, que é retornado após a chamada de sistema open () ou pipe (). O arquivo precisa ser aberto antes de ler o arquivo. Ele abre automaticamente no caso de chamar a chamada do sistema pipe ().

Esta chamada retornaria o número de bytes lidos (ou zero no caso de encontrar o fim do arquivo) em caso de sucesso e -1 em caso de falha. Os bytes de retorno podem ser menores que o número de bytes solicitados, caso não haja dados disponíveis ou o arquivo seja fechado. O número de erro adequado é definido em caso de falha.

Para saber a causa da falha, verifique com a variável errno ou função perror ().

#include<unistd.h>

ssize_t write(int fd, void *buf, size_t count)

A chamada de sistema acima é para gravar no arquivo especificado com argumentos do descritor de arquivo fd, um buffer adequado com memória alocada (estática ou dinâmica) e o tamanho do buffer.

O ID do descritor de arquivo é para identificar o respectivo arquivo, que é retornado após a chamada de sistema open () ou pipe ().

O arquivo precisa ser aberto antes de gravá-lo. Ele abre automaticamente no caso de chamar a chamada do sistema pipe ().

Esta chamada retornaria o número de bytes escritos (ou zero caso nada seja escrito) em caso de sucesso e -1 em caso de falha. O número de erro adequado é definido em caso de falha.

Para saber a causa da falha, verifique com a variável errno ou função perror ().

Programas de exemplo

A seguir estão alguns programas de exemplo.

Example program 1 - Programa para escrever e ler duas mensagens usando pipe.

Algoritmo

Step 1 - Crie um tubo.

Step 2 - Envie uma mensagem para o tubo.

Step 3 - Recupere a mensagem do tubo e escreva-a na saída padrão.

Step 4 - Envie outra mensagem para o tubo.

Step 5 - Recupere a mensagem do tubo e escreva-a na saída padrão.

Note - A recuperação de mensagens também pode ser feita após o envio de todas as mensagens.

Source Code: simplepipe.c

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

int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   
   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", readmessage);
   return 0;
}

Note- Idealmente, o status de retorno precisa ser verificado para cada chamada do sistema. Para simplificar o processo, não são feitas verificações para todas as chamadas.

Etapas de execução

Compilação

gcc -o simplepipe simplepipe.c

Execução / Saída

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell

Example program 2 - Programa para escrever e ler duas mensagens através do pipe usando os processos pai e filho.

Algoritmo

Step 1 - Crie um tubo.

Step 2 - Crie um processo filho.

Step 3 - O processo pai grava no tubo.

Step 4 - O processo filho recupera a mensagem do canal e a grava na saída padrão.

Step 5 - Repita os passos 3 e 4 mais uma vez.

Source Code: pipewithprocesses.c

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

int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   pid = fork();
   
   // Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

Etapas de execução

Compilation

gcc pipewithprocesses.c –o pipewithprocesses

Execution

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

Comunicação bidirecional usando tubos

A comunicação de tubulação é vista como comunicação unilateral, ou seja, o processo pai grava e o processo filho lê ou vice-versa, mas não ambos. No entanto, e se o pai e a criança precisarem escrever e ler os tubos simultaneamente, a solução é uma comunicação bidirecional usando tubos. Dois tubos são necessários para estabelecer uma comunicação bidirecional.

A seguir estão as etapas para alcançar a comunicação bidirecional -

Step 1- Crie dois tubos. O primeiro é para o pai escrever e o filho ler, digamos como pipe1. O segundo é para a criança escrever e os pais ler, digamos como pipe2.

Step 2 - Crie um processo filho.

Step 3 - Feche as extremidades indesejadas, pois apenas uma extremidade é necessária para cada comunicação.

Step 4 - Feche as extremidades indesejadas no processo pai, leia o final do pipe1 e grave o final do pipe2.

Step 5 - Feche as extremidades indesejadas no processo filho, escreva no final do pipe1 e leia o final do pipe2.

Step 6 - Realize a comunicação conforme necessário.

Programas de amostra

Sample program 1 - Alcançar a comunicação bidirecional usando tubos.

Algoritmo

Step 1 - Crie pipe1 para o processo pai gravar e o processo filho ler.

Step 2 - Crie pipe2 para o processo filho gravar e o processo pai ler.

Step 3 - Feche as extremidades indesejadas do tubo do lado pai e filho.

Step 4 - Processo pai para escrever uma mensagem e processo filho para ler e exibir na tela.

Step 5 - Processo filho para escrever uma mensagem e processo pai para ler e exibir na tela.

Source Code: twowayspipe.c

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

int main() {
   int pipefds1[2], pipefds2[2];
   int returnstatus1, returnstatus2;
   int pid;
   char pipe1writemessage[20] = "Hi";
   char pipe2writemessage[20] = "Hello";
   char readmessage[20];
   returnstatus1 = pipe(pipefds1);
   
   if (returnstatus1 == -1) {
      printf("Unable to create pipe 1 \n");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);
   
   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 \n");
      return 1;
   }
   pid = fork();
   
   if (pid != 0) // Parent process {
      close(pipefds1[0]); // Close the unwanted pipe1 read side
      close(pipefds2[1]); // Close the unwanted pipe2 write side
      printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
   } else { //child process
      close(pipefds1[1]); // Close the unwanted pipe1 write side
      close(pipefds2[0]); // Close the unwanted pipe2 read side
      read(pipefds1[0], readmessage, sizeof(readmessage));
      printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
      write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
   }
   return 0;
}

Etapas de execução

Compilação

gcc twowayspipe.c –o twowayspipe

Execução

In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello