Buscar..
Introducción
Creación y uso básicos.
Las canalizaciones anónimas, o simplemente tuberías, son objetos gestionados por el kernel expuestos a procesos como un par de descriptores de archivos, uno para el término de lectura y otro para el término de escritura. Se crean a través de la función pipe
(2):
int pipefds[2];
int result;
result = pipe(pipefds);
En caso de éxito, pipe()
registra el descriptor para el final de lectura del conducto en el índice 0 de la matriz proporcionada, y el descriptor para el extremo de escritura en el índice 1; estos índices son análogos a los números de descriptores de archivos convencionales para las secuencias estándar.
Una vez creado un conducto, uno utiliza las funciones de E / S de POSIX para escribir en el extremo de escritura o leer desde el final de lectura:
Proceso 1:
ssize_t bytes_written = write(pipefds[1], "Hello, World!", 14);
Proceso 2:
char buffer[256];
ssize_t bytes_read = read(pipefds[0], buffer, sizeof(buffer));
Alternativamente, uno puede usar fdopen()
para envolver uno de los dos extremos de la tubería en una estructura de FILE
para usar con las funciones de C stdio:
FILE *write_end = fdopen(pipefds[1]);
if (write_end) {
fputs("Hello, World!");
}
Las tuberías tienen búferes de E / S finitos y se bloquearán las escrituras normales en tuberías con búferes completos. Por lo tanto, los conductos no son un mecanismo seguro para que un hilo se comunique consigo mismo, ya que si el hilo que se escribe en un tubo también es el único que lee, entonces, tan pronto como el bloque de escritura se bloquee, el hilo no se bloquea.
Una tubería persiste siempre que cualquier proceso tenga una descripción de archivo abierto para cualquiera de los extremos de la tubería. La función de close
(2) se puede usar para cerrar un extremo de tubería representado por un descriptor de archivo, y fclose
(3) se puede usar para cerrar un extremo de tubería a través de un FILE
envuelto alrededor de él.
Estableciendo una tubería para un proceso hijo.
Los descriptores de archivo y los objetos de FILE
son recursos por proceso que no pueden intercambiarse entre procesos a través de la E / S ordinaria. Por lo tanto, para que dos procesos distintos se comuniquen a través de un conducto anónimo, uno o ambos procesos participantes deben heredar un extremo de conducto abierto del proceso que creó el conducto, que debe ser el proceso principal o un antepasado más lejano. El caso más simple es aquel en el que un proceso padre quiere comunicarse con un proceso hijo.
Debido a que el proceso hijo debe heredar la descripción de archivo abierto necesaria de su padre, primero se debe crear el conducto. El padre entonces se bifurca. Normalmente, cada proceso será estrictamente un lector o estrictamente un escritor; en ese caso, cada uno debe cerrar el extremo del tubo que no pretende usar.
void demo() {
int pipefds[2];
pid_t pid;
// Create the pipe
if (pipe(pipefds)) {
// error - abort ...
}
switch (pid = fork()) {
case -1:
// error - abort ...
break;
case 0: /* child */
close(pipefds[0]);
write(pipefds[1], "Goodbye, and thanks for all the fish!", 37);
exit(0);
default: /* parent */
close(pipefds[1]);
char buffer[256];
ssize_t nread = read(pipefds[0], sizeof(buffer) - 1);
if (nread >= 0) {
buffer[nread] = '\0';
printf("My child said '%s'\n", buffer);
}
// collect the child
wait(NULL);
break;
}
}
Conectando dos procesos hijos a través de una tubería.
La conexión de dos procesos secundarios a través de una tubería se realiza conectando cada uno de los dos hijos al padre a través de diferentes extremos de la misma tubería. Por lo general, el padre no será parte de la conversación entre los niños, por lo que cierra sus copias de los dos extremos de la tubería.
int demo() {
int pipefds[2];
pid_t child1, child2;
if (pipe(pipefds)) {
// error - abort ...
}
switch (child1 = fork()) {
case -1:
// error - abort
break;
case 0: /* child 1 */
close(pipefds[0]);
write(pipefds[1], "Hello, brother!", 15);
exit(0);
default: /* parent */
// nothing
}
switch (child1 = fork()) {
case -1:
// error - abort
break;
case 0: /* child 2 */
char buffer[256];
ssize_t nread;
close(pipefds[1]);
nread = read(pipefds[0], buffer, sizeof(buffer) - 1);
if (nread < 0) {
// handle error
} else {
buffer[nread] = '\0';
printf("My brother told me '%s'\n", buffer);
}
exit(0);
default: /* parent */
// nothing
}
// Only the parent reaches this point
close(pipefds[0]);
close(pipefds[1]);
if (child1 >= 0) {
wait(NULL);
if (child2 >= 0) {
wait(NULL);
}
}
}
Creando una tubería de estilo shell
Una tubería de estilo shell consta de dos o más procesos, cada uno con su salida estándar conectada a la entrada estándar del siguiente. Las conexiones de salida a entrada están construidas en tuberías. Para establecer un conjunto de procesos similar a una tubería, uno crea procesos secundarios conectados a la tubería como se describe en otro ejemplo, y además utiliza la función dup2
(2) para duplicar cada extremo de la tubería en el descriptor de archivo estándar apropiado de su proceso. En general, es una buena idea cerrar el descriptor de archivo final de la tubería original, especialmente si, como suele ser el caso, uno tiene la intención de que el hijo ejecute un comando diferente.
// Most error handling omitted for brevity, including ensuring that pipe ends are
// always closed and child processes are always collected as needed; see other
// examples for more detail.
int demo() {
int pipefds[2];
pid_t child1 = -1, child2 = -1;
pipe(pipefds);
switch (child1 = fork()) {
case -1:
// handle error ...
break;
case 0: /* child 1 */
close(pipefds[0]);
dup2(pipefds[1], STDOUT_FILENO);
close(pipefds[1]);
execl("/bin/cat", "cat", "/etc/motd", NULL);
exit(1); // execl() returns only on error
default: /* parent */
// nothing
}
switch (child2 = fork()) {
case -1:
// handle error ...
break;
case 0: /* child 2 */
close(pipefds[1]);
dup2(pipefds[0], STDIN_FILENO);
close(pipefds[0]);
execl("/bin/grep", "grep", "[Ww]ombat", NULL);
exit(1); // execl() returns only on error
default: /* parent */
// nothing
}
// Only the parent reaches this point
close(pipefds[0]);
close(pipefds[1]);
wait(NULL);
wait(NULL);
}
Uno de los errores más comunes en la configuración de una tubería es olvidar que uno o más de los procesos involucrados cierran los extremos de las tuberías que no está utilizando. Como regla general, el resultado final debe ser que cada extremo de tubería sea propiedad de exactamente un proceso, y que solo esté abierto en ese proceso.
En un caso como la función de demostración anterior, si el segundo hijo o el padre no cierran las pipefds[1]
, el segundo hijo quedará colgado, ya que grep
continuará esperando la entrada hasta que vea EOF, y eso no será observado siempre que el extremo de escritura de la tubería permanezca abierto en cualquier proceso, como el proceso padre o el segundo hijo.