Forum Programmation.c Ecritures disques non bloquants

Posté par  .
Étiquettes : aucune
3
28
oct.
2011

Bonjour,

Je développe sur une carte embarquée ARM BeagleBoard xM, sur laquelle j'ai installée Debian.

Le système est installé et boot sur une carte MicroSD.

J'ai un programme C qui a des contraintes de temps de réponse de l'ordre des 50ms à respecter.

Lorsque je lance le programme normalement (./prog), mon temps de réponse est toujours respecté.

Cependant lorsque je redirige la sortie stdout du programme vers un fichier pour faire des logs (./prog > log), le programme se bloque de temps en temps (environ une seconde) lorsque le système effectue les écritures disques (sur la carte SD). Car le système effectue les écritures disques du buffer stdout.

Ce sont donc les printf() qui deviennent bloquants lors de ces écritures disque.

Je cherche une solution qui me permettrai de continuer à enregistrer les logs sur le disque, tout en évitant les bloquages causés par les écritures disques.

J'ai pensé a quelques solutions :

  • utiliser le service rsyslog :
    ça ne convient pas car il y'a des printf toutes les 50ms, ca fait beaucoup pour rsyslog

  • monter un système de fichier ramfs ou tmpfs :
    ça ne va pas car j'ai peu de ram...

  • passer par une FIFO dans le programme :
    créer un PIPE, envoyer les printf dans ce pipe, et envoyer les données du pipe vers stdout à partir d'un thread,
    mais c'est un peu lourd

  • monter le système de fichier avec l'option "sync"
    cela rend l'ensemble du système beaucoup plus lent

Peut-être qu'il existe des solutions plus efficaces, mais je n'en voit pas.

Merci

  • # Autres pistes

    Posté par  (site web personnel) . Évalué à 1.

    Non testé:
    - Essaie plutôt mount -o async.
    - ./prog | tee log > /dev/null
    - Si tee est lui-aussi bloquant, tu peux réécrire une variante avec un tampon d'entrée plus grand, mais ça revient un peu au même que la solution à 2 threads.
    Tu ne dis rien sur le débit des données produites par ton programme ; s'il dépasse les capacités de la carte SD mais que tu as du temps CPU de disponible, la solution est peut-être de compresser la sortie de ton programme (ex: ./prog | gzip -9 > log.gz).

  • # non-blocking I/O

    Posté par  . Évalué à 2.

    Tu mets ton FD non-blocking, et avant d'écrire tu appelles select() en le passant dans le writing set: si tu peux écrire, écris, sinon bufferise.
    Note que si le taux d'écriture de ton process est assez faible, tu peux utiliser un bête :
    $ ./monprog | cat > log
    cat va aussi bloquer sur certains write(), mais tant que le pipe n'est pas plein, ton process ne bloquera pas.
    Il faut donc grosso modo que :
    taux d'écriture < taille PIPE / blocage max de write
    (valeurs typiques: taille PIPE = 64KB, blocage max de write ~ 1s avec un BUFSIZ de l'ordre de 8192)

    • [^] # Re: non-blocking I/O

      Posté par  (site web personnel) . Évalué à 3.

      Ça ne va pas fonctionner : le mode non-bloquant ne marche que pour les tubes (pipes) ou le réseau. Les écritures sur disque sont toujours potentiellement bloquantes, ou pour le dire autrement select te dira toujours « le disque est prêt » (parce qu'effectivement le disque est toujours prêt, il est juste lent).

      Il n'existe pas de solution totalement satisfaisante mais les deux approches classiques sont les suivantes :

      1. Utiliser des entrées/sorties asynchrones (async I/O) : il y a une API POSIX pour ça. La référence avec la GNU libc : http://www.gnu.org/s/hello/manual/libc/Asynchronous-I_002fO.html#Asynchronous-I_002fO. Problème majeur : en fait, ça ne marche pas, ou alors pas très efficacement. Si je ne me trompe pas, la libc utilise des threads par derrière pour l'implémenter, il n'y a pas de support direct du noyau.
      2. Utiliser un fichier mappé en mémoire, avec prefetch : l'idée est d'utiliser mmap pour accéder au fichier comme une zone mémoire, puis madvise(POSIX_MADV_WILLNEED) pour dire qu'on va avoir besoin d'un bout du fichier. Ensuite, mincore permet de savoir si une partie du fichier est en cache, donc si le prefetch demandé par madvise a été honoré, et si tu vas effectivement bloquer. Référence : http://www.gnu.org/s/hello/manual/libc/Memory_002dmapped-I_002fO.html#Memory_002dmapped-I_002fO Problèmes : en théorie madvise et mincore peuvent bloquer, et surtout je ne sais pas si c'est utilisable pour faire des écritures non-bloquantes (je suppose que non). C'est surtout une technique pour les lectures non-bloquantes.

      Dans ton cas, je suppose qu'utiliser les async I/O de POSIX ou mettre en place ton propre thread est la meilleure solution.

  • # buffers et théorie des files d'attente

    Posté par  (site web personnel) . Évalué à 2.

    Dans tous les cas, si ton programme génère des données de manière soutenue tel que le récepteur (ta carte SD) ne peut pas suivre, tu vas devoir soit bloquer soit perdre de l'information. Si la vitesse de génération des données est « raisonnable » pour le récepteur sur un temps donné, il suffit d'avoir un buffer en conséquence entre les deux (ce qui peut se faire dans le programme, dans la bibliothèque qui se charge des I/O, avec un programme intermédiaire ou au niveau du noyau).

    Après en pratique C10K donne un bon résumé des options possibles sous Unix même si la problèmatique est un peu différente : http://www.kegel.com/c10k.html

    pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.