Tag C
13/2/2010 Jouons avec kvm
J'ai codé un petit programme tout simple qui fait clignoter les leds de mon routeur dès qu'un service est par terre. Ma méthode (qui ne doit pas être la meilleure mais je m'en fout un peu) consiste à regarder régulièrement la liste des processus et détecter l'arrêt d'un service quand le processus associé n'existe plus. J'ai commencé avec un script shell, puis étant assez mauvais pour tout ce qui ressemble de près ou de loin à du script j'ai décidé de le faire en C (langage d'excellence n'est ce pas...?).
Je me suis volontairement compliqué la tache en utilisant des fonctions très proches du système, ainsi s'il est possible d'obtenir une liste de processus via ps(1) avec un popen(3) super crade ou encore sysctl(3) j'ai voulu utiliser kvm(3) kernel memory interface qui est en programmation système la méthode la plus appropriée (d'ailleurs 'ps' est codé avec ça), c'est juste une interface qui va nous permettre d'accéder au données du kernel (une copie bien entendu), ici la liste des processus en cours.
Le hic c'est que si les fonctions de kvm sont très standard l'implémentation est très différente suivant les systèmes, comme à peu près toute implémentation, l'important c'est que l'interface soit standard. Ainsi à la lecture de la structure qui décrit les processus kinfo_proc de sys/user.h on se dit chouette pleins d'info à disposition, sauf que sur OpenBSD c'est pas du tout la même forme (ni le même fichier d'ailleurs). D'où l'importance d'une interface riche et c'est ce que nous promet kvm.
Assez causé passons au code (je n'explique pas ou peu le code en dehors des fonctions kvm)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <err.h> #include <libgen.h> #include <fcntl.h> #include <limits.h> #include <paths.h> #include <kvm.h> #include <sys/param.h> #include <sys/sysctl.h> #include <sys/wait.h> #include <sys/user.h> void start_leds(void) { /* Ici on execute /usr/sbin/gioctl -q gpio0 led3 (on|off) * suivant la parité du conteur statique 'i' * 60 fois avec une pause d'une seconde. */ static int i = 1; switch(fork()) { case -1: err(1, "fork"); break; case 0: execl("/usr/sbin/gpioctl", "gpioctl", "-q", "gpio0", "led3", (i%2) ? "on" : "off", (char*)NULL); err(1, "execl"); break; default: wait(NULL); break; } sleep(1); if (i++ <= 60) return start_leds(); return; } int main(int argc, char *argv[]) { kvm_t *kd; char berr[_POSIX2_LINE_MAX]; struct kinfo_proc *p, *procs; int n, j, k, found = 0; char **pargv; if (argc < 2) errx(1, "Usage check_proc procs ..."); /* On accède aux données du kernel qui tourne actuellement (NULL) */ if (!(kd = kvm_open(NULL, NULL, NULL, O_RDONLY, berr))) errx(1, "%s", berr); /* On demande la liste des processus, kinfo_proc est décrite * dans sys/sysctl.h sur OpenBSD et sys/user.h sur FreeBSD. * Il nous la donne dans un tableau continu de pointeurs de * taille qu'on récupère dans 'n'. */ if (!(procs = kvm_getprocs(kd, KERN_PROC_ALL, 0, &n))) err(1, "kvm_getprocs: %s", kvm_geterr(kd)); for (k = 1; k < argc; k++) { for (j = 0, p = procs; j < n; j++, p++) { /* On accède à la liste des arguments du programme qui a * généré le processus courant. */ pargv = kvm_getargv(kd, p, 0); if (pargv && pargv[0] && strstr(pargv[0], argv[k])) { found++; break; } } } if (-1 == kvm_close(kd)) err(1, "kvm_close"); if (found != argc-1) start_leds(); return 0; }
Et l'exécution, on veut tester si un processus du nom de 'dhcpd' tourne sur le système, si ce n'est pas le cas on lance le clignotement de la led pendant une minute :
# gcc -o check_proc check_proc.c -lkvm # ./check_proc dhcpd
Le mien s'exécute dans un cron toute les minutes :
* * * * * /root/bin/check_proc named dhcpd adsuck ntpd
3/10/2009 Un système de ramasse miette en C
Je viens d'implémenter dans mon shell un système de ramasse miette. Le choix de faire un tel système est particulièrement adapté pour un shell ou pour tout programme qui est fortement cyclique et avec un cycle assez court où chaque allocation peut être libérée rapidement.
En gros l'astuce consiste à créer des fonctions wrapper pour free(3) et malloc(3). Une fois que tous nos appels d'allocation et de libération de mémoire passent par eux il est facile de créer une structure de type pile qui empile l'adresse de chaque allocation pour ensuite tout libérer d'un coup à la fin du cycle.
Pour cela j'ai utilisé les SLIST de sys/queue.h pour la pile que voilà :
typedef struct stack_s { void * adr; SLIST_ENTRY(stack_s) next; } stack_s;
Ensuite le wrapper pour malloc (notez qu'on déclare la tête de pile comme variable globale (dans le fichier avec les fonctions wrapper) pour pouvoir y accéder facilement.
/* déclaration de la pile mstack (qu'on initialise à NULL) */ static SLIST_HEAD(, stack_s) mstack = SLIST_HEAD_INITIALIZER(&mstack); /* Le wrapper pour malloc */ void * xmalloc(size_t size) { void *ret; stack_s *el; /* l'élément a rajouter dans la pile */ ret = malloc(size); if (!ret) { perror("Malloc "); exit (EXIT_FAILURE); } el = malloc(sizeof(*el)); el->adr = ret; SLIST_INSERT_HEAD(&mstack, el, next); return ret; } /* Cette fonction vide toute la pile et libère la mémoire */ void stack_delete(void) { stack_s *el; while (!SLIST_EMPTY(&mstack)) { el = SLIST_FIRST(&mstack); SLIST_REMOVE_HEAD(&mstack, next); free(el->adr); free(el); } return; }
Voilà, maintenant faites tous vos allocations temporaires avec _malloc et videz la pile de temps en temps (ie à la fin du cycle général de votre programme).
Par exemple dans la boucle principale ça donne quelque chose comme ça :
for (;;) { machin = xmalloc(sizeof(*machin) * 10); /* ... ... */ stack_delete(); }
J'espère que c'est compréhensible, si vous avez une question n'hésitez surtout pas à me demander plus d'information.
26/9/2009 Petite présentation de sys/queue.h
Dans vos programmes C, il arrive très fréquemment d'utiliser des listes chaînée (de toutes sortes), afin de ne pas avoir à recréer le monde à chaque fois vous pouvez utiliser les macros super pratiques de queue(3). Voici une petite introduction à l'utilisation de sys/queue.h avant d'aller se plonger dans le man pour aller plus loin.
Avec sys/queue.h , vous pouvez manipuler facilement quatres sortes de listes :
- Listes simplement chainées (SLIST) de type pile
- Listes doublement chainées (LIST) de type pile
- Listes simplement chainées (STAILQ) de type file
- Listes doublement chainées (TAILQ) de type file
Voici un exemple de SLIST :
Le header :
#ifndef HEADER_H #define HEADER_H #include <sys/queue.h> typedef struct MaStruct { /* ici on met nos elements par exemple : */ char *str; /* * On déclare notre liste avec SLIST_ENTRY qui est equivalente à * struct { struct MaStruct *sle_next; } next; */ SLIST_ENTRY(MaStruct) next; } MaStruct; #endif /* HEADER_H */
Ensuite voyons comment manipuler cette liste :
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "header.h" int main(void) { /* SLIST_HEAD crée une variable pour acceder à notre liste */ SLIST_HEAD(, MaStruct) head; /* on crée un element MaStruct */ MaStruct *p; /* On l'initialise à NULL */ SLIST_INIT(&head); p = malloc(sizeof(*p)); p->str = malloc(sizeof(char) * 10); strcpy(p->str, "blah"); /* On insere p dans la SLIST */ SLIST_INSERT_HEAD(&head, p, next); /* On empile un deuxième element */ p = malloc(sizeof(*p)); p->str = malloc(sizeof(char) * 10); strcpy(p->str, "blu"); SLIST_INSERT_HEAD(&head, p, next); /* * Il est très facile de parcourir la liste * que ce soit pour du traitement ou de * la libération de mémoire */ SLIST_FOREACH(p, &head, next) { /* ici p est l'element courant */ printf("element : %s\n", p->str); } /* exemple de libération */ while (!SLIST_EMPTY(&head)) { p = SLIST_FIRST(&head); SLIST_REMOVE_HEAD(&head); free(p->str); free(p); } return 0; }
Parmis les macros interessantes pour les SLIST il y a :
SLIST_FIRST(&head); /* renvoie la tête de liste */ SLIST_REMOVE_HEAD(&head, next); /* supprime la tete de liste */ SLIST_EMPTY(&head); /* renvoie vrai si la liste est vide */
Ben entendu, il y a beaucoup plus de possibilités avec LIST et TAILQ... Mais globalement vous devriez avoir compris le principe.
La lecture de sys/queue.h devrait vous éclairer pour comprendre comment marchent toutes ces macros, c'est assez facile à comprendre si vous avez (comme moi) un niveau moyen en C.
Voyez aussi ce papier d'iMil.