Cahier des charges

Accueil Remonter Suivante

Bienvenue dans ce qui est le début de la présentation de notre projet : ExploreurMaker, qui consiste en la réalisation d'un exploreur de fichiers sur les systèmes Unix/Linux.

Nous vous rappelons que notre groupe est composé de quatre individus Sébastien Deckmyn (qui est le chef de projet), Nicolas Le Coz, Benoît Merouze et enfin Julien Perruche codant tous les quatres sur des systèmes Unix/Linux.

Ceci étant, pour notre première soutenance, et donc notre premier rapport, nous avons essayé qu'il soit le plus clair et concis possible. Nous y présentons les différents aspects de la réalisation de ce projet, c'est-à-dire les différentes tâches faites et à faire par la suite. Voici donc un plan de ce rapport :

  • Modification du cahier des charges
  • Nos choix (pour La chartre graphique et site WEB)
  • La conception des différents modules
  • La conclusion pour faire la transition entre la première et la deuxième soutenance

Mais avant d'avoir pu commencer à effectuer quoi que ce soit nous avons du nous familiarisé avec l'environnement Unix c'est-à-dire à apprendre à utiliser les commandes rudimentaires d'un shell (Korn, Bourne, Bash...), le langage C (la syntaxe, la compilation...) mais aussi pour la présentation du travail et pour apprendre à faire des IHM il fallait faire des interfaces avec la libraire GTK+.

A la demande de monsieurs Boullay nous avons revu le planning individuel qui était déséquilibré.

Nous allons aussi subdiviser le module de gestion des applications parce qu'il nous semble le plus long et le plus ardu que tous les autres modules.

En effet on peut distinger dans ce module trois grandes parties qui sont rélativement indépendantes.

  • Module pour la communication entre Exploreur Makeur et les autres programmes
  • Module pour le recencement des différentes types d'appications
  • Module pour la configuration et recherche d'applications

Le premier module consiste à faire communiquer l'explorateur avec les logiciels qui utilisera pour exploiter un fichier (lancer l'application, transmission des données entre l'explorateur et l'application du fichiers, fermeture de l'application...).

Le deuxième module quant à lui classera les différentes types d'appplications et assurera un contrôle des données suivant l'application pour éviter par exemple d'ouvrir une image avec un lecteur sonore.

Et enfin le dernier module doit permettre à l'utilisateur de choisir l'application suivant le fichier (utilisera le deuxième module) et de faire le lien entre les deux autres modules. C'est à dire qui doit gérer le signal de lancement en connaissant les données du deuxième module.

Voici la mise à jour pour la première soutenance :

Soutenance 1 Soutenance 2 Soutenance 3 S finale
Deckmyn Implémentation Apprentissage Gestion Gestion des
Sébastien base de données de GTK+ d'installation packages
(classer fichiers) standards
IHM de la BD Gestion des
SGBD (gestion extensions Finalisation
base de donnée)
Doc
et aide
Le Coz Apprentissage Implémentation Implémentation Finalisation
Nicolas de GTK+ du module pour du module pour
les informations la gestion des Doc
Définition de la sur les fichiers fichiers (copie...) et aide
chartre graphique
Finalisation de Finalisation
Interface de base l'interface de l'IHM
Mérouze Apprentissage Gestion des Gestion des Finalisation
Benoît de GTK+ applications applications
Doc
Editeur Site WEB Site WEB et aide
Pérruche Apprentissage Algorithmes pour Module pour la Finalisation
Julien de GTK+ recherche fichiers configuration
et recherche Doc
Réflexion algo Implémentaion d'application et aide
de recherche module recherche
Implémentation
pattern
matching

Une chartre graphique est un ensemble de règles afin de définir pour les concepteurs mais surtout pour l'utilisateur des concepts pour avoir une interface intuitive et claire.

Nous définissons une chartre graphique afin d'optimiser la prise en main par l'utilisateur du logiciel mais aussi pour faciliter l'apprentissage des fonctions même les plus ardues du programme sans nécessité de la part de l'utilisateur de quelconques connaissances informatiques mais seulement son intuition et ses facultés d'observation.

De plus cette chartre permet aux développeurs d'uniformiser leurs IHM (Interface Homme Machine). C'est-à-dire que si dans ce projet on uniformise l'interface, alors le code de l'IHM s'en trouvera aussi uniformiser ce qui optimisera la compréhension et la prise en main du code entre développeurs. Ainsi le programmeur d'IHM au bout d'un certain temps utilisera les mêmes objets et les mêmes méthodes. La qualité de l'interface dépendant de sa clarté et donc de la simplicité, chaque développeur doit s'efforcer d'utilisé le moins d'objets possibles (dans notre cas d'objets GTK) afin d'obtenir la clarté graphique espérée.

Nous allons juste énumérer les grandes sections des règles afin de ne pas allourdir le rapport.

  • Les symbôles
  • Les options
  • Les actions

Ces règles seront consultables sur notre internet, sur l'aide du programme et sur la documentation du logiciels.

Dans notre logiciel nous mettrons une rubrique sous forme d'aide (rubrique "chartre graphique") afin que l'utilisateur ait tout moment puisse la consulter. Cependant l'utilisateur n'a pas besion de connaitre toute la chartre car il y a une partie qui attrait du code en langage C de l'IHM.

Nous allons successivement présenter ce que chaque développeur a fait et expliqué leurs méthodes et raisonnements pour la résolution de leurs problèmes, sur la période de décembre 2001 à janvier 2002. Avant de commencer ces modules il a fallu pour Sébastien , Nicolas, Julien et Benoit maitriser le langage C, ou C++ pour certains, et aussi avoir des notions en GTK+. Sébastien pour cette première partie, c'est occupé de la gestion des packages, Nicolas d'un exploreur de base, Julien de la recherche et enfin Benoit d'un éditeur de texte mais aussi du site WEB.

Le Gestionnaire de packages est un des modules d'Explorer Maker. Il fonctionne de la même manière qu'un explorateur de fichiers. La différence est qu'il classe les fichiers différemment. Au lieu de partir de la racine du disque et de lire séquentiellement les entrées de chaque répertoire, le gestionnaire de packages regroupe les fichiers par applications. A la racine, on a la liste des packages installés sur la machine. Lorsque l'on développe chacun d'eux, on obtient la liste des fichiers, ainsi que l'arborescence des répertoires, constituant chaque packages. L'utilisateur peut alors gérer ses packages de la même manière qu'il gère les autres fichiers.

Le gestionnaire de packages regroupe les fichiers par packages. Cela nécessite donc une base de données afin de classer ces fichiers. Cette base de données est essentiellement constituées de listes (pour les fichiers) et d'arbres généraux (pour les répertoires) . Avant de commencer à implémenter directement la base de donnée, j'ai commencé par programmer une bibliothèque contenant les types listes et arbres généraux. J'ai ensuite utilisé les fonctions de cette bibliothèque pour développer la base de données. Je vais donc commencer par présenter cette bibliothèque.

J'ai commencé à développer cette bibliothèque au début de l'année (scolaire). Au départ elle n'avait rien a voir avec le projet. Le but est de regrouper tous les types de données standards que l'on utilise fréquemment dans les projets et que l'on voit en cours. Ce sont les listes, les tableaux, les arbres (binaire, B-arbres, généraux) etc. J'ai décidé de l'inclure dans le projet car nous verrons qu'elle nous est très utile. D'un point de vue général, cette bibliothèque est comparable à la Glib. Son implémentation est toutefois différente.

Tout d'abord, j'ai utilisé le langage C++. Cela pour deux raisons :

  • c'est un langage orienté objet, or mon but est de crée des objets listes, arbres ...
  • le C++ est plus strict tout en restant très proche du C.

Ensuite, par rapport à la Glib les objets sont implémentés de manière à ce que le stockage des données utilise un peu moins de mémoire soit plus facile d'accès pour l'utilisateur.

Bien évidemment, les objets qui sont crées sont destinés à recevoir n'importe quel type de données. Il n'est pas question de créer par exemple un type arbre qui se limite à stocker que des entiers. Tout comme la Glib, les objets sont capable de stoker n'importe quoi. Le but est de créer des objets qui fonctionnent dans le cas général et qui peuvent être réutilisés dans n'importe quel autre projet. Cette bibliothèque sera donc utilisée de manière dynamique.

Je vais maintenant expliquer plus en détail le fonctionnement de ces objets et leur différences avec ceux de la Glib. Dans les explications suivantes, je prendrai l'exemple d'une liste chaînée.

Voici le début de la déclaration de l'objet liste simplement chaînée. Je ne donne pas les autres déclarations (liste doublement chaînée et arbre général) étant donnée qu'elles sont similaires à celle de la liste simplement chaînée.

typedef struct T_List* P_List;
struct T_List
{
  private:
    dword datasize;
    Pbyte first, last;
    StdFunc destructor;
  public:
    void Init ( dword size, StdFunc destruct );
    void Reset ( dword size, StdFunc destruct );
    Pointer AddFirst ( Pointer data );
    Pointer AddLast ( Pointer data );
    ...
};

Tous les champs des objets sont en mode private pour plus de sécurité. Ces données sont importantes car elles sont utilisés à chaque fois que l'on appelle une méthode de l'objet.

Le champ datasize existe dans chaque objet. Il contient la taille des données que l'utilisateur souhaite enregistrer. Par exemple si l'utilisateur veux enregistrer des nombres de type float, il initialise la liste pour des données de 4 octets. Cette initialisation ne peut s'effectuer que lorsque l'on initialise ou que l'on remet la liste à zéro (méthodes Init et Reset). Les objets ne sont en effet pas prévus pour stocker les éléments de taille variable. Dans le cas des chaînes de caractères par exemple, on utilisera une liste chaînée de pointeurs sur chaîne de caractères.

La méthode pour stoker les données en mémoire est un peu différente de celle de la Glib. D'habitude, pour déclarer une liste chaînée, on procède de la manière suivante. Voici la déclaration de la GSList.

struct _GSList
{
  gpointer data;
  GSList *next;
};

On a donc un élément de base de la liste qui est déclaré. Cette structure contient un pointeur vers les données utilisateur et un pointeur vers l'élément suivant de la liste.

Dans ma bibliothèque, les objets ne possèdent pas un type élément de base prédéfini. En fait, c'est comme la structure précédente de la GsList sauf que au lieu d'avoir un pointeur vers les données utilisateurs, on stocke directement les données utilisateur dans cette structure. Cette structure a donc une taille variable en fonction des données que l'on veut stocker. C'est pour cette raison qu'on ne peut pas déclarer ce type de structure (enfin si on peut ca m'intéresse mais pour l'instant j'ai pas trouvé). On a donc une espèce de structure virtuelle dans laquelle l'adresse des champs est recalculée en fonction de la valeur contenue dans datasize.

Ce type d'implémentation possède trois avantages.

  • Comme on a plus de pointeur intermédiaire entre la structure et les données utilisateur, on gagne 4 octets par élément de liste (pour un architecture 32 bits).
  • On utilise le même pointeur pour accéder à la fois aux données utilisateur et la l'élément de la liste. (les données utilisateur étant placées au début de la structure).
  • Les allocations de mémoire donc gérées automatiquement pour les données utilisateur.

Contrairement aux listes chaînées de la Glib qui ne se contentent que de stocker les adresses des données, les listes de ma bibliothèque stockent réellement les données de l'utilisateur.

On peut ajouter des données de deux manières différentes selon le cas de figure. Si la donnée à stocker existe déjà en mémoire. On utilise la méthode suivante (ajout au début de la liste par exemple) :

int data;

data = 5;
list.AddFirst( &data );

Cette fonction va crée un nouvel élément de la liste et va copier non pas l'adresse de data mais son contenu, c'est à dire ici 5. (On aura préalable initialisée la liste pour des données de sizeof(int) octets).

Maintenant si on veut directement enregistrer la valeur 5, au lieu de donner l'adresse de data, on donne l'adresse NULL.

int *p;

p = list.AddFirst( NULL );
*p = 5;

la fonction crée également un nouvel élément de liste mais n'enregistre rien dans la zone des données utilisateur. Comme toutes les fonctions d'ajout de chaque objet renvoient un pointeur sur la nouvelle zone de mémoire allouée, l'utilisateur peut ensuite ajouter ses données dans cette zone. Dans l'exemple précédent, j'ai utilisé un type int comme données mais on peut enregistre n'importe quoi du moment que l'on initialise la taille des élément à stocker dans la liste.

Dans le cas de la suppression des élément, j'ai rajouté dans chaque objet un champ destructeur qui est un pointeur de fonction. Cette fonction que l'utilisateur peut définir est appelée à chaque fois que l'on supprime un élément. Par exemple si l'utilisateur stocke dans la liste des pointeurs qui pointent sur d'autres zones de mémoire allouées, il faut d'abord libérer ces zones avant de supprimer l'élément de la liste. C'est le rôle de cette fonction. Elle est propre à l'utilisateur car seul l'utilisateur connaît les types de données qu'il manipule. Dans le même cas avec la Glib, c'est à l'utilisateur de se débrouiller pour libérer lui-même ces zones de mémoires ce qui dans certains cas n'est pas pratique. C'est pour cette raison que j'ai introduit cette fonction qui libère ces zones de mémoire automatiquement.

Voilà en résumé le fonctionnement des objets de ma bibliothèque. J'essaie de faire en sorte que tous les objets aient des points communs de manière à faciliter leur utilisation. Par exemple, tous les objets possèdent les champs datasize et le pointeur de fonction destructor. Ils possèdent également tous les méthodes Init et Reset.

J'ai également crée parallèlement à cela un programme de test qui permet manipuler ces objets et de simuler un maximum de cas pour vérifier qu'il n'y pas d'erreurs dans les algorithmes. Il est nécessaire de s'assurer qu'il n'y a pas de bugs car comme je l'ai expliqué au début, le but est de réutiliser cet bibliothèque dans n'importe quel autre projet, à commencer par celui de cette année.

La base de donnée a pour but de classer les fichiers en fonction de leur appartenance à tel ou tel package. En mémoire, cette base est représentée pas des listes et des arbres généraux. Ces listes et arbres ont été préalablement implémentés dans une bibliothèque présentés dans la partie précédente. Cette bibliothèque sera utilisé de manière dynamique.

Pour commencer j'ai crée un type de base qui est T_Package. C'est une structure qui regroupe toutes les informations concernant un package ainsi qu'un arbre général. L'arbre générale permet de reconstituer l'arborescence du package. Chaque noeud de l'arbre contient le nom du répertoire et une liste. Cette liste contient les noms des fichiers appartenant au package et contenu dans le répertoire. Les fils du noeud correspondent aux sous répertoires. Pour finir, la base de donnée est une liste de T_Package.

D'un point de vue général, la structure est assez complexe à programmer (c'est une liste d'arbres dont chaque noeud contient une liste). C'est pour cette raison que j'ai commencé par implémenter d'abord séparément les types listes et arbres généraux. Ensuite, une fois que la bibliothèque est terminée, il suffit d'assembler ces types les uns avec les autres. C'est beaucoup plus facile et cela produit des algorithmes plus structurés, donc plus clairs.

J'ai ensuite implémenté les fonctions Ajouter et Supprimer un fichier dans un T_Package. Ces fonctions prennent en paramètre le nom complet d'un fichier. Puis elles parcourent l'arborescence du package puis la liste de fichier correspondant. Elle créent ou suppriment des noeuds au passage si besoin. En fait, là encore je réutilise les fonctions de ma bibliothèque.

L'avantage de cette structure en arborescence est que les opérations d'ajout, recherche et suppression s'effectuent rapidement. En effet comme l'arbre sert à trier les fichiers par répertoires, la recherche d'un fichier s'effectue plus rapidement. L'inconvénient est qu'elle est plus complexe à implémenter et utilise plus de mémoire. Etant donné que l'on travaille sur des machines ayant beaucoup de mémoire (comparé à ce que demande notre application) j'ai choisit de privilégier la vitesse de traitement des données.

Le système NetBSD possède déjà une base de donnée contenant les noms des fichiers de chaque packages. Cette base de données est enregistrée sur le disque dur sous forme de fichiers. Les accès intensifs aux fichiers ralentissent considérablement les applications. Par conséquent, il est nécessaire de charger ces informations en mémoires. J'ai donc programmé des fonctions qui ont pour rôle de lire ces fichiers et d'interpréter leur contenu afin de reconstituer la base de donnée en mémoire. C'est la base de données en mémoire qui sera affichée puis utilisée pour effectuer par la suite des modifications.

Je vais vous présenter successivement les différentes parties composant l'exploreur en respectant l'ordre chronologique de l'implèmentation.

Ce module consiste à obtenir les différentes informations possibles que l'on peut savoir sur un fichier ce qui conditionnera, par la suite, son utilisation.

Grâce à ce module, l'utilisateur pourra savoir différentes choses sur un certain fichier ou répertoire (qui est aussi traité comme un certain type de fichier) comme le nom, le chemin, les dates de dernière modification, d'accès, les droits, la taille... Pour cela nous avons utilisé la librairie stat.c qui nous fournit des fonctions pour initialiser un pointeur sur une structure contenant des informations attacher à un nom de fichier. Ainsi avec cette structure on peut déterminer sur ce fichier :

  • nom
  • chemin
  • permission d'accès et type du fichier
  • nombre de liens physiques
  • programme ou fichier propriétaire
  • numéro de périphériques (i-noeuds)
  • taille en octets
  • date du dernier accès
  • date du dernier changement de status (droits, propriètaire)
  • date de dernière modification
  • taille de bloc en octets la mieux adaptée pour les entrée-sorties sur ce fichier

Le nom et le chemin sont indispensables pour ouvrir et traiter ce fichier (et même pour intialiser la struture fournis par la librairie stat.c)

Les permissions d'accès et les types du fichier est une des nombreuses caractèristiques d'Unix. En effet, Unix est un environnement multi-utilisateurs donc on doit donc faire cohabiter les données de groupes d'utilisateurs, d'utilisateurs au sein d'un groupe etc... Ainsi sur un fichier on peut autoriser à l'utilisateur, au même groupe que l'utilisateur et aux autres le droit de lecture, le droit d'écriture et le droit d'éxecution. En fait la struture de stat.c renvoit un champs de bits avec ces informations (et d'autres que l'on verra ensuite). Donc pour les droits on a besion de 9 bits. Les poids forts donnent le mask pour les droits du propriètaire, les poids moyens correspondent aux droits du groupe et enfin les poids faibles les droits du reste des utilisateurs. Donc chaque ensemble est codé sur 3 bits avec comme poids fort le droit de lecture; le poids moyen, le droit d'écriture et le poids faible correspond au droit d'éxécution.

Les champs de bits représentant les droits

Sous Unix il existe des fichiers qui sont des liens physiques, c'est-à-dire que c'est un fichier qui ne contient que comme informations le chemin et le nom d'un autre fichier. Un fichier pouvant avoir plusieurs noms on compte le nombre de noms et ce champs conserve ce nombre de liens sur l'i-noeud.

On peut aussi déterminer à quel programme ou quel fichier appartient le fichier grâce à son numéro UID1 car ce fichier contient l'UID du fichier porpriètaire.

Le numéro d'i-noeud est un membre défini pas la norme POSIX2. Ce champs permet de définir de manière unique le fichier. La durée de vie de ce numéro est comprise entre le démarrage et l'arrêt de la machine parce que, d'un tel cycle à l'autre, ce numéro peut être différent suivant la lecture des disques ou encore la création de nouveaux fichiers.

On peut aussi obtenir la taille du fichier en octet sans l'ouvrir autrement on modifirait sans aucune raison la date de dernier accès à ce fichier.

On peut aussi savoir différentes informations dans le temps sur le fichier. Ainsi on peut déterminer à la millisecondes prêt la dernière modifcation du fichier, le dernier accès à ce fichier mais aussi la dernière modification de propriètaires et de droits.

Et enfin on peut déterminer un multiple de flux optimal pour le traitement du fichier. Ceci n'interressera en rien l'utilisateur débutant mais dès qu'on utilisera ce fichier comme stock de données cela devient très intéressant. En autre ce champ nous sera très utile lorque que l'on implémentera le module pour la gestion des fichiers (copie, suppression) car le connaissant on pourra optimiser le traitement et la communication par tubes sur ce fichier.

Le listage de fichier consiste en fait à recenser tous les fichiers d'un répertoire et d'en obtenir toutes les informations décrites ci-dessus. Il suffit donc de connecter un flux sur le répertoire afin d'obtenir le contenu des fichiers, on réalise cette prouesse technologique grace aux fonctions fournies par la libraire standard du langage C dirent.h. Ensuite sur ces fichiers, on applique le module implémenté précedement, ainsi à la fin de la lecture on obtiendra toutes les informations nécessaires sur ce réperoire comme :

  • les droits d'accès sur ce réperoire
  • le nombre de fichiers et de répertoires qu'il contient
  • une liste chaine représentant la liste de fichiers du répertoire et un noeud de cette liste correspond aux informations d'un fichier.

Notre programme possède plusieurs parties :

  • Un menu de gestion de l'application et de ses options
  • Une arborescence pour parcourir les répertoires
  • Un listage de fichiers pour observer et bientôt exécuter ou regarder dans les entrailles du fichier

Je vais vous présenter ces différentes parties selon leurs interactions (signaux émis, signaux reçus)

La librairie GTK+ fournit des méthodes pour afficher un arbre sous forme d'arborescence avec la possibilité d'ouvrir et de fermer ses noeuds. Mais aussi de rattacher des signaux à l'arbre lorsqu'un noeud est sélectionné ou encore quand l'arborescence est dépliée ou repliée.

Dans notre cas on affiche d'abord la racine de l'arborescence et à chaque extension de l'arborescence on ajoute un niveau et on teste chaques nouveaux sous répertoires pour savoir si lui aussi il a des répertoires. Si il a au moins un répertoires (différent de "." et de "..") on arrête le test et on ajoute un noeud marqueur sinon on ajoute rien et c'est une feuille (il n'a pas de carré pour le dépliement ou repliement). Avec cette méthode, on optimise la mise à jour du l'arborescence, cela evite de faire trop d'itérations pour ajouter des noeuds à l'arbre. Pour le marqueur qui en dans un noeud, dès que ce noeud est ouvert il est suprimé et on ajoute alors les sous répertoires. Ce marqueur nous donne deux indices :

  • savoir si ce noeud a déjà été ouvert
  • permettre le parcours dans ce noeud (autrement si il n'était pas là le noeud serait considérer comme une feuille)

L'affchage de l'arborescence

Comme GTK possède son gestionnaire de signaux gtk_main() il suffit alors, pour faire interagir les actions de l'utilisateur sur l'interface, de connecter des signaux sur des objets GTK. Ainsi par exemple pour l'arborescence on a connecté un signal lorsque l'arbre est déplié et dès que ce signal est émis on lance fonction de rappel avec comme paramètre un pointeur sur l'arbre, le noeud déplié et des données. Ainsi on peut facilement déterminer quel noeud a été déplié, extraire son nom et alors mettre à jour l'arborescence c'est à dire la branche ouverte. Pour la selection c'est exactement la même chose sauf que pour les données on a un pointeur sur la liste de fichier, la boite de saisie et le label au dessus du listage afin de tous les mettre à jour. GTK nous fournit les outils pour le faire avec un appel a une fonction.

Une fois l'implémentation des modules décrite au dessus l'affichage du listage de fichiers n'est plus qu'une affaire de GTK+. Ainsi on affiche tous les fichiers d'un répertoire, sa taille, ses différentes dates, des permissions... Dans un futur relativement proche on pourra aussi modifié de types de listage (option qui sera choisi dans le menu de configuration) pour le mettre sous forme d'icone ou de liste comme décris dans la chartre graphique.

L'affichage du listage de fichiers

Nous avons réalisé un menu afin de faciliter à l'utilisateur la gestion de l'application ou de la création d'un nouvelle exploreur, nous développerons plus en détail dans la partie des processus. Mais dès lors, on peut dire qu'avec ce menu l'utilisateur peut tuer la fenêtre sans le shell et sans le gestionnaire de fenêtre. Il pourra aussi bientôt configurer toutes les options et utilisera tous les modules à partir de ce menu, comme la recherche de fichier, de gestionnaire de packages, les selections, les copies...

Voici ExplorerMaker avec ses différentes parties

Sous Unix, toute tâche qui est en cours d'éxécution est représentée par un processus. Un processus est une entité comportant à la fois des données et du code. C'est une sorte de programme en cours d'éxécution. On distingue dans le système un processus des autres grâce à son numéro d'identification PID3. Il existe une hiéarchie entre les processus car tous les processus du système ont un père ou ont eut un père (processus zombie) sauf bien sûr le processus init. Les processus sont aussi organisés en groupe et comme dans tout les groupes il y a un leader.

Lorsqu'on utilise ExplorerMaker, il se peut que l'on a besoin d'un autre programme. Ainsi on crée un processus fils qui a pour père le processus créé par le shell pour lancer ExplorerMaker. Pour cela on utilise les appels sytème fork() et pour le lancement d'un programme ou d'une commande, l'appel de l'une des fonctions exec(). Ainsi ces deux processus peuvent facilement s'influencer :

  • Exploreur Maker peut fermer les sous programmes lancer (en tuant le processus fils) car lors de la création du processus fork() retourne au père le PID du fils créé
  • Si l'Exploreur Maker est fermé on peut soit tué tous les processus fils devenus processus zombie ou les faire adopter par le processus init suivant la gestion du signal émis (en fait lorsque le procesus père est tué il envoit un signal à tous ses fils)

Comme nous l'avons dis avant, notre explorateur est référencié dans le système par un processus. Dès qu'il lancera un nouveau programme pour traiter le fichier exécuté à partir de l'exploreur, et il n'a plus aucun lien avec le programme si ce n'est le PID du processus exécutant le code du programme. Donc l'explorateur connaissant ce PID, il sait à qui envoyer les signaux et les données. Le transfert de données se fait que dans un sens : de l'explorateur au programme. C'est pourquoi le tube de communication s'avère le meilleur moyen pour échanger des informations entre l'explorateur et le programme (lecture et écrite de données unidirectionnel).

Les threads repondent à la portabilité de la norme POSIX 1.c ce qui nous conforte dans notre choix des threads. Le mot thread est anglais signifiant "fil d'éxecution". C'est en fait un déroulement particulier de certaines parties de code du programme en cours de progression. Ces entités sont légérement comparable aux processus mais elles réclament moins de ressources. De plus du point de vue du noyau les threads sont invisibles contrairement aux processus, donc un thread est une partie de code interne aux programmes ce ui est plus sércurisant (même adressage de données). De plus la communication entre threads est plus simplistes qu'entres processus. Cependant l'accès aux mêmes données de plusieurs threads demande au programmeur de synchroniser les actions des threads pour ne pas corrompre les données du programme. Ainsi avec les threads on peut avoir une application qui effectura plusieurs tâches à la fois (comme une copie de fichiers et une explorations dans un répertoire).

L'ordonnacement des threads correspont à l'ordonnance des processus. On trouve trois algorithmes pour l'ordonnacement :

  • algorithme FIFO (First In First Out)
  • algortihme RR (Round Robin ou plus connu sous le nom de tourniquet)
  • algorithme OTHER

Le premier algorithme est celui de la file d'attente. Le thread ayant la gentillesse la moins élévée est executé et renvoyé à la fin de la file correspondant à sa gnetilesse et au suivant idem et ainsi de suite. En suite l'ordonnaceur passe à la file de priorité moindre. Mais dès qu'un thread de priorité plus élévée est prêt alors il est éxécuté. Ce mécanisme se répéte jusqu'à la fin du programme. Cet ordonnacement est le plus violent et le plus égoiste (c'est à dire que les threads ne sont pas équitablement réparti). C'est le plus fort qui a toujours raisons.

Le deuxième algorithme reprend le même principe que le premier à l'exception prêt qu'un laps de temps d'éxécution est défini suivant la priorité. La seule différence réside donc uniquement dans le cas où plusieurs threads sont simultanément prêts avec la même priorité et si aucun thread de plus haute priorité n'est prêt. Avec le premier algo, le premier thread qui arrive s'éxécute jusqu'à ce qu'il s'arrête de lui même. Mais avec l'ordonnance RR et un autre thread peut arrêter le premier au bout d'un certain laps de temps.

Le troisième alogrithme n'est pas vraiment défini pas POSIX 1.c donc pour le moment on parlera pas.²

On veut créer un logiciel s'utilisant sur des systèmes Unix/Linux mais aucun fournisseur en particulier. Il faut qu'il fonctionne à l'identique sur plusieurs sytèmes i686, i386, alpha, sun. Tout d'abord on doit utiliser des libraires qui sont portables sur tous ces types de stations de travail. C'est pourquoi ces librairies répondent aux normes ANSI ou pour la gestion des processus plus évoluer à la norme POSIX (1, 1.b 1.c).

Ainsi pour l'exploreur on utilisera c'est différentes librairies :

  • Les librairies standards comme stdio, stat, stdlib, errno, time... assurent une portabalité définie pour la norme ANSI. C'est à dire que l'implèmentation et le fonctionnement du programme est indépendant de la station de travail. Par exemple, toutes les machines n'ont pas la même architecture et donc rien ne nous garantir que sur un système un int sera codé sur 4 octets et sur un autre sur 8 octets. Donc le fonctionnement du programme ne doit pas dépendre de la taille d'un int sinon il n'est pas portable.
  • Il existe deux normes pour les libraires sur les processus et les signaux. Il y a une partie de ces libraries qui sont définies par la norme ANSI C, comme par exemple la définition des signaux (SIGARBRT, SIGCHLD), ou pour la manipulation des processus fork()) (création d'un processus), signal() : signal.h ) et une autre partie plus performante, defini, elle, par les normes Posix.1 et Posix.1b. Ainsi la norme Posix.1 ajoute d'autres fonctionnalité comme des flags pour la gestion de certains signaux, la configurations des ensembles de signaux, sig...set()). Et la norme Posix.1b nous fournis des outils et des signaux pour le développement d'application en temps-réel (signaux temps-réel) mais dans notre cas il ne nous intéresse pas du tout.
  • Et enfin pour la gestion des threads il existent la norme Posix.1c, il en existe d'autres mais qui ne sont pas compatibles Posix, donc on utilisera les threads parfois appellés Pthread.

Ainsi en utilisant ces libraries et en codant correctant (ne pas faires des expressions dont l'évaluation varie d'un système à un autre) nous garantient une portabilité sur la plupart des stations Unix, il n'y a plus qu'à recomplier le programme pour son utilisation.

Le but de cet éditeur est de pouvoir modifier du texte facilement. Il se devait donc d'être des plus simples. En fait, il s'agissait de faire un programme très proche de l'éditeur standard Microsoft\textregistered Bloc-notes des différentes versions de MS Windows.

Ainsi, l'utilisateur qui installera notre explorateur de fichiers aura également cet éditeur d'installé, et pourra alors ouvrir des fichiers à partir de l'eplorateur, même s'il n'a pas installé un des éditeurs standard de Linux (Emacs par exemple).

Comme le reste du projet, l'interface de l'éditeur est faite avec GTK. L'élément principal du programme est un GtkText, c'est à dire un objet Texte en GTK. Et les fonctions principales sont la sauvegarde et l'ouverture de fichiers.

Le but de cet éditeur était également de se familiarisé à la fois avec le langage C et le GTK. La conception de menus a été instructive, ainsi que les quelques fonctions qui ont été implémentée.

Pour la petite histoire, lors de l'implémentation de l'éditeur, tous les fichiers du programme ont été supprimés par erreur. A ce moment, les menus venaient d'être terminés. Cette petite erreur a coûté très cher en temps, nous n'avons pas trouvé le moyen de récupérer les fichiers, et il a fallut 2 ou 3 jours pour refaire ce qui avait été fait depuis le début, n'ayant plus les documents qui avaient servis auparavant. Mais la nouvelle version ainsi refaite est alors bien mieux faite, ayant alors acquis de bonnes bases en GTK.

Figure
Voici l'éditeur pour voir les entrailles des fichiers

Les GtkTexts font partie des objets les plus puissants que propose le GTK. Ils permettent d'avoir simplement une entrée de texte multiligne complète. Ils peuvent afficher du texte en couleur ou dans une police définie. De plus un texte édité avec un GtkText peut comporter jusqu'à quatre milliards de caractères !

Un GtkText comporte de plus beaucoup de racourcis clavier, comparables à ceux de Emacs. Mais surtout la plupart des touches utilisées sous Bloc-notes se retrouvent dans un GtkText, ainsi la touche <Fin> envoie le curseur à la fin de la ligne et non à la fin du document. On retrouve par exemple aussi les racourcis classiques pour couper, copier et coller du texte de Windows : Ctrl+X, Ctrl+C et Ctrl+V.

Dans la hiérarchie du GTK, l'objet GtkText hérite de l'objet GtkEditable. Ainsi, en plus des fonctions spécifiques au GtkText, les fonctions spécifiques au GtkEditable permettent de gérer les sélections de texte et les fonctions Couper, Copier, Coller et Supprimer, avec un "presse-papier" (clipboard) invisible, propre au GtkEditable.

Le GtkText permet de choisir si les lignes sont coupées ou non si elles sont trop longues pour être affichées. Par défaut, les lignes trop longues sont coupées, le reste étant affiché en-dessous. On peut aussi décider de couper simplement la ligne, sans afficher le reste. Dans notre éditeur on a laissé afficher la ligne en-dessous. On pourrait imaginer une barre de défilement horizontale permettant de se déplacer en largeur dans le texte et ainsi ne pas afficher la partie coupée d'une ligne comme c'est fait par défaut. Mais la version que nous utilisons de GTK ne permet pas encore cette possibilité. Nous utilisons tout de même une barre de défilement vertical, ce qui paraît normal pour un éditeur, sinon on serait limité en taille de texte.

Dans notre cas, on parle de sauvegarder un texte, c'est à dire sauvegarder les données d'un GtkText vers un fichier. Et on dit qu'on ouvre un fichier, c'est à dire qu'on lit le texte contenu dans un fichier.

Ces deux fonctions (sauvegarde et ouverture) se trouvent dans une unité séparée de l'unité principal. Elles n'utilisent ainsi aucune variable globale. On utilise deux fonctions dans le programme principal pour appeller chacune d'elle, elles servent à modifier le titre de la fenêtre principale pour afficher le nom du fichier dedans. On vide en plus le GtkText avant d'appeller la fonction d'ouverture de fichier. Et pous la sauvegarde, on vérifie si on a déjà le nom du fichier de destination, si on ne l'a pas on doit appeler la fonction pour choisir ce fichier.

La fonction de sauvegarde de texte est très simple. Elle prend en paramètre le GtkText contenant le texte à sauvegarder, et une chaîne de caractères contenant le nom du fichier dans lequel le texte sera sauvegardé. Une fonction de GTK nous permet de récupérer facilement le contenu d'un GtkEditable :

gchar * gtk_editable_get_chars (GtkEditable *editable,
                                gint start_pos,
                                gint end_pos);

Elle prend en paramètre le GtkEditable, la position dans le texte à partir de laquelle on veut récupérer du texte start_pos, et la position jusqu'à laquelle on veut aller end_pos. Elle renvoie une chaine de caractère (gchar étant une redéfinition de GTK du type char). Ainsi on l'appelle avec start_pos à 0 (début du texte) et end_pos à -1, un nombre négatif désignant la fin du texte dans cette fonction. Pour écrire dans le fichier, avec la fonction fwrite de la librairie stdio.h, on a besoin de la longueur du texte, ce qu'on récupère avec la fonction applicable sur notre GtkText :

guint gtk_text_get_length (GtkText *text);

Elle renvoie ainsi la longueur du texte (gint étant une redéfinition de GTK du type int).

L'ouverture d'un fichier est plus compliquée, nous ne l'expliquerons pas en détail. La fonction d'ouverture de fichier prend en paramètres notre GtkText, le nom du fichier à ouvrir, et la police de caractère utilisée pour afficher le texte. On pourrait également prendre en paramètre la couleur, mais nous avons trouver inutile de gérer la couleur du texte dans cet éditeur. Une fois avoir récupéré le contenu d'un fichier texte dans une variable chaîne de caractères, on utilise la fonction spécifique aux GtkTexts permettant d'insérer une chaîne de caractères dans un GtkText :

void        gtk_text_insert                 (GtkText *text,
                                             GdkFont *font,
                                             GdkColor *fore,
                                             GdkColor *back,
                                             const char *chars,
                                             gint length);

Cette fonction nous permet, dans l'ordre des paramètres, de choisir la police (font), les couleurs de d'avant et d'arrière plans. Elle a besoin évidemment du GtkText dans lequel on insère le texte, de la chaîne de caractères à insérer (chars), et de la longueur de cette chaîne (length).

Il y a deux façons de faire des menus. La première est la méthode dite "manuelle". C'est celle que nous avons utilisée avant de supprimer par erreur les fichiers sources de l'éditeur. Dans les divers documents que l'on avait, elle était dite plus difficile que la deuxième, mais au premier abort, elle paraissait plus intuitive. En l'utilisant on s'est aperçu qu'on était tout de même limité, et que c'était une méthode assez lourde à écrire. Il s'agit d'implémenter chaque élément de menu un par un, en gérant les signaux sur chacun pour attribué les fonctions qui doivent être lancées lorsu'on clique dessus, et leurs propriétés.

Quand il a fallut recommencer l'implémentation de l'éditeur, nous avons décider d'essayer d'utiliserla deuxième méthode. Il s'agit de l'utilistaion de GtkItemFactory, une üsine à menus". Cette méthode s'est avérée effectivement plus facile, et surtout moins lourde à utiliser. Elle permet de plus d'affecter à des éntrées de menus (items) des racourcis clavier qui exécutent la fonction liée à un élément. On définit dans un premier un temps un GtkItemFactoryEntry, c'est une structure qui définit chaque branche de menu et chaque élément de menu. On leur attribue dans cette structure le racourci clavier, la fonction à lancer quand on le sélectionne, et le paramètre à envoyer à la fonction en question. Il y a ensuite juste une fonction qui cré les menus à partir d'un objet barre de menus (menubar) en paramètre, l'attribuant à la fenêtre principale (placée en paramètre également). Le GtkItemFactoryEntry définit plus haut en variable globale, est utilisée dans cette fonction pour créer les menus. Il n'est ainsi pas difficile de modifier les menus de l'éditeur.

La méthode que l'on a utilisée finallement nous a obligé a créer des fonctions à un paramètre de type int (ou gint) appellant des fonction nécessitant plus de paramètres de GTK. C'est le reproche qu'on pourrait faire à "l'usine à menus". Par exemple, pour supprimer la sélection en cours, il faut appeller la fonction GTK :

void        gtk_editable_delete_selection   (GtkEditable *editable);
On est alors obligé de faire une fonction qui sera appellée par l'entrée de menu, qui se contente d'appeller cette fonction. Et le paramètre de cette fonction est donc forcément une variable globlae au programme. Mais d'une façon générale, le GTK fonctionne beacoup ainsi. On est souvent obligé de faire des fonctions simples de cette manière.

Pour faire un programme en GTK, il faut utiliser des "boîtes". C'est le principe du raisonnement par objets. On met un objet dans une boîte, qui est elle-même dans une autre boîte, et ainsi de suite.

Pour l'éditeur, il n'y avait pas besoin de beaucoup de boîtes. On utilise une boîte "verticale", c'est à dire que les éléments qui sont dans cette boîte sont ajouter les uns après les autres verticalement. Dans cette boîte on a la barre de menu et la barre de défilement vertical qui sert également de boîte. Dans cette dernière, on met le GtkText. En fait la barre de défilement agit sur son contenu, ici il n'agit que sur le GtkText.

Le fonctionnement des boîtes et des barres de défilement n'est pas très difficile. Mais il n'est pas évident au début de comprendre comment s'emboîtent les objets GTK. Ces boîtes servent surtout pour placer les objets, donc il faut bien comprendre leur fonctionnement pour faire ce que l'on veut.

Pour sauvegarder le texte en cours, en ouvrir un, on a besoin de séléctionner le fichier source, ou de destination. GTK propose un objet ßélection de fichier" (file selection) qui permet d'afficher simplement une boîte de dialogue à partir de laquelle on peut choisir un fichier. Il est complet, il est composé d'un "mini explorateur de fichiers" et d'une entrée de texte éditable pour le nom du fichier.

On a donc une fonction qui cré cette boîte de dialogue. On définit dans celle-ci ce qu'on veut faire lorsque l'utilisateur clique sur le bouton Ök", c'est à dire une fois qu'il a séléctionné le fichier. Comme cette fonction peut-être appellée pour sauvegarder ou pour ouvrir un texte, elle prend simplement un paramètre entier, s'il est à 1, elle appelle la fonction pour ouvrir un fichier, sinon elle appelle la fonction pour sauvegarder un texte.

On a également besoin d'une fonction auxiliaire, appellée quand on clique sur le bouton Ök", pour enregistrer le nom de fichier sélectionné. Elle prend simplement en paramètre la boîte de dialogue de sélection de fichier. Et, grâce à cette fonction GTK :

gchar*      gtk_file_selection_get_filename (GtkFileSelection *filesel);
on récupère ce nom de fichier dans notre variable globale contenant l'ancien nom de fichier, filesel étant le boîte de dialogue.

Boite de dialogue de sélection de fichier :

Figure
Boite de dialogue pour la selection d'un fichier

La sélection de la police est, dans son fonctionnement, proche de la sélection de fichier. GTK propose un objet ßélection de police" (font selection) qui permet d'afficher une boîte de dialogue pour choisir la police de caractère.

De même que précédemment, on a une fonction pour créer cette boîte de dialogue, et une pour enregistrer la police choisie dans une variable globale de type GdkFont.

Comme pour le Bloc-notes de MS Windows, il ne s'agit pas de modifier la police localement, mais sur tout le texte ouvert. La fonction gtk_text_insert, qu'on a vu plus haut (GtkText), permet d'insérer du texte avec une certaine police et une certaine couleur. Si on voulait modifier la police localement, on pourrait sauver la séléction dans une variable chaîne de caractère, supprimer la sélection et réinsérer cette variable avec la police choisie. C'est donc possible. On a préféré tout simplement sauvegarder le texte en cours dans une variable chaîne de caractères temporaire, supprimer le contenu du GtkText, puis réinsérer le texte contenu dans la variable avec la nouvelle police. Tout ceci se fait dans la fonction appellée une fois que l'on clique sur le bouton Ök" de la boîte de dialogue de sélection de police.

Comme sous tout éditeur de texte, il faut vérifier que l'utilisateur a bien enregistré son travail avant de quitter l'application. Le programme a donc une variable booléenne globale (gboolean) pour ceci. Si elle est "true", c'est que le texte a été modifié, sinon c'est qu'il n'a pas été modifié depuis sa dernière sauvegarde (ou depuis son ouverture). En fait, à chaque fois que l'on sauvegarde ou que l'on ouvre un fichier, la variable est mise à "false". Sachant qu'un GtkEditable emet un signal "changed" dès qu'il est modifié, on "connecte" ce signal à une fonction très simple qui modifie la valeur de la varaible si elle est "false".

Quand on ferme l'éditeur, le programme appelle une fonction qui vérifie l'état de la variable booléenne. Si elle est "false" (le texte n'a donc pas été modifié), l'application se ferme tout simplement. Sinon, on ouvre une boîte de dialogue demandant à l'utilisateur s'il veut ou non quitter l'éditeur sans sauvegarder. Il y alors trois boutons, le premier pour sauvegarder le texte, le second pour quitter, et le troisième pour retourner à l'éditeur (Annuler).

Figure
Boite de dialogue pour la fermeture (si modification)

L'éditeur étant une application à part entière, nous avons décidé de créer une boîte de dialogue simple affichant quelques informations sur l'éditeur (nom de l'application et version). Il n'y a rien de spécial à dire là-dessus, cette fenêtre étant la dernière que nous ayont fait, elle a été très rapide à faire.

L'implémentation de l'éditeur, malgré ce que l'on pensait au début, n'a pas été très simple. Le programme final n'est pas très difficile, mais il y a eu un gros travail de recherche auparavant. On a fait appel à un livre pour les informations sur le GtkText avant que tout soit supprimé par erreur. Ensuite il fallait faire sans le livre. GTK propose un tutoriel officiel en anglais, mais il n'est pas encore complet. On a également trouvé des versions françaises de ce tutoriel, encore moins complet (rien sur les GtkTexts).

Puis, à force de cliquer un peu au hasard sur le site officiel de GTK (http://www.gtk.org), on a fini par trouver des pages sur chaque librarie de GTK, donnant ainsi toutes les fonctions liées à chaque objet GTK. Bien que ces pages ne soient pas très détaillées, ce sont les informations qui ont le plus servies pour la conception de l'éditeur.

Il existe aussi les sources d'un programme faisant le tour de la plupart des possibilités de GTK qui existent. Il est bien fait et a bien servi pour faire les boîtes de sélection de fichier et de police. Mais il n'a pas été évident à trouver, peu de gens (dans notre entourage en tout cas) le connaissent, et il ne se trouve pas sur le site officiel de GTK. A force de chercher on l'a finallement trouver sur le site de l'association EPX d'EPITA !

Figure
Une recherche de fichier avec le pattern matching

Notre projet ExplorerMaker doit permettre de se déplacer aisément dans l'arborescence des dossiers et d'avoir facilement accès à un répertoire ou fichier particulier même si l'utilisateur ne connaît qu'une partie du nom. Pour cela nous devons implémenter un algorithme de recherche qui ne se résume pas à une simple comparaison de deux chaînes caractère par caractère mais une comparaison plus subtile utilisant le pettern matching, où l'utilisateur pourra remplacer la partie du nom inconnue par des symboles spécifiques.

La partie du nom inconnue doit donc être remplacée par des symboles correspondants chacun à une catégorie de carctères particuliers : ce sont de wildcards.

Voici à quoi correspondent les wildcards utilisés :

  • # : Un chiffre [0..9]
  • ? : Une lettre [a..z, A..Z]
  • @ : Un caractère alphanumérique [a..z, A..Z, 0..9]
  • * : N'importe quelle suite de caractères même nulle

Ainsi pour trouver "Fichier234.txt" on pourra taper, par exemple, le pattern suivant : "F?*er@##.*".

Pour cette soutenance a recherche ne concerne que les noms de fichiers ou repertoires donc il faut que le nom testé soit entierement identifié (et non pas en partie).

Le principe de base consiste donc à faire une comparaison améliorée entre deux chaînes de caractères (une chaîne quelconque et une chaîne représentant le pattern), si ce qui est représenté par le pattern est reconue dans la chaîne alors la recherche est positive le nom à été trouvé.

Si on ne considère pas l'étoile ('*'), il suffit juste de matcher le pattern caractère par caractère avec une fonction qui vérifie que le caractère courrant du pattern correspond au caractère courant de la chaîne ou que le symbole courant du pattern correspond à un caractère compris dans le bon intervalle représentatif de celui-ci. Une fonction récursive est donc bien appropriée. Si on considère l'étoile, cela devient plus compliqué. En effet, l'étoile represente n'importe quoi, et il faut identifier ce qui la suit. Il faut donc parcourir la chaîne jusqu'à ce que, ce qui suit l'étoile soit identifié, sinon la recherche est négative.

Une bonne façon de se représenter l'algorithme est d'imaginer un automate.

Par exemple pour le pattern " Fi*i?r " :

Figure
L'automate du pattern matching

Tout d'abord, pour comparer les caractères on a implémenter une fonction match qui prend en paramètre deux caractères (le premier est de la chaine, le second est du pattern). La fonction identifie le premier au second avec un switch et retourne 1 s'il a été identifié, 0 sinon.

Puis il faut une fonction petternmatch qui parcours les deux chaînes en tenant compte de l'étoile. La fonction est recursive est prends en paramettre les deux chaînes, leur longueur l'indice de position de chacune et la position de la dernière étoile.

Le pseudo-code de cette fonction deduit de l'automate est (avec str la chaine à comparer et patt le pattern):

fonction patternmatch(<str>, <patt>, indiceStr i, indicePatt j, star)
  si i <= longueur(str) alors
    si (patt[j] = '*') alors
      j <- j+1
      /* on enregistre la position de j dans star */
      star <- j   
    fin si
    si match(str[i], patt[j]) alors
      si on est à la fin des deux chaînes alors
        on retourne 1
      sinon      /* on continue avec les deux caractères suivants */
        retourne patternmatch(str, patt, i+1, j+1, star)
      fin si
    sinon
      si (i = 0) et (j = O) alors  
        /* si les premier caractères ne correspondent pas */
        on retourne 0
      sinon
        /* on reprend à partir de la dernière étoile */
        retourne patternmatch(str, patt, i+1,star, star) 
      fin si
    fin si
  sinon
    /* on est à la fin de la chaîne et le pattern n'est pas identifié */
    on retounrne 0 
  fin si
Fin fonction

Ce tableau présente très clairement et très rapidement qui a fait quoi.

Sébastien Deckmyn Nicolas Le Coz Benoit Merouze Julier Perruche
La bibliothèque La chartre L'éditeur Le pattern matching
dynamique graphique
Recherche de fichiers
La base de données Informations et
listage de fichiers
Le gestionnaire de
packages IHM explorateur
Gestion processus
et approche
multi-threads

Là encore un tableau pour présenter les objectifs de chacuns pour la deuxième soutenance (fin Février):

Sébastien Deckmyn Nicolas Le Coz
IHM de la Base Module pour la gestion
de données (BD) de fichiers (copie...)
Finialisation de Application
la gestion de BD multi-threads
Benoit Merouze Julier Perruche
Début module de Module de recherche
communication sur et dans les
entre Explorer fichiers
et l'extérieur

Voilà quelques mois que nous avons commencé ce projet, et dès lors on peut constater qu'il prend vite forme grâce à quatre étudiants très motivés. Il n'y qu'à constater ce qui a été fait et ce qui devaient être fait (d'après le cahier des charges) pour s'apercevoir qu'on a pratiquement une soutenance d'avance sans avoir baclé nos modules car ces premiers modules fonctionnent correctement. Pour la deuxème soutenance on essayera de garder la même cadence même si pour préparer la première soutenance on avait plus de temps (2-3 mois).


Footnotes:

1 UID : User IDentifier

2 POSIX : norme définie pour la gestion des signaux sous Unix et qui reprend de manière plus performante la norme ANSI sur la gestion classique des signaux

3 PID : numéro d'identification de processus Process IDentifier


File translated from TEX by TTH, version 2.00.
On 26 Feb 2002, 22:10.

Retour au haut de la page

Pour toute question ou problème concernant ce site Web, envoyez un courrier électronique à bencool@netcourrier.com.
Dernière modification : 21 février 2002.