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