Skip to content

Linux Attitude

Le libre est un état d'esprit

Archive

Category: Développement

Niveau :      
Résumé : processus ; thread

Quelques personnes m'ont récemment demandé la différence entre un processus et un thread. Avant de bien en comprendre la différence, nous avons besoin de savoir deux choses : à quoi sert un noyau ? et comment fonctionne un processus ?

Base

Un noyau sert, entre autre, à gérer des tâches. Il permet à différents codes d'être exécutés chacun dans un monde séparé. Ces différents code ne peuvent pas empiéter sur ce que fait le voisin car le noyau les en empêche. C'est la protection, qu'on trouve implémentée au niveau même du processeur (celle qui défaillait dans windows 95).

Le noyau fournit aux processus un moyen de communiquer avec le monde extérieur ou entre eux (par des fichiers puisque tout est fichier). Ainsi chaque processus se voit comme ayant un accès complet au processeur et à une RAM de 4Go (pour un processeur 32 bits). Une partie de ces 4Go n'est en pratique pas modifiable car elle appartient au noyau (le dernier Giga). C'est le noyau qui se débrouille pour lui mettre à disposition ces 4Go, soit en lui associant de la vraie ram à la demande (en utilisant la notion de paging du processeur, à travers la fonction brk du noyau), soit en en mappant une partie sur le disque à la demande (fonction map), soit enfin en lui cachant qu'ils ne sont pas réellement disponibles (en stockant le contenu dans la swap).

Exemple de mapping mémoire : ram.png

Le noyau fait aussi en sorte que les processus ne consomment pas tout le processeur, il les répartit dans le temps pour leur en donner un morceau à chacun. C'est un service rendu par le scheduler du noyau.


continue reading...

Niveau :      
Résumé : écrire, réécrire

Factoring

Supposons que vous développiez un projet personnel. Vous êtes le seul à spécifier les besoins, le seul à spécifier l'architecture, le langage ... Malgré cela vous constatez la plupart du temps que le résultat est moyen, voire médiocre (ou acceptable si vous êtes très doués). Pourquoi ? Simplement parce qu'un programme doit toujours être développé au moins deux fois.

Vous devez être conscient que la première version d'un programme sera toujours un prototype. Sans exception. Mais cela va me coûter cher me direz-vous ! Hé bien pas tant que ça. Puisque vous êtes conscients que la première version sera un prototype, vous y mettrez beaucoup moins d'efforts. D'autre part la deuxième version sera plus courte à écrire puisque vous avez acquis toute l'expérience nécessaire. Et enfin les bugs de conceptions, les plus durs à résoudre, pourront être évités et donc vous gagnerez du temps.

Un prototype c'est un programme qui a toutes les fonctionnalités du produit fini (ou presque, les demandes évoluant souvent en cours de route). Mais il n'intègre pas nécessairement tous les détails, toutes les options ou une interface graphique soignée. Un prototype ne doit pas être la base de la version suivante du programme. La version finale doit être repensée en partant de 0 (on pourrait même dire que le langage doit être changé). Par contre, rien n'interdit de faire du copier/coller depuis le prototype vers la version finale, c'est même recommandé car un grand nombre de fonctions ont déjà été développées correctement.

Vous aurez compris que ce que je viens de vous décrire est le refactoring. Rien de bien nouveau, seulement vous devez être conscient que pour faire un logiciel dans lequel vous ne perdrez pas votre temps, vous devrez refactorer au moins une fois.

Refactoring

Maintenant , si vous développez pour une entreprise, le processus de développement est quasiment toujours le même. Un client a fait un cahier des charges plus ou moins complet, assisté d'un commercial qui en a profité pour lui faire de nombreuses promesses. Ensuite ce cahier des charges a été traduit par un analyste (peut-être vous même) en un schéma d'architecture ou un schéma UML avec des use case pour les plus méticuleux. Très bien, on pourrait aller jusqu'à dire que c'est fait dans les règles de l'art. C'est ici que le boulot de développeur (et les ennuis) commence. Il s'agit de prendre l'analyse et d'en faire, presque mécaniquement, le logiciel désiré.

Et vous en avez tous fait l'expérience, ça ne marche jamais comme il faut. Il y a de nombreux endroits où le bât blesse. Tout d'abord le client ne sait pas ce qu'il veut, normal il s'agit d'un produit qu'il n'a jamais vu ni utilisé. Ensuite le commercial en a profité pour lui vendre quelque chose de génial, normal c'est son boulot, mais pas nécessairement réalisable. Ensuite l'analyste a fait ce qu'il a pu pour déduire de ce cahier des charges écrit en langage courant un schéma qui peut être informatisé. Et enfin le développeur fait ce qu'il peut en fonction des contraintes du langage utilisé.

C'est le même cas que précédemment, en légèrement plus compliqué surtout s'il y a beaucoup d'intervenants. L'extreme programming pallie ce problème en prônant un refactoring permanent et un bouclage fréquent avec le client.

Ne perdez plus votre temps, mettez des bouts d'extreme programming dans votre activité.

Niveau :      
Résumé : logs apache en php

Un petit article pour tous ceux qui ont un site sur sur un hébergement mutualisé et qui n'ont pas accès aux logs apache. C'est assez gênant, on est limité dans le choix des outils de statistiques. On a quelques outils purement php peu efficaces, ou des outils web externes qui stockent les données pour vous (mais à qui appartiennent-elles ?).

Pour permettre d'utiliser les outils courants (awstats, awffull, webalizer ...), j'ai écrit un petit script php qui génère des logs au format apache. Il y a plusieurs inconvénients, tout d'abord, on ne loggue que les appels au php, pas les images, ni les binaires ... D'autre part, le code s'exécute nécessairement avant la fin du code lui-même. Donc il y a certaines informations dont on ne dispose pas, comme la taille totale générée, le code de retour ou des éléments des en-têtes renvoyés.

Ce script s'utilise très simplement, il suffit d'inclure le fichier et éventuellement de préciser le format à utiliser et le nom du fichier de log. En pratique l'entrée de log se fera à la fin de l'exécution du php, ce qui permet de mesurer le temps d'exécution. Le logformat s'écrit exactement sous la même forme que celui d'apache, les informations inconnues seront simplement remplacées par un '-' . Notez aussi que le %r (ligne contenant la requête intégrale) est généré à partir des informations disponibles.

A mettre dans votre code php :

$alog_logFormat = "%h %l %u %t \"%r\" %>s %b"; // optionnel
$alog_logFile   = "/tmp/access.log"; // optionnel
include_once("ApacheLog.php");

Attention, le nom du fichier est relatif au script appelant.

Vous trouverez le fichier en pièce attachée à cet article.

Niveau :      
Résumé : brainfuck ; beef

Aujourd'hui détente avec un langage de programmation que vous n'utiliserez que très rarement. Il s'agit de brainfuck.

Brainfuck est un langage Turing-complet, ce qui veut dire qu'il est possible d'écrire en brainfuck n'importe quelle fonction qu'il serait possible d'écrire avec un autre langage.

Brainfuck ne dispose que de 8 fonctions, toutes s'écrivent sur un seul caractère. ces fonctions manipulent un unique pointeur. Ce pointeur peut se déplacer d'unité en unité sur un tableau infini (ou presque) en mémoire. Sur ces cases mémoire, il peut incrémenter et décrémenter à volonté les valeurs. Tout doit se faire avec ces bases. Ainsi pour ajouter 5 à une case mémoire, vous devez incrémenter 5 fois le contenu de la case, et je ne vous parle pas des multiplications ;-)

Les fonctions de base sont :

  • > : Incrémenter le pointeur
  • < : Décrémenter le pointeur
  • + : Incrémenter l'octet pointé
  • - : Décrémenter l'octet pointé
  • . : Écrire l'octet pointé sur la sortie
  • , : Lire un octet pour le mettre dans la case pointée
  • [ : Saute au ] correspondant si le contenu du pointeur est nul (goto if 0)
  • ] : Saute au [ correspondant si le contenu du pointeur est non nul (goto if !0)

Vous trouverez quelques programmes d'exemple en brainfuck sur le site de brainfuck

Pour développer en brainfuck, il vous faut un interpréteur. Vous le trouverez sous le nom de beef chez debian.

Malgré le peu de lisibilité des programmes obtenus, vous constaterez que c'est le langage dans lequel cat s'écrit de la façon la plus courte possible

,[.,]

Et maintenant, pour vous amuser, essayez de coder l'addition, la multiplication voire la division (sans regarder sur wikipedia s'il vous plait !). Ça vous ouvrira un peu l'esprit et vous changera du sudoku.

Après être devenu un pro, passez au calcul en virgule flottante.

Niveau :      
Résumé : cmsg_type = SCM_RIGHTS

Ne vous-êtes vous jamais posé la question de comment fait apache pour lancer des processus à l'avance tout en continuant de recevoir les connexions sur le processus père ? Les cours de programmation sur les sockets ne proposent que deux façons de coder un serveur. Vous écoutez (listen) sur un processus et à chaque nouvelle connexion d'un client (accept) vous obtenez une nouvelle socket. Ensuite, soit vous créez (fork) un nouveau processus qui communiquera uniquement avec ce client sur la socket ainsi créée, soit vous gérez toutes les sockets dans le même processus (merci select).

Bien, je vous propose de continuer à faire gérer les connexions par des processus fils, tout en lançant ceux-ci à l'avance. Pour cela, il suffit de faire en sorte qu'un processus puisse communiquer un file descriptor (ou une socket) à un autre. La solution est relativement simple si on est habitué à la gestion des sockets en C. Elle se trouve dans man 7 PF_UNIX à la section "Ancillary Messages".

Les socket unix permettent de communiquer certaines informations non communicables par le réseau. Dans notre cas, nous allons communiquer des file descriptor. Il nous faut donc une socket unix pour communiquer entre les différents processus. Vous trouverez en attaché le source pour deux programme. Le premier (listener) écoute les connexions sur le port tcp 2000, dès qu'une connexion est ouverte, il communique le fd résultant au second (worker) qui va simplement afficher un petit message. Notez que la communication passe par une socket unix (/tmp/transmit) et que listener doit être lancé en premier.

Voici un aperçu des fonctions intéressantes :

Transmission du file descriptor

void fd_transmit(int unixfd, int fd)
{
 [ ... ]
        /* on transmet le minimum de données (1 octet) 
         * pour que le message soit pris en compte */
        iov.iov_base    = &one_char;
        iov.iov_len     = 1;
        msg.msg_iov     = &iov;
        msg.msg_iovlen  = 1;

        /* on spéficie un header spécial précisant qu'on transmet une socket */
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_len   = CONTROLLEN;
        cmsg->cmsg_type  = SCM_RIGHTS;
        *(int *)CMSG_DATA(cmsg) = fd;

        msg.msg_control = cmsg;
        msg.msg_controllen = CONTROLLEN;

        /* ce type de message doit être envoyé avec sendmsg */
        rval = sendmsg(unixfd, &msg, 0);
[ ... ]
}

continue reading...

Niveau :      
Résumé : bibliothèque

Une bibliothèque (et non pas une librairie) est un fichier qui contient des fonctions que vous allez réutiliser dans plusieurs programmes. Il en existe de plusieurs sortes.

Les bibliothèques statiques sont des fichiers qui seront inclus à votre programme lors de la compilation. Ce qui grossira d'autant plus le binaire résultant. Avantage, il sera indépendant de l'installation de bibliothèques par l'utilisateur. Pratique quand vous voulez faire installer un logiciel par tout le monde (plus de problème de version).

Les bibliothèques dynamiques sont des fichiers qui seront chargés automatiquement par le système lors du lancement de votre programme. Elles permettent de l'alléger de beaucoup, mais rendent ce dernier dépendant de l'installation du système. Ce sont bien évidemment les plus utilisées.

Comment se différencient-elles ? Petit exemple en C.
lib.c :

#include <stdio .h>
void hello()
{
	printf("Hello world !\n");
}

main.c :

int main()
{
	hello();
	return 0;
}

On va découper la compilation en étapes élémentaires pour bien la comprendre :


continue reading...

Niveau :      

Savoir relire le code d'un autre est indispensable. Et ce pour plusieurs raisons, soit parce que vous voulez vous mettre à travailler sur un projet existant, soit parce que vous voulez simplement fournir un patch pour un code qu'on vous a fourni (probablement open source). Nous allons donc voir de quoi il s'agit, puis mettre en pratique sur apache.

Pour vous lancer dans l'aventure, il peut vous être utile de savoir utiliser ctags ou etags. De plus, connaître des techniques de lecture rapide vous permettra d'aller plus vite.

Plantons le décor, nous voulons faire un patch à apache 2.2 pour permettre d'ajouter dans les logs les durées de récupération de données par mod_proxy. Récupérons le code source et partons de ce qu'on sait (et n'oublions pas de le dupliquer pour nous simplifier la création de patch par la suite).

$ cp -a apache2-2.2.6 apache2-2.2.6.old
$ cd apache2-2.2.6
$ find . -type f | grep "c$" | xargs ctags

On sait qu'actuellement, grâce à mod_log_config, on peut logguer les durées individuelles des requêtes. Le code source semble divisé logiquement, nous allons donc lire modules/loggers/mod_log_config.c. Parcourons-le rapidement, on constate un certain nombre de fonctions log_*, probablement pour écrire dans les logs. Étant donné la façon dont elles sont appelées, il doit y avoir une référence dans un tableau quelque part. On la trouve en fin de fichier, ainsi que la fonction qui nous intéresse : log_request_duration_microseconds.

Lisons-la :

$ vi -t log_request_duration_microseconds

Deux choses intéressantes :

  • apr_time_now() -> au vu du nom et de la précision, ça doit donner la date et l'heure en microsecondes. Donc on sait comment on va calculer notre durée.
  • r->request_time et request_rec *r -> on en déduit qu'il existe une structure par requête dans laquelle on pourra stocker les dates de début et de fin.

Cherchons maintenant où appeler ces fonctions pour calculer les durées. Nous allons donc lire ./modules/proxy/mod_proxy.c. On le parcourt en largeur et on constate que :

  • C'est plutôt bien commenté
  • Les noms de fonction ont un sens
  • La majorité des fonctions s'appelle proxy_*

continue reading...