Algorithme parallélisé: Je n'arrive pas à l'écrire avec fork()

Algorithme parallélisé: Je n'arrive pas à l'écrire avec fork() - C - Programmation

Marsh Posté le 01-10-2007 à 16:24:57    

Alors voilà donc l'algo du programme:
 
J'ai deux processus qui tournent en parallèle:
- Un processus qui va lire en boucle des données dans une file d'attente, puis les réécrire dans une liste de connexions réseaux.
- Un processus qui ouvre un port d'administration en écoute et qui accepte les connexions réseaux avant de les ajouter à la liste des connexions.
 
En fait il y a d'autres processus, mais vis-à-vis des deux précédents, ils ne font qu'écrire des messages dans la file d'attente (en plus de leurs tâches respectives).
 
En fait la file d'attente fait office de liste d'attente des messages à envoyer.
 
Mais je veux que les messages soit recus par tous ceux qui se connectent au port d'administration du programme, d'où le processus qui lit en continue la file d'attente avant de réecrire les messages dans chacune des connexions réseaux de la liste.
 
Alors pour transcrire cet algo en C:
- utilisation d'un pipe pour la file d'attente, avec accès exclusif via une sémaphore.
- utilisation de sockets TCP pour les connexions réseaux.
- utilisation de fork pour créer les différents processus.
- utilisattion de mémoire partagée pour la liste des connexions.
 
Si le fork qui s'occupe d'envoyer les messages peut lire le pipe sans problème, celui-ci étant ouvert avant sa création, il ne peut par contre pas écrire dans les sockets puisqu'il sont ouvert dans un autre fork postérieurement à sa création.
 
Je tiens absolument à conserver fork pour la parrallélisation des autres processus et j'aimerai ne pas mélanger fork et thread.
 
Ma question serait donc du genre y a-t-il un moyen pour qu'un processus envoie à un autre processus un descripteur de fichier ouvert par ce premier ?
 
Bien sur je suis ouvert à tout autre suggestion.
 
Cordialement.

Reply

Marsh Posté le 01-10-2007 à 16:24:57   

Reply

Marsh Posté le 01-10-2007 à 16:44:25    

oui ça se fait, maintenant faut me laisser le temps de retrouver comment

Reply

Marsh Posté le 01-10-2007 à 16:56:20    

Ca serait pas une histoire de sendmsg() avec SCM_RIGHTS pour le type de message ? (Oui je suis aussi en train de chercher en parallèle ^^)
 
Seulement cela nécessite que mon processus qui envoie les messages sur les sockets, en plus de lire le pipe fasse des recvmsg non ? Et comme bien sur je peux pas faire un fork qui s'occupe du pipe et un autre qui s'occupe du recvmsg car je retomberai dans la même problématique, je vois pas trop comment faire une boucle while propre...
 
Je sens que je vais être obligé de mélanger thread et fork (ou alors revoir mon algo mais là je sèche...)

Reply

Marsh Posté le 01-10-2007 à 18:54:38    

sigmatador a écrit :


Ma question serait donc du genre y a-t-il un moyen pour qu'un processus envoie à un autre processus un descripteur de fichier ouvert par ce premier ?


Ouvert par "fopen()" ? Théoriquement un FILE* n'étant qu'un pointeur, tu "devrais" pouvoir passer les infos de ce pointeur vers un autre processus mais je trouve ça hyper sale car en général, si 2 processus doivent se partager un même fichier, le fichier est alors ouvert avant le fork et les deux y ont accès.
 
A mon avis tu devrais arriver à faire plus simple. Déjà pourquoi tu gères l'exclusivité du pipe via des sémaphore alors que son exclusivité est déjà gérée par le noyau ? Et surtout pourquoi tu utilises un pipe pour gérer ta file d'attente alors que t'as les msq qui peuvent le faire aussi ?

Message cité 1 fois
Message édité par Sve@r le 01-10-2007 à 18:55:54

---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 01-10-2007 à 20:30:35    

Sve@r a écrit :

Ouvert par "fopen()" ? Théoriquement un FILE* n'étant qu'un pointeur, tu "devrais" pouvoir passer les infos de ce pointeur vers un autre processus mais je trouve ça hyper sale car en général, si 2 processus doivent se partager un même fichier, le fichier est alors ouvert avant le fork et les deux y ont accès.

Sauf si je me trompe, certes FILE* n'est qu'un pointeur vers une structure, mais vu que fopen utilise open, la structure FILE doit bien contenir un index de la table des descripteurs de fichiers associée au processus courant, index qui n'aurait aucun sens (en tout cas aucun sens cohérent) dans un autre processus ayant forké avant l'ouverture du dis fichier (la table des descripteurs de fichiers étant propre à chaque processus).
 
De toute facon les descripteurs de fichiers que je cherche à passer sont des sockets, je n'ai donc pas de structure FILE mais un simple int, index dans la table des descripteurs de fichiers.
 

Sve@r a écrit :


Déjà pourquoi tu gères l'exclusivité du pipe via des sémaphore alors que son exclusivité est déjà gérée par le noyau ?

Perso je connaissais rien aux pipes, j'ai regardé des exemples sur le net, quand plusieurs processus cherchaient à écrire parallèlement dans un même pipe, l'exemple utilisaient une sémaphore pour s'assurer de l'exclusivité de l'accès en écriture ou en lecture. Mais bon si c'est superflue, j'en ai pour 10 secondes à virer le code correspondant.
 

Sve@r a écrit :

Et surtout pourquoi tu utilises un pipe pour gérer ta file d'attente alors que t'as les msq qui peuvent le faire aussi ?

Je connaissais pas les message queues, je viens de regarder vite fait, exact ca aurait pu faire l'affaire, mais les pipes tout autant. Par contre le code serait tout de même un peu plus lourd qu'avec les pipes. Le seul avantage c'est que n'importe quel processus peut se connecter à la queue s'il a connaissance de la clé correspondante, mais vu que le pipe est crée dans le premier processus avant tout fork, cela ne me sert à rien.
 

Sve@r a écrit :

A mon avis tu devrais arriver à faire plus simple.

Bah écoute... si t'as une suggestion au niveau de l'algo je suis preneur :D J'y ai pas mal reflechis, à part envoyer les descripteurs de fichiers au processus voulu, ou passer aux pthreads pour la gestion des connexions reseaux au niveau du port d'administration... je vois pas   :whistle:  
 

Reply

Marsh Posté le 02-10-2007 à 09:02:48    

/me débile

 

google "passing descriptor"

 

Ce qui m'ennuie, c'est que j'ai vu un bon article là dessus il n'y a pas longtemps, et j'arrive pas à remettre la main dessus.


Message édité par Taz le 02-10-2007 à 09:07:49
Reply

Marsh Posté le 02-10-2007 à 10:51:28    

arf, j'étais parti dans "sharing descriptor" ^^, donc c'est bien du sendmsg avec SCM_RIGHTS, m'enfin maintenant j'ai des exemples utilisables, merci.
 
Quelqu'un me confirme que ca sert à rien mon mutex sur le pipe ? ^^


Message édité par sigmatador le 02-10-2007 à 10:51:56
Reply

Marsh Posté le 02-10-2007 à 11:41:07    

moi aussi j'ai quiché, je cherchais moving/exchanging/sending
 
mutex sur le pipe ? à quel endroit, entre qui et qui ?

Reply

Marsh Posté le 02-10-2007 à 12:12:02    

J'ai plein de processus parallèles qui vont tous chercher à écrire dans un même pipe, j'ai mis un mutex (en fait une sémaphore utilisée comme mutex) avant chaque écriture, pour être sur que celle-ci ne sera pas tronquée si le processus en cours est preempté par un autre processus voulant écrire lui aussi dans le pipe.
 
Sve@r me dit qu'apparement, le noyau gère déjà cela et donc que le mutex est superflue.

Reply

Marsh Posté le 02-10-2007 à 13:20:41    

le noyau il est gentil, mais si ton écriture est interrompue, ou incomplète, ça risque de pas faire du joli joli ... pourquoi ne pas faire des pipes séparés tout simplement ?

Reply

Marsh Posté le 02-10-2007 à 13:20:41   

Reply

Marsh Posté le 02-10-2007 à 14:19:28    

Un pipe pour chaque tâches tu veux dire ? Je vois pas comment c'est possible vu que de nouvelles tâches se créent et se finissent continuellement, à moins bien sur de créer un pool de pipe et en ne permettant qu'au maximum autant de tâche parallèles qu'il y a de pipe dans le pool, mais ca commence à faire artillerie lourde pour mon petit programme ^^

Reply

Marsh Posté le 02-10-2007 à 14:21:49    

je comprends pas bien. le pipe est entre qui et qui ? entre parent et fils ?

Reply

Marsh Posté le 02-10-2007 à 14:41:21    

C'est peut-être parce que ca fait depuis hier que je suis dedans que cela me semble simple et que du coup j'explique pas bien ^^
 
Essayons mieux alors:
 
'processus 1' crée le pipe, crée un fils 'processus 2', puis passe son temps à lire le pipe et à traiter ce qu'il en sort.
 
'Processus 2' lui va créer continuellement des fils qui vont effectuer des tâches, chacun de ses fils quand il a finit écrit les informations de fin dans le pipe.
 
Un seul processus lit le pipe, mes plusieurs écrivent dedans, en gros il y a un processus qui collecte l'ensemble des informations envoyées par les autres processus via un pipe.

Reply

Marsh Posté le 02-10-2007 à 15:29:27    

sigmatador a écrit :

C'est peut-être parce que ca fait depuis hier que je suis dedans que cela me semble simple et que du coup j'explique pas bien ^^
 
Essayons mieux alors:
 
'processus 1' crée le pipe, crée un fils 'processus 2', puis passe son temps à lire le pipe et à traiter ce qu'il en sort.
 
'Processus 2' lui va créer continuellement des fils qui vont effectuer des tâches, chacun de ses fils quand il a finit écrit les informations de fin dans le pipe.
 
Un seul processus lit le pipe, mes plusieurs écrivent dedans, en gros il y a un processus qui collecte l'ensemble des informations envoyées par les autres processus via un pipe.


Tu peux peut-être utiliser to processus 2 comme une sorte de concentrateur : à chaque fois que p2 spawne un fils, il crée un tuyau pour communiquer avec le fils en question. Quand il n'est pas en train de forker, p2 passe le reste de son temps à surveiller l'ensemble des tuyaux correspondant à ses fils (à coup de select ou poll ou autre chose), et retransmet les infos dans le pipe vers p1.
 
La seule chose qui m'embête dans ce genre de solution, c'est que les messages sont recopiés par p2, ce qui n'est pas efficace s'il y a beaucoup de données...


---------------
TriScale innov
Reply

Marsh Posté le 02-10-2007 à 17:00:21    

Exact, cette solution bien que plus gourmande, aurait été par contre très propre au niveau du design.
 
Enfin bon, ca m'aurait pas mal compliqué la vie alors là avec un seul pipe et un mutex, ca marche très bien ^^.
 
Par contre si tu as une idée aussi lumineuse pour me passer du signal/sendmsg (voir post ci-dessus) je suis preneur ^^

Message cité 1 fois
Message édité par sigmatador le 02-10-2007 à 17:19:51
Reply

Marsh Posté le 02-10-2007 à 17:06:51    

Bon pour ceux que ca pourrait intéresser, maintenant que je sais envoyer un file descriptor à un processus, et ce à l'aide d'un socketpair et des fonctions sendmsg et recvmsg, tout marche impec. Donc merci du coup de main ^^.
 
Là je bosse sous Linux, mais dans mes recherches je suis tombé sur 2 fonctions bien pratique de Solaris, send_fd et recv_fd, je vous laisse deviner à quoi ca sert.
 
Voici leur équivalent Linux à l'aide des fonctions sendmsg et recvmsg

Code :
  1. int send_fd(int sp, int fd)
  2. {
  3.     char buf[1], tmp[sizeof(struct cmsghdr) + sizeof(int)];
  4.     struct msghdr msg;
  5.     struct iovec iov[1];
  6.     struct cmsghdr *cmmag = (struct cmsghdr *)tmp;
  7.     if (fd >= 0)
  8.     {
  9.         msg.msg_control = (caddr_t)cmmag;
  10.         msg.msg_controllen = sizeof(struct cmsghdr) + sizeof(int);
  11.     }
  12.     else
  13.     {
  14.         msg.msg_control = (caddr_t)NULL;
  15.         msg.msg_controllen = 0;
  16.     }
  17.     cmmag->cmsg_level = SOL_SOCKET;
  18.     cmmag->cmsg_type = SCM_RIGHTS;
  19.     cmmag->cmsg_len = sizeof(struct cmsghdr) + sizeof(int);
  20.     *(int *)CMSG_DATA(cmmag) = fd;
  21.     msg.msg_name = NULL;
  22.     msg.msg_namelen = 0;
  23.     iov[0].iov_base = buf;
  24.     iov[0].iov_len  = 1;
  25.     msg.msg_iov     = iov;
  26.     msg.msg_iovlen  = 1;
  27.     return sendmsg(sp, &msg, 0);
  28. }


Code :
  1. int  recv_fd(int sp)
  2. {
  3.     char buf[1], tmp[sizeof(struct cmsghdr) + sizeof(int)];
  4.     struct msghdr msg;
  5.     struct iovec iov[1];
  6.     struct cmsghdr *cmmag = (struct cmsghdr *)tmp;
  7.     msg.msg_control  = (caddr_t)cmmag;
  8.     msg.msg_controllen = sizeof(struct cmsghdr) + sizeof(int);
  9.     msg.msg_name = NULL;
  10.     msg.msg_namelen = 0;
  11.     iov[0].iov_base = buf;
  12.     iov[0].iov_len = 1;
  13.     msg.msg_iov = iov;
  14.     msg.msg_iovlen = 1;
  15.     if (recvmsg(sp, &msg, 0) < 0)
  16.     {
  17.         return -1;
  18.     }
  19.     if (msg.msg_controllen !=sizeof(struct cmsghdr) + sizeof(int) )
  20.     {
  21.         return -1;
  22.     }
  23.     return *(int *)CMSG_DATA(cmmag);
  24. }


 
et un exemple d'utilisation qui utilise un socket pair pour la communication

Code :
  1. {
  2.     int spd[2], fd1, fd2;
  3.     if(socketpair(AF_UNIX, SOCK_STREAM, 0, spd) == -1)
  4.     {
  5.         perror("socketpair" );
  6.         return EXIT_FAILURE;
  7.     }
  8.     fd1 = open(...)
  9.     if(fork())
  10.     {
  11.         send_fd(spd[0], fd1);
  12.     }
  13.     else
  14.     {
  15.         fd2 = recv_fd(spd[1]);
  16.     }
  17. }


 
Perso je gère l'envoie et la reception avec un signal. Après l'appel à send_fd je fais un kill du pid du processus qui va bien, qui lui s'interrompt, fait un recv_fd, rajoute le fd à la liste, puis reprend son execution.


Message édité par sigmatador le 02-10-2007 à 17:23:52
Reply

Marsh Posté le 02-10-2007 à 17:33:46    

sigmatador a écrit :

Exact, cette solution bien que plus gourmande, aurait été par contre très propre au niveau du design.
 
Enfin bon, ca m'aurait pas mal compliqué la vie alors là avec un seul pipe et un mutex, ca marche très bien ^^.
 
Par contre si tu as une idée aussi lumineuse pour me passer du signal/sendmsg (voir post ci-dessus) je suis preneur ^^

Je vois mal comment tu pourrais te passer du signal. Par contre, tu peux peut-être remplacer le sendmsg par un segment de mémoire partagée, mais je sais pas si ça servirait à grand chose...
 
Sinon, t'as regardé un peu comment fonctionnaient des serveurs comme Apache en mode préfork ? Ils doivent mettre en place exactement les techniques dont tu as besoin...


---------------
TriScale innov
Reply

Marsh Posté le 02-10-2007 à 18:00:20    

sigmatador a écrit :

C'est peut-être parce que ca fait depuis hier que je suis dedans que cela me semble simple et que du coup j'explique pas bien ^^
 
Essayons mieux alors:
 
'processus 1' crée le pipe, crée un fils 'processus 2', puis passe son temps à lire le pipe et à traiter ce qu'il en sort.
 
'Processus 2' lui va créer continuellement des fils qui vont effectuer des tâches, chacun de ses fils quand il a finit écrit les informations de fin dans le pipe.
 
Un seul processus lit le pipe, mes plusieurs écrivent dedans, en gros il y a un processus qui collecte l'ensemble des informations envoyées par les autres processus via un pipe.


Bon, suis hyper en retard mais si p1 crée un pipe (pipe mémoire via pipe(int[2]) évidemment) et génère p2 alors p2 a naturellement accès au pipe. Puis si p2 génère un fils "f" alors "f" a aussi accès au pipe. Et si f écrit dans le pipe, p1 peut le lire. Evidemment s'il y a "n" fils et que les "n" veulent écrire en même temps on peut penser que ça coincera mais même comme ça ça pourra aller car lorsque le premier des "n" aura écrit, le pipe sera bloqué par le noyau et personne d'autre ne pourra y écrire tant que le père "p1" n'aura pas lu.
On peut même arriver à gérer le cas d'une écriture incomplète proposée par Taz en mettant un marqueur de fin dans le flot d'infos...

Message cité 1 fois
Message édité par Sve@r le 02-10-2007 à 18:17:31

---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 02-10-2007 à 18:05:24    

franceso a écrit :

Je vois mal comment tu pourrais te passer du signal. Par contre, tu peux peut-être remplacer le sendmsg par un segment de mémoire partagée, mais je sais pas si ça servirait à grand chose...


Non car la valeur du file descriptor n'aura aucun sens dans le processus parent, celui-ci ayant forké avant l'ouverture du socket
 

franceso a écrit :

Sinon, t'as regardé un peu comment fonctionnaient des serveurs comme Apache en mode préfork ? Ils doivent mettre en place exactement les techniques dont tu as besoin...


Oui apache fait exactement ca, il fait remonter les file descriptor des sockets vers son pool de processus. Mais dans le cas d'apache c'est dans un but d'optimisation afin de ne pas forker bêtement à chaque connexion entrante.
 
Dans mon cas j'espèrais qu'un remodèlement du design de l'application pourrait m'éviter cela mais j'y crois plus trôt ^^. (si, il y avait les pthreads, mais jaime pas mélanger fork et threads, et puis sous linux je préfère les forks ^^)


Message édité par sigmatador le 04-10-2007 à 12:53:42
Reply

Marsh Posté le 03-10-2007 à 10:39:39    

Sve@r a écrit :

Bon, suis hyper en retard mais si p1 crée un pipe (pipe mémoire via pipe(int[2]) évidemment) et génère p2 alors p2 a naturellement accès au pipe. Puis si p2 génère un fils "f" alors "f" a aussi accès au pipe. Et si f écrit dans le pipe, p1 peut le lire.

+1
J'ai fait un projet d'info sur ce modèle là, et ça marche très bien.
 
 

Sve@r a écrit :

Evidemment s'il y a "n" fils et que les "n" veulent écrire en même temps on peut penser que ça coincera mais même comme ça ça pourra aller car lorsque le premier des "n" aura écrit, le pipe sera bloqué par le noyau et personne d'autre ne pourra y écrire tant que le père "p1" n'aura pas lu.
On peut même arriver à gérer le cas d'une écriture incomplète proposée par Taz en mettant un marqueur de fin dans le flot d'infos...

Euh, là je comprends pas trop ce que tu veux dire : effectivement, si tu supposes que chacune des écritures est atomiques, tu n'as pas besoin de synchroniser les différents écrivains. Par contre, dans le cas des écritures incomplètes, même avec un marqueur de fin de message je ne vois pas comment tu te passes d'une synchronisation explicite des écrivains.
 
Ex (je numérote les écrivains e1 ... eN)
 
e1 -> DEBUT1 début des données1
e2 -> DEBUT2 début des données2
e1 -> continuation des données1
e2 -> fin des données2 FIN2
e1 -> fin des données1 FIN1
 
Malgré les marqueurs de début/fin de message, tu récupères dans le tuyau des données complètement mélangées et indiscernables. Donc à mon avis tu es obligé de passer par un mécanisme du genre mutex/sémaphore/autre


---------------
TriScale innov
Reply

Marsh Posté le 03-10-2007 à 16:35:17    

franceso a écrit :

Euh, là je comprends pas trop ce que tu veux dire : effectivement, si tu supposes que chacune des écritures est atomiques, tu n'as pas besoin de synchroniser les différents écrivains. Par contre, dans le cas des écritures incomplètes, même avec un marqueur de fin de message je ne vois pas comment tu te passes d'une synchronisation explicite des écrivains.
 
Ex (je numérote les écrivains e1 ... eN)
 
e1 -> DEBUT1 début des données1
e2 -> DEBUT2 début des données2
e1 -> continuation des données1
e2 -> fin des données2 FIN2
e1 -> fin des données1 FIN1
 
Malgré les marqueurs de début/fin de message, tu récupères dans le tuyau des données complètement mélangées et indiscernables. Donc à mon avis tu es obligé de passer par un mécanisme du genre mutex/sémaphore/autre


Il est impossible à e2 de commencer à écrire si les données de e1 n'ont pas été d'abord lues. Comme tu le dis, c'est atomique et c'est géré par le noyau. C'est d'ailleurs comme ça que fonctionne le spool d'impression. T'as un processus qui écrit dans un pipe (lpr) et un processus qui lit le pipe et qui va benner le tout dans l'imprimante (lpd). Et si "n" utilisateurs lancent leur impression "en même temps" ben le premier qui a lancé "lpr" prend la main et les autres sont obligés d'attendre leur tour. C'est le noyau qui gère les accès pipe et ça t'évite de te faire c... à les gérer toi-même...
Bien entendu il est aussi impossible à e1 de continuer à écrire. Donc si t'adoptes la méthode "noyau" faut que tu capitalises toutes tes infos dans un seul write(). Heureusement t'as les structures pour ça...

Message cité 1 fois
Message édité par Sve@r le 03-10-2007 à 16:40:44

---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 03-10-2007 à 16:58:29    

Sve@r a écrit :


Il est impossible à e2 de commencer à écrire si les données de e1 n'ont pas été d'abord lues. Comme tu le dis, c'est atomique et c'est géré par le noyau. C'est d'ailleurs comme ça que fonctionne le spool d'impression. T'as un processus qui écrit dans un pipe (lpr) et un processus qui lit le pipe et qui va benner le tout dans l'imprimante (lpd). Et si "n" utilisateurs lancent leur impression "en même temps" ben le premier qui a lancé "lpr" prend la main et les autres sont obligés d'attendre leur tour. C'est le noyau qui gère les accès pipe et ça t'évite de te faire c... à les gérer toi-même...
Bien entendu il est aussi impossible à e1 de continuer à écrire. Donc si t'adoptes la méthode "noyau" faut que tu capitalises toutes tes infos dans un seul write(). Heureusement t'as les structures pour ça...

Donc pour peu que tu utilises directement l'appel système "write", le noyau te garantit que ton écriture dans le tuyau sera forcément atomique ? Quelle que soit la quantité d'informations que tu envoies ?
 
Merci bien, c'est bon à savoir :jap: Je pensais que suivant la taille des données, certains write étaient atomiques et d'autres non...
 
 
 
[EDIT] voici ce que dit le standard : http://www.opengroup.org/onlinepub [...] write.html

Citation :

Write requests of {PIPE_BUF} bytes or less shall not be interleaved with data from other processes doing writes on the same pipe. Writes of greater than {PIPE_BUF} bytes may have data interleaved, on arbitrary boundaries, with writes by other processes, whether or not the O_NONBLOCK flag of the file status flags is set.

Donc finalement, ça dépend bien de la taille des données.

Message cité 2 fois
Message édité par franceso le 03-10-2007 à 17:04:05

---------------
TriScale innov
Reply

Marsh Posté le 03-10-2007 à 20:31:03    

franceso a écrit :

[EDIT] voici ce que dit le standard : http://www.opengroup.org/onlinepub [...] write.html

Citation :

Write requests of {PIPE_BUF} bytes or less shall not be interleaved with data from other processes doing writes on the same pipe. Writes of greater than {PIPE_BUF} bytes may have data interleaved, on arbitrary boundaries, with writes by other processes, whether or not the O_NONBLOCK flag of the file status flags is set.

Donc finalement, ça dépend bien de la taille des données.


Evidemment t'as aussi les caractéristiques à prendre en compte. A une certaine époque les pipe étaient limités à 1Ko donc tu pouvais pas écrire plus d'un ko de façon atomique. Maintenant je crois qu'ils sont passés à 4Ko mais le problème demeure. Cependant, 4ko de données ça donne déjà une certaine marge. Et sinon ben tu reviens à ton idée première => semctl() + semop()


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 04-10-2007 à 09:59:56    

Sve@r a écrit :


Evidemment t'as aussi les caractéristiques à prendre en compte. A une certaine époque les pipe étaient limités à 1Ko donc tu pouvais pas écrire plus d'un ko de façon atomique. Maintenant je crois qu'ils sont passés à 4Ko mais le problème demeure. Cependant, 4ko de données ça donne déjà une certaine marge. Et sinon ben tu reviens à ton idée première => semctl() + semop()

Oui, c'est bien 4ko maintenant :

> grep PIPE_BUF /usr/include/linux/limits.h
#define PIPE_BUF        4096    /* # bytes in atomic write to a pipe */


 
Sinon, si tu veux des messages plus grands c'est peut-être plus simple de gérer ça au niveau du protocole plutôt que d'utiliser des sémaphores. Genre le protocole permet de définir des messages en plusieurs parties, par exemple (comme tu le suggérais) sur la base de marqueurs de fin de message.
 
En tous cas, si j'avais su tout ça au moment de mes projets d'info en école, je me serais pas emm**dé à synchroniser explicitement mes écrivains à coup de signaux... :jap:


---------------
TriScale innov
Reply

Marsh Posté le 04-10-2007 à 10:10:03    

Euh il me semble que sur un 2.6 récent, Linus a amélioré la triperie et que derrière c'est un FIFO 12 pages.

Reply

Marsh Posté le 04-10-2007 à 10:34:37    

Taz a écrit :

Euh il me semble que sur un 2.6 récent, Linus a amélioré la triperie et que derrière c'est un FIFO 12 pages.

Les 4096bytes c'est sur un 2.6.12 (donc pas trop récent...)
 
Peut-être que ça a changé depuis.


---------------
TriScale innov
Reply

Marsh Posté le 04-10-2007 à 10:45:16    

franceso a écrit :

Les 4096bytes c'est sur un 2.6.12 (donc pas trop récent...)
 
Peut-être que ça a changé depuis.

Toutes façons, c'est peut-être un peu fragile de s'appuyer là dessus, si la taille de message augmente, ça va commencer à poser problème.

Reply

Marsh Posté le 04-10-2007 à 12:27:45    

franceso a écrit :

Donc pour peu que tu utilises directement l'appel système "write", le noyau te garantit que ton écriture dans le tuyau sera forcément atomique ? Quelle que soit la quantité d'informations que tu envoies ?
 
Merci bien, c'est bon à savoir :jap: Je pensais que suivant la taille des données, certains write étaient atomiques et d'autres non...
 
 
 
[EDIT] voici ce que dit le standard : http://www.opengroup.org/onlinepub [...] write.html

Citation :

Write requests of {PIPE_BUF} bytes or less shall not be interleaved with data from other processes doing writes on the same pipe. Writes of greater than {PIPE_BUF} bytes may have data interleaved, on arbitrary boundaries, with writes by other processes, whether or not the O_NONBLOCK flag of the file status flags is set.

Donc finalement, ça dépend bien de la taille des données.


 
Dis donc, j'en apprend des choses utiles dans ce msg ^^.
 
Le coup de l'atomicité, c'est pareil avec les sockets ? Genre quand send me retourne le nombre d'octets envoyés, je suis sur qu'il ont été envoyé de manière continue ?


Message édité par sigmatador le 04-10-2007 à 12:33:44
Reply

Marsh Posté le 04-10-2007 à 12:29:21    

franceso a écrit :

Oui, c'est bien 4ko maintenant :

> grep PIPE_BUF /usr/include/linux/limits.h
#define PIPE_BUF        4096    /* # bytes in atomic write to a pipe */


 
Sinon, si tu veux des messages plus grands c'est peut-être plus simple de gérer ça au niveau du protocole plutôt que d'utiliser des sémaphores. Genre le protocole permet de définir des messages en plusieurs parties, par exemple (comme tu le suggérais) sur la base de marqueurs de fin de message.
 
En tous cas, si j'avais su tout ça au moment de mes projets d'info en école, je me serais pas emm**dé à synchroniser explicitement mes écrivains à coup de signaux... :jap:


 
C'est un petit programme (< 1500 lignes), je pense rester aux sémaphores, c'est ce qui reste le plus simple à gérer.


Message édité par sigmatador le 04-10-2007 à 12:34:01
Reply

Marsh Posté le 04-10-2007 à 12:40:57    

Par contre j'ai un autre problème maintenant ^^.
 
En fait je pensais que le passage de descriptor réglait tout mes problèmes, mais il en crée un autre >_<.
 
Voilà un exemple du problème: lorsque je crée un socket, je le passe à 2 fils via un fork, et à un parent via send_fd.
 
Si l'un des 2 fils décide de fermer la connexion, l'autre fils dès qu'il fera un recv ou un send recevra un joli return -1 et saura qu'il doit alors fermer lui aussi la socket. De même si c'est le peer qui ferme la connexion, les 2 fils se prennent un gros return -1 et ferme la socket.
 
Mais cela ne se passe pas aussi bien avec le parent. Je sais que le descripteur qu'il recoit est bon car si je send ou recv dessus lorsque la connexion est ok, ca fonctionne parfaitement. Mais si la connexion a été fermé par un des fils ou par le peer, recv ou send bloque le processus, cela ne retourne pas -1 bien que la socket ait été fermée.
 
C'est mon utilisation de sendmsg dans send_fd/recv_fd qui est foireuse ou est-ce que c'est un problème inhérent au passage de descripteur et je vais devoir changer d'approche ?
 
Je me doute bien que là on commence à rentrer dans les cas limites de la prog unix, mais on sait jamais, p-e que vous avez une idée ^^


Message édité par sigmatador le 04-10-2007 à 12:45:31
Reply

Marsh Posté le 04-10-2007 à 14:07:59    

Je ne pense pas que l'atomicité soir valable aussi pour les sockets. Dans la norme, c'est précisé uniquement pour les FIFO et les pipes. Mais Sve@r a l'air beaucoup plus au courant que moi de ce genre de détails.
 
Sinon, je ne suis pas sûr d'avoir encore bien compris ton problème : si j'ai bien les connections sont acceptées par le processus serveur, qui s'occupe aussi d'écrire dans les sockets les données qu'il lit en permanence dans le pipe. Si c'est bien ça, alors pourquoi est-ce que les différents autres fils ont besoin de connaitre les sockets ?


---------------
TriScale innov
Reply

Marsh Posté le 04-10-2007 à 14:33:34    

Déjà, ce n'est pas le processus qui accepte les connexions qui s'occupe de lire dans le pipe. C'est pour ca que j'ai eu besoin d'envoyer le descripteur du socket au processus qui lit le pipe.
 
Ensuite le processus qui accepte les connexions, après avoir envoyé le descripteur, il fork un fils (dans l'exemple en haut j'en ai mis 2, c'était juste pour tester). Il envoie le socket au processus qui lit le pipe afin que ce client puisse recevoir les informations. Mais, le fils ne meurt pas pour autant, en effet, si chaque fils recevra "bêtement" l'ensemble des informations du pipe, chacun pourra indépendamment envoyer des commandes d'administrations.
 
edit: mon post suivant doit être plus clair ^^


Message édité par sigmatador le 04-10-2007 à 15:23:51
Reply

Marsh Posté le 04-10-2007 à 14:55:52    

http://photomaniak.com/upload/out.php/i110154_fork.png
 
- Le processus 'tâches' crée un fils pour chaque tâche à effectuer
- Chacun des fils 'tâche' envoie des informations sur le travail effectué dans le pipe.
- Le processus 'admins' crée un fils pour chaque client qui se connecte et envoie le socket au processus 'journal'
- Chacun des fils 'admin' permet aux clients d'intéragir avec le programme
- Le processus 'journal' envoie à chacun des sockets qu'il a recu le contenu qu'il lit dans le pipe
 
Voilà j'espère que c'est plus clair.
 
Tout ceci marche parfaitement sauf que:
- Quand un client admin coupe sa connexion, le processus admin correspondant va sur son recv se voir retourner 0 ou -1 et donc il ferme le descripteur avant de se terminer --> Ca c'est OK.
- Par contre le processus 'journal' lui, la prochaine fois qu'il va vouloir écrire sur le socket d'un client qui a fermé sa connexion, il ne va pas recevoir d'erreur mais rester bloqué --> Ca c'est PAS OK.
- Pourtant le socket qu'il a recu est bon, puisque tant que le client est connecté, il recoit bien le contenu du pipe.
 
Donc il me reste à trouver pourquoi 'journal' ne se voit pas retourner une erreur lorsqu'il essaie d'écrire dans un socket qui a été fermé.
 
Bon néanmoins, il y a 2 solutions de remplacement possibles, soit le processus 'admin' devient un pthread de 'journal', soit les 2 processus fusionnent et je gère le pipe et le socket en accept en même temps avec un select.
 
Mais bon, j'aurais bien aimé savoir ce qui cloche pour que le send de journal ne detecte pas la fermeture du socket.
 
edit: oui j'ai essayé de paralléliser au maximum mon algo ^^

Message cité 1 fois
Message édité par sigmatador le 04-10-2007 à 15:24:41
Reply

Marsh Posté le 04-10-2007 à 16:17:23    

merci pour les explications, c'est beaucoup plus clair :jap:
 

sigmatador a écrit :

Bon néanmoins, il y a 2 solutions de remplacement possibles, soit le processus 'admin' devient un pthread de 'journal', soit les 2 processus fusionnent et je gère le pipe et le socket en accept en même temps avec un select.

Perso, je choisirais cette deuxième solution.
 

sigmatador a écrit :

Mais bon, j'aurais bien aimé savoir ce qui cloche pour que le send de journal ne detecte pas la fermeture du socket.

Tu as essayé de voir ce qu'il se passe au niveau du processus "admins" (celui qui accepte la connection) ? Est-ce qu'un write chez lui détecte la fermeture de la socket ou pas ? Ce test devrait permettre de déterminer si le problème vient de la transmission du fd ou d'ailleurs.


---------------
TriScale innov
Reply

Marsh Posté le 04-10-2007 à 16:37:13    

franceso a écrit :

Perso, je choisirais cette deuxième solution.


Ca ma gonflé, j'étais déjà entrain d'adapter mon code au select  :D  
 

franceso a écrit :

Tu as essayé de voir ce qu'il se passe au niveau du processus "admins" (celui qui accepte la connection) ? Est-ce qu'un write chez lui détecte la fermeture de la socket ou pas ? Ce test devrait permettre de déterminer si le problème vient de la transmission du fd ou d'ailleurs.

Oui le processus admin detecte sans problème la fermeture, un recv ou un send retourne -1. J'ai même testé avec 2 processus admins concurrent sur le même socket, si le peer ferme la connexion, les 2 le detect, si l'un des 2 le ferme, l'autre le detecte aussi. Et dans tous les cas, le processus journal est dans les choux quelque soit celui qui ferme le socket.

Reply

Marsh Posté le 04-10-2007 à 16:59:52    

franceso a écrit :

Je ne pense pas que l'atomicité soir valable aussi pour les sockets.


SURTOUT PAS !!! Rien n'est moins atomique qu'un socket (et en plus si on bosse en mode non connecté on peut même avoir le paquet "n + 1" qui arrive avant le paquet "n" )...

franceso a écrit :

Dans la norme, c'est précisé uniquement pour les FIFO et les pipes.


Exactement (d'ailleurs pipe === fifo) [:ddr555]
 

franceso a écrit :

Mais Sve@r a l'air beaucoup plus au courant que moi de ce genre de détails.


Facile à tester avec 2 terminaux (vive kde/gnome)

  • dans le premier terminal on fait un "mknod fifo p" puis un "date >fifo" => curseur bloqué
  • dans le 2° on fait un "cat fifo" => la date s'affiche et le curseur se débloque dans le premier terminal...


Message édité par Sve@r le 04-10-2007 à 17:07:37

---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 22-10-2007 à 17:58:22    

Bon je remonte le post car j'ai un nouveau problème avec mon appli (aucun rapport avec l'ancien problème si ce n'est que cela relève aussi du parallélisme sous linux)
 
Le design actuel de l'application est comme décrit par le dessin posté quelques messages plus haut sauf que les processus journal et admin ont fusionné en un seul qui surveille à la fois le pipe de log et le socket admin en ecoute avec un select.
 
Les processus admins recoivent bien les messages des tâches qu'ils ont demandé à surveiller, ca ca marche impec.
 
Mais maintenant je voudrais aussi que les admins puissent influer sur le comportement des tâches en modifiant certaines règles.
 
Pour cela, j'ai simplement crée pour chacune des règles à implémenter, de la mémoire partagée afin d'y stocker les valeurs qui influencent le comportement des tâches, les processus tâches ne fesant que lire dedans et adapter leur comportement en conséquence, les processus admins ne faisant qu'écrire dedans suivant les commandes recues.
 
Tout ceci marchait impec jusqu'à ce que je cherche à implémenter la dernière règle, là c'est le drame ^^
 
Il s'agit d'un pool d'expression regulière, un processus tâche va par exemple tester les données qu'il a recu sur chacune des expressions régulières du pool jusqu'à ce qu'il en trouve une (ou pas) qui match. Puis en fonction de si oui ou non il en a trouvé une qui match, il aura un comportement approprié.
 
Avec les autres régles, je connais le nombre d'élément qui les composent et leur taille à l'avance, donc je peux allouer la mémoire nécessaire à l'avance sous forme de mémoire partagé.
 
Mais avec les expressions régulières, c'est loin d'être aussi simple. J'utilise PCRE qui me compile le pattern qu'il recoit du socket admin, sauf que le résultat retourné est stocké sur le tas, et donc le pointeur retourné n'a aucune validité dans les processus tâches. Je pourrais alors  adapter la mémoire partagée pour qu'elle ne stoque plus le pointeur mais directeemnt la structure, sauf que cette dernière est de taille variable et je ne peux donc prévoir à l'avance la taille à allouer pour le pool.
 
Alors pour remédier à ce problème je vois plusieurs solution (de la plus pourrie à la moins pourrie ^^):
1- Convertir tous les fork en pthread, le tas serait commun à toutes les tâches, et donc le pointeur retourné par pcre_compile serait valide partout --> je suis franchement franchement pas fan de cette solution.
2- Les processus tâches ne vont pas effectuer eux-même le pcre_exec mais envoyer les données à matcher au processus journal via un pipe qui renverra le résultat par ce dernier --> dans le genre je bouffe de la ressource pour rien... et en plus je vais bien boussillé l'execution en parallèle des tâches à moins de créer un pool de thread dédié au pattern matching coté processus journal.

Message cité 2 fois
Message édité par sigmatador le 22-10-2007 à 18:04:49
Reply

Marsh Posté le 22-10-2007 à 18:09:54    

3- La solution que je voudrais implémenter --> ma préférée mais je sais pas si c'est fesable ^^
 
Cela consiste en gros à gérer son propre tas. Avant tout fork, je me crée un segment de mémoire partagée de taille conséquente, qui sera donc accessible de partout. Et je possède une implémentation de malloc et free qui au lieu d'allouer la taille demandé sur le tas du processus le fait dans la mémoire partagée.
 
Un truc du genre:
void *my_malloc(my_heap, size_t);
void my_free(my_heap, void *);
 
C'est fesable sous Windows avec HeapCreate je crois, c'est fesable sous AIX avec umalloc, mais sous Linux je n'est rien trouvé de standard pour faire quelque chose du genre, ni de librairie toute prête pour gérer cela.
 
Donc si quelqu'un a une idée pour implémenter rapidement la solution 3, je suis preneur, mais si j'ai fait un long post d'explication avant celui-ci, c'est que je suis aussi ouvert à une eventuelle solution 4 auquelle je n'aurais pas pensé ^^.


Message édité par sigmatador le 22-10-2007 à 18:14:03
Reply

Marsh Posté le 22-10-2007 à 18:35:36    

sigmatador a écrit :

Mais avec les expressions régulières, c'est loin d'être aussi simple. J'utilise PCRE qui me compile le pattern qu'il recoit du socket admin, sauf que le résultat retourné est stocké sur le tas, et donc le pointeur retourné n'a aucune validité dans les processus tâches. Je pourrais alors  adapter la mémoire partagée pour qu'elle ne stoque plus le pointeur mais directeemnt la structure, sauf que cette dernière est de taille variable et je ne peux donc prévoir à l'avance la taille à allouer pour le pool.

Question bête mais je la pose quand même au cas où : tu peux pas stocker directement le pattern en mémoire partagée ? A charge au processus tâche de faire son pcre_compile...


---------------
TriScale innov
Reply

Marsh Posté le 22-10-2007 à 18:42:34    

franceso a écrit :

Question bête mais je la pose quand même au cas où : tu peux pas stocker directement le pattern en mémoire partagée ? A charge au processus tâche de faire son pcre_compile...


Si ca je peux (en definissant une taille maximale pour le pattern), mais niveau perf, compiler le pattern à chaque fois, ca va être pire que la solution 2, est de loin.
 
Les tâches n se créent et se terminent assez rapidement. En période de pointe, l'appli va en traiter un bon paquet par seconde, j'essaie donc de designer le programme avec un soucis de performance afin que le serveur sur lequel il va tourner tienne la charge.
 
edit: et oui mes problèmes à moi ca vous change des histoires de sapins hein ^^ ? (je suis en train de lire les post en dessous pour mechanger les idées là ^^)


Message édité par sigmatador le 22-10-2007 à 18:53:30
Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed