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

  1. #1
    Membre habitué
    Inscrit en
    Janvier 2005
    Messages
    491
    Détails du profil
    Informations forums :
    Inscription : Janvier 2005
    Messages : 491
    Points : 172
    Points
    172
    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
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 401
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 401
    Points : 23 780
    Points
    23 780
    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 émérite
    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
    Points : 2 505
    Points
    2 505
    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 habitué
    Inscrit en
    Janvier 2005
    Messages
    491
    Détails du profil
    Informations forums :
    Inscription : Janvier 2005
    Messages : 491
    Points : 172
    Points
    172
    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 émérite
    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
    Points : 2 505
    Points
    2 505
    Par défaut
    Tu le fais exprès ou tu n'as pas lu mon message ?

  6. #6
    Membre habitué
    Inscrit en
    Janvier 2005
    Messages
    491
    Détails du profil
    Informations forums :
    Inscription : Janvier 2005
    Messages : 491
    Points : 172
    Points
    172
    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
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 401
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 401
    Points : 23 780
    Points
    23 780
    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.

  8. #8
    Membre habitué
    Inscrit en
    Janvier 2005
    Messages
    491
    Détails du profil
    Informations forums :
    Inscription : Janvier 2005
    Messages : 491
    Points : 172
    Points
    172
    Par défaut
    Désolé si je suis un peu long à la détente mais c'est la première fois que je manipule ces truc là... Merci en tout cas de tes explications. Malgrés tout, je ne vois pas encore tout à fait pourquoi il y aurait blocage dans mon cas. Voilà comment je vois les choses:

    1 - Lors du fork, les deux processus démarrent.

    2 - Une fois stdin/stdout redirigés, le pipe lecteur du processus A (celui qui va lancer qvoronoi) se met en attente de lecture avec read...

    3 - Dans l'autre processus B, on va dans un premier temps copier toutes les données à envoyer dans une même chaine, puis balancer ca dans le pipe via une seule instruction write, pour ensuite faire appel à read qui va se mettre en attente de la sortie de qvoronoi.

    4 - Pendant ce temps le pipe lecteur s'est débloqué à la réception des données envoyées par B, et lance qvoronoi avec les données qu'il a lu. (ici dans mon cas, l'instruction read se termine uniquement lorsqu'il a lu l'ensemble de mes données, que j'ai envoyé en un seul bloc, non? Car dans ce cas, qvornoi se lance uniquement lorsque toutes les données sont reçu, et donc B est alors déjà en attente à prioris -> les deux premiers points du message de matafan ne s'applique pas)

    5 - Qvoronoi récupère ces données, fait ses calculs, et écrit les résultats dans stdout (là il me manque la manière dont il le fait: séquentielle ou en une fois comme je le fait pour lui envoyer les données en entrée...).

    6 - A ce moment là, write se débloque et récupère la sortie.

    Là ou je ne comprends (toujours) pas c'est comment dans mon cas il peut y avoir interblockage? Cf. ce que j'ai dit précédement, je me trompe peut être, mais les deux processus ne peuvent pas écrire en même temps... Si qvoronoi est lancé une fois que tout a été lu, B est forcément en attente de la sortie pendant que qvoronoi écrit dans le pipe...

    A noter également que mon programme se termine sans problème, j'ai juste mes données de sorties qui sont tronquées... A noter également que qvoronoi doit travailler avec l'ensemble des données d'entrées et ne peut pas le faire séquentiellement: il lit toute les données d'entrées, les traitent d'un bloc, et retourne le résultat.

  9. #9
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 401
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 401
    Points : 23 780
    Points
    23 780
    Par défaut
    4 - Pendant ce temps le pipe lecteur s'est débloqué à la réception des données envoyées par B, et lance qvoronoi avec les données qu'il a lu. (ici dans mon cas, l'instruction read se termine uniquement lorsqu'il a lu l'ensemble de mes données, que j'ai envoyé en un seul bloc, non? Car dans ce cas, qvornoi se lance uniquement lorsque toutes les données sont reçu, et donc B est alors déjà en attente à prioris -> les deux premiers points du message de matafan ne s'applique pas)
    On ne peut pas garantir que toutes tes données soient transmises en une seule fois à travers le tube. Mais surtout, man 2 read spécifie :

    If count is greater than SSIZE_MAX, the result is unspecified.

    A noter également que mon programme se termine sans problème, j'ai juste mes données de sorties qui sont tronquées...
    Dans ce cas, reporte-toi à mon post initial (le numéro #2). Ces symptomes ressemblent beaucoup à une mort prématurée de ton processus.

  10. #10
    Membre habitué
    Inscrit en
    Janvier 2005
    Messages
    491
    Détails du profil
    Informations forums :
    Inscription : Janvier 2005
    Messages : 491
    Points : 172
    Points
    172
    Par défaut
    If count is greater than SSIZE_MAX, the result is unspecified.
    C'est sur que dans ce cas...

    A la limite je peux faire une boucle dans B envoyant successivement des blocs de 4096 caractères dans A et en envoyant une chaine finale "STOP" pour signaler la fin, et idem dans A, une boucle récupérant les blocs et les stockant tant qu'on n'a pas recu le signal de fin...

    Une fois que B recoit le signal final, il peut alors traiter cet ensemble de données et appeler qvoronoi...

    Une dernière question et aprés je me débrouillerai: si je fait des appels successifs à read (boucle tant qu'on ne recoit pas le mot STOP) dans A, est-ce que lorsque j'appèlerai qvoronoi, celui-ci recevra l'intégralité des données? Est-ce qu'il ne recevra pas plutôt le dernier bloc de N < 4096 caractères?

    Merci beaucoup en tout cas de tes réponses

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