Curso de programación en C para GNU/Linux (VII)

En la pasada entrega utilizamos la forma más rudimentaria de comunicación entre procesos: las señales. Esta vez vamos a analizar una manera mucho más provechosa de comunicar datos entre procesos: las tuberías. También hablaremos de los mecanismos IPC de comunicación entre procesos, pero no lo haremos con tanto detalle O:-)

 

Tuberías

Las tuberías o “pipes” simplemente conectan la salida estándar de un proceso con la entrada estándar de otro. Normalmente las tuberías son de un solo sentido, imaginemos lo desagradable que sería que estuviéramos utilizando un lavabo y que las tuberías fueran bidireccionales, lo que emanaran los desagües sería bastante repugnante. Por esta razón, las tuberías suelen ser “half-duplex”, es decir, de un único sentido, y se requieren dos tuberías “half-duplex” para hacer una comunicación en los dos sentidos, es decir “full-duplex”. Las tuberías son, por tanto, flujos unidireccionales de bytes que conectan la salida estándar de un proceso con la entrada estándar de otro proceso.

Cuando dos procesos están enlazados mediante una tubería, ninguno de ellos es consciente de esta redirección, y actúa como lo haría normalmente. Así pues, cuando el proceso escritor desea escribir en la tubería, utiliza las funciones normales para escribir en la salida estándar. Lo único especial que sucede es que el descriptor de fichero que está utilizando ya no corresponde al terminal (ya no se escriben cosas por la pantalla), sino que se trata de un fichero especial que ha creado el núcleo. El proceso lector se comporta de forma muy similar: utiliza las llamadas normales para recoger valores de la entrada estándar, solo que ésta ya no se corresponde con el teclado, sino que será el extremo de la tubería. Los procesos están autorizados a realizar lecturas no bloqueantes de la tubería, es decir, si no hay datos para ser leídos o si la tubería está bloqueada, se devolverá un error. Cuando ambos procesos han terminado con la tubería, el inodo de la tubería es desechado junto con la página de datos compartidos.

El uso de un pipe a mí me recuerda a los antiguos “teléfonos” que hacíamos mi hermana y yo con dos envases de yogur y una cuerda muy fina. Uníamos los fondos de los yogures con la cuerda, y cuando ésta estaba muy tensa, al hablar por un yogur, se escuchaba en la otra parte. Era un método divertido de contar secretos, pero tenía el mismo inconveniente que los pipes: si uno de los dos estaba hablando, el otro no podía hacerlo al mismo tiempo o no valía para nada. Era una comunicación unidireccional, al contrario de lo que pasa con los teléfonos modernos:

cursoc06.gif

Una tubería es unidireccional, como los teléfonos de yogur.

La utilización de tuberías mediante el uso de la shell es “el pan nuestro de cada día”, cualquier administrador de sistemas medianamente preparado encadena comandos y comandos mediante tuberías de forma natural:

txipi@neon:~$ cat /etc/passwd | grep bash | wc –lines
     11

Los comandos “cat”, “grep” y “wc” se lanzan en paralelo y el primero va “alimentando” al segundo, que posteriormente “alimenta” al tercero. Al final tenemos una salida filtrada por esas dos tuberías. Las tuberías empleadas son destruidas al terminar los procesos que las estaban utilizando.

Utilizar tuberías en C es también bastante sencillo, si bien a veces es necesario emplear lápiz y papel para no liarse con tanto descriptor de fichero. Ya vimos anteriormente que para abrir un fichero, leer y escribir en él y cerrarlo, se empleaba su descriptor de fichero. Una tubería tiene en realidad dos descriptores de fichero: uno para el extremo de escritura y otro para el extremo de lectura. Como los descriptores de fichero de UNIX son simplemente enteros, un pipe o tubería no es más que un array de dos enteros:

int tuberia[2];

Para crear la tubería se emplea la función pipe(), que abre dos descriptores de fichero y almacena su valor en los dos enteros que contiene el array de descriptores de fichero. El primer descriptor de fichero es abierto como O_RDONLY, es decir, sólo puede ser empleado para lecturas. El segundo se abre como O_WRONLY, limitando su uso a la escritura. De esta manera se asegura que el pipe sea de un solo sentido: por un extremo se escribe y por el otro se lee, pero nunca al revés. Ya hemos dicho que si se precisa una comunicación “full-duplex”, será necesario crear dos tuberías para ello.

int tuberia[2];

pipe(tuberia);

Una vez creado un pipe, se podrán hacer lecturas y escrituras de manera normal, como si se tratase de cualquier fichero. Sin embargo, no tiene demasiado sentido usar un pipe para uso propio, sino que se suelen utilizar para intercambiar datos con otros procesos. Como ya sabemos, un proceso hijo hereda todos los descriptores de ficheros abiertos de su padre, por lo que la comunicación entre el proceso padre y el proceso hijo es bastante cómoda mediante una tubería. Para asegurar la unidireccionalidad de la tubería, es necesario que tanto padre como hijo cierren los respectivos descriptores de ficheros. En la siguiente figura vemos cómo un proceso padre puede enviarle datos a su hijo a través de una tubería. Para ello el proceso padre cierra el extremo de lectura de la tubería, mientras que el proceso hijo cierra el extremo de escritura de la misma:

cursoc07.gif

El proceso padre y su hijo comparten datos mediante una tubería.

La tubería “p” se hereda al hacer el fork() que da lugar al proceso hijo, pero es necesario que el padre haga un close() de p[0] (el lado de lectura de la tubería), y el hijo haga un close() de p[1] (el lado de escritura de la tubería). Una vez hecho esto, los dos procesos pueden emplear la tubería para comunicarse (siempre unidireccionalmente), haciendo write() en p[1] y read() en p[0], respectivamente. Veamos un ejemplo de este tipo de situación:

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

#define SIZE 512

int main( int argc, char **argv )
{
  pid_t pid;
  int p[2], readbytes;
  char buffer[SIZE];

  pipe( p );

  if ( (pid=fork()) == 0 )
  { // hijo
    close( p[1] ); /* cerramos el lado de escritura del pipe */

    while( (readbytes=read( p[0], buffer, SIZE )) > 0)
      write( 1, buffer, readbytes );

    close( p[0] );
  }
  else
  { // padre
    close( p[0] ); /* cerramos el lado de lectura del pipe */

    strcpy( buffer, "Esto llega a traves de la tuberia\n" );
    write( p[1], buffer, strlen( buffer ) );

    close( p[1] );
  }
  waitpid( pid, NULL, 0 );
  exit( 0 );
}

La salida de este programa no es muy espectacular, pero muestra el funcionamiento del mismo: se crean dos procesos y uno (el padre) le comunica un mensaje al otro (el hijo) a través de una tubería. El hijo al recibir el mensaje, lo escribe por la salida estándar (hace un write() en el descriptor de fichero 1). Por último cierran los descriptores de ficheros utilizados, y el padre espera al hijo a que finalice:

txipi@neon:~$ gcc pipefork.c –o pipefork
txipi@neon:~$ ./pipefork
Esto llega a traves de la tuberia

Veamos ahora cómo implementar una comunicación bidireccional entre dos procesos mediante tuberías. Como ya hemos venido diciendo, será preciso crear dos tuberías diferentes (a[2] y b[2]), una para cada sentido de la comunicación. En cada proceso habrá que cerrar descriptores de ficheros diferentes. Vamos a emplear el pipe a[2] para la comunicación desde el padre al hijo, y el pipe b[2] para comunicarnos desde el hijo al padre. Por lo tanto, deberemos cerrar:

  • En el padre:
    • el lado de lectura de a[2].
    • el lado de escritura de b[2].
  • En el hijo:
    • el lado de escritura de a[2].
    • el lado de lectura de b[2].

Tal y como se puede ver en la siguiente figura:

cursoc08.gif

Dos procesos se comunican bidireccionalmente con dos tuberías.

El código anterior se puede modificar para que la comunicación sea bidireccional:

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

#define SIZE 512

int main( int argc, char **argv )
{
  pid_t pid;
  int a[2], b[2], readbytes;
  char buffer[SIZE];

  pipe( a );
  pipe( b );

  if ( (pid=fork()) == 0 )
  { // hijo
    close( a[1] ); /* cerramos el lado de escritura del pipe */
    close( b[0] ); /* cerramos el lado de lectura del pipe */

    while( (readbytes=read( a[0], buffer, SIZE ) ) > 0)
      write( 1, buffer, readbytes );
    close( a[0] );

    strcpy( buffer, "Soy tu hijo hablandote por
            la otra tuberia.\n" );
    write( b[1], buffer, strlen( buffer ) );
    close( b[1] );
  }
  else
  { // padre
    close( a[0] ); /* cerramos el lado de lectura del pipe */
    close( b[1] ); /* cerramos el lado de escritura del pipe */

    strcpy( buffer, "Soy tu padre hablandote
            por una tuberia.\n" );
    write( a[1], buffer, strlen( buffer ) );
    close( a[1]);

    while( (readbytes=read( b[0], buffer, SIZE )) > 0)
      write( 1, buffer, readbytes );
    close( b[0]);
  }
  waitpid( pid, NULL, 0 );
  exit( 0 );
}

La salida de este ejemplo es también bastante simple:

txipi@neon:~$ dospipesfork.c -o dospipesfork
txipi@neon:~$ ./dospipesfork
Soy tu padre hablandote por una tuberia.
Soy tu hijo hablandote por la otra tuberia.

Avancemos en cuanto a conceptos teóricos. La función dup() duplica un descriptor de fichero. A simple vista podría parecer trivial, pero es muy útil a la hora de utilizar tuberías. Ya sabemos que inicialmente, el descriptor de fichero 0 corresponde a la entrada estándar, el descriptor 1 a la salida estándar y el descriptor 2 a la salida de error estándar. Si empleamos dup() para duplicar alguno de estos descriptores en uno de los extremos de una tubería, podremos realizar lo mismo que hace la shell cuando enlaza dos comandos mediante una tubería: reconducir la salida estándar de un proceso a la entrada estándar del siguiente. El prototipo de la función dup() es el siguiente:

int dup(int oldfd);
int dup2(int oldfd, int newfd);

La función dup() asigna el descriptor más bajo de los disponibles al descriptor antiguo, por lo tanto, para asignar la entrada estándar a uno de los lados de un pipe es necesario cerrar el descriptor de fichero 0 justo antes de llamar a dup():

close( 0 );
dup( p[1] );

Como dup() duplica siempre el descriptor más bajo disponible, si cerramos el descriptor 0 justo antes de llamarla, ese será el descriptor que se duplique. Para evitar líos de cerrar descriptores con vistas a ser duplicados y demás, se creo dup2(), que simplemente recibe los dos descriptores de fichero y los duplica:

dup2( p[1], 0 );

El siguiente ejemplo emplea estas llamadas para concatenar la ejecución de dos comandos, “cat” y “wc”. El proceso hijo realiza un “cat” de un fichero, y lo encamina a través de la tubería. El proceso padre recibe ese fichero por la tubería y se lo pasa al comando “wc” para contar sus líneas:

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

#define COMMAND1 "/bin/cat"
#define COMMAND2 "/usr/bin/wc"

int main( int argc, char **argv )
{
  pid_t pid;
  int p[2];

  pipe(p);

  if ( (pid=fork()) == 0 )
  { // hijo
    close(p[0]); /* cerramos el lado de lectura del pipe */
    dup2(p[1], 1); /* STDOUT = extremo de salida del pipe */
    close(p[1]); /* cerramos el descriptor de fichero que sobra
                         tras el dup2 */

    execlp(COMMAND1, COMMAND1, argv[1], NULL);

    perror("error"); /* si estamos aquí, algo ha fallado */
    _exit(1); /* salir sin flush */
  }
  else
  { // padre
    close(p[1]); /* cerramos el lado de escritura del pipe */
    dup2(p[0], 0); /* STDIN = extremo de entrada del pipe */
    close(p[0]); /* cerramos el descriptor de fichero que sobra
                         tras el dup2 */

    execlp(COMMAND2, COMMAND2, NULL);

    perror("error"); /* si estamos aqui, algo ha fallado */
    exit(1); /* salir con flush */
  }

  return 0;
}

La salida de este programa es bastante predecible, el resultado es el mismo que encadenar el comando “cat” del fichero pasado por parámetro, con el comando “wc”:

txipi@neon:~$ gcc pipecommand.c -o pipecommand
txipi@neon:~$ ./pipecommand pipecommand.c
     50     152     980
txipi@neon:~$ cat pipecommand.c | wc
     50     152     980

Como vemos, las llamadas a comandos y su intercomunicación mediante tuberías puede ser un proceso bastante lioso, aunque se utiliza en multitud de ocasiones. Es por esto que se crearon las llamadas popen() y pclose(). Mediante popen() tenemos todo el trabajo sucio reducido a una sola llamada que crea un proceso hijo, lanza un comando, crea un pipe y nos devuelve un puntero a fichero para poder utilizar la salida de ese comando como nosotros queramos. La definición de estas funciones es la siguiente:

FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

El siguiente código es una muestra clara de cómo se puede hacer una llamada utilizando tuberías y procesos hijo, de forma sencillísima:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>

#define SIZE PIPE_BUF

int main(int argc, char *argv[])
{
  FILE *file;
  char *command= "ls .";
  char buffer[SIZE];

  file=popen( command, "r" );

  while( !feof( file ) )
  {
    fscanf( file, "%s", &buffer );
    printf( "%s\n", buffer );
  }

  pclose( file );

  return 0;
}

Nuestro programa simplemente crea un proceso hijo que será reemplazado por una llamada al comando “ls .”, y se nos devolverá un puntero a un fichero que será el resultado de ese comando. Leemos ese fichero y lo escribimos por pantalla. Al finalizar, cerramos la tubería con pclose(). La salida de este programa, es esta:

txipi@neon:~$ gcc popen.c -o popen
txipi@neon:~$ ./popen
dospipesfork
dospipesfork.c
pipecommand
pipecommand.c
pipefork
pipefork.c
popen
popen.c

Linux también soporta tuberías con nombre, denominadas habitualmente FIFOs, (First in First out) debido a que las tuberías funcionan como una cola: el primero en entrar es el primero en salir. A diferencia de las tuberías sin nombre, los FIFOs no tiene carácter temporal sino que perduran aunque dos procesos hayan dejado de usarlos. Para crear un FIFO se puede utilizar el comando “mkfifo“ o bien llamar a la función de C mkfifo():

int mkfifo(const char *pathname, mode_t mode);

Esta función recibe dos parámetros: “pathname” indica la ruta en la que queremos crear el FIFO, y “mode” indica el modo de acceso a dicho FIFO. Cualquier proceso es capaz de utilizar un FIFO siempre y cuando tengan los privilegios necesarios para ello. No nos extenderemos más en la creación de tuberías con nombre ya que su manejo es bastante similar a lo visto hasta ahora.

IPC System V

Colas de mensajes

Mediante las colas de mensajes un proceso puede escribir mensajes que podrán ser leídos por uno o más procesos diferentes. En GNU/Linux este mecanismo está implementado mediante un array de colas de mensajes, msgque. Cada posición de este array es una estructura de tipo msgid_ds que gestionará la cola mediante punteros a los mensajes introducidos en ella. Estas colas, además, controlan cuándo fue la última vez que se escribió en ellas, y proporcionan dos colas de espera: una para escritores de la cola y otra para lectores de la cola.

Cuando un proceso escribe un mensaje en la cola de escritura, éste se posiciona al final de la misma (tiene una gestión FIFO) si es que existe espacio suficiente para ser albergado (Linux limita el número y tamaño de los mensajes para evitar ataques de Denegación de Servicio). Previo a cualquier escritura, el sistema comprueba si realmente el proceso está autorizado para escribir en la cola en cuestión, comparando las credenciales del proceso con los permisos de la cola. Asimismo, cuando un proceso quiere leer de esa cola, se realiza una comprobación similar, para evitar que procesos no autorizados lean mensajes importantes. Si un proceso desea leer un mensaje de la cola y no existe ningún mensaje del tipo deseado, el proceso se añadirá a la cola de espera de lectura y se cambiará de contexto para que deje de estar activo.

La implementación práctica en C de colas de mensajes queda fuera del alcance de este curso O:-)

Semáforos

Un semáforo es un mecanismo del sistema para evitar la colisión cuando dos o más procesos necesitan un recurso. Los semáforos IPC reflejan bastante fielmente la definición clásica de Dijkstra, realmente son variables enteras con operaciones atómicas de inicialización, incremento y decremento con bloqueo. Cada semáforo es un contador que se inicializa a un determinado valor. Cuando un proceso hace uso del recurso asignado a ese semáforo, el contador se decrementa una unidad. Cuando ese proceso libera el recurso, el contador del semáforo se incrementa. Así pues, el contador de un semáforo siempre registra el número de procesos que pueden utilizar el recurso actualmente. Dicho contador puede tener valores negativos, si el número de procesos que precisan el recurso es mayor al número de procesos que pueden ser atendidos simultáneamente por el recurso.

Por recurso entendemos cualquier cosa que pueda ser susceptible de ser usada por un proceso y pueda causar un interbloqueo: una región de memoria, un fichero, un dispositivo físico, etc. Imaginemos que creamos un semáforo para regular el uso de una impresora que tiene capacidad para imprimir tres trabajos de impresión simultáneamente. El valor del contador del semáforo se inicializaría a tres. Cuando llega el primer proceso que desea imprimir un trabajo, el contador del semáforo se decrementa. El siguiente proceso que quiera imprimir todavía puede hacerlo, ya que el contador aún tiene un valor mayor que cero. Conforme vayan llegan procesos con trabajos de impresión, el contador irá disminuyendo, y cuando llegue a un valor inferior a uno, los procesos que soliciten el recurso tendrán que esperar. Un proceso a la espera de un recurso controlado por un semáforo siempre es privado del procesador, el planificador detecta esta situación y cambia el proceso en ejecución para aumentar el rendimiento. Conforme los trabajos de impresión vayan acabando, el contador del semáforo irá incrementándose y los procesos a la espera irán siendo atendidos.

Es muy importante la característica de atomicidad de las operaciones sobre un semáforo. Para evitar errores provocados por condiciones de carrera (“race conditions”), los semáforos protegen su contador, asegurando que todas las operaciones sobre esa variable entera (lectura, incremento, decremento) son atómicas, es decir, no serán interrumpidas a la mitad de su ejecución. Recordamos que estamos en un entorno multiprogramado en el que ningún proceso se asegura que vaya a ser ejecutado de principio a fin sin interrupción. Las actualizaciones y consultas de la variable contador de un semáforo IPC son la excepción a este hecho: una vez iniciadas, no son interrumpidas. Con esto se consigue evitar fallos a la hora de usar un recurso protegido por un semáforo: imaginemos que en un entorno en el que hay cuatro procesadores trabajando concurrentemente, cuatro procesos leen a la vez el valor del contador del semáforo anterior (impresora). Supongamos que tiene un valor inicial de tres. Los cuatro procesos leen un valor positivo y deciden usar el recurso. Decrementan el valor del contador, y cuando se disponen a usar el recurso, resulta que hay cuatro procesos intentando acceder a un recurso que sólo tiene capacidad para tres. La protección de la variable contador evita este hecho, por eso es tan importante.

La implementación práctica en C de semáforos IPC queda fuera del alcance de este curso O:-)

Memoria compartida

La memoria compartida es un mecanismo para compartir datos entre dos o más procesos. Dichos procesos comparten una parte de su espacio de direccionamiento en memoria, que puede coincidir en cuanto a dirección virtual o no. Es decir, imaginemos que tenemos dos libros compartiendo una página. El primer libro es “El Quijote de la Mancha”, y el segundo es un libro de texto de 6º de primaria. La página 50 del primer libro es compartida por el segundo, pero puede que no corresponda con el número de página 50, sino que esté en la página 124. Sin embargo la página es la misma, a pesar de que no esté en el mismo sitio dentro del direccionamiento de cada proceso. Los accesos a segmentos de memoria compartida son controlados, como ocurre con todos los objetos IPC System V, y se hace un chequeo de los permisos y credenciales para poder usar dicho segmento. Sin embargo, una vez que el segmento de memoria está siendo compartido, su acceso y uso debe ser regulado por los propios procesos que la comparten, utilizando semáforos u otros mecanismos de sincronización.

La primera vez que un proceso accede a una de las páginas de memoria virtual compartida, tiene lugar un fallo de página. El Sistema Operativo trata de solventar este fallo de página y se da cuenta de que se trata de una página correspondiente a un segmento de memoria compartida. Entonces, se busca la página correspondiente a esa página de memoria virtual compartida, y si no existe, se asigna una nueva página física.

La manera mediante la que un proceso deja de compartir una región o segmento de memoria compartida es bastante similar a lo que sucede con los enlaces entre ficheros: al borrarse un enlace no se procede al borrado del fichero enlazado a no ser que ya no existan más enlaces a dicho fichero. Cuando un proceso se desenlaza o desencadena de un segmento de memoria, se comprueba si hay más procesos utilizándolo. Si es así, el segmento de memoria continúa como hasta entonces, pero de lo contrario, se libera dicha memoria.

Es bastante recomendable bloquear en memoria física la memoria virtual compartida para que no sea reemplazada (“swapping”) por otras páginas y se almacene en disco. Si bien un proceso puede que no use esa página en mucho tiempo, su carácter compartido la hace susceptible de ser más usada y el reemplazo provocaría una caída del rendimiento.

La implementación práctica en C de la comunicación mediante memoria compartida queda fuera del alcance de este curso O:-)

30 pensamientos en “Curso de programación en C para GNU/Linux (VII)

  1. Alejandra M. (Venezuela)

    Muy bueno el curso. Pero me gustaría que ofrecieras tambien la implementación práctica de las colas de mensajes, semáforos y memoria compartida. Gracias.

    Responder
  2. mery

    Hola, muy bueno tu cursito, pero fijate q me gustaria q me ayudaras con esto que stoy tratand de hacer, el encabezado d mi tarea es el sgte: La shell debe proporcionar un prompt, lo que identifica el modo de espera de comandos de la shell. Luego, debe leer un comando desde teclado y parsear la entrada para identificar el comando y sus argumentos. Finalmente debe ejecutar el comando ingresado en un proceso concurrente, para lo cual debe usar el llamado a sistema fork() y algunas de las variantes de exec(). Los comandos a soportar son ejecutados en foreground, es decir, la shell ejecuta y espera por el término de su ejecución antes de imprimir el promtp para esperar por el siguiente comando. La correcta implementación de esta etapa lo hace merecedor de un 30% de la nota.

    Nota: Si se presiona <enter> sin previo comando, la shell simplemente imprime nuevamente el prompt.

    y aki esta lo que llevo:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <iostream>
    #include <string.h>

    using namespace std;

    struct ingresoPrompt{
    char com1;
    char com2;
    char com3;
    char com4;
    char com5;
    }ingreso;

    // que vuelva a salir el prompt si se presiona un enter

    int main()
    {

    pid_t pid_hijo;
    int estado;

    while(1){

    cout << "prompt@udec $ ";

    char *cmd;
    string lista;

    cin>>cmd;
    // cin>>lista;

    cout<<""<<cmd<<endl;
    if((strcmp(cmd,"exit"))==0){
    //cout<<"entra al exit"<<endl;
    exit(0);
    }
    cout << "proceso padre " << endl;

    pid_hijo = fork();

    if(pid_hijo == -1){
    perror("falla en fork");
    exit(1);
    }
    else if( pid_hijo == 0){
    cout << "proceso hijo" << endl;
    execl("/bin/ls", "ls", cmd, (char *)0);
    cout<<"falla en execl"<<endl;
    exit(1);
    }else {
    cout << "todavia en proceso padre" << endl;
    if (waitpid(pid_hijo, &estado, 0) == -1){
    cout<<"waitpid interrumpido"<<endl;
    exit(1);
    }
    if (WIFEXITED(estado)){
    cout << "Proceso hijo termino con estado "
    << WEXITSTATUS(estado) << endl;
    } else {
    cout<<"error en waitpid"<<endl;
    exit(1);
    }
    }
    }
    exit(0);

    }

    Lo malo es q hasta el momento no puedo hacer la estructura para leer toda una linea de comando de una. 🙁

    puxis te agradeceria una ayudita..

    Gracias 😉

    Responder
  3. Juan Pablo

    Tenis ke usar la funcion strtok y te falta la parte del sigchild ke tambien va en la parte uno de la tarea…

    Suerte si la terminas me la mandas para cachar…

    Nos vemos en clases.

    Responder
  4. Luis Felipe Restrepo

    Viejo, excelente info la de que se ve por aca. y los dibijos no los cambies ni por el carajo, estan brutales!!!!

    OldSchool + Paint

    Responder
  5. Carlos

    Hola, tengo un problemita y quizá me puedan dar alguna idea por acá. Necesito implementar una pequeña base de datos, con varios servidores y clientes, utilizando memoria compartida y semáforos. Cada servidor manejará una única tabla (registro) de nUplas de datos. El problema es que no puedo usar un semáforo por cada nUpla, ni siquiera dinámicamente, ya que eventualmente podría estar usando un semáforo por cada nUpla.
    Si alguién puede tirarme una pista de cómo encarar el problema, eternamente agradecido.

    Responder
  6. j

    txipi eres un crack! No te imaginas lo que me ha servido todo este curso que has hecho de c y linux. Te estoy muy agradecido.

    Sigue asi. Gracias

    Responder
  7. Rafael

    Como podría implementar las pipes si el padre se comunica con el primer hijo creado (el padre escribe en la pipe), este con otro de los hijos creados y a su vez este último otra vez con el padre (el padre lee de la pipe).

    Responder
  8. Pingback: Curso de programación en C para GNU/Linux (VIII) « txipi:blog

  9. Peter

    no me jodas,estoy buscando ejemplos de semaforos y no has puesto ni uno….xD
    por lo demas esta delujo! y los dibujos molan paint 100% xD venga un saludo felicidades por el blog

    Responder
  10. Furiwel

    Buenas, tenia una pregunta sobre esto:
    tal como en una comunicacion padre-hijo, el padre con un
    write(bla[ESCRIBIR], char, int_longitud), puede enviar un char a su hijo, me pregunto si el padre podria mandar un fichero a su hijo. Algo asi como:
    write(bla[ESCRIBIR], fichero, int_longitud), y en el caso de que se pudiera, que seria int_longitud?

    Gracias.

    Responder
  11. Erick Orrego

    Hola, muchas gracias por la información de Pipes contenida en esta entrada de blog, me va a ayudar a hacer mi tarea de Pipes ya que está bien explicado el código, le doy un 100!, saludos

    Responder
  12. Miguel

    Muchas gracias por este curso que tienes publicado.
    Me ha venido estupendo para entender de una vez en que casos es util una tuberia, ademas lo has explicado todo de una forma muy amena y facil de entender ( no como en mis apuntes de lso ^^).

    Gracias y un saludete.

    Responder
  13. Pingback: Pipes en C – Linux – Programación

  14. Anna

    Hola, tengo unas dudas con un programa y querria que alguien me ayedase:

    #include
    #include
    #include

    int n, seguir;
    void rut1() {printf(“Señal 1\n”); seguir=0;
    signal (SIGUSR1, rut1);}
    void rut2() {printf(“Señal 2\n”); seguir=1; n++;
    signal (SIGUSR2, rut2);}
    void main()
    {
    int p1[2],i;
    seguir = 1;
    n = 0;
    pipe(p1);
    signal (SIGUSR1, rut1);
    signal (SIGUSR2, rut2);
    switch(fork()) {
    case 0: close(1);
    dup(p1[1]);
    close(p1[0]); close (p1[1]); /*cerramos el lado de lectura i de escritura del pipe*/
    while(seguir);
    pause();
    write(1, &n, sizeof(int));
    break;
    default: close(0);
    dup(p1[0]);
    close(p1[0]); close (p1[1]); /*cerramos el lado de lectura i de escritura del pipe*/
    read(0, &i, sizeof(int));
    printf(“He recibido un %d”, i);
    while(seguir);
    }
    printf(“El valor de n és %d”, n);
    }

    Las preguntas son:
    a)Supongamos que no se produce ningun error. Explicar paso a paso la ejecución del código.
    b)Qué sequencia de señales tiene que recibir el proceso hijo para llegar a hacer una escritura?
    c)Antes que el hijo no llegue al write, cuantas señales puede recibir el padre i de que tipo?
    d) Cual de los dos procesos, padre o hijo, bajo las mismas condiciones de ejecución o de señales enviadas, usará mas CPU?

    Muchas gracias.

    Responder

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *