Sommaire Drivers     Index Drivers


Drivers

Ecrire des Drivers nécessite des connaissances additionnelles sur le fonctionnement interne de BeOS. Vous devrez suivre soigneusement les règles exposées dans ce chapitre pour écrire un Driver. Ces règles diffèrent de celles utilisées pour écrire une application standard - si votre Driver tente de faire des choses interdites, il peut faire planter le système.

Cette introduction décrit la manière dont les Drivers interagissent avec le Noyau BeOS.


Le Noyau et les créateurs de Drivers

Le Noyau BeOS dispose des fonctionnalités de base du système d'exploitation : Il sait démarrer le processus de boot, gérer la mémoire et les threads, il contient le gestionnaire du bus PCI, le gestionnaire du bus ISA, le Driver du système de fichiers (devfs, qui gère /dev), le système de fichiers racine (rootfs, qui gère /), et d'autres choses encore.

Mais ce n'est pas assez pour satisfaire les besoins de nombreuses applications, le Noyau utilise alors des compléments (add-ons) qui procurent des fonctionnalités additionnelles. Durant le boot, ces compléments sont chargés pour gérer des systèmes de fichiers "réels", des périphériques, des bus, etc.

Bien que les compléments au Noyau Be supportent une large gamme de matériels - des disques aux joy-sticks - tout n'est pas pris en compte. Les développeurs peuvent avoir besoin de développer leurs propres Drivers pour leurs produits.


Types de compléments du Noyau

Il existe trois types de compléments du Noyau :

Les Drivers sont des compléments qui communiquent directement avec les périphériques.

Les Modules sont des compléments de l'espace Noyau qui procurent une API utilisée par les Drivers (ou par d'autres Modules).

Les systèmes de fichiers sont des compléments qui supportent des systèmes de fichiers spécifiques comme BFS, DOSFS, HFS, etc.

Les Drivers et les Systèmes de fichiers, tout en étendant les fonctionnalités du Noyau, sont toujours accessible via l'espace utilisateur : Les Applications peuvent les ouvrir et les adresser en utilisant des descripteurs de fichiers. Les Modules, au contraire, font partie du Noyau. Les Applications n'y ont pas accès ; ils sont mis à disposition pour être strictement utilisés par le Noyau ou des compléments au Noyau.

Drivers

Un Driver est un complément qui reconnaît un périphérique spécifique (ou une classe de périphériques) et permet au reste du système de communiquer avec lui. Le plus souvent cette communication met en jeu un quelconque protocole spécifique au Driver. Par exemple, si le système veut utiliser une carte Ethernet ou une carte graphique, il doit charger le Driver qui sait comment communiquer avec cette carte. De même, le code qui sait dialoguer avec une classe de périphériques (disques SCSI ou ATAPI, périphériques ATA ou USB, etc.) doit être implémenté sous la forme d'un Driver.

Modules

Les Modules fournissent une API uniforme destinée à d'autres Modules ou à des Drivers. Un Module ressemble à une bibliothèque (library) se comportant comme un dépositaire du code partagé entre plusieurs Drivers.

Disons par exemple que vous disposez d'un Driver qui dialogue avec un périphérique SCSI connecté à un bus SCSI. Un ordinateur peut posséder de multiples bus SCSI. Comme tous les périphériques SCSI utilisent le même ensemble de commandes, indépendant du contrôleur particulier qui les envoie, cet ensemble de commandes peut être (et en fait est) implémenté en tant que Module. Le Module SCSI sait comment gérer toutes les cartes SCSI supportées par BeOS ; l'API définie par le Module SCSI est partagée et augmentée des Modules de périphériques SCSI spécifiques (disques durs, scanners, lecteurs de CD, etc.). Les Modules de périphériques SCSI sont gérés par un Module de bus SCSI, qui sait se débrouiller avec de multiples bus et les encapsuler pour les Drivers. Ainsi les Drivers n'ont affaire qu'à l'API du gestionnaire de bus, ce qui rend la vie des créateurs de Drivers beaucoup plus facile.

Be fournit des gestionnaires de bus SCSI, USB, IDE, and PCMCIA.

Systèmes de fichiers

Les compléments Systèmes de fichiers prennent en charge des systèmes de fichiers disques et réseaux tels que BFS, HFS, FAT, ISO 9660, CIFS, etc. En créant de nouveaux compléments, les développeurs peuvent donner accès à des disques formatés pour d'autres Systèmes de fichiers.


Interactions avec le Noyau

Le Noyau fournit nombre de services que Drivers et Modules peuvent utiliser. Ces services sont :

Le Noyau fournit également, au niveau utilisateur, une API Posix-like pour accéder aux périphériques. Une application peut ouvrir un périphérique via open(), et utiliser read(), write(), et ioctl() pour y accéder.

Les fonctions Posix sont convertis en appels système vers le Noyau, qui les communique ensuite, via devfs, au Driver approprié.


devfs

Le Noyau gère les Drivers par l'intermédiaire de devfs, le système de fichier monté sur /dev durant le boot. Pour être accessible, un Driver doit se rendre "public" en ajoutant une entrée dans l'arborescence de /dev. Les fonctions Posix de base (open(), read(), write(), readv(), writev(), ioctl(), and close()) peuvent alors être utilisées.

Devfs rend les Drivers accessibles selon les besoins dans /dev ; Cela se produit généralement la première fois qu'un programme parcoure le répertoire à la recherche d'un sous-répertoire de /dev. Le Noyau sait où publier les Drivers dans /dev sur la base de leur emplacement dans /boot/beos/system/add-ons/kernel/Drivers/dev. Par exemple, le Driver ATAPI est publié dans /dev/disk/ide/atapi, le Driver se trouve dans /boot/beos/system/add-ons/kernel/Drivers/dev/disk/ide/atapi. Ben mon colon !

Vous pouvez consulter l'arborescence des périphériques en utilisant la commande "ls" à partir d'une fenêtre du Terminal. "ls / dev" montrera la racine de l'arborescence des périphériques, "ls /dev/disk" affiche les bus, "ls /dev/disk/ide" affichera les périphériques IDE, et ainsi de suite.

En réalité, les Drivers tendent à se publier eux-mêmes à de multiples emplacements dans l'arborescence de /dev, alors au lieu de placer des copies du Driver dans l'arborescence de .../Drivers/dev, les exécutables sont placés dans /boot/beos/system/add-ons/kernel/Drivers/bin, et des liens symboliques sont créés dans .../Drivers/dev à l'emplacement approprié. (la même chose existe pour les Drivers dans /boot/home/config/add-ons/kernel/Drivers/....).


Principes d'implémentation des Drivers

La plus grande part de la stabilité de BeOS est atteinte en construisant un mur quasi impénétrable entre le Noyau et les applications utilisateur. Les Drivers sont des brèches dans ce mur. Si un Driver se comporte mal ou "plante", il existe une forte probabilité de comportement erratique ou même de crash du système. Il est absolument critique que les Drivers soient non seulement soigneusement testés avant livraison, mais qu'ils respectent à la lettre les règles.


Espace Noyau / espace Utilisateur

Placer autant de code que possible au sein de l'espace utilisateur est un moyen de réduire le risque que le Driver provoque une défaillance du système. Il faut créer un Driver qui charge seulement dans l'espace Noyau le code prenant en charge les interactions de bas niveau qui doivent impérativement y avoir lieu, puis qui charge ensuite dans l'espace utilisateur le code effectuant les tâches restantes. Si le complément ne fonctionne pas, le système continue à tourner - Seul le Driver fait défaut.

Placer autant de code que possible au sein de l'espace utilisateur a également pour avantage qu'il est beaucoup plus aisé de déboguer le code qui s'exécute dans cet espace. Les techniques traditionnelles de déboguage, qui ne fonctionnent pas dans l'espace Noyau, sont ici utilisables et il y a moins de chance de planter le système durant le processus.


Synchronisation de programme

En principe les "verrous en boucle" (spinlocks) ne sont pas recommandés. Un spinlock est une petite boucle de programme qui surveille la réalisation d'une condition, tournant indéfiniment jusqu'à ce que la condition soit rencontrée (c'est ce qu'on appelle une "attente active"). Ce procédé consomme des ressources processeur et, en principe, n'est pas recommandée.

Il est plutôt recommandé d'utiliser les sémaphores au lieu des spinlocks ; il est cependant impossible d'acquérir un sémaphore lorsqu'on gère une interruption. S'il est nécessaire de synchroniser du code durant la gestion d'une interruption, il faut obligatoirement employer un spinlock. En résumé :

Là où vous employez un spinlock pour protéger une section de code critique, vous devrez inactiver les interruptions. Bien sûr, s'il s'agit d'un gestionnaire d'interruptions, celles-ci sont déjà inactives et n'ont pas besoin d'être explicitement désactivées. Les gestionnaires d'interruptions incorporent les interruptions E/S installés à l'aide de install_io_interrupt() et les timers installés en appelant add_timer().

Fonctions disponibles avec les Spinlocks

Quand un spinlock s'exécute, les actions suivantes sont permises. Tout autre action est interdite.

Si vous faîtes autre chose dans un spinlock, vous ne respectez pas les règles, alors ne le faîtes pas.

Utilisation des Spinlocks

Vous devez vous assurer qu'à chaque acquire_spinlock() correspond un release_spinlock(). De plus, si des spinlocks sont enchâssés, ils doivent être relâchés dans l'ordre logique, c'est à dire dans l'ordre inverse de leur mise en fonction.

Le Noyau garde la trace des spinlocks en fonction et en attente. Le Noyau présuppose que les spinlocks sont initialisés à zéro, mis en fonction et relâchés dans l'ordre logique.

En gardant la trace des spinlocks, le Noyau peut détecter et rompre les étreintes mortelles (deadlocks) sur les systèmes multiprocesseurs.


Inactiver les interruptions

Le seul moment où vous devez le cas échéant inactiver une interruption, c'est juste avant d'entrer dans une section de code critique protégée par un spinlock. Il n'y a aucune autre raison de le faire, donc ne le faîtes pas.

Vous devez réactiver les interruptions le plus rapidement possible après leur désactivation. Vous ne devez jamais, sous aucun prétexte, laisser les interruptions inactives durant plus de 50 microsecondes. Ce qui signifie aussi que le code de votre gestionnaire d'interruption (qui s'exécute avec les interruptions implicitement désactivées) doit s'exécuter en 50 microsecondes ou moins.

Fonctions disponibles pendant que les interruptions sont inactives

Si les interruptions sont inactivées et sans spinlock, les actions suivants sont possibles, en plus de celles listées ci-dessus dans "Fonctions disponibles pendant les spinlocks":

S'il vous apparaît nécessaire d'appeler une fonction qui n'est pas autorisée dans la liste, contactez le Support de développement Be à devsupport@be.com et exprimer vos besoins ; nous serons toujours heureux d'en discuter avec vous.

Pas de blocage

Il est crucial que votre gestionnaire d'interruptions ne bloque jamais, soit directement (en acquérant un sémaphore par exemple) ou indirectement (en appelant une fonction qui peut bloquer).

Un blocage peut se produire dans un surprenant grand nombre de fonctions BeOS. C'est évident avec acquire_sem(), mais vous devez être averti que c'est possible dans des fonctions comme malloc() ou read_port(). Même accéder à des zones mémoire non protégées peut provoquer un blocage, pour cause de conflit avec la mémoire virtuelle.

Mettons les point sur les i : Si la fonction BeOS désirée n'est pas dans la liste autorisée ci-dessus, ne l'appelez pas.

Pas de préemption

Votre gestionnaire d'interruptions ou votre spinlock ne peut être préempté. Une préemption peut survenir si vous appelez release_sem() ou release_sem_etc() sans spécifier le flag B_DO_NOT_RESCHEDULE. Normalement, release_sem() laisse le scheduler préempter votre tâche pour permettre à d'autres tâches d'acquérir le sémaphore le plus vite possible. En spécifiant le flag B_DO_NOT_RESCHEDULE, vous demander au scheduler d'autoriser votre tâche à continuer son exécution après avoir libéré les sémaphore.

Si votre gestionnaire d'interruption veut faire en sorte que la préemption soit immédiatement gérée, il doit spécifier le flag B_DO_NOT_RESCHEDULE en appelant release_sem(), et ensuite retourner B_INVOKE_SCHEDULER. De cette façon le scheduler gère immédiatement la préemption lors du retour de votre gestionnaire d'interruption, au lieu de reprendre la tâche interrompue. C'est tout spécialement utile si votre code a appelé release_sem_etc() pour libérer un sémaphore qui permettra à un autre programme de s'exécuter ailleurs (tel le code dans l'espace utilisateur de votre Driver).

 


Encore une fois, lors d'un appel à release_sem_etc(), soyez certain de spécifier le flag B_DO_NOT_RESCHEDULE pour éviter toute préemption.


En résumé, l'ordre des actions à respecter est le suivant :

Fichier en E / S

Un Driver a parfois besoin d'accéder à des fichiers sur disque. Peut-être parce que le Driver a besoin de lire un fichier des préférences. Il y a deux manière de procéder. Utiliser des appels E / S Posix, ou employer les propriétés de l'API Driver de BeOS. Cette dernière méthode est préférable.

Utiliser des appels Posix

Sous BeOS, les Drivers peuvent accéder aux fichiers sur disque en employant les fonctions E / S Posix de bas niveau : open(), close(), read(), write(), etc. Il n'y aucune action spécifique préalable. Juste open() le fichier et faire ce qui est prévu.

Deux extensions Posix peuvent se révéler utiles lors de l'écriture du programme du Driver qui accède au fichier : readv() et writev().

   int readv(int fd, const struct iovec *vector, size_t count);
   
   int writev(int fd, const struct iovec *vector, size_t count);
   
   struct iovec {
      __ptr_t iov_base;
      size_t iov_len;
   };

Ces fonctions fournissent le moyen de lire et d'écrire des portions contiguës de fichier via des tampons multiples. vector est un pointeur sur un tableau qui contient count enregistrements vecteur, chacun contenant un pointeur sur un tampon et la taille du tampon. readv()remplit ces tampons avec les données du fichier et writev() les écrit, dans l'ordre, dans le fichier.

En cas de succès, readv() retourne le nombre d'octets lus.

Par exemple, si votre programme a besoin d'écrire deux tampons séparés de 1K dans un fichier, l'un après l'autre, vous pourriez procéder comme suit :

   struct iovec v[2];
   v[0].iov_base = &buffer1;
   v[0].iov_len = 1024;
   v[1].iov_base = &buffer2;
   v[1].iov_len = 1024;
   if (writev(fd, &v, 2) != B_OK) {
      /* error */
   }

Exécuter des E /S vectorisées est souvent plus rapide que de faire de multiples appels à read() et à write().

Les propriétés de l'API de Driver

Si votre Driver est chargé avant le système de fichier du disque sur lequel réside votre fichier de préférence, le Driver ne pourra charger ses paramètres via les appels Posix. Les propriétés de L'API de Driver vous tireront alors d'affaire. Cf. la section "Propriétés de l'API de Driver" pour plus de détails.


Sommaire Drivers     Index Drivers


The Be Book,
...in lovely HTML...
for BeOS Release 5.

Copyright © 2000 Be, Inc. All rights reserved..