Cette manière de travailler nous a permis d'apporter quelques améliorations par rapport au sujet demandé et quelques optimisations par rapport à un code simple. Nous allons donc discuter de ces personnalisations dans cette section.
Bien évidemment, il est tout à fait possible d'ouvrir le fichier spécial de lecture et celui d'écriture en même temps. En revanche, pour les même raisons que précédemment, lorsqu'il y a une opération de lecture (respectivement d'écriture) sur un périphérique, il n'est pas possible de réaliser une opération d'écriture (respectivement de lecture). Cela est réalisé par la variable in_use de la structure private_dummy (propre à chaque périphérique), protégé par le sémaphore in_use_critique de cette même structure.
On pourrait être amener à penser que cette politique est une limitation, mais en fait, en plus de préserver l'intégrité de la liste doublement chainée où sont stockées les données de l'utilisateur, cela préserve aussi l'intégrité des données elles-même. En effet, on peut facilement imaginer que si deux processus écrivent en même temps dans le périphérique et que celui gère cette situation (en mélangeant le flux), les données ainsi stockées n'auraient pas une grande signification...
Mais qu'en est-t-il vraiment lorsqu'une telle situation arrive, faut-il que le second processus soit bloqué, ou non ? En fait, nous avons fais le choix de laisser le choix à l'utilisateur :) ! Ainsi, en fonction du mode d'ouverture (paramètre flags de la fonction open(2)) le pilote ne se comportera pas de la même manière (bloquant ou non). Pour cela, nous vérifions simplement si l'utilisateur a positionné le drapeau O_NONBLOCK.
Certes l'utilisation d'une liste doublement chainée n'est pas une mauvaise idée, mais ce n'est pas non plus remarquable... En fait, nous nous sommes concentré sur le traitement de ces listes dans les boucles. En effet, nous avons fait en sorte de sortir un maximum de code des boucles afin des les rendre plus rapide, car il est mauvais de rester trop longtemps dans le code noyau sans rendre la main ! Nous avons donc fait en sorte que les tests de sortie de boucle ne soit pas pollués par des cas particuliers. Nous pouvons citer pas exemple le cas d'une écriture, où le test pour savoir si la liste est vide est avant la boucle (et nous alloueons donc un élément en cas de besoin). Il faut bien noter qu'en plus d'enlever des intructions de la boucle, cela enlève surtout des conditions qui peuvent être désastreuse en terme de performances sur certaines architectures...
Certe, avec un noyau préemptif cela pourrait être résolu, mais faut-il encore avoir compilé son noyau avec cette fonctionnalité... Nous n'avons fait aucune hypothèse en ce qui concerne cela, et nous avons choisi de gérer cette situation par nous même. C'est ainsi que nous avons rajouté des appels à l'ordonnanceur dans nos boucles. Les résultats sont à la hauteur de nos espérances, puisque pour le même test, le système est complêtement fonctionnel tout le long du stockage.
Il faut tout de même noter que l'opération d'écriture (ou de lecture) est alors plus lente, mais cela ne veut pas dire que l'on reste plus longtemps dans le code noyau, bien au contraire. Ce ralentissement du processus en cours d'opération est du au fait qu' on le réordonnance souvent, mais le système globale est infiniment plus rapide.
Afin de supprimer les bogues liés à l'utilisation de la mémoire dynamique, nous avons fait en sorte de limiter les appels à kmalloc et kfree. Ainsi, tout ce qui pouvait être fait en static l'a été, et lorsque l'on pouvait grouper ces appels, nous ne nous sommes pas privé. Par exemple, au début nous utilisions un tableau (dynamique) d'entier pour savoir si le péripérique était occupé, puis nous avons remplacé ce système en ajoutant la variable in_use (qui a le même rôle) dans la structure private_dummy . Etant donné qu'il existe un tableau de cette structure, nommé device_fifo, qui a exactement la bonne taille (puisqu'il a la même correspondante entre ces éléments et les périphériques), nous avons donc un code plus propre (tout ce qui concerne un périphérique est dans une unique structure) et surtout nous nous affranchissons d'une allocation et d'une libération (qui sont souvent l'origine d'un bogue).
Toujours en ce qui concerne la mémoire dynamique, nous prenons soin de mettre les pointeurs à NULL lorsqu'on les désalloue. Ainsi, si on le réutilise par mégarde, on est prévenu immédiatement :) !
Il y a encore d'autre détails que l'on a oublié de mentionner (par exemple que l'on vérifie tous les retours de fonctions) ou qui n'ont pas une grande utilité par rapport à la compréhension de notre projet (par exemple le respect du Linux Kernel Coding Style), mais nous préférons ne pas ennuyer le lecteur d'avantage :) !