Impossible de bloquer la lecture à partir du tube nommé (FIFO) sous Linux

C’est très bizarre que je n’arrive pas à faire ce travail. Voici mon architecture: j’ai un canal nommé qui communiquera entre un processus de lecteur root toujours en cours d’exécution et plusieurs processus de rédacteur d’application. Le processus de lecture doit être blocking pendant que les rédacteurs ne nonblocking . C’est donc ce que je fais dans le processus de lecture qui s’exécutera avec root privilège root .

lecteur.c

 #define PIPE_ID "/dev/shm/mypipe" // This function configures named pipe void configure_pipe() { // So that others can write umask(0000); if(mkfifo(PIPE_ID, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH | S_IWGRP | S_IWOTH) != 0) { perror("mkfifo error\n"); } } 

En fonction principale:

 int main (int argc, char **argv) { int Process_Pipe_ID; configure_pipe(); // main loop which never ends while(1) { if((Process_Pipe_ID = open(PIPE_ID, O_RDONLY | O_NONBLOCK)) < 0) { perror("pipe file open error\n"); exit(-1); } int bytes_read = 0; Message_Struct_t msg; // loop to keep reading from the pipe if there are messages to be read while((bytes_read = read(Process_Pipe_ID, &msg, sizeof(Message_Struct_t))) != 0) { if(bytes_read < 0) { perror("Pipe reading error in Scheduling agent\n"); exit(-1); } printf("Read: %d, first: %d, second: %d\n", bytes_read, msg.first, msg.second); fflush(stdout); } close(Process_Pipe_ID); } } 

Je m’attends à ce que ce lecteur ne soit pas bloqué à l’ open mais il devrait continuer à lire à partir du tuyau nommé s’il y a quelque chose sur le tuyau. Ensuite, s’il reçoit 0 ce qui signifie EOF (rien de disponible dans le canal), il devrait alors fermer le descripteur de fichier et l’ouvrir à nouveau pour continuer à essayer de lire à partir du canal. C’est un peu occupé à attendre .

Je m’attends à ce que le bytes_read soit exactement le sizeof(Message_Struct_t) (24 octets) parce que j’ai configuré mon écrivain comme atomique. 24 octets est inférieur à PIPE_BUF , donc Linux assure qu’il est atomique tant que je ne dépasse pas la taille limite d’un tuyau. Je ne suis aucun moyen de dépasser la taille limite. Mes programs écriture sont comme des clients; ils viennent, exécutent et se terminent. Donc, le côté écriture du tuyau n’est pas toujours open . Ceci est mon écrivain très simple:

écrivain.c

 void writeInts(int first, int second) { Process_Pipe_ID = open(PIPE_ID, O_WRONLY | O_NONBLOCK); Message_Struct_t msg; msg.first = first; msg.second = second; int num; if((num = write(Process_Pipe_ID, &msg, sizeof(msg))) < 0) { perror("Error in writing\n"); exit(-1); } else printf("%d bytes wrote to pipe.\n", num); close(Process_Pipe_ID); } 

Cependant, j’obtiens une sortie très étrange. Je n’ai rien à l’écran (pour le reader.c ) avant d’appuyer sur enter . Lorsque j’appuie sur enter, j’obtiens ce qui suit:

 Read: 1, first: 0, second: 0 Read: 1, first: 0, second: 0 Read: 1, first: 0, second: 0 

Lorsque j’appuie sur une autre touche puis que enter , je reçois ceci:

 Read: 1, first: 0, second: 0 aa Read: 3, first: 0, second: 0 aaa Read: 4, first: 0, second: 0 

Je n’ai aucune idée de ce qui se passe réellement et de la marche à suivre. Je veux avoir une lecture bloquante alors que les rédacteurs sont non bloquants et atomiques. J’ai beaucoup cherché et ensuite écrit le code, mais il est très étrange de ne pas pouvoir le faire fonctionner.

C’est très bizarre que je n’arrive pas à faire ce travail.

Eh bien pas vraiment. Vous avez des exigences étranges et des assertions fragiles que vous essayez de respecter.

Le processus de lecture doit être bloquant.

Noooo … Pourquoi voudriez-vous faire une telle limitation? Considérer

 ssize_t blocking_read(fd, void *buf, size_t len) { struct timeval timeout; fd_set fds; int r; ssize_t n; /* First, do a speculative read, just in case there is data already available. */ n = read(fd, buf, len); if (n >= 0) return n; else if (n != -1) { /* Paranoid check, will never happen .*/ errno = EIO; return -1; } else if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; /* Wait for data to become available. */ FD_ZERO(&fds); while (1) { FD_SET(fd, &fds); timeout.tv_sec = 60; /* One minute */ timeout.tv_usec = 0; /* and no millionths of seconds */ r = select(fd + 1, &fds, NULL, NULL, NULL, &timeout); if (r < 0) return -1; /* errno set by select() */ else if (!r) continue; /* Timeout */ n = read(fd, buf, len); if (n >= 0) return n; else if (n != -1) { /* Paranoid check, will never happen .*/ errno = EIO; return -1; } else if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; } } 

Il agit comme une lecture bloquante sur les descripteurs bloquants et non bloquants. Si rien ne se passe, il se réveille une fois par minute, mais vous pouvez l’accorder jusqu’à ce que la durée soit suffisante. (Je considérerais des valeurs comsockets entre une seconde et 86400 secondes, environ un jour. N’importe quoi de plus long est simplement idiot. N’oubliez pas qu’il s’agit d’un délai d’attente, et non d’un sumil ordinaire: toute transmission de signal ou de données entrantes l’activera immédiatement.)

Avec cela, vous pouvez initialement créer la FIFO en mode 0400 ( r-------- ), ouvrez-la O_RDONLY | O_NONBLOCK O_RDONLY | O_NONBLOCK dans le lecteur, puis utilisez par exemple fchmod(fifofd, 0222) pour changer de mode (0222 = -w--w--w- ) afin d’autoriser les écrivains. Rien de tout cela ne bloque. Aucun des auteurs essayant d’ouvrir la FIFO ne réussira tant que le lecteur n’est pas prêt.

Le lecteur n’ouvre et ne ferme pas la FIFO; il continue à appeler blocking_read() .

Si les auteurs ouvrent le registre FIFO non bloquant en écriture seulement ( O_WRONLY | O_NONBLOCKING ), ils échoueront avec errno = ENXIO s’il n’y a pas de lecteur ou avec errno = EACCES si le lecteur est en cours d’exécution, mais pas encore prêt. Quand il y a un lecteur, l’écriture réussira à moins que le lecteur ne puisse pas suivre. (Lorsque le lecteur a le tampon plein, les auteurs obtiendront une erreur avec errno = EAGAIN ou errno = EWOULDBLOCK .)

Les rédacteurs peuvent facilement effectuer une écriture non bloquante, avec un délai personnalisable pour attendre que l’écriture devienne possible. c’est une fonction très similaire à blocking_read() ci-dessus.

Je m’attends à ce que le bytes_read soit exactement le sizeof (Message_Struct_t) (24 octets) parce que j’ai configuré mon écrivain comme atomique. 24 octets est inférieur à PIPE_BUF, donc Linux assure qu’il est atomique tant que je ne dépasse pas la taille limite d’un tuyau.

Dans des conditions optimales, peut-être.

Par exemple, si un utilisateur echo 1 > your_pipe par exemple, echo 1 > your_pipe juste lorsqu’un rédacteur écrit un message, vous perdez les limites de ce dernier. Le lecteur obtient les deux octets ( 1 et newline) et la partie initiale du message, la lecture suivante obtient les deux derniers octets de ce message et la partie initiale du message suivant, tant qu’il y a des écrivains écrivant dans le socket rapide ou plus rapide que le lecteur peut lire.

Comme les pipes et les FIFO ne préservent jamais les frontières des messages, votre approche est extrêmement fragile. Une meilleure approche consisterait à utiliser un socket de datagramme qui préserve les limites des messages.

Je ne suis aucun moyen de dépasser la taille limite. Mes programmes d’écriture sont comme des clients; ils viennent, exécutent et se terminent. Donc, le côté écriture du tuyau n’est pas toujours ouvert.

Vous pourriez facilement dépasser la taille limite.

Il n’y a qu’un tampon de canal, et il peut devenir plein (et donc les écritures non bloquantes échouent), s’il y a plus de rédacteurs que le lecteur ne peut en suivre. Il est facile de faire en sorte que cela se produise: par exemple, si le lecteur manipule les données en utilisant deux enregistreurs simultanés (comme un Bash for ((;;)) ; do printf 'message' > fifo ; done ) remplira le et provoquer l’échec de tous les rédacteurs non bloquants avec errno = EAGAIN ou errno = EWOULDBLOCK .

Ce n’est pas que théorique. il est facile de prouver en utilisant Bash et mknod en pratique.


J’ai l’impression que OP construit un désastre imminent avec sa combinaison actuelle d’exigences, en particulier en utilisant un tube (ou FIFO) pour le transfert de datagramme.

Personnellement, je voudrais utiliser un socket de datagramme de domaine Unix lié à un chemin, probablement /var/run/yourservice . Cela garantirait les limites des messages (deux messages différents ne seront pas mélangés, comme ils le peuvent avec des tubes ou des FIFO). Le lecteur et les auteurs peuvent tous deux utiliser des données auxiliaires pour passer un SCM_CREDENTIALS , ce qui permet au lecteur de vérifier l’ID utilisateur et l’ID de groupe utilisé par l’auteur.

(Le rédacteur peut choisir entre des identités réelles ou effectives. Le kernel vérifie toujours les champs de message auxiliaires SCM_CREDENTIALS et ne permet pas l’envoi de données incorrectes. En d’autres termes, les champs de message auxiliaires SCM_CREDENTIALS seront toujours corrects au moment où le message a été envoyé. envoyé .)

(Notez qu’en utilisant un protocole de datagramme, le lecteur ne peut pas vérifier les détails du processus qui a envoyé le message, car au moment où le lecteur reçoit le message auxiliaire SCM_CREDENTIALS, l’expéditeur d’origine aurait pu exécuter un autre processus ou quitter le système d’exploitation en réutilisant le message. ID de processus pour un autre nouveau processus Pour vérifier quel fichier exécutable était utilisé pour envoyer un message, il fallait un protocole en mode connexion, tel que le socket de stream de domaine Unix, l’envoyeur envoyant deux ou trois messages, tous avec le même message auxiliaire SCM_CREDENTIALS. C’est assez difficile à faire correctement, donc la plupart des programmeurs considèrent cette vérification comme une mauvaise chose.)