IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

Processus externe et tubes


Sujet :

C

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Inscrit en
    Janvier 2005
    Messages
    491
    Détails du profil
    Informations forums :
    Inscription : Janvier 2005
    Messages : 491
    Par défaut Processus externe et tubes
    Hello!

    Dans mon programme C arive un moment ou je fais appel a un programme externe. Jusque là, je le faisais de cette manière:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    system("cat input.txt | extprog o C > output.txt")

    Comme vous pouvez le constater, ca génère deux fichier temporaires. J'aimerai à présent faire l'équivalent de cette commande en me passant de ces deux fichiers. Pour cela, j'ai utilisé une méthode basée sur les tubes, où je crée simplement deux tubes et je lance un processus fils pour pouvoir envoyer sur stdin et récupérer la sortie de stdout comme il se doit sans passer par des fichiers. En voici le code principal:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
     
    int pr[2], pw[2] ;
    int flag ;
    char buf[1000],
          *inout = my_malloc(M_BUFSIZE*sizeof(char)) ;
    pid_t child ;
     
    flag = pipe(pr) ; if(flag == -1) return NULL ;
    flag = pipe(pw) ; if(!flag == -1) return NULL ;
     
    child = fork() ;
    if(child < 0) return NULL ;
     
    if(child == 0) {
    	close(pw[1]) ; close(pr[0]) ;
    	dup2(pw[0], STDIN_FILENO)  ; close(pw[0]) ;
    	dup2(pr[1], STDOUT_FILENO) ; close(pr[1]) ;
    	execlp("qvoronoi", "qvoronoi", "p", "i", "Pp", "Fn", NULL) ;
    }
    else {
    	close(pw[0]) ; close(pr[1]) ;
    	sprintf(inout, "3 rbox D3\n%d\n", N) ;
    	for(i = 0; i <  N ; i++){
    		sprintf(buf, "%f %f %f \n", atom[N][M_X], atom[N][M_Y], atom[N][M_Z]) ;
    		strcat(inout, buf) ;
    	}
    	write(pw[1], inout, strlen(inout)+1) ;
    	close(pw[1]) ;
     
    	flag = read(pr[0], inout, M_BUFSIZE) ; fprintf(stdout, "%d\n", flag) ;
    	close(pr[0]) ;
    }
    Du coté de l'entrée, ca semble fonctionner: le programme externe se lance correctement et recoit l'ensemble des données d'entrée, et l'on recoit ensuite la sortie en retour. Le problème, c'est que les données de sortie sont incomplètes: l'appel a write m'indique qu'il lit trés exactement 4096 caractères, puis plus rien... Et je sais qu'elle est incomplète car la sortie du programme esxterne génère parfois des fichiers de 2-3 MO...

    Et honnètement là je sèche je ne comprends pas pourquoi... Ya t-il une limite dans la lécture des données via un pipe? Si quelqu'un a une idée de l'origine du problème...
    Merci d'avance!

  2. #2
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 477
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 477
    Par défaut
    usque là, je le faisais de cette manière:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    system("cat input.txt | extprog o C > output.txt")
    UUOC ! Utilise la redirection de l'entrée standard :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    extprog o C < input.txt > output.txt



    Pourquoi un « ! », ici ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    flag = pipe(pr) ; if(flag == -1) return NULL ;
    flag = pipe(pw) ; if(!flag == -1) return NULL ;
    Les pipes sous Unix sont bufferisés et 4096 est la taille par défaut, en octets, de la file d'attente d'un tube. Ça se change avec ulimit, au moins sous Linux.

    Ensuite, as-tu vérifié si l'un de tes programmes se prenait un signal ou provoquait une segfault ? L'ennui, quand on fait des boucles avec les tubes entre deux processus sans faire attention est que chaque processus est écrivain vis-à-vis de l'autre. Donc, dès qu'il y en a un qui se termine, ça provoque la mort de l'autre, qui se prend un SIGPIPE.

  3. #3
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2008
    Messages
    1 515
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Octobre 2008
    Messages : 1 515
    Par défaut
    Les communications bidirectionelles entre processus posent quelques problèmes. Pour les faire fonctionner c'est un plus compliqué que simplement ouvrir deux pipes.

    Imagine un process A qui envoie des données à un process B, et qui lit aussi les données en provenance de B. Si dans A tu te contente de faire ce que tu as fait, c'est à dire d'envoyer la totalité des données vers B, puis que lire la totalité de la sortie de B alors :

    - A commence à envoyer des données vers B
    - B les traites et sort des données, qui s'accumulent dans le pipe puisque personne ne les lit (et oui, A est toujours à envoyer ses données, pas encore à les lire)
    - Au bout d'un moment le pipe de sortie de B se remplit. A ce moment B se bloque dans write().
    - Du coup, B ne lit plus les données en provenance de A.
    - Au bout d'un moment, le pipe d'entrée de B se remplit. A se moment A se bloque dans write().

    Et voilà, tu as un beau deadlock avec A bloqué en attendant que B lise les données du premier pipe, et B bloqué en attendant que A lise les données du deuxième pipe.

    Une solution possible serait de modifier A pour faire les écritures vers B et les lectures depuis B dans deux threads différents. Une autre solution est d'ouvrir le pipe en mode non bloquant (O_NONBLOCK), puis de jouer du select() à chaque fois qu'un write ou qu'un read échoue avec errno == EAGAIN.

  4. #4
    Membre éclairé
    Inscrit en
    Janvier 2005
    Messages
    491
    Détails du profil
    Informations forums :
    Inscription : Janvier 2005
    Messages : 491
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    Pourquoi un « ! », ici ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    flag = pipe(pr) ; if(flag == -1) return NULL ;
    flag = pipe(pw) ; if(!flag == -1) return NULL ;
    Pour rien c'est une erreur
    Citation Envoyé par Obsidian Voir le message
    Les pipes sous Unix sont bufferisés et 4096 est la taille par défaut, en octets, de la file d'attente d'un tube. Ça se change avec ulimit, au moins sous Linux.

    Ensuite, as-tu vérifié si l'un de tes programmes se prenait un signal ou provoquait une segfault ?
    Oui, aucun problème.

    Ce que je ne comprends pas ici, c'est que l'envoie des données qui contiènnent également nettement plus de caractères que 4096, ne pose pas de problème, alors que leur réception oui...

    De plus, à prioris ce que je veux faire est simple: j'envoie toutes mes données en un bloc (une seule chaine) au processus fils qui va lancer le programme (ce qui à prioris ne pose pas de problème de blockage puisque j'envoie tout d'un coup (?)), et je récupère (en théorie...) toute les données en un bloc également... Pourquoi la sortie est limitée à 4096 caractères et pas l'entrée?

  5. #5
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2008
    Messages
    1 515
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Octobre 2008
    Messages : 1 515
    Par défaut
    Tu le fais exprès ou tu n'as pas lu mon message ?

  6. #6
    Membre éclairé
    Inscrit en
    Janvier 2005
    Messages
    491
    Détails du profil
    Informations forums :
    Inscription : Janvier 2005
    Messages : 491
    Par défaut
    Lu mais pas bien compris; je suis débutant pr ce qui concerne l'utilisation des pipes...

    Et vu ta réaction j'hésite a te demander de nouvelles explications

  7. #7
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 477
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 477
    Par défaut
    Citation Envoyé par vinzzzz Voir le message
    Lu mais pas bien compris; je suis débutant pr ce qui concerne l'utilisation des pipes...
    Il nous manque le code du processus fils (« qvoronoi ») pour être catégoriques, mais ce que Matafan disait, c'est que tu t'es probablement mis en situation de deadlock, ou interblocage (ou encore « étreinte fatale » comme je l'ai entendu dire :-). En gros, tes processus s'attendent mutuellement, donc il n'y a aucune chance que la situation avance d'elle-même.

    Lorsque tu lis un pipe en mode bloquant (par défaut), ton processus est mis en attente sur l'instruction read tant qu'il n'y a rien à lire. De la même façon, un processus qui écrit dans un même pipe avec write reste bloqué lui-aussi s'il n'y a pas de lecteur pour faire sortir les caractères insérés par le processus écrivain. À un détail près toutefois : un tube est doté d'une mémoire tampon de 4096 par défaut qui permet à l'écrivain de commencer à émettre même si le lecteur n'est pas prêt à recevoir.

    Or, si tes deux processus, parce que mal synchronisés, par exemple, se mettent à écrire en même temps, et que leurs données dépassent 4096 octets (en comptant ce qui est émis en une fois + ce qui se trouve encore dans le tube), les deux processus vont rester bloqués et aucun d'eux n'aura la chance d'avancer un peu plus loin pour faire le read qui fera un peu de place et permettra à ton système de processus de se remettre à tourner.

    Donc, pour résoudre le problème, il faut réviser la conception de ton soft et tu a plusieurs approches pour le faire :

    • Soit tu fais du half-duplex. Tu t'arranges pour qu'un seul processus prenne la parole à la fois pendant que l'autre l'écoute. Dès qu'il a fini, l'autre processus répond, et on recommence ;
    • Soit tu contrôles la quantité de données que tu balances dans tes tubes. Le problème est que je ne connais pas de manière portable d'obtenir la taille réelle du buffer d'un tube en C ;
    • Soit tu rends tes tubes non-bloquants et tu gères le code d'erreur en conséquence le cas échéant. C'est ce que je ferais. Il faut utiliser fcntl() avec F_SETFL et O_NONBLOCK pour rendre un tube non-bloquant car on ne peut pas passer les flags à l'ouverture.

Discussions similaires

  1. Réponses: 4
    Dernier message: 15/10/2009, 07h06
  2. Runtime.exec et processus externe
    Par snyouf dans le forum Langage
    Réponses: 3
    Dernier message: 29/06/2009, 16h58
  3. Réponses: 7
    Dernier message: 18/07/2007, 16h40
  4. [Système]communiquer avec un processus externe sous windows
    Par tweety dans le forum Général Java
    Réponses: 4
    Dernier message: 14/11/2005, 17h17
  5. Utilisation de processus et de tubes
    Par al85 dans le forum Linux
    Réponses: 2
    Dernier message: 05/12/2004, 12h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo