Hacking Guide (fr)
De HurdFr_Wiki.
+------+
| |
,--- | | <----. ``The GNU Hurd is the GNU project's
,' | | `-. replacement for the Unix kernel.
| +------+ `. The Hurd is a collection of servers
v | `. that run on the Mach micro-kernel to
+------+ | +------+ | implement file systems, network
| | `. | | | protocols, file access control, and
| | `--> | | | other features that are implemented
| | | | | by the Unix kernel or similar kernels
+------+ +------+ ,' (such as Linux).''
^ | _,'
| +------+ +-' --- http://hurd.gnu.org/
| | | ,-'|
`. | | -' ,' This is the
| | | |
`. +------+ ,' H u r d H a c k i n g G u i d e
`. ,'
`----------' Version 0.2_1 - Mar 25, 2002
Ceci est une version traduite, wikifiée et modifiée du [Hacking Guide]
| © Copyright original | 2001, 2002 | Wolfgang Jährling | <wolfgang@pro-linux.de> |
| © Traduction originale | 2004 | Colin Pitrat | <colin.pitrat@rez-gif.supelec.fr> |
| © Modifications | 2006 | HurdFR | <wiki@hurdfr.org> |
| | |
| GFDL | |
| Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la Licence de documentation libre GNU, version 1.1 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans texte de dernière page de couverture. | Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. |
Sommaire |
A propos de ce document
Conventions
Le numéro de version de ce document suit la convention <hurd version>_<document release>. Cela signifie que la version 0.2_7 est la septième version depuis le Hurd 0.2 et que la version 0.4_1 serait la première version pour le Hurd 0.4 (qui n'est bien sûr pas encore disponible :)).
-
$(HURD)désigne le répertoire des sources du Hurd. -
$(GNUMACH)désigne le répertoire des sources de GNU Mach. -
$(MIG)désigne le répertoire des sources de MiG. -
$(GLIBC)désigne le répertoire des sources de la GNU Libc.
Une commande du shell commence par $ pour une commande d'un utilisateur normal et par # pour une commande du superutilisateur :
$ diff -u libtrivfs.old/open.c libtrivfs/open.c # reboot
Les prompts des autres programmes sont représentés de la manière dont ils apparaissent dans l'application. Par exemple, le prompt de GDB est représenté par (gdb) :
(gdb) break trivfs_S_io_write
J'essaierai de suivre les GNU Coding Standards dans mes exemples en C : http://www.gnu.org/prep/standards_toc.html
Sujet
Ce document est une introduction à la programmation pour le Hurd et GNU/Mach. Le but de ce guide est d'aider les personnes intéressées pour débuter à coder sur le Hurd ou pour l'étendre (en écrivant des translators[1] ). Il utilise beaucoup de références aux sources du Hurd et de GNU/Mach. Il est donc recommandé de les parcourir avant de le lire. En fait, les sources du Hurd sont bien écrites et suffisamment commentées pour pouvoir être lues sans trop de difficultés.
Le Hurd semble très compliqué et difficile à maîtriser au premier coup d'œil. Mais il ne l'est pas car vous n'avez pas besoin de tout comprendre pour débuter, vous pouvez le faire petit à petit à utiliser à chaque étape ce que vous savez. Il y a aussi des bibliothèques qui rendent la création de certains translators classiques assez faciles. Je pense que le seul problème est l'absence d'une bonne documentation comme « Linux Module Programming Guide » ou autre, qui rend possible l'apprentissage étape par étape. Ce document tente de combler ce manque.
Mach et MiG ne sont pas décrits en détail ici, donc si vous désirez des informations spécifiques les concernant, je vous recommande la lecture du GNU Mach Reference Manual[1]
et la documentation sur MiG disponible sur internet.
Le Hurd Hacking Guide n'est pas supposé être une référence complète mais aider à débuter. La seule véritable référence pour l'instant est ce que l'on peut trouver d'écrit dans les sources du Hurd.
L'ordre des chapitres de ce document était à l'origine basé sur un mail de Farid Hajji[1]
, qui semble basé sur $(HURD)/doc/navigating.
Contributions
Ce document est basé sur la traduction originale, par Colin Pitrat. Elle a été mise sur ce wiki avec son autorisation expresse[1] . Elle est bien entendu modifiable, améliorable, perfectible, extensible.
+------+
| | B e a
,-> | Hurd | -. p a r t o f
,' | | `. i t
| +------+ v
Prérequis
- Vous devriez avoir quelques bases concernant le Hurd.
- Le site du Hurd(en)
- Une présentation(en) par Markus Brinkmann
- Un document(en) par Gaël Le Mignot
- Cette présentation(fr, PDF) par HurdFR
- Des connaissances plus précises sont bien sûr les bienvenues : le design du Hurd(en)
- Savoir ce qu'est un translator est une bonne chose. Vous devriez vous pencher sur cette introduction(en) de Markus Brinkmann.
- Les sources du Hurd et de GNU Mach sont assez utiles :
#! /bin/sh
cd $HOME
mkdir hurd-cvs
cd hurd-cvs/
# Use the empty string password:
cvs -d:pserver:anoncvs@cvs.savannah.nongnu.org:/cvsroot/hurd login
for module in hurd gnumach
do
cvs -z3 -d:pserver:anoncvs@cvs.savannah.nongnu.org:/cvsroot/hurd \
co $module
done
- Avoir GNU/Hurd installé sur votre ordinateur pourrait aider aussi[1]
.
- Se plonger dans les fichiers d'en-tête des bibliothèques du Hurd est dangereux parce que vous pouvez vous y noyer facilement car vous ne trouverez pas la logique de toutes ces structures. Peut-être qu'une liste de où trouver quoi aidera :
$ cd $(HURD) && egrep '^struct [^;*]+$' */*.h
- Si vous connaissez les principes de Mach, ce qu'est MiG, etc..., alors cela vous aidera certainement beaucoup, mais ça ne devrait pas être indispensable. Connaître un peu le noyau de Linux peut aussi aider dans une certaine mesure.
- Oh, vous devez aussi connaître un peu le langage de programmation C...
Courte introduction au Hurd et à Mach
- We're way ahead of you here. The Hurd has always been on the cutting edge of not being good for anything. (Roland McGrath)
- Nous avons un train d'avance sur vous. Le Hurd a toujours été à la limite de n'être bon à rien.
- In short: just say NO TO DRUGS, and maybe you won't end up like the Hurd people. (Linus Torvalds)
- Pour faire simple: dites juste non à la drogue, et peut-être que vous ne finirez pas comme les fans du Hurd.
Tout logiciel digne de ce nom nécessite un moyen de communication entre ses composants. Actuellement, des moyens comme CORBA ou les composants XPCOM de Mozilla sont utilisés à cette fin. L'avantage du Hurd sur les autres systèmes est qu'il fournit un tel moyen et ne nécessite pas de modifier les applications existantes pour en tirer partie. Comment fait-il ?
Comme vous le savez peut-être déjà, le Hurd est une collection de serveurs tournant sur un microkernel, actuellement Mach. Dans un environnement Mach, les communications entre programmes sont principalement faites par l'envoi de messages à travers ce que l'on appelle des ports, et qui sont en fait une sorte du file de messages. Pour chaque port, il y a une tâche ayant la permission de réception (receive-permission, cette tâche reçois les messages envoyés sur ce port). D'autres tâches peuvent avoir la permission d'envoi (send-permission, possibilité d'envoyer des messages sur ce port) ou la permission d'envoi unique (send-once-permission qui est utilisé pour avoir la réponse d'un serveur car les ports sont unidirectionnels) pour ce port, ou même aucune permission.
The communication framework is provided by the microkernel, Mach. Communication is done by sending messages through so-called "ports", which are a kind of message queue that lies in the microkernel. For each port, there is one and only one task with receive permission (i.e. this task receives the messages someone sends to this port). Other tasks might have a send-permission or a send-once-permission (which is used for getting a reply from a server, because ports are one-way channel) for this port, or even no permission at all.
Si vous lisez $(GNUMACH)/include/mach/port.h, vous pourrez noter qu'il y a d'autres droits sur les ports : une autorisation send ou send-once devient une dead name si la tâche ayant le droit de réception est détruite. Ce droit ne peut donc être utilisé pour rien d'autre. C'est seulement un remplaçant. Un autre droit est le port set, que Marcus Brinkmann définit comme suit [1]
- Un port set est un groupe de ports. Il est interessant de grouper les ports si vous voulez le prochain message arrivant sur n'importe lequel des ports pour les quels vous avez la receive-permission. Dans le Hurd, on utilise aussi les classes de ports fournies par libports.
- En fait, on est pas très strict au niveau du vocabulaire lorsque l'on parle de ports. [...] Vous pouvez voir les droits sur les ports comme des capacités associées aux ports et aux groupes de ports, si vous préferez.
Comment fait un processus pour trouver un port particulier ? C'est très simple : En passant par le système de fichiers. Par exemple, l'utilisation d'un serveur pour le système de fichiers ext2 se fait à travers le nœud où le système de fichier est monté (remarquez qu'il n'y a pas la notion de montage dans le monde du Hurd, c'est juste la façon dont ce serait nommé sous Unix ; le terme correct sous le Hurd est mettre en place un translator'.
Votre client mail favoris ne supporte pas les signatures aléatoires ? Écrivez un translator pour signature aléatoire[1]
(qui retourne une nouvelle signature à chaque fois que
vous le lisez). Et le mieux est : tous les clients mail peuvent utiliser cette fonction ! Voyez-vous comment le Hurd encourage la réutilisation du code ? Comprenez vous pourquoi GNU/Hurd ne nécessite pas de modification des programmes pour profiter des avantages qu'il procure ?
Si vous désirez en savoir plus à propos du système de fichiers du Hurd, je vous recommande chaudement de lire cette présentation du Hurd.
On peut dire que le système de fichiers est l'espace de nom pour les services, ce qui est aussi vrai dans l'autre sens : l'espace de nom pour les services est le système de fichiers. C'est une chose très importante à comprendre. Bien que le système de fichiers soit la voie la plus courante pour atteindre un port, il y'en a d'autres; par exemple, vous pouvez obtenir le port dans un message.
Si vous vous demandez pourquoi je compare ce genre de communication avec CORBA, la citation suivante provenant de la publication Vers une nouvelle stratégie de conception des OS pourrait vous aider à comprendre.
- Avec des translators, le système de fichier peut agir en tant que « point de rendez-vous » pour des applications qui ne sont pas similaires à des fichiers.
- Considérez un service qui met en oeuvre une certaine version du protocole de X, et utiliserait des messages Mach comme mode de transport fondamental. Pour chaque affichage de X, un fichier peut être créé avec le programme approprié en tant que
translator. Les clients X ouvriraient simplement ce fichier. Peu d'opérations sur les fichiers seraient utiles (lire et écrire, par exemple, seraient inutiles), mais les nouvelles opérations (XCreateWindow ou XDrawText) pourraient devenir significatives.
- Dans ce cas, le protocole fichier est uniquement utilisé pour manipuler les caractéristiques du nœud servant de « point de rendez-vous ». Ce nœud n'a pas besoin de supporter d'opérations E/S, mais il peut répondre à de tels messages par un code message_not_understood.
Les bases de Mach et MiG
Les ports de Mach
Nous allons maintenant nous pencher de plus près sur Mach. Je sais que vous voudriez commencer à écrire des translators aussi vite que possible mais vous aurez vraiment besoin de quelques bases sur Mach. Les ports de Mach sont beaucoup utilisés dans le Hurd, ce sera donc notre point de départ.
Tout d'abord, faisons bien la distinction entre les ports, les droits sur les ports et les noms de ports. Marcus Brinkmann à écrit (sur IRC) :
-
mach_port_test un nom de port, il désigne une entrée dans l'espace de noms des ports, qui est associée à un droit dead name, un droit send-once ou une combinaison de droits receive et send avec éventuellement plusieurs utilisateurs. - Supposons que vous ayez un
mach_port_t, et que vous vouliez lui envoyer un message. Pour ce faire, vous passez la tâchemach_task_self(), le nom de port, l'id du message et les arguments. La tâche est utilisée pour récupérer l'espace de noms IPC, le nom de port est utilisé pour trouver l'entrée dans cet espace de nom. L'entrée renseigne Mach sur les droits que vous avez sur le port qui porte ce nom. - Il n'y a qu'un seul nom de port pour tous les droits receive/send que vous pouvez avoir sur un port. Mais il y a des noms différents pour les droits send-once, parce que ça facilite la gestion par le Mach - et par les programmes utilisateurs aussi.
Mach définit le type natural_t, qui est le type natif pour la machine, par exemple 32 bits pour processeur 32-bit. natural_t est toujours non-signé, mais il y a une version signée qui s'appelle integer_t. La
definition pour une plateforme i386 peut être touvée dans $(GNUMACH)/i386/include/mach/i386/vm_types.h. Dans ce fichier se trouvent d'autres types intéressants, mais ils ne nous seront pas utiles pour l'instant.
Comme les descripteurs de fichiers Unix, les noms de port de Mach sont
de simples et ennuyeux entiers. Dans le fichier $(GNUMACH)/include/mach/port.h vous pouvez trouver les définitions suivantes :
typedef natural_t mach_port_t; typedef mach_port_t *mach_port_array_t;
(Pour la plupart des types de données fournits par le Mach, il existe un type *_array_t.) Le numéro de port identifie un unique port (dans l'espace de nom de la tâche), ainsi une valeur de mach_port_t est souvent désignée comme un nom de port.
Une valeur de MACH_PORT_DEAD (soit ~0) représente un droit de port qui est mort, alors que MACH_PORT_NULL (cf port.h) indique l'absence de port ou de droit sur le port. Vous pouvez vérifier avec MACH_PORT_VALID (port) si un port n'a pas l'une de ces valeurs.
Pour les droits sur les ports, il existe les macros suivantes :
-
MACH_PORT_RIGHT_RECEIVE -
MACH_PORT_RIGHT_SEND -
MACH_PORT_RIGHT_SEND_ONCE -
MACH_PORT_RIGHT_PORT_SET -
MACH_PORT_RIGHT_DEAD_NAME -
MACH_PORT_RIGHT_NUMBER
qui sont de type mach_port_right_t :
typedef natural_t mach_port_right_t;
Ce type est utile lorsque l'on désire agir sur un droit particulier d'un port. Souvent cependant, on désire utiliser un groupe de droits car on peut avoir plusieurs droits sur un seul port. Pour cela, on utilisera le type suivant :
typedef natural_t mach_port_type_t; typedef mach_port_type_t *mach_port_type_array_t;
Une variable mach_port_type_t peut soit porter la valeur MACH_PORT_TYPE_NONE, qui représente bien sûr un groupe de droits vide, ou un ensemble de macros MACH_PORT_TYPE_SEND, MACH_PORT_TYPE_RECEIVE etc. combinées avec un OR bit à bit. Il y a aussi plusieurs combinaisons prédéfinies :
| Macro | Combinaison | MACH_PORT_TYPE_SEND_RECEIVE | MACH_PORT_TYPE_SEND, MACH_PORT_TYPE_RECEIVE | MACH_PORT_TYPE_SEND_RIGHTS | MACH_PORT_TYPE_SEND, MACH_PORT_TYPE_SEND_ONCE | MACH_PORT_TYPE_PORT_RIGHTS | MACH_PORT_TYPE_SEND_RIGHTS, MACH_PORT_TYPE_RECEIVE | MACH_PORT_TYPE_PORT_OR_DEAD | MACH_PORT_TYPE_PORT_RIGHTS, MACH_PORT_TYPE_DEAD_NAME | MACH_PORT_TYPE_ALL_RIGHTS | MACH_PORT_TYPE_PORT_OR_DEAD, MACH_PORT_TYPE_PORT_SET |
|---|
Ne confondez pas les MACH_PORT_RIGHT_* avec les macros MACH_PORT_TYPE_*. Leurs noms sont similaires, mais ont une signification et une valeur différents.
Vous pourrez trouver plus de détails sur l'IPC (Inter Process Communication : Communication inter-processus) du Mach dans le Manuel de Référence de GNU Mach, Chapitre 4[1]
MiG
Faire des RPCs (Remote Procedure Calls : Appel de procédures distantes) en envoyant des messages Mach n'est pas trivial. Rendre la tâche plus simple est le but de MiG (Mach Interface Generator : Générateur d'interfaces pour Mach). Vous devez écrire un fichier de définition de l'interface, le faire traiter par MiG et il fournit deux fichiers source C et un fichier d'en-tête, qui s'occupe de la mécanique des ports de Mach pour vous. Dès lors vous pouvez envoyer des messages par de simples appels de fonction.
Bien sûr, vous n'avez à le faire que si vous désirez mettre en place votre propre interface. Le Hurd contient déjà plusieurs interfaces. Les fonctions appropriées sont dans la glibc, et vous n'avez donc rien à faire avant de vous en servir.
La syntaxe des fichiers MiG est similaire au Pascal et ne devrait pas être dure à comprendre. Nous verrons quelques détails plus loin.
Les interfaces du Hurd
Comprendre les concepts est une chose importante, mais ça ne suffira pas pour faire quoi que ce soit. Vous avez aussi besoin de connaître quelques choses à propos des interfaces qui rendent possible l'usage de ces concepts.
Vous trouverez les definitions des interfaces dans les fichiers $(HURD)/hurd/*.defs. Les plus importantes sont io.defs, password.defs, fsys.defs, fs.defs et auth.defs. L'interface login.defs est intéressante, mais pas encore implémentée et ne le sera peut être jamais. Les interfaces *_reply.defs sont - bien sûr - pour les réponses.
Vous devriez vraiment jeter un œil aux interfaces du Hurd maintenant. Dans le prochain chapitre, nous verrons comment les utiliser.
A quoi ça ressemble vraiment ?
Écrire un fichier sur la sortie standard
Regardons d'un peu plus près un programme qui affiche le contenu d'un fichier sur la sortie standard à la manière hurd. Notez que la manière habituelle est toujours utilisable. Glibc fournit toujours les fonctions comme write () pour le Hurd. Les parties spécifiques au Hurd de la glibc sont dans $(GLIBC)/hurd/, les parties dépendante du Mach sont dans $(GLIBC)/sysdeps/mach/hurd/.
/* dump.c - Dump a file to stdout in a "hurdish" way.
* Copyright (C) 2001, 2002 Wolfgang Jährling <wolfgang@pro-linux.de>
* Distributed under the terms of the GNU General Public License.
* This is distributed "as is". No warranty is provided at all.
*/
#define _GNU_SOURCE 1
#include <hurd.h>
#include <hurd/io.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <error.h>
int
main (int argc, char *argv[])
{
file_t f;
mach_msg_type_number_t amount;
char *buf;
error_t err;
if (argc != 2)
error (1, 0, "Usage: %s <filename>", argv[0]);
/* Open file */
f = file_name_lookup (argv[1], O_READ, 0);
if (f == MACH_PORT_NULL)
error (1, errno, "Could not open %s", argv[1]);
/* Get size of file (buggy! See below) */
err = io_readable (f, &amount);
if (err)
error (1, err, "Could not get number of readable bytes");
/* Create buffer */
buf = malloc (amount + 1);
if (buf == NULL)
error (1, 0, "Out of memory");
/* Read */
err = io_read (f, &buf, &amount, -1, amount);
if (err)
error (1, errno, "Could not read from file %s", argv[1]);
buf[amount] = '\0';
mach_port_deallocate (mach_task_self (), f);
/* Output */
printf ("%s", buf);
return 0;
}
Vous pouvez compiler ce programme comme suit :
$ gcc -g -o dump dump.c
Penchons-nous sur les parties intéressantes de ce programme :
#define _GNU_SOURCE 1
Je vous conseille de toujours définir cette macro pour les programmes spécifiques a GNU/Hurd, sans quoi vous ne pourrez même pas les compiler. Definissez-la avant d'inclure un quelconque fichier d'en-tête. Pour des programmes plus important, vous serez plutôt tentés de passer -D_GNU_SOURCE à gcc.
file_t f;
Un file_t est en fait un mach_port_t, mais nous utilisons un file_t pour montrer clairement que nous allons l'utiliser pour ouvrir un fichier.
char *buf;
Vous aurez peut-être noté que nous allons utiliser ce tampon[1]
dans une situation où nous devrions passer undata_t(qui est définit comme un char *), mais$(HURD)/hurd/hurd_types.haffirme pourdata_tet quelques autres types :
- Ces noms n'existent qu'a cause de défaillances de MiG. Vous ne devriez pas les utiliser dans des sources C ; utilisez les types C standard à la place.
error_t err;
Il y a aussi le type kern_return_t, mais dans le Hurd, error_t est le meilleur choix. Marcus Brinkmann explique pourquoi :
-
kern_return_test le type d'erreur Mach pourmach_msgpar exemple, donc si vous faîtes un RPC, et désirez le faire d'une manière compatible avec le Mach, utilisezkern_return_t. MAIS pour le Hurd, utilisezerror_t, car c'est compatible avec les types d'erreur de laglibc. Vous pouvez toujours convertir
depuis kern_return_t vers un error_t sur les systèmes GNU.
if (argc != 2) error (1, 0, "Usage: %s <filename>", argv[0]);
Juste au cas où vous ne seriez pas familiers avec la fonction error () je citerai /include/error.h (Souvenez vous que l'on utilise pas /usr dans le Hurd...) :
/* Ecrivez un message avec `fprintf (stderr, FORMAT, ...)'; si ERRNUM est non nul, faites suivre de ": " et strerror (ERRNUM). si STATUS est non nul, terminez le programme avec `exit (STATUS)'. */ extern void error (int status, int errnum, const char *format, ...);
Maintenant l'action arrive vraiment. On essaie d'ouvrir un fichier avec la fonction de la glibc file_name_lookup (). Cette fonction retourne MACH_PORT_NULL si la tentative à échoué. O_READ est une extension de GNU et est identique à la constante POSIX O_RDONLY. De même, O_WRITE est identique à O_WRONLY.
/* Open file */ f = file_name_lookup (argv[1], O_READ, 0); if (f == MACH_PORT_NULL) error (1, errno, "Could not open %s", argv[1]);
Bien sûr, nous pourrions lire le fichier caractère par caractère, mais dans cet exemple, nous supposons que c'est un fichier normal et que nous pouvons le lire en une fois, donc nous utilisons io_readable () pour connaître la taille du fichier (ça ne marchera pas pour des fichiers comme /dev/random !), io_readable () nous dit seulement quelle quantité de données est disponible à l'instant de son appel. On crée alors un tampon pour le fichier complet :
/* Get size of file */ err = io_readable (f, &amount); if (err) error (1, err, "Could not get number of readable bytes"); /* Create buffer */ buf = malloc (amount + 1); if (buf == NULL) error (1, 0, "Out of memory");
Maintenant, tout ce que nous avons à faire est de lire le fichier dans le tampon. On ajoute un octet nul à la fin, afin de pouvoir l'utiliser comme une chaîne (on suppose que le fichier ne contient pas de caractère nul, ce qui n'est pas parfait, mais pour l'exemple on ne s'en préoccupe pas).
/* Read */ err = io_read (f, &buf, &amount, -1, amount); if (err) error (1, errno, "Could not read from file %s", argv[1]); buf[amount] = '\0';
Remarquez que l'on passe &buf, qui est un poiteur de pointeur. buf pourrait être modifié, mais cette décision doit être laissée au receveur du message io_read.
Si vous regardez $(HURD)/hurd/io.defs, vous vous demanderez sans doute
pourquoi on passe 5 arguments à io_read qui n'en attend que 4. io_object, data, offset et amount sont écrits dans le fichier .defs, alors que /include/hurd/io.h a un argument supplémentaire (appelé dataCnt) après data, qui est tout a fait logique : Nous avons aussi besoin de connaître la quantité de données que nous avons. Ajouter cet argument est fait automatiquement par MiG.
Nous en avons terminé avec le fichier, nous pouvons donc le fermer. Cela se fait en desallouant le port :
mach_port_deallocate (mach_task_self (), f);
C'est tout.
Créer une copie d'un fichier
Nous allons maintenant essayer de copier un fichier. Mais cette fois, nous allons le faire correctement: si le translator qui fournit le fichier prend un certain temps pour fournir ses données, nous attendrons. Nous allons donc lire jusqu'à rencontrer un EOF. Nous saurons que nous aurons atteint la fin du fichier si notre appel à io_read() nous donne zero octets de données.
/* copy.c - Copy a file in a "hurdish" way.
* Copyright (C) 2001 Wolfgang Jährling <wolfgang@pro-linux.de>
* Distributed under the terms of the GNU General Public License.
* This is distributed "as is". No warranty is provided at all.
*/
#define _GNU_SOURCE 1
#include <hurd.h>
#include <hurd/io.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <error.h>
#define BUFLEN 10 /* Arbitrary */
int
main (int argc, char *argv[])
{
file_t in, out;
mach_msg_type_number_t rd_amount, wr_amount;
char *buf, *ptr;
error_t err;
if (argc != 3)
error (1, 0, "Usage: %s <inputfile> <outputfile>", argv[0]);
/* Create buffer */
buf = malloc (BUFLEN + 1);
if (buf == NULL)
error (1, 0, "Out of memory");
/* Open files */
in = file_name_lookup (argv[1], O_READ, 0);
if (in == MACH_PORT_NULL)
error (1, errno, "Could not open %s", argv[1]);
out = file_name_lookup (argv[2], O_WRITE | O_CREAT | O_TRUNC, 0640);
if (out == MACH_PORT_NULL)
error (1, errno, "Could not open %s", argv[2]);
/* Copy */
while (1)
{
/* Read */
err = io_read (in, &buf, &rd_amount, -1, BUFLEN);
if (err)
error (1, err, "Could not read from file %s", argv[1]);
if (rd_amount == 0)
break;
/* Write */
ptr = buf;
do
{
err = io_write (out, ptr, rd_amount, -1, &wr_amount);
if (err)
error (1, err, "Could not write to file %s", argv[2]);
rd_amount -= wr_amount;
ptr += wr_amount;
}
while (rd_amount);
}
mach_port_deallocate (mach_task_self (), in);
mach_port_deallocate (mach_task_self (), out);
return 0;
}
Les parties intéressantes sont :
out = file_name_lookup (argv[2], O_WRITE | O_CREAT | O_TRUNC, 0640);
Ici, nous créons un fichier avec la permission 640 si il n'existe pas, et récupérons sa sortie.
/* Read */ err = io_read (in, &buf, &rd_amount, -1, BUFLEN); if (err) error (1, err, "Could not read from file %s", argv[1]); if (rd_amount == 0) break;
Comme nous l'avons dit plus haut : si on ne peut lire aucun octet, cela signifie que nous avons atteint la fin du fichier. Ca ne veut pas dire qu'il n'y a pas de donnée disponible à cet instant: Si nous lisions depuis /dev/random par exemple, et qu'il n'y aurait pas de données à lire à cet instant, l'appel ne nous dirait pas qu'il n'y a pas de données, mais bloquerait jusqu'à l'arrivée de nouvelle donnée.
/* Write */
ptr = buf;
do
{
err = io_write (out, ptr, rd_amount, -1, &wr_amount);
if (err)
error (1, err, "Could not write to file %s", argv[2]);
rd_amount -= wr_amount;
ptr += wr_amount;
}
while (rd_amount);
Il n'y a pas de certitude que io_write () acceptera immédiatement toutes les données que nous essayerons de lui envoyer, donc nous devrons sans doute réessayer, mais seulement avec les données qui n'ont pas été acceptées.
Notes finales
Finalement, je voudrais préciser que sur GNU/Hurd, il n'y a pas de raison de ne pas utiliser l'excellente extension non standard de GCC. Par exemple, les fonctions imbriquées sont fréquemment utilisées dans les sources du Hurd. Il peut être très utile d'en savoir un peu sur ces extensions, donc vous devriez sans doute faire :
$ info gcc "C Extensions"
Un bon exemple de code hurdiste est $(HURD)/init/init.c, donc vous devriez y jeter un coup d'oeil. Vous ne comprendrez sans doute pas tout, mais ça n'est pas gênant. Vous trouverez d'autres sources que vous voudrez peut-être lire dans $(HURD)/utils/ et $(HURD)/sutils/.
Les bibliothèques du Hurd (Aperçu)
Il y a un certain nombre de bibliothèques qui rendent l'écriture d'un translator plus facile.
- Libtrivfs
- Est utilisée pour les translators triviaux. Dans ce cas, un translator trivial est un translator qui ne donne accès qu'à un seul fichier (nœud), et non à un répertoire complet ou même un système de fichiers. Comme les premiers translators qu'écrivent tous les nouveaux codeurs sous le Hurd sont de ce type, cette bibliothèque est la première que vous devriez étudier.
- Libnetfs
- Est la bibliothèque pour des translators donnant accès à un système de fichiers complet, et contrôlant pas directement les données sous jascentes, comme c'est le cas dans
ftpfs,nfsetshadowfs[1]
, par exemple. Libnetfs sera probablement renommée libfsserver.
- Libdiskfs
- Est aussi destinée aux systèmes de fichiers complets, mais est utilisée dans les cas ou le translator controle les données sous jascentes, comme par exemple
ext2fs,UFSettmpfs. - Libtreefs
- Est morte. Elle n'a jamais été terminée, et personne ne l'utilise. Vous ne devriez pas non plus.
- Libports
- Fournit des fonctions pour travailler avec les ports. Elle peut aussi être vu comme une abstraction des fonctionnalités que le Hurd attend d'un système transmettant des messages.
- Libstore
- L'explication suivante peut être trouvée dans le fichier d'en-tête de libstore :
Un
store(une réserve) est un bloc de stockage de taille fixe, qui peut être lu et éventuellement écrit. Cette bibliothèque implémente plusieurs interfaces différentes qui autorisent la réserve à être utilisé avec plusieurs types de stockage -- fichiers, mémoire, tâches, etc. Elle permet aussi de combiner et de filtrer les réserves de différentes manières. - Libiohelp
- bibliothèque fournissant des fonctions utiles aux serveurs d'entrées/sorties.
- Libthreads
- Est la bibliothèque des cthreads. Cette bibliothèque viens du micronoyau Mach et à été développée avant que le standard des threads POSIX existe. Les threads POSIX seront disponibles à l'avenir, mais pour l'instant, cette bibliothèque est utilisée pour le multithreading.
- Libihash
- fournit des fonctions destinées aux hash-table à clefs entières.
- Libps
- Routines pour récupérer et afficher des informations sur les processus.
- Libshouldbeinlibc
- Nomen est omen.
Un exemple utilisant trivfs
GNU/Linux et GNU/Hurd
Avant de regarder de plus prêt comment utiliser trivfs, regardons comment ce serait fait dans un système GNU/Linux. Je ne vais pas entrer dans les détails de cette exemple, car ce n'est pas très important pour nous, mais comme beaucoup sont familiers avec la programmation (noyau) sous Linux, il pourrait être interessant de comparer la façon dont les choses sont faites sous GNU/Linux par rapport à GNU/Hurd.
Écrire un module pour le noyau de Linux, pour un périphérique comme /dev/one (qui bien sur, donne une infinité de 1 si vous la lisez...) est facile en théorie. Un module pour le noyau 2.4.x dans un fichier dédié ressemblerait à ça :
/* linux-one.c - Linux kernel module for /dev/one.
* Copyright (C) 2000, 2001 Wolfgang Jährling <wolfgang@pro-linux.de>
* Distributed under the terms of the GNU General Public License.
* This is distributed "as is". No warranty is provided at all.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/wrapper.h>
#include <asm/uaccess.h>
#define ONE_NAME "one"
#define ONE_MAJOR 100 /* Major device file number */
static int is_opened = 0;
/* Someone wants to open the file */
static int
device_open (struct inode *inode, struct file *file)
{
/* We allow only one simultaneous usage */
if (is_opened)
return -EBUSY;
is_opened = 1;
MOD_INC_USE_COUNT; /* Module can't be unloaded now */
return 0; /* Could be opened */
}
/* The file is closed again */
static int
device_release (struct inode *inode, struct file *file)
{
is_opened = 0;
MOD_DEC_USE_COUNT; /* Module may be unloaded now */
return 0;
}
/* Somebody wants to have lots of one's */
static ssize_t
device_read (struct file *file, char *buf, size_t len, loff_t *offset)
{
int i;
static char one = 1;
for (i = 0; i < len; i++)
if (copy_to_user (&buf[i], &one, 1))
return -EFAULT;
return len;
}
/* Now he/she wants to write something... */
static ssize_t
device_write (struct file *file, const char *buf, size_t len,
loff_t *offset)
{
/* ...but we don't care */
return len;
}
/* Let's put the supported operations in a structure */
struct file_operations one_operations =
{
NULL, /* Owner module... wonder what this means :-) */
NULL, /* seek */
device_read,
device_write,
NULL, /* readdir */
NULL, /* poll */
NULL, /* ioctl */
NULL, /* mmap */
device_open
NULL, /* flush */
device_release,
NULL, NULL, NULL, NULL, NULL /* Some others */
};
/* This is automatically called when the module is loaded */
int
init_module (void)
{
int result = register_chrdev (ONE_MAJOR, ONE_NAME, &one_operations);
if (result < 0) /* Could not register character device */
{
printk (KERN_ERR "Couldn't register device: %d.\n", result);
return result;
}
printk (KERN_INFO "Loading the %s module.\n", ONE_NAME);
return 0;
}
/* This gets called when unloading the module */
void
cleanup_module (void)
{
int result = unregister_chrdev (ONE_MAJOR, ONE_NAME);
if (result < 0)
printk (KERN_ERR "Couldn't unregister device: %d.\n", result);
else
printk (KERN_INFO "Unloading the %s module.\n", ONE_NAME);
}
Nous implémentons simplement les opérations habituelles come read (), close () et open(), plaçons des pointeurs sur ces fonctions dans une structure et enregistrons le tout comme un périphérique de caractères.
Dans l'étape suivante, nous compilerions ce fichier source en un fichier objet que nous chargerions en root grace à insmod(8) et créerions le fichier /dev/one avec :
# cd /dev && mknod one c 100 1
C'est simple à comprendre - mais difficile à mettre en pratique, pour (au moins) quatre raisons : Premièrement, une petite erreur dans le code peut causer un kernel panic; deuxièmement, vous avez besoin d'être superutilisateur pour faire tout cela; troisièmement, vous ne pouvez utiliser les fonctions de la bibliothèque GNU C, et d'autres bibliothèques utiles comme la GLib ; quatrièmement, vous devez utiliser un numéro de périphérique non utilisé (J'ai utilisé 100 ci-dessus en espérant que personne n'avait fait de même avant).
Avec le Hurd, les choses fonctionnent différemment. Bien sûr, la structure globale est un peu différente, mais aussi (et c'est plus important) l'environnement du code est plus sympathique : C'est l'environment normal (user space) que nous connaissons tous. Cela signifie que vous pouvez développer un translator comme n'importe quel autre programme. Le seul désavantage est que l'interface de programmation est un peu plus complexe que celle du noyau Linux. Mais si vous comprenez l'exemple ci-dessus, vous n'aurez pas de problème à suivre le translator qui vient, qui implémente la même fonctionnalité.
En fait, il fournit plus de fonctionalités, car libtrivfs nous force à en implémenter plus; quand vous implémenterez un vrai translator, vous devriez aussi vous préoccuper des options passées, car parfois, l'utilisateur demande seulement "--help", mais j'ai essayé de conserver cet exemple aussi simple que possible. Lorsque vous écrivez des programmes pour le système GNU, il est recommandé de parser les options avec la fonction argp[1]
Implémenter les fonctions de rappel de trivfs
Dans le système GNU/Hurd, les appels systèmes usuels d'Unix sont fournis par la bibliothèque GNU C qui les transforme en messages transmis aux ports respectifs. Cela signifie que vous n'écrirez pas d'implémentations directes pour des fonctions comme read (), mais des fonctions utilisant des arguments comme par exemple io_read (). Lorsque l'on utilise la bibliothèque trivfs, nous devons implémenter des routines avec des arguments quelque peu différents. Par exemple, les arguments de trivfs_S_io_read () sont :
| Type | Name | Description |
|---|---|---|
| struct trivfs_protid * | cred | Credentials |
| mach_port_t* | reply | The port where the reply will be sent |
| mach_msg_type_name_t | reply_type | The rights we have on the above port |
| vm_address_t * | data | Pointer to the place where you should write you reply data to |
| mach_msg_type_number_t< | data_len | Here you should store, how much data you actually return. Initialy, this
is set to the size of the already available memory at *data. |
| off_t | offs | Seek a position. If offs is -1, use the internal file pointer. Ignore it
if the object is not seekable. |
| mach_msg_type_number_t | amount | How much data you should write |
La fonction trivfs_S_io_read () du translator Hello, world (cf $(HURD)/trans/hello.c) est un bon exemple de comment implémenter une telle fonction. L'implémentation de notre "one" translator sera un exemple un peu moins complet.
Voilà à quoi ressemble notre fonction :
error_t
trivfs_S_io_read (struct trivfs_protid *cred,
mach_port_t reply, mach_msg_type_name_t reply_type,
vm_address_t *data, mach_msg_type_number_t *data_len,
off_t offs, mach_msg_type_number_t amount)
{
/* Deny access if they have bad credentials. */
if (!cred)
return EOPNOTSUPP;
else if (! (cred->po->openmodes & O_READ))
return EBADF;
if (amount > 0)
{
int i;
/* Possibly allocate a new buffer. */
if (*data_len < amount)
*data = (vm_address_t) mmap (0, amount, PROT_READ|PROT_WRITE,
MAP_ANON, 0, 0);
/* Copy the constant data into the buffer. */
for (i = 0; i < amount; i++)
((char *) *data)[i] = 1;
}
*data_len = amount;
return 0;
}
C'est la fonction de rappel la plus complexe de notre translator. Les autres sont plus simples.
Vous devriez toujours retourner EOPNOTSUPP (Operation non supportée) si cred est mauvais, et EBADF (Mauvais descripteur de fichier) si le bit nécessaire n'est pas en place.
Si l'utilisateur veut lire plus d'octets (le nombre dans amount) que le tampon peut contenir, nous devons allouer de la mémoire supplémentaire. Souvenez vous que nous avons eu à passer un pointeur sur le pointeur sur notre tampon, lorsque nous avons appelé io_read (). C'était pour cette raison. Nous allouons de la mémoire avec mmap () car nous voulons un bloc mémoire aligné. Maintenant que nous sommes sûrs d'avoir assez d'espace où écrire les données, nous pouvons commencer à la remplir de uns. Notez que *data est une vm_address_t et que nous devons déréférencer le pointeur pour pouvoir l'utiliser.
Si vous comprenez la fonction ci-dessus, je doute que vous ayez un problème avec la routine d'écriture suivante, qui ne fait presque rien :
kern_return_t
trivfs_S_io_write (struct trivfs_protid *cred,
mach_port_t reply, mach_msg_type_name_t replytype,
vm_address_t data, mach_msg_type_number_t datalen,
off_t offs, mach_msg_type_number_t *amout)
{
if (!cred)
return EOPNOTSUPP;
else if (!(cred->po->openmodes & O_WRITE))
return EBADF;
*amout = datalen;
return 0;
}
Mis à part pour l'habituelle vérification d'erreurs, cette fonction ne fait qu'indiquer que toute donnée que l'utilisateur voulait écrire l'a été avec succès - ce qui est logique car nous ignorons toute écriture sur notre fichier.
Il y a plusieurs fonctions de rappel que nous implémenterons d'une manière similaire à la fonction write. Vous pouvez trouvez ces fonctions dans la source complète de notre translator.
Une autre fonction de rappel est trivfs_S_io_readable (). Elle sera appelée si quelqu'un veut savoir quelle quantité de donnée nous pouvons fournir immédiatement. Bien sûr, cette quantitée est infinie, et comme l'a dit Marcus Brinkmann[1]
:
- Si vous pouvez délivrez une quantité illimitée d'octets sans délai, je pense que la valeur la plus élevée possible qui tient dans
mach_msg_type_number_test la plus appropriée. (J'espère que les applications peuvent s'arranger avec ça).
Souvenons nous que si quelque chose peut aller mal, ça ira mal. Par exemple, notre programme d'exemple dump.c ci dessus gérerait assez mal une telle situation. C'est pourquoi j'ai écrit l'implémentation suivante qui est un peu paranoïaque et ne cause pas de problème si une application n'est pas capable de gérer une grande valeur correctement :
kern_return_t
trivfs_S_io_readable (struct trivfs_protid *cred,
mach_port_t reply, mach_msg_type_name_t replytype,
mach_msg_type_number_t *amount)
{
if (!cred)
return EOPNOTSUPP;
else if (!(cred->po->openmodes & O_READ))
return EINVAL;
else
*amount = 10240; /* Dummy value: 10k */
return 0;
}
La dernière fonction de rappel interessante est trivfs_S_io_select(), qui est bien commentée dans la source complète ci-dessous.
Autres fonctions de rappel de trivfs
Nous devons travailler un peu plus avant d'avoir un translator trivfs complet : Nous devons définir des symboles qui contiennent l'information générale à propos de notre translator.
/* Trivfs hooks. */ int trivfs_fstype = FSTYPE_MISC; /* Generic trivfs server */ int trivfs_fsid = 0; /* Should always be 0 on startup */
Dans la plupart des cas, vous utiliserez FSTYPE_MISC pour trivfs_fstype.
Les autres valeurs possibles sont (d'après
$(HURD)/hurd/hurd_types.h):
- FSTYPE_IFSOCK - PF_LOCAL socket naming point
- FSTYPE_DEV - GNU Special file server
- FSTYPE_TERM - GNU Terminal driver
Dans trivfs_allow_open, vous spécifiez les permissions initiales pour votre translator :
int trivfs_allow_open = O_READ | O_WRITE;
Et avec les variables suivantes, vous spécifiez quels types d'accès sont réellement implémentés :
/* Actual supported modes: */ int trivfs_support_read = 1; int trivfs_support_write = 1; int trivfs_support_exec = 0;
La fonction main()
Les translators sont des programmes normaux, et en tant que tels, ils nécessitent une fonction main (). La bibliothèque trivfs ne définit pas de telle fonction, donc nous devons le faire nous même. Notre programme peut être lancé en tant que programme normal, ou en tant que translator donc nous devons faire la différence entre ces deux cas. Cela peut se faire en testant si le port de bootstrap vaut MACH_PORT_NULL. Si c'est le cas, le programme n'a pas été lancé en tant que translator. Généralement, un translator terminera dans ce cas. Si, cependant, notre port ne vaut pas MACH_PORT_NULL, nous devrions initialiser libtrivfs et désallouer le port de bootstrap. Enfin, nous mettrons en place le translator. Notre fonction main () (qui ne s'occupe pas des arguments passés en ligne de commande) ressemble à cela :
int
main (void)
{
error_t err;
mach_port_t bootstrap;
struct trivfs_control *fsys;
task_get_bootstrap_port (mach_task_self (), &bootstrap);
if (bootstrap == MACH_PORT_NULL)
error (1, 0, "Must be started as a ''translator''");
/* Reply to our parent */
err = trivfs_startup (bootstrap, 0, 0, 0, 0, 0, &fsys);
mach_port_deallocate (mach_task_self (), bootstrap);
if (err)
error (1, err, "trivfs_startup failed");
/* Launch. */
ports_manage_port_operations_one_thread (fsys->pi.bucket,
trivfs_demuxer, 0);
return 0;
}
Vous vous demandez peut-être pourquoi nos fonctions on des noms comme trivfs_S_io_read (). La raison est simple : nous n'avons pas besoin d'enregistrer nos fonctions de rappel. Nous leur donnons juste le nom adéquat, et libtrivfs fera le reste pour nous. Bien sûr, ces noms de fonctions ont un sens, comme l'explique Marcus Brinkmann :
-
io_readest le nom de la RPC dans le fichier.defs. Le préfixeS_précise que c'est l'accès au serveur qui est implémenté ici, plutôt que l'encapsulation/decapsulation des messages. Le préfixetrivfs_signifie que ce n'est pas la RPC de base, mais que lemach_port_test en fait converti en une autre structure. C'est fait dans intran. - Généralement, on récupère le
mach_port_ten premier argument, mais dans l'accèslibtrivfs, c'est un autre. Utilisez un grep pour intran dans$(HURD)/libtrivfs/*pour voir comment les ports sont reliés aux structures delibtrivfs.
La source complète
Arrêtons-nous là. Je laisse de coté des détails peu importants, que vous devriez trouver dans le code complet ci-dessous. Vous pouvez compiler ce fichier avec
$ gcc -g -o one hurd-one.c -ltrivfs -lfshelp
D'autres sources d'information interessantes sont $(HURD)/trans/hello.c et $(HURD)/trans/null.c.
/* hurd-one.c - A trivial single-file ''translator''
Written by Wolfgang Jährling <wolfgang@pro-linux.de>, 2001
This is based on hurd/trans/hello.c. The hello.c source says:
Copyright (C) 1998, 1999, 2001 Free Software Foundation, Inc.
Gordon Matzigkeit <gord@fig.org>, 1999
It also uses parts of hurd/trans/null.c. The null.c source says:
Copyright (C) 1995,96,97,98,99,2001 Free Software Foundation, Inc.
Written by Miles Bader <miles@gnu.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA */
#define _GNU_SOURCE 1
#include <hurd/trivfs.h>
#include <stdlib.h> /* exit () */
#include <error.h> /* Error numers */
#include <fcntl.h> /* O_READ etc. */
#include <sys/mman.h> /* MAP_ANON etc. */
/* Trivfs hooks. */
int trivfs_fstype = FSTYPE_MISC; /* Generic trivfs server */
int trivfs_fsid = 0; /* Should always be 0 on startup */
int trivfs_allow_open = O_READ | O_WRITE;
/* Actual supported modes: */
int trivfs_support_read = 1;
int trivfs_support_write = 1;
int trivfs_support_exec = 0;
/* May do nothing... */
void
trivfs_modify_stat (struct trivfs_protid *cred, struct stat *st)
{
/* .. and we do nothing */
}
error_t
trivfs_goaway (struct trivfs_control *cntl, int flags)
{
exit (EXIT_SUCCESS);
}
error_t
trivfs_S_io_read (struct trivfs_protid *cred,
mach_port_t reply, mach_msg_type_name_t reply_type,
vm_address_t *data, mach_msg_type_number_t *data_len,
off_t offs, mach_msg_type_number_t amount)
{
/* Deny access if they have bad credentials. */
if (!cred)
return EOPNOTSUPP;
else if (!(cred->po->openmodes & O_READ))
return EBADF;
if (amount > 0)
{
int i;
/* Possibly allocate a new buffer. */
if (*data_len < amount)
*data = (vm_address_t) mmap (0, amount, PROT_READ|PROT_WRITE,
MAP_ANON, 0, 0);
/* Copy the constant data into the buffer. */
for (i = 0; i < amount; i++)
((char *) *data)[i] = 1;
}
*data_len = amount;
return 0;
}
kern_return_t
trivfs_S_io_write (struct trivfs_protid *cred,
mach_port_t reply, mach_msg_type_name_t replytype,
vm_address_t data, mach_msg_type_number_t datalen,
off_t offs, mach_msg_type_number_t *amout)
{
if (!cred)
return EOPNOTSUPP;
else if (!(cred->po->openmodes & O_WRITE))
return EBADF;
*amout = datalen;
return 0;
}
/* Tell how much data can be read from the object without blocking for
a "long time" (this should be the same meaning of "long time" used
by the nonblocking flag. */
kern_return_t
trivfs_S_io_readable (struct trivfs_protid *cred,
mach_port_t reply, mach_msg_type_name_t replytype,
mach_msg_type_number_t *amount)
{
if (!cred)
return EOPNOTSUPP;
else if (!(cred->po->openmodes & O_READ))
return EINVAL;
else
*amount = 10000; /* Dummy value */
return 0;
}
/* Truncate file. */
kern_return_t
trivfs_S_file_set_size (struct trivfs_protid *cred, off_t size)
{
if (!cred)
return EOPNOTSUPP;
else
return 0;
}
/* Change current read/write offset */
error_t
trivfs_S_io_seek (struct trivfs_protid *cred, mach_port_t reply,
mach_msg_type_name_t reply_type, off_t offs, int whence,
off_t *new_offs)
{
if (! cred)
return EOPNOTSUPP;
else
return 0;
}
/* SELECT_TYPE is the bitwise OR of SELECT_READ, SELECT_WRITE, and
SELECT_URG. Block until one of the indicated types of i/o can be
done "quickly", and return the types that are then available.
TAG is returned as passed; it is just for the convenience of the
user in matching up reply messages with specific requests sent. */
kern_return_t
trivfs_S_io_select (struct trivfs_protid *cred,
mach_port_t reply, mach_msg_type_name_t replytype,
int *type, int *tag)
{
if (!cred)
return EOPNOTSUPP;
else
if (((*type & SELECT_READ) && !(cred->po->openmodes & O_READ))
|| ((*type & SELECT_WRITE) && !(cred->po->openmodes & O_WRITE)))
return EBADF;
else
*type &= ~SELECT_URG;
return 0;
}
/* Well, we have to define these four functions, so here we go: */
kern_return_t
trivfs_S_io_get_openmodes (struct trivfs_protid *cred, mach_port_t reply,
mach_msg_type_name_t replytype, int *bits)
{
if (!cred)
return EOPNOTSUPP;
else
{
*bits = cred->po->openmodes;
return 0;
}
}
error_t
trivfs_S_io_set_all_openmodes (struct trivfs_protid *cred,
mach_port_t reply,
mach_msg_type_name_t replytype,
int mode)
{
if (!cred)
return EOPNOTSUPP;
else
return 0;
}
kern_return_t
trivfs_S_io_set_some_openmodes (struct trivfs_protid *cred,
mach_port_t reply,
mach_msg_type_name_t replytype,
int bits)
{
if (!cred)
return EOPNOTSUPP;
else
return 0;
}
kern_return_t
trivfs_S_io_clear_some_openmodes (struct trivfs_protid *cred,
mach_port_t reply,
mach_msg_type_name_t replytype,
int bits)
{
if (!cred)
return EOPNOTSUPP;
else
return 0;
}
int
main (void)
{
error_t err;
mach_port_t bootstrap;
struct trivfs_control *fsys;
task_get_bootstrap_port (mach_task_self (), &bootstrap);
if (bootstrap == MACH_PORT_NULL)
error (1, 0, "Must be started as a ''translator''");
/* Reply to our parent */
err = trivfs_startup (bootstrap, 0, 0, 0, 0, 0, &fsys);
mach_port_deallocate (mach_task_self (), bootstrap);
if (err)
error (1, err, "trivfs_startup failed");
/* Launch. */
ports_manage_port_operations_one_thread (fsys->pi.bucket,
trivfs_demuxer, 0);
return 0;
}
Deboguer un translator
Ce chapitre nécessite des connaissances sur l'utilisation de GDB, le débogueur GNU. Si vous n'avez pas utilisé GDB avant, je vous recommande la lecture de la session d'exemple dans la documentation Texinfo de GDB. Si info et la documentation sont installées sur votre système, faites simplement
$ info gdb "Sample Session"
Maintenant, comment debugger un translator ? C'est plutôt simple, mais je vais vous expliquer quand même. La manière la plus simple est de démarrer votre programme en tant que translator actif :
$ gcc -g -o one one.c -ltrivfs -lfshelp $ settrans -ac foo one
Maintenant, le translator est en route. Vous pouvez le voir dans la liste des processus :
$ ps Aux
(Le ps POSIX n'est pas encore disponible, donc ps aux ne fonctionnera pas, désolé.) Maintenant, nous avons besoin de nous rattacher au processus lancé. Par exemple, si le PID était 357, nous ferions :
$ gdb one 357
On peut alors insérer des points d'arrêt, puis laisser le translator continuer :
(gdb) break trivfs_S_io_read (gdb) c
Maintenant, vous devriez utiliser un autre terminal, et entrez une commande comme
$ cat foo
puis retournez au terminal contenant GDB. Vous verrez qu'il s'est arreté au point d'arrêt. Vous pouvez maintenant debugger comme d'habitude. Facile n'est-ce pas ?
Une fois terminé, utilisez
(gdb) quit
et dites que vous voulez détacher le processus. Nous pouvons conclure en disant que vous n'avez pas besoin de technique spécifique pour debugger un translator.
Maintenant, vous avez sans doute une idée de la façon dont on peut développer des serveurs pour le Hurd (des translators). Souvent, vous aurez besoin de plus que ce que fournit libtrivfs. Pour des information (pas forcément à jour) sur les autres bibliothèques, vous pouvez regardez le Hurd Reference Manual (info hurd), aussi disponible dans $(HURD)/doc/hurd.texi. Pour des informations plus à jour, il faut se référer aux fichiers d'en-tête appropriés.
Annexes
Foire Aux Questions
_
.-[_]<-.
v | _`. T h e G N U H u r d
[_] `->[_]|
^ _ ,',' ...be a part of it!
`.[_]-+-'
`.__.'
- Comment un translator peut-il accéder aux noeuds sous jascents ? Un translator
gzipdoit le faire par exemple. - Le noeud sous jascent est renvoyé par
fsys_startup (). (cf$(HURD)/hurd/fsys.defs).
- Peut-on empiler les translators ?
- Oui, empiler les translators actifs est possible, mais c'est impossible avec les translators passifs.
- Qu'est-ce qu'un
protid? -
protsignifie protection. Toute structureprotiddéfini un client unique de notre translator. Vous pouvez accéder aux informations respectives par le champspo(per-open) de la structure.
- Quel type de threads devrais-je utiliser si je veux écrire un programme fonctionnant à la fois sous GNU/Linux et GNU/Hurd ?
- Utilisez plutôt les pthreads, nous les aurons bientôt...
- Comment peut-on dire que l'on est compatible POSIX sans avoir de pthreads ?
- Premièrement, les pthreads sont optionnels dans les spécifications POSIX. Deuxièmement, nous avons des pthreads. Par exemple, GNU Portable Threads (pth) fournissent une émulation non préemptive des pthreads. Cela semble etre en accord avec les standards, mais certes assez peu utile puisque la majeure partie des programmes supposent que le multi-thread est préemptif. Jeroen Dekkers travaille sur de vrais pthreads, qui fonctionnent partiellement pour l'instant.
- Dans le manifeste GNU, RMS écrit que C et LISP seront des langages système. Qu'en est-il ?
- L'interpréteur de Scheme Guile est une partie du projet GNU. Pour l'instant il ne fournit que les fonctionnalités POSIX, mais il n'y a pas de raison que personne n'ajoute de choses spécifiques à GNU. Ajouter le support pour divers langages serait bien en fait. Ce n'est cependant pas urgent.
- Pourquoi apprendre à utiliser Mach si le Hurd change pour L4 bientôt ?
- Pour l'instant, le Hurd utilise Mach, et vous avez besoin d'en connaître les bases pour travailler sous le Hurd. Peut-être que le Hurd tournera sur L4 dans quelque temps, mais ce n'est pas encore le cas.
Notes de bas de page

