Archives du blog

Chasser les pertes de mémoire en C sous Linux

Pour un fois je vais pouvoir parler de ce que je fais pour le boulot. En effet, j’ai planché sur un sujet qui peut servir à autrui.
Un des programmes écrit par un de mes prédécesseur avait une fâcheuse tendance à prendre de plus en plus de mémoire jusqu’à bloquer la machine (tssss un process lancé en root).

Chasser les fuites mémoire est toujours un peu hasardeux, il suffit de peu pour que ça devienne mission impossible.

J’ai travaillé principalement avec deux « outils » : valgrind et /proc

L’approche valgrind

valgrind est un utilitaire dédié à l’inspection de la mémoire d’un programme. Il note toutes les zones allouées et vérifie si elles sont correctement désallouées a la fin de celui-ci.

Il existe de très bons tuto sur le net pour son utilisation, par exemple celui-ci : http://fr.openclassrooms.com/informatique/cours/debuguer-facilement-avec-valgrind

L’approche /proc/*/status

Bon ce n’est pas vraiment une méthode très académique, mais elle permet de se rendre compte précisément de la consommation mémoire en temps réel de son programme.

Le noyau Linux met a disposition de l’utilisateur un grand nombre d’information sur les processus en cours d’exécution.

Beaucoup de ses informations sont disponibles dans le point de montage /proc.

En ce qui nous concerne, chaque processus, connu par son pid met a disposition les informations sur sa conso mémoire dans le fichier /proc/<pid du processus>/status.

 $ cat /proc/523/status
 Name: firefox
 State: S (sleeping)
 Tgid: 523
 Pid: 523
 PPid: 1
 TracerPid: 0
 Uid: 1000 1000 1000 1000
 Gid: 1000 1000 1000 1000
 FDSize: 128
 Groups: 4 20 24 25 29 30 33 44 46 103 104 114 115 126 146 147 148 1000 1004 1010
 VmPeak: 1259364 kB
 VmSize: 1164072 kB
 VmLck: 0 kB
 VmPin: 0 kB
 VmHWM: 412984 kB
 VmRSS: 365880 kB
 VmData: 622216 kB
 VmStk: 200 kB
 VmExe: 92 kB
 VmLib: 116232 kB
 VmPTE: 1784 kB
 VmSwap: 0 kB
 Threads: 34
 SigQ: 0/95953
 SigPnd: 0000000000000000
 ShdPnd: 0000000000000000
 SigBlk: 0000000000000000
 SigIgn: 0000000000001000
 SigCgt: 0000000f800144af
 CapInh: 0000000000000000
 CapPrm: 0000000000000000
 CapEff: 0000000000000000
 CapBnd: ffffffffffffffff
 Cpus_allowed: f
 Cpus_allowed_list: 0-3
 Mems_allowed: 00000000,00000001
 Mems_allowed_list: 0
 voluntary_ctxt_switches: 3852953
 nonvoluntary_ctxt_switches: 874330
 

La zone qui m’intéresse dans le cas d’une zone mémoire c’est la ligne VmRSS (qui vaut ici 365880 kB).
Celle-ci reflete la quantité de mémoire alloué actuellement par le processus. Attention à la valeur des autres zones, certaines comme VmSize comptabilisent aussi les librairies partagées et les fichiers ouverts (même s’il ne sont pas chargés en mémoire !), il ne s’agit donc pas forcement de mémoire utilisée !

Grace à la lecture de ce pseudo fichier il est possible de vérifier en cours d’utilisation la mémoire consommée et de tracer son évolution à partir du programme.

En somme Valgrind permet de chasser la mémoire utilisée et /proc de vérifier qu’il n’y a pas de cas tordus qui pourraient passer à travers.

Pour automatiser la chose

un petit script

pid=`ps -fe | grep [nom du process] |grep -v grep | awk '{print $2}'` && cat /proc/$pid//status |grep Vm

une petite fonction

Vu qu’on me l’a demandé j’ai aussi une petite fonction (très fortement inspirée de celle-ci ) pour afficher dans la log l’état de la mémoire (en même temps que les éléments clef du programme)

int memStats()
{
  char * line;
  char *vmsize;
  char *vmdata;
  char *vmrss;
  char *vmstk;

  size_t len;

  FILE *f;
 
  vmsize = NULL;
  vmdata = NULL;
  vmrss = NULL;
  vmstk = NULL;
  len = 128;
  line = malloc ( len * sizeof( char ) ) ;

  char * path2status = malloc( 128 * sizeof( char ) );
  sprintf (path2status, "/proc/%d/status", getpid() );

  f = fopen(path2status, "r");
  if (!f)
  {
    free ( path2status );
    free ( line );
    return 1;
  }
  free ( path2status );
  /* Read memory size data from /proc/pid/status */
  while (!vmsize || !vmrss || !vmdata || !vmstk )
  {
    if ( fgets ( line, len, f ) == NULL )
    {
      /* Some of the information isn't there, die */
      free(line);
      fclose( f ) ;
      return 1;
    }

    /* Find VmSize */
    else if (!strncmp(line, "VmSize:", 7))
    {
      vmsize = strdup(&line[7]);
    }

    /* Find VmRSS */
    else if (!strncmp(line, "VmRSS:", 6))
    {
      vmrss = strdup(&line[7]);
    }

    /* Find VmData */
    else if (!strncmp(line, "VmData:", 7))
    {
      vmdata = strdup(&line[7]);
    }
    /* Find VmStk */
    else if (!strncmp(line, "VmStk:", 6))
    {
      vmstk = strdup(&line[7]);
    }

  }
  free(line);

  fclose(f);

  /* Get rid of " kB\n"*/
  len = strlen(vmsize);
  vmsize[len - 4] = 0;
  len = strlen(vmstk);
  vmstk[len - 4] = 0;
  len = strlen(vmrss);
  vmrss[len - 4] = 0;
  len = strlen(vmdata);
  vmdata[len - 4] = 0;

  /* Output results to stderr */
  fprintf( stderr, "<%s;%s;%s;%s>\n", vmrss, vmsize, vmstk, vmdata);

  free(vmstk);
  free(vmsize);
  free(vmrss);
  free(vmdata);
  return 0;
}

Bonne chasse …

Avec des Plugins c’est plus festif

Il y a deux semaines je vous avais parlé de mon Bus Arduino (qu’entre temps j’ai nommé ARV, comme Arduino Rendez-Vous). J’ai décidé de le faire évoluer pour que le collector puisse faire d’autres choses.

J’ai imaginé plusieurs architectures pour pouvoir évoluer facilement suivant les besoins et j’en suis arrivé à développer des plugins pour le collector.

Un plugin c’est quoi ?

Un plugin c’est une librairie partagée (dll ou .so suivant votre plateforme) ayant des noms de fonction normalisée.

Tous les plugins pour une même application auront donc des fonctions et des variables en communs : celles qui seront appelées par le programme « maitre ».

Ce programme va donc ouvrir chaque plugin à charger et récupérer un pointeur sur chaque fonction/variable qu’il compte utiliser.

Comment ça s’implémente ?

On commence par « ouvrir » le plugin

void * plugin = dlopen (fileName, RTLD_NOW);
 if (!plugin)
 {
  printf ("Cannot load %s: %s\n", fileName, dlerror ());
  return 1;
 }

et après on peu « lier » une variable  (par exemple la variable pluginName contenu dans le plugin)

char * pPluginName = dlsym (plugin, "pluginName");
result = dlerror ();
if (result)
[...] traitement des cas d'erreur

ou une fonction

typedef int (*init_f) ( char * _subject, sirElement * config );
init_f init = dlsym (plugin, "init");
 result = dlerror ();

Notez bien le typedef du prototype de fonction.

Pour chaque plugin chargé je stocke un pointeur vers une fonction d’initialisation du plugin (init), qui va me permettre d’ouvrir des fichiers ou de me connecter à une base de donnée, et une fonction de traitement du message reçu (parse).

Le chargement ? quel chargement ?

Oui, car il faut charger tous les plugins qu’on compte utiliser, j’ai opté pour un fichier de paramétrage (collector.ini dont un exemple est reproduit ici) qui va définir les plugins à charger et quand les utiliser.

[base]
pluginDir=plugins
[plugins]
arduinoTP.StationMeteo=stationMeteo.so
arduino.StationMeteo=stationMeteo.so
arduinoTP.StationMeteo=toFile.so
[stationMeteo]
DBHost=127.0.0.1
DBName=BaseArduino
DBUser=UserArduino
DBPassword=MotDePasseArduino
[toFile]
#Par défaut le plugin prends "toFile.csv", on peut aussi le fixer par configuration
fileOut=
[toFile:arduinoTP.StationMeteo]
#On peu aussi définir des paramétrage pour un couple plugin/sujet
fileOut=fichierOut.csv

Détaillons ce fichier de configuration

La section [base] contient pour le moment le chemin vers le répertoire des plugins. Tous les plugins devront être dedans au risque de ne pas être chargés.

La section [plugins] détaille les plugins à charger. Attardons nous un peu plus sur cette syntaxe.

arduinoTP.StationMeteo=stationMeteo.so

Il y a donc une clef  (arduinoTP.StationMeteo) qui à une valeur (stationMeteo.so),par cette directive on définie quel plugin utiliser (stationMeteo.so) lors de la réception d’un message de type « arduinoTP.StationMeteo ». C’est la fonction parse() du plugin qui sera utilisée pour traiter le message.

Le plugin stationMeteo.so a pour mission de stocker dans une base MySQL les message reçus (deux photoresistances et 5 capteurs de température), dans le but d’être accessible sur mon site web personnel.

Il est possible de mettre plusieurs ligne ayant une même clef afin de multiplier les actions à réaliser sur réception d’un message (par exemple stocker un message en base ET faire agir un autre arduino en envoyant un autre message sur le bus)

Le plugin de type stationMeteo a droit à une section de paramétrage, j’y ai placé les paramètres d’accès à la base de donnée.

[stationMeteo]
DBHost=127.0.0.1
DBName=BaseArduino
DBUser=UserArduino
DBPassword=MotDePasseArduino
J’ai écrit un autre plugin : toFile.so qui se contente d’écrire ce qu’il reçoit dans un fichier. C’est aussi une bonne méthode pour illustrer une autre façon de paramètrer des plugins.
[toFile]
#Par défaut le plugin prends "toFile.csv", on peut aussi le fixer par configuration
fileOut=
[toFile:arduinoTP.StationMeteo]
#On peu aussi définir des paramétrage pour un couple plugin/sujet
fileOut=fichierOut.csv

toFile a deux sections de configuration : [toFile] et [toFile:arduinoTP.StationMeteo]. Dans le second cas vos pouvez reconnaître un nom de message qui a déjà été paramétré dans la section [plugins]. Cela implique qu’un message de type arduinoTP.StationMeteo utilisera un plugin initialisé grâce à la section [toFile:arduinoTP.StationMeteo] et [toFile] dans les autres cas.

Dans le cas présent cela influe sur le nom de fichier, mais cette méthode est applicable par tout les plugin installé pour avoir des particularité suivant le message reçu (base de donnée différente, fichier différent, …).

Concernant la lecture du fichier d’ini

Par défaut le C n’a pas de librairie d’accès standard pour ce genre de fichier alors j’en ai ré-implémenté une aux fonctions minimales (sir.c dans le repository). J’avais d’abord cherché à utiliser ce qui existe déjà mais j’avais la contrainte de la taille (n’oublions pas que je compile pour une machine ayant 1.5Mo de filesystem ^^) , dans mes pérégrinations je suis tombé sur iniParser qui fait le boulot de recherche dans un .ini mais qui n’accepte pas d’avoir plusieurs clefs identiques, et comme j’utilise cette fonction pour multiplier les actions sur réception d’un message … j’ai été contraint d’en réécrire un plus simple.
L’accès au code se trouve comme d’habitude : ici

Usage Client/Serveur pour arduino

Afin de piloter mon arduino j’aimerais bien avoir accès simplement à une interface de pilotage.

Pour ce faire il me faut un comportement « réseau », moi ce qui me viens en premier c’est TCP/IP . J’ai donc commencé une implémentation d’un système client/serveur qui peut accepter plusieurs clients et plusieurs arduinos.

Je suis parti sur une architecture assez évolutive héritée de mon passé professionnel à utiliser Tibco Rendez-vous (« Middleware Orienté Message » comme on dit chez le Marketing) . J’ai donc implémenté un serveur sur lesquels peuvent se connecter des clients (jusque là rien de bien nouveau) . Ces clients peuvent être purement réseau (on les appellera client) ou avoir une patte sur le réseau et une sur le port série (on les appellera collecteur).

Le collecteur et le client sont presque identique, à la différence de l’implémentation de la lecture/écriture du port série pour le premier. Le code du client risque d’ailleurs de disparaitre au profit du collecteur que je trouve plus « puissant ».

Il est possible sur un client de taper du texte, il sera envoyé au serveur qui relayera le message à tous les clients connectés (sauf celui l’ayant envoyé), ça c’est le comportement d’un IRC. Le collecteur récupère des infos depuis la ligne série et peu aussi écrire des commandes vers cette derniere. Là on commence à avoir plus d’interet, non ?

Prenons un exemple d’utilisation.

Lançons le serveur (fenêtre du milieu)

./server

pour un test complet lançons un client « simple » (fenêtre du haut)

./collector 127.0.0.1 client
 et dans une autre fenêtre lançons le collecteur (fenêtre du bas)
./collector 127.0.0.1 collecteurMeteo /dev/ttyACM0 arduino.stationMeteo

Collecteur va écouter ce que l’arduino écrit sur sa liaison série et envoyer ça au serveur qui va répercuter ça à tous les autres clients. Ces clients peuvent les afficher, les stocker en base, faire agir d’autres arduino, … . Les possibilités sont infinies.

Afin de ne pas se mélanger les pinceaux chaque message envoyé est constitué de trois champs :

* la source : un nom identifiant l’émetteur du message

* le sujet : un texte décrivant le but du message . Par convention j’ai utilisé une série de mots séparés par des points : info.client.connect pour indiquer qu’un client s’est connecté.

* le message : champ  texte libre. A vous de vous définir un sous protocole pour communiquer avec vos créations

Le code source est accessible ici, bon c’est encore en travaux donc ne vous attendez pas à avoir un truc pil poil au top ^^. Je me suis basé sur l’exemple suivant pour l’implémentation du client/serveur, pas de fork, pas de thread , pas de haine. Il faut encore que je fasse du nettoyage pour permettre une plus grande clarté dans le code.

Tout est publié sous licence GPL v2, donc servez vous, mais tenez moi au courant de vos créations.