mercredi 29 janvier 2014

Have you ever encountered a compiler bug ?

Bon, c'est pas la première fois que ça m'arrive mais cette fois j'ai mis du temps à réagir... Pour préparer un nouvel article sur le C++11 je testais différentes choses autour des rvalue-references. L'article viendra plus tard donc... Finalement les problèmes rencontrés n'avaient rien à voir avec les rvalue-references, mais je m'entêtais; il a fallu du temps pour extraire la substantifique moelle. Mon exemple sur les rvalue-references essayait de montrer comment ce mécanisme permet d'optimiser le code en évitant de dupliquer trop d'objets en autorisant le contrôle des déplacements de valeur; comme les compilateurs C++ font aussi de l'élimination de copie (voir un billet précédent) je souhaitais donc supprimer la copy-elision afin d'effectuer des mesures sur un code analysable simplement par lecture de son code source. Finalement, le problème a surgit quand bien même les rvalue-references ne sont pas utilisées. Voici le code qui pose problème :
#include <iostream>
#include <string>
#include <utility>
using namespace std;

int main() {
  string s = string("11") + "22" + "33" + "44";
  cout << s << endl;

  string s2 = "11";
  cout << s2 << endl;

  string s3 = s2 + "22";
  cout << s3 << endl;
  return 0;
}
Bien entendu, les deux premières lignes sont bien suffisantes, mais j'ai laissé quelques autres petites tests mineurs.
Premier test avec une compilation ordinaire (donc avec copy-elision) :
% g++ -o rv4 rv4.cpp
% ./rv4
11223344
11
1122
Rien de mystérieux ni d'anormal. Compilons en enlevant la copy-elision :
% g++ -fno-elide-constructors -o rv4 rv4.cpp
% ./rv4

11

Comme on l'observe facilement, il y a un problème; mais l'observation n'est pas complète car cet exemple est trop épuré. Avec des structures plus complexes que les string l'effet est tout à fait surréaliste, les structures de données sont dans des états bizarres et les affichages difficilement interprétables.
Pire, en compilant en conformité avec le standard C++11, le résultat n'est pas le même!
% g++ -std=c++11 -fno-elide-constructors -o rv4 rv4.cpp
% ./rv4                                            
11223344
11

Lorsque ceci est mélangé à l'utilisation de la sémantique de déplacement (move) je vous laisse imaginer ma perplexité lorsque combiné avec un type un poil plus subtil utilisant des pointeurs et de l'allocation dynamique!
Alors quelle version de g++ est impactée ? Il semble que de nombreuses le soient. La mienne est :
% g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.0.0
Thread model: posix
%
Le rapport de bug est disponible ici. L'un des commentaires daté du 11 janvier 2014 indique que le bug a été corrigé, ce que je n'ai pu encore tester mais que je m'empresserais de faire à la prochaine mise à jour. Si vous êtes intéressé par la correction, la voici : commit.

Conclusion : doit-il y a voir une conclusion ? Peut-être que la réalité de l'informatique nous oblige à croire que nous faisons naturellement trop d'erreurs et avons donc un défaut de confiance en notre propre code, voire trop de confiance en les compilateurs. Vivement les compilateurs certifiés... Je sais il y en a et des français y travaillent : citons Xavier Leroy (ou sa page Wikipedia) et Cminor, ou quelqu'un de plus proche encore de nous : YuRuG. Ou encore, vivement des spécifications pas trop tordues pour les langages, peut-être C++ est-il arrivé dans un état qui causera sa propre perte ? ou l'enfermera dans une niche d'où il ne sortira plus ?

DjiBee

vendredi 24 janvier 2014

Linux sur Qemu dans votre navigateur

Linux dans votre navigateur!


D'accord, ça date de Mai 2011. (cf http://bellard.org/jslinux/tech.html
Mais ça m'émerveille autant à chaque fois. 

Un Linux qui démarre en 9 secondes sur un Qemu écrit en JavaScript.
Il y a même un emacs (et ed). J'ai même saisi et compilé le code de l'article c-copy-elision-et-copy-initialization.html. Avec un peu de patience, mais ça fonctionne.

jeudi 23 janvier 2014

typage, template et sauce tomate en C++…

Mais encore ? En répondant à une question sur StackOverflow je me suis dit qu'un petit article valait le coup sur le sujet.

La question était (voir SO pour plus de détails) en gros :

J'aimerais simplifier l'écriture de cout << a << ',' << b ',' << c << endl; est-il possible d'utiliser un truc du genre myPrinter(cout) << a << b << c << endl; ? On m'a dit que oui, comment faire ?
Bon c'est sûr que c'est un truc de fainéant mais intéressons-nous à la question car il y a quelques petites choses à dire.

L'idée est assez simple finalement : utiliser un type (myPrinter) qui implémente des opérateurs de sortie sur flux et ajoute à chaque écriture une virgule. So let's go :
#include <iostream>
using namespace std;
class myPrinter {
  ostream &o;
public:
  myPrinter(ostream &o) : o(o) {}
  template <typename T> myPrinter &operator<<(const T &t) {
    o << t << ',';
    return *this;
  }
};
int main() {
  myPrinter(cout) << 25 << 100.5 << "hello";
}
On encapsule le flux dans une instance de la classe, puis on redéfinit tous les sorties via un template de méthode surchargeant l'opérateur <<.
Deux problèmes apparaissent :

  1. (pas grave) la dernière impression est toujours suivie par une virgule,
  2. (grave) si on essaie d'envoyer endl le code ne compile pas (essayez-vous même), et le message est vraiment pénible à décoder (punition standard avec les templates foireux).
Occupons-nous du cas grave. En fait, on se méprend généralement sur la nature de endl. Il y a fort à parier que presque n'importe qui répondrait qu'il s'agit d'une constante dont la valeur est la fin de ligne sur le OS local. Évidemment (?!) non. Why? Parce que sinon cela imposerait une sémantique de gestion de la fin de ligne au flux lui-même. C'est donc une fonction, laquelle est appelée en retour par le flux lorsqu'on l'envoie sur le flux; la fonction écrivant alors sur le flux les caractères nécessaires pour réaliser une fin de ligne puis en général déclenche le flush()! endl est donc typé comme une fonction prenant un flux en entrée et renvoyant un flux. Il nous faut donc rajouter une méthode permettant de gérer endl (et autres bizarreries du même genre) :

#include <iostream>
using namespace std;
class myPrinter {
  ostream &o;
public:
  myPrinter(ostream &o) : o(o) {}
  template <typename T> myPrinter &operator<<(const T &t) {
    o << t << ',';
    return *this;
  }
  myPrinter &operator<<(ostream& (*pf)(ostream&)) {
    o << pf;
    return *this;
 }
};
int main() {
  myPrinter(cout) << 25 << 100.5 << endl << 123 << endl;
}
Yeah!

Maintenant le cas simple. Pour le régler n'importe qui se jetterait sur une solution utilisant un booléen permettant de déterminer si on est au début d'un affichage ou après un endl et donc éviterait d'afficher la virgule de trop, comme ici :
#include <iostream>
using namespace std;
class myPrinter {
  ostream &o;
  bool start;
public:
  myPrinter(ostream &o) : o(o), start(true) {}
  template <typename T> myPrinter &operator<<(const T &t) {
    if (start) o << t;
    else o << ',' << t;
    start = false;
    return *this;
  }
  myPrinter &operator<<(ostream& (*pf)(ostream&)) {
    o << pf;
    start = true;
    return *this;
 }
};
int main() {
  myPrinter(cout) << 25 << 100.5 << "hello" << endl << 123 << endl;
}
Yeah!

L'inconvénient (à mes yeux) est qu'on fait un test à chaque sortie (test du booléen). Est-ce vraiment utile ? Non bien sûr! Il suffit de se rappeler que le mécanisme que l'on cherche est un simple automate a deux états (sortie avec virgule, sortie sans virgule) et qu'on bascule de l'un à l'autre à l'impression d'un endl. Alors on va implémenter cet automate via des types! Ce qui donne
#include <iostream>
using namespace std;
class myPrinter {
private:
  class myPrinter2{
    myPrinter &s;
  public:
    myPrinter2(myPrinter &s) : s(s) {}
    template <typename T> myPrinter2 &operator<<(const T &t) {
      s.o << ',' << t ;
      return *this;
    }
    myPrinter &operator<<(ostream& (*pf)(ostream&)) {
      s.o << pf;
      return s;
    }
  };
  ostream &o;
  myPrinter2 s2;
public:
  myPrinter(ostream &o) : o(o), s2(*this) {}
  template <typename T> myPrinter2 &operator<<(const T &t) {
    o << t;
    return s2;
  }
  myPrinter &operator<<(ostream& (*pf)(ostream&)) {
    o << pf;
    return *this;
  }
};
int main() {
  myPrinter(cout) << 25 << 100.5 << endl << 123 << endl;
}
Yeah! Et le tour est joué. Qu'avons-nous fait ? Nous avons laissé le compilateur dérouler un automate sur les types successifs d'une expression...  Finalement rien de mystérieux.

Attention ce code contient peut-être un bug. En effet, il n'y a peut-être pas que endl qui soit une fonction.  Sauriez-vous réparer la classe en conséquence ? Je n'ai pas de réponse simple et immédiate sous la main...

Sinon, sauriez-vous implanter un automate plus compliqué ? I will show you some day...

DjiBee

mardi 21 janvier 2014

Le graal : Code Simplicity

Un blog intéressant sur le développement : Code Simplicity. Y'a pas que des choses justes, mais c'est fort intéressant et pose des questions.

DjiBee

lundi 20 janvier 2014

Concours de commentaires!


Ceux qui ont subi mes TP savent que j'aime bien relever des petits casse-tête de debug en C. Au cours de mes promenades, j'ai trouvé une jolie pépite. Fort classique mais tellement élégamment tournée... J'en rigole encore. Du coup, j'en ai fait une version avec des commentaires à la mesure du code, du moins je l'espère. 

Voilà le code initial:

char * printStringInBuffer (char * fmt, va_list pa)
{
   char * res;
   res = (char *)calloc(MAX_STRING_LENGTH, sizeof(char));
   if (res == NULL)

       return NULL;
   if (vsprintf(res, fmt, pa) >= MAX_STRING_LENGTH) {   
       res[MAX_STRING_LENGTH-1] ='\0';
       return NULL;
   }
  
return res;
}

Voilà le code avec mes commentaires
char * printStringInBuffer (char * fmt, va_list pa)

{
   char * res;
   res = (char *)calloc(MAX_STRING_LENGTH, sizeof(char));
   if (res == NULL)
       return NULL;
   if (vsprintf(res, fmt, pa) >= MAX_STRING_LENGTH) {
       /* Oups, j'ai bavé.... ça fait pas propre,
          essuyons la tâche! */
       res[MAX_STRING_LENGTH-1] ='\0'; /* Ah, là ça déborde plus! */
       /* Ah oui, mais après le buffer on voit 
        * encore mes traces. Euh, bon allez, je 
        * me casse en sifflotant, l'air de rien 
        * Suis jamais passé ici, moi. */
       return NULL;
       /* Et hop, ni vu ni connu! Celui qui trouvera 
        * que c'est moi qui ai cassé le tas, et fait 
        * une fuite mémoire sera bien malin!
        * En fait un free aurait aussi été très futé 
        * pour brouiller les pistes,
        * mais ça faisait
un bug en moins! */
   }
  
return res;
}

Quels commentaires auriez-vous mis?
LeFA

 

samedi 18 janvier 2014

Recette: Elévation de marshmallow sur structure de spaghettis crus, sauce scotch et ficelle

Ingrédients :
Pour 4 étudiants: 
  • 20 spaghettis crus
  • 1 marshmallow
  • 1 mètre de scotch
  • 1 mètre de ficelle
Durée :
  • 18 minutes
Le but du "jeu" est de faire la construction la plus haute possible en 18 minutes. On peut utiliser tout ou partie des ingrédients, et exclusivement ces ingrédients. Le marshmallow (entier) doit être au sommet de la construction. Celle-ci doit tenir seule.



 C'est tout.

Ah, oui, c'est tout...
Sauf que:

  • Le marshmallow pèse plus lourd que l'on imagine (pour des spaghettis),
  • Les spaghettis s'incurvent quand on met du poids dessus,
  • Le scotch ne colle pas aussi bien aux spaghettis que l'on s'y attendait,
  • Faire des fagots de spaghettis n'est pas aussi simple qu'espéré,
  • Et il faut se mettre d'accord sur la manière de faire,
  • Et, les ressources sont limitées,
  • Et le temps est limité,
  • Et le prof tourne en rond autour en faisant le décompte du temps, bonjour le stress,
Bref,  pourquoi la réalité est-elle si différente des hypothèses initiales? En clair, l'algorithme de construction semblait simple, sa mise en œuvre un peu moins :-(

C'est un exercice trouvé sur le web, pour en savoir plus, notamment sur quels groupes réussissent , quels groupes ne réussissent pas...
Voir : marshmallowchallenge

Et maintenant les étapes, et en bas de page , le podium!


Quelques étapes :
  • Il n'est pas permis de faire des trous dans la table pour faire tenir les spaghettis! =>














  • <= un travail de précision











  •  Ah, prolongation de spaghettis, attention fragile.=>




  • <=  Une ébauche de fondation








  •  Un premier prototype démontrable! =>










  •  <= Peut-on monter des spaghettis, comme des piliers de béton?






  •  Une idée qui se répand? =>










  • <= Ambitieux pour une première étape?











Fin du suspens!

                                                                      N° 1






  • <= N°2
  •  N° 3 =>

jeudi 16 janvier 2014

C++, "copy elision" et copy-initialization

What the hell are these ?

Prenons une classe C++ ordinaire définissant un constructeur à un argument, un constructeur par copie et un destructeur, soit :

#include <iostream>
using namespace std;

class MaClasse {
public:
  MaClasse(int i) {
    cout << "ctor " << this << " avec i=" << i << endl;
  }
  MaClasse(const MaClasse &m) {
    cout << "ctor " << this << " avec " << &m << endl;
  }
  ~MaClasse() {
    cout << "dtor " << this << endl;
  }
};

int main() {
  MaClasse c1(1);
  MaClasse c2 = 2;

  cout << "fin" << endl;
  return 0;
}

Compilons ordinairement avec gcc (par exemple), puis exécutons :

% g++ -o test test.cpp
% ./test
ctor 0x7fff52a7f9d8 avec i=1
ctor 0x7fff52a7f9d0 avec i=2
fin
dtor 0x7fff52a7f9d0
dtor 0x7fff52a7f9d8
%

Ceci donne à penser qu'on obtient une construction ordinaire dans les deux cas, en utilisant le fait qu'on a à faire à la syntaxe d'initialisation traditionnelle, e.g. int i=4, et fonctionnelle, e.g. int i(4), sachant que l'existence d'un constructeur à un argument autorise (soit-disant) cette syntaxe d'initialisation par uniformité syntaxique.

En réalité, la sémantique est plus complexe (désormais?) puisque si cette fois on compile de la façon suivante, on obtient :

% g++ -fno-elide-constructors -o test test.cpp
% ./test
ctor 0x7fff4fce09d8 avec i=1
ctor 0x7fff4fce09c8 avec i=2
ctor 0x7fff4fce09d0 avec 0x7fff4fce09c8
dtor 0x7fff4fce09c8
fin
dtor 0x7fff4fce09d0
dtor 0x7fff4fce09d8
%

Où l'on observe qu'un objet temporaire (celui d'adresse 0x7fff4fce09c8) a été créé à l'aide d'un constructeur à un argument (avec la valeur 2), puis que cet objet temporaire a été utilisé dans la construction par copie pour l'objet désiré in-fine. L'objet temporaire étant alors immédiatement détruit.

Le mécanisme est dit de copy elision, puisque, par défaut dans gcc, on élimine cette copie inutile (création d'un objet temporaire, copie, puis suppression) afin d'éviter des constructions/destructions trop nombreuses (la plaie de la programmation objet mal contrôlée).
Bien entendu, ce mécanisme est employé élégamment lors des passages d'arguments de type objet et retour de fonctions afin d'éviter les copies lors de passage par valeur (la plaie du C++).

Néanmoins, il me semble que le mécanisme ne devrait pas être visible pour les initialisations, si effectivement on utilisait une simple uniformité syntaxique de sorte que O o(v) serait syntaxiquement équivalent à O o=v. Ce que l'expérience prouve être faux (pour s'en convaincre encore il suffit de qualifier le constructeur à un argument d'explicit, ce qui a pour effet de provoquer une erreur de compilation dans les deux cas).

Dans l'expérience, il semble donc que l'opérateur d'initalisation = soit traité à la manière de l'opérateur d'affectation =, c'est-à-dire que le compilateur tente d'effectuer une conversion de la valeur vers une valeur du type de l'objet à affecter, puis réaliser l'affectation; ceci dans le cadre d'une initialisation, d'où le refus lorsqu'explicit est employé.
Or initialisation et affectation sont a-priori des opérations bien différentes et pourraient être traitées comme telles. Ici, privilège est donné à la construction par copie, à laquelle une élision est appliquée, d'ailleurs :

La norme C++11 précise l'existence de trois syntaxes d'initialisation :
  1. T x(a)
  2. T x = a
  3. T x{a}
Note: pour le troisième cas, prière de compiler avec -std=c++11.

D'après §8.5 de la norme ISO/IEC JTC1 SC22 WG21 N 3690, les cas 1 et 3 sont appelés direct-initialization, signifiant ainsi qu'aucun objet temporaire n'est requis.
Le cas 2 est appelé copy-initialization, signifiant que l'objet x est normalement créé par copie d'un objet de type compatible; mais que la copy elision permet d'éliminer (voir §12.8).

L'enfer se cache dans les détails…
Il n'existe pas d'équivalence syntaxique ou sucre syntaxique (du moins pour les types objets) entre initialisation avec liste à un argument et initialisation avec opérateur =, pas d'équivalence opérationnelle non plus d'ailleurs (sauf dans le cas de l'optimisation par élision), il n'y a qu'une équivalence sémantique (dénotationnelle) entre initialisation avec liste d'argument à un argument et initialisation avec opérateur =.
Djibee

Wanna play with direct and copy initialization ? See Herb Sutter problem #36.

Scala & Eclipse, une danse à deux vraiment technique

Elias Abou Haydar est le propriétaire d'un blog orienté technique dans lequel on trouve des infos sur l'intrication Eclipse/Scala « Eclipse Toolbox ».

DjiBee

Un petit tour au paradigme ?

Neal Ford a écrit d'excellent textes à propos de la programmation fonctionnelle et du paradigme fonctionnel. En plus, y'a des vrais morceaux de fruits dedans : Scala, groovy, etc. « Functionnal Thinking ».

DjiBee

PS: si vous suivez le cours « programmation comparée » de Yurug, je suis sûr que ça vous intéressera... mais chut!

mardi 14 janvier 2014

Vous voulez savoir comment écrire un jeu sur iOS ?

Alors lisez le tutorial How to Make a Platform Game Like Super Mario Brothers.

DjiBee

Science vs Ingénierie

Le monsieur Dijkstra sévissait encore à propos de la dichotomie entre « science » et « ingénierie » en informatique dans le document EWD1110 (god bless University of Texas). Le paragraphe de conclusion est particulièrement signifiant, il termine par :
it's the poor engineer who has no use for scientific method, it is the poor scientist who cannot contribute to engineering.
Finalement, ce doit être un sage...

DjiBee on behalf Yurug

Haskell vs Java

Voici la retranscription d'une lettre ouverte de M. Dikstra à propos du choix de Java comme langage d'enseignement : Dijkstra on Haskell and Java.

Djibee on behalf Yurug

Préparer ses icônes pour iOS et Androïd

Icon Slayer vous permet de créer en ligne tous les fichiers icônes à toutes les tailles nécessaires pour iPad-iPhone-Androïd... Très pratique.

DjiBee

GameDev

En cherchant des informations sur le pavage du plan par des tuiles hexagonales, les systèmes de coordonnées, les structures de données et les algorithmes afférents, je suis tombé sur une perle. Un joli site plein d'informations parfaitement illustrées et réactives (c'est encore mieux pour comprendre les algorithmes que de voir leur dynamique).
Il s'agit donc du site de M. Amit Patel : Red Blob Games. Le site est doublé d'un blog Blob In Games.
Toutes ces structures et algorithmes ont un point commun : le développement de jeu, mais au-delà c'est tout aussi intéressant.

DjiBee


lundi 13 janvier 2014

Qu'est ce que st_nlink exactement ?

Le problème est de déterminer si l'interprétation HFS+ (le système de fichier natif de MacOSX) est conforme au standard POSIX. En général, on considère l'interprétation conduisant à déduire du nombre de liens associés à un inœud de type répertoire, le nombre de sous-répertoires qu'il possède (en supposant d'ailleurs qu'aucun hard-link n'est autorisé autrement que via mkdir()).

Les gnous en disent :
The number of hard links to the file. This count keeps track of how many directories have entries for this file. If the count is ever decremented to zero, then the file itself is discarded as soon as no process still holds it open. Symbolic links are not counted in the total.

Le standard POSIX 1003.1-1988 affirme :
§5.6.1 The value of the member st_nlink shall be set to the number of links to the file. 
§2.3 link. See directory entry. 
§2.3 directory entry (or link). An object that associates a filename with a file. Several directory entries can associate names with the same file.
Creusons un peu plus loin dans POSIX :
§2.3 file. An object that can be written to, or read from, or both. A file has certain attributes, including access permissions and type. File types include regular file, character special file, block special file, FIFO special file, and directory. Other types of files may be defined by the implementation.
§2.3 filename. A name consisting of 1 to [NAME_MAX] bytes used to name a file. The characters composing the name may be selected from the set of all character values excluding the slash character and the null character. The filenames dot and dot-dot have a special meaning; see pathname resolution §2.4. A filename is sometimes referred to as a pathname component.
Ce qui est remarquable (à mes yeux) est que dans ces définitions aucune référence explicite n'est faite au contenant des directory entries. On suppose habituellement que les directory entries sont nécessairement contenues dans des fichiers de type répertoire, mais ce n'est nullement précisé. Ce qui est précisé c'est le contenu d'un répertoire :
§2.3 directory. A file that contains directory entries. No two directory entries in the same directory shall have the same name.
Par conséquent, il n'est pas (à mes yeux) interdit d'imaginer que st_nlink pour les répertoires se comporte comme dans MacOSX, c'est-à-dire correspond au nombre d'entrées dans le répertoire. À tout fichier HFS+ correspond un lien vers le répertoire qui le contient.

Attention! Je ne prétends pas ici que HFS+ est POSIX-compliant! D'ailleurs qu'est-ce que cela signifierait ? Les filesystems ne sont pas en eux-même conformes à une norme sémantique, fusse t-elle POSIX. Les systèmes de fichiers ne sont que des structures de données, il n'y a qu'une couche logicielle qui puisse imposer une sémantique. J'affirme donc que la sémantique POSIX est respectée par le couple OSX/HFS+, autrement dit que via OSX la sémantique est respectée. D'ailleurs OSX n'est autre que Darwin, une personnalité FreeBSD de Mach. Je l'affirme a contrario de nombreux articles Internet sur le sujet.

Question subsidiaire : existe t-il dans une distribution Unix classique (je n'ai pas de définition autre que le sens commun) un outil standard qui utilise la valeur du champ st_nlink pour faire quelque chose d'intéressant ? J'exclue d'office un outil type fsck dont le rôle est justement d'examiner la structure du filesystem à des fins de réparation par exemple...

DjiBee

GOTO, c'est parfois beau...

Pour continuer dans la foulée des articles à pattes d'éléphant, j'aime bien cet article-ci qui répond à celui de Dijsktra "Structured programming with go to statements de Donald Knuth".

A-t-on réussi à faire une synthèse de ces deux positions? La réponse dans un prochain post ;-)

GOTO, c'est pas beau...

« C'est dans les vieux pots qu'on fait les bonnes soupes » disaient nos grand-mères. De nombreux écrits de Edsger Wybe Dijkstra (pour en savoir plus sur ce grand homme consultez donc sa notice Wikipédia, ce n'est pas un mauvais départ) sont disponibles un peu partout et dont la lecture ne peut faire que du bien.
                                                                                               
Je conseille donc le fameux « GO-TO statement considered harmful » gentiment offert par l'Université du Texas.

DjiBee

vendredi 10 janvier 2014

Linux: /proc/[pid]/io syscr - syscw


Dans le système de fichiers  /proc, Linux fournit un fichier /proc/[pid]/io pour chaque processus.  Ce fichier contient les informations suivantes:
$ cat /proc/$$/io
rchar: 3753772
wchar: 44053
syscr: 36162
syscw: 276
read_bytes: 3014656
write_bytes: 4096
cancelled_write_bytes: 4096



  • Les champs rchar et wchar fournissent le nombre d'octets lus par le processus au moment où l'on consulte ce fichier io.
  • Les champs syscr et syswr donnent le nombre d'appels systèmes read et write effectués par le processus, les appels readv, pread et writev, pwrite sont inclus dans ce décompte.
  • Les champs read_bytes et write_bytes indiquent le nombre d'octets transférés depuis le disque à l’initiative de ce processus
  • Le champ cancelled_write_bytes tente de compter les octets qui auraient du être écrits sur disque mais qui n'ont jamais atteints le disque... parce que ce processus à, par exemple, détruit ou tronquer le fichier.
La description de ce fichier se trouve dans la page manuel proc(5)

Après observation, lorsqu'un processus se termine, ses compteurs syscr et syscw sont cumulés avec ceux de son père lorsque celui-ci fait waitpid du fils. Rien d'étrange? Cela ressemble à ce qui est fait, connu et enseigné sur les temps cpu utilisateur et système des fils. Si le père est déjà terminé, c'est init, qui récupèrera ces informations

Certes. On peut aussi ajouter d'autres champs qui sont reportés d'un fils mort vers un processus père: le nombre de fautes de page  majeures, le nombre de fautes de page mineures...

Il y a une différence dans le traitement de ces informations: 
  • Les temps CPU des fils morts sont cumulés dans des champs dédiés du descripteur du processus père, distinct des champs utilisés pour comptabiliser le temps CPU du processus père lui-même
  • Les compteurs d'appel système read et write sont directement cumulés avec ceux du père! D'ailleurs le descripteur de processus ne prévoit pas de compteurs spécifiques pour les valeurs des fils décédés.
Pourquoi? A ce jour, je n'en ai pas la moindre idée. 

Comment le vérifie-t-on ?  De la manière suivante :


  1. On commence par prendre l'état du fichier io du shell courant
  2. On exécute une commande dd simple effectuant un read et un write
  3. On reprend l'état du fichier io du shell courant
  4. La différence entre 1 et 3 nous donne un delta de référence de read/write pour le shell
  5. On exécute une commande dd effectuant 11 read et 11 write
  6. On reprend l'état du fichier io du shell courant
  7. On calcule la différence entre 3 et 6
  8. On compare les résultats entre 4 et 7... Différence : +10 syscr et +10 syscw!
 Voilà le shell script utilisé (io.sh) :
set -x
cat /proc/$$/io
dd if=/dev/zero of=/dev/null bs=1 count=1
cat /proc/$$/io
dd if=/dev/zero of=/dev/null bs=1 count=11
cat /proc/$$/io


Et voilà le résultat simplifié!
cat /proc/4173/io
syscr: 5
syscw: 1
dd if=/dev/zero of=/dev/null bs=1 count=1
1 byte (1 B) copied, 2.851e-05 s, 35.1 kB/s
cat /proc/4173/io
syscr: 26           # Donc le nombre de read est de 26 - 5 : 21
syscw: 8            # Donc le nombre de write est de 8 - 1 :  7
dd if=/dev/zero of=/dev/null bs=1 count=11 # On fait 10 read et 10 write de plus
11 bytes (11 B) copied, 3.5497e-05 s, 310 kB/s
cat /proc/4173/io
syscr: 57 # Nombre de read : 57 - 26 -> 31 (au lieu de 21, soit +10)
syscw: 25 # Nombre de write: 25 - 8  -> 17 (au lieu de 7, soit +10)

D'où quelques questions:
  • Quel usage était initialement prévu pour ces métriques?
  • Comment peut-on se servir de cette métrique, puisqu'elle n'indique pas ce que l'on attend?
Moralité:
  • Les documentations associées aux métriques sont à prendre avec précaution
  • Il est préférable de vérifier les choses de visu.
LeFA