Pourquoi les pilotes et les firmwares sont-ils presque toujours écrits en C ou ASM et non en C ++?

Je suis juste curieux de savoir pourquoi les pilotes et les firmwares sont presque toujours écrits en C ou en Assembly, et non en C ++?

J’ai entendu dire qu’il y avait une raison technique à cela.

Est-ce que quelqu’un le sait?

Beaucoup d’amour, Louise

Parce que la plupart du temps, le système d’exploitation (ou une “bibliothèque d’exécution”) fournit la fonctionnalité stdlib requirejse par C ++.

Dans C et ASM, vous pouvez créer des exécutables nus, qui ne contiennent aucune dépendance externe.

Toutefois, Windows prenant en charge le stdlib C ++, la plupart des pilotes Windows sont écrits en C ++ (un sous-ensemble limité).

En outre, lorsque le microprogramme est écrit ASM, c’est généralement parce que (A) la plate-forme sur laquelle il s’exécute n’a pas de compilateur C ++ ou (B) qu’il existe des contraintes de vitesse ou de taille extrêmes.

Notez que (B) n’a généralement pas été un problème depuis le début des années 2000.

Le code du kernel s’exécute dans un environnement très différent de celui de l’espace utilisateur. Il n’y a pas de séparation des processus, donc les erreurs sont beaucoup plus difficiles à récupérer. les exceptions sont pratiquement hors de question. Il y a différents allocateurs de mémoire, il peut donc être plus difficile d’obtenir de new et de delete pour fonctionner correctement dans un contexte de kernel. Il y a moins de bibliothèque standard disponible, ce qui rend beaucoup plus difficile l’utilisation d’un langage comme le C ++.

Windows permet l’utilisation d’un sous – ensemble très limité de C ++ dans les pilotes du kernel. essentiellement, les choses qui pourraient être sortingvialement traduites en C, telles que les déclarations de variables en des lieux autres que le début des blocs. Ils déconseillent l’utilisation de new et delete et ne prennent pas en charge RTTI ou la plupart des bibliothèques standard C ++.

Mac OS X utilise I / O Kit , qui est un framework basé sur un sous-ensemble limité de C ++, mais pour autant que je sache, plus complet que celui autorisé sous Windows. Il s’agit essentiellement de C ++ sans exceptions et RTTI.

La plupart des systèmes d’exploitation de type Unix (Linux, BSD) sont écrits en C, et je pense que personne n’a jamais vraiment vu l’intérêt d’append un support C ++ au kernel, étant donné que C ++ dans le kernel est généralement limité.

1) “Parce que ça a toujours été comme ça” – cela explique en fait plus que vous ne le pensez – étant donné que les API sur presque tous les systèmes actuels étaient à l’origine écrits sur un modèle basé sur C ou ASM, C et ASM, il est souvent plus facile de suivre le rythme que de tirer parti de C ++.

2) Environnement – Pour utiliser toutes les fonctionnalités du C ++, vous avez besoin d’un environnement d’exécution, dont certains sont simplement difficiles à fournir à un pilote. C’est plus facile à faire si vous limitez votre jeu de fonctionnalités, mais entre autres, la gestion de la mémoire peut devenir très intéressante en C ++, si vous n’avez pas beaucoup de tas. Les exceptions sont également très intéressantes à considérer dans cet environnement, tout comme RTTI.

3) “Je ne vois pas ce que ça fait”. Il est possible pour tout programmeur raisonnablement qualifié d’examiner une ligne de C et d’avoir une bonne idée de ce qui se passe au niveau du code machine pour implémenter cette ligne. Évidemment, l’optimisation change quelque peu, mais pour la plupart, vous pouvez savoir ce qui se passe. En C ++, en cas de surcharge d’opérateur, de constructeurs, de destructeurs, d’exceptions, etc., il devient très difficile de savoir ce qui va se passer sur une ligne de code donnée. Lorsque vous écrivez des pilotes de périphériques, cela peut être mortel, car vous devez souvent savoir si vous allez interagir avec le gestionnaire de mémoire ou si la ligne de code affecte (ou dépend) des niveaux d’interruption ou du masquage.

Il est tout à fait possible d’écrire des pilotes de périphériques sous Windows en utilisant C ++ – je l’ai fait moi-même. La mise en garde est que vous devez faire attention aux fonctionnalités C ++ que vous utilisez et à partir desquelles vous les utilisez.

Hormis un support d’outils et une portabilité du matériel plus étendus, je ne pense pas qu’il y ait une raison convaincante de se limiter à C. Je vois souvent des trucs compliqués codés à la main en C qui peuvent être faits plus naturellement en C ++:

  • Le regroupement en “modules” de fonctions (à usage non général) qui ne fonctionnent que sur la même structure de données (souvent appelée “object”) -> Utiliser les classes C ++.
  • Utilisation d’un pointeur “handle” pour que les fonctions du module puissent fonctionner avec les “instances” des structures de données -> Utiliser les classes C ++.
  • Utilisation de macros de type fonction -> Modèles C ++ et fonctions inline
  • Comportement d’exécution différent dépendant d’un ID de type avec soit vtable faite à la main (“descriptor”) ou dissortingbué avec une instruction switch -> polymorphism C ++
  • Arithmétique de pointeur sujette aux erreurs pour regrouper / démasquer des données depuis / vers un port de communication ou utilisation de structures non portables -> Concept de stream C ++ (pas nécessairement std :: iostream)
  • Préfixe l’enfer de tout pour éviter les conflits de noms: espaces de noms C ++

Aucune des fonctionnalités C ++ décrites ci-dessus ne coûte plus cher que les implémentations C écrites à la main. Il me manque probablement un peu plus. Je pense que l’inertie de C dans ce domaine est davantage liée à l’utilisation de C principalement.

Bien sûr, vous ne pourrez peut-être pas utiliser la STL librement (ou pas du tout) dans un environnement restreint, mais cela ne signifie pas que vous ne pouvez pas utiliser C ++ comme un “meilleur C”.

La principale raison de l’utilisation de C au lieu de Java extrêmement protégé est qu’il est très facile de voir quelle mémoire est utilisée pour une opération donnée. C est très orienté vers l’adressage. La principale préoccupation lors de l’écriture du code du kernel est d’éviter de référencer la mémoire susceptible de provoquer une erreur de page à un moment inopportun.

C ++ peut être utilisé uniquement si le run-time est spécialement adapté pour référencer uniquement des tables internes en mémoire fixe (non paginable) lorsque la machine en exécution est appelée implicitement, par exemple en utilisant une vtable lors de l’appel de fonctions virtuelles. Cette adaptation spéciale ne sort pas du lot la plupart du temps.

L’intégration de C avec une plate-forme est beaucoup plus facile car il est facile de supprimer C de sa bibliothèque standard et de garder le contrôle des access à la mémoire totalement explicite. Donc, ce qui est aussi un langage bien connu, c’est souvent le choix des concepteurs d’outils du kernel.

Modifier : suppression de la référence aux nouveaux appels et à la suppression des appels (ceci était faux / trompeur); remplacé par l’expression plus générale “machines d’exécution”.

La raison pour laquelle C, pas C ++ est utilisé n’est PAS:

  • Parce que C ++ est plus lent
  • Ou parce que c-runtime est déjà présent.

C’EST parce que C ++ utilise des exceptions. La plupart des implémentations des exceptions de langage C ++ sont inutilisables dans le code du pilote car les pilotes sont appelés lorsque le système d’exploitation répond aux interruptions matérielles. Lors d’une interruption matérielle, le code du pilote n’est PAS autorisé à utiliser des exceptions car cela pourrait / pourrait provoquer des interruptions récursives. En outre, l’espace de stack disponible pour coder dans le contexte d’une interruption est généralement très faible (et non modifiable en raison de la règle de non-exception).

Vous pouvez bien sûr utiliser new (std :: nothrow), mais comme les exceptions en c ++ sont maintenant omniprésentes, cela signifie que vous ne pouvez pas vous fier au code de la bibliothèque pour utiliser la sémantique std :: nothrow.

C’EST aussi parce que C ++ a donné quelques fonctionnalités de C: – Dans les pilotes, le placement de code est important. Les pilotes de périphérique doivent pouvoir répondre aux interruptions. Le code d’interruption DOIT être placé dans des segments de code “non paginés” ou mappés de manière permanente dans la mémoire, car si le code était dans la mémoire paginée, il pourrait être renvoyé lorsqu’il est appelé, ce qui provoquerait une exception interdite. Dans les compilateurs C utilisés pour le développement de pilotes, il existe des directives #pragma capables de contrôler le type de fonctions de mémoire qui se terminent. Comme le pool non paginé est une ressource très limitée, vous NE voulez PAS marquer votre pilote entier comme non paginé: C ++ génère cependant beaucoup de code implicite. Constructeurs par défaut par exemple. Il n’y a aucun moyen de cocher le code généré implicitement C ++ pour contrôler son emplacement, et parce que les opérateurs de conversion sont appelés automatiquement, les audits de code ne permettent pas de garantir qu’aucun effet secondaire n’appelle le code paginé.

Donc, pour résumer: – La raison C, et non C ++, est utilisée pour le développement de pilotes, car les pilotes écrits en C ++ consumnt des quantités déraisonnables de mémoire non paginée ou bloquent le kernel du système d’exploitation.

Les commentaires que je rencontre pour expliquer pourquoi un magasin utilise C pour un système embarqué par rapport à C ++ sont les suivants:

  1. C ++ produit un gonflement du code
  2. Les exceptions C ++ prennent trop de place.
  3. Le polymorphism C ++ et les tables virtuelles utilisent trop de mémoire ou de temps d’exécution.
  4. Les gens de la boutique ne connaissent pas le langage C ++.

La seule raison valable peut être la dernière. J’ai vu des programmes en langage C qui intègrent la POO, les objects de fonction et les fonctions virtuelles. Il devient très moche très vite et gonfle le code.

La gestion des exceptions en C, une fois implémentée correctement, prend beaucoup de place. Je dirais à peu près la même chose que C ++. L’avantage des exceptions C ++: elles sont dans la langue et les programmeurs n’ont pas à repenser la roue.

La raison pour laquelle je préfère C ++ à C dans les systèmes embarqués est que C ++ est un langage typé plus puissant. Plus de problèmes peuvent être trouvés au moment de la compilation, ce qui réduit le temps de développement. En outre, C ++ est un langage plus facile pour implémenter des concepts orientés object que C.

La plupart des raisons contre le C ++ concernent les concepts de conception plutôt que le langage réel.

C est très proche d’un langage d’assemblage indépendant de la machine. La plupart des programmes de type OS sont au niveau “bare metal”. Avec C, le code que vous lisez est le code réel. C ++ peut cacher des choses que C ne peut pas.

Ceci est juste mon avis, mais j’ai passé beaucoup de temps dans ma vie à déboguer les pilotes de périphériques et les choses liées à l’OS. Souvent en regardant le langage d’assemblage. Restez simple au niveau le plus bas et laissez le niveau d’application s’améliorer.

Les pilotes Windows sont écrits en C ++.
Les pilotes Linux sont écrits en c car le kernel est écrit en c.

La raison pour laquelle les pilotes et les firmwares sont principalement écrits en C ou en ASM est que les bibliothèques d’exécution ne sont pas dépendantes. Si vous deviez imaginer ce pilote imaginaire écrit en C ici

 #include 

 #define OS_VER 5.10
 #define DRIVER_VER "1.2.3"

 int drivermain (driverstructinfo ** dsi) {
    if ((* dsi) -> version> OS_VER) {
        (* dsi) -> InitDriver ();
        printf ("FooBar Driver Loaded \ n");
        printf ("Version:% s", DRIVER_VER);
        (* dsi) -> Dispatch = fooDispatch;
    }autre{
        (* dsi) -> Exit (0);
    }
 }

 annuler fooDispatch (driverstructinfo * dsi) {
    printf ("Dispatched% d \ n", dsi-> GetDispatchId ());
 }

Notez que la prise en charge de la bibliothèque d’exécution doit être extraite et liée pendant la compilation / link, cela ne fonctionnera pas car l’environnement d’exécution (c’est-à-dire lorsque le système d’exploitation est en phase de chargement / initialisation) n’est pas complètement configuré. il n’y aurait aucune idée sur la façon d’ printf , et cela sonnerait probablement le glas du système d’exploitation (une panique du kernel pour Linux, un écran bleu pour Windows) car il n’y a aucune référence sur la façon d’exécuter la fonction.

Autrement dit, avec un pilote, le code du pilote a le privilège d’exécuter du code avec le code du kernel qui partagerait le même espace, ring0 est le privilège d’exécution du code (toutes les instructions sont autorisées), ring3 est l’avant de le système d’exploitation s’exécute en (privilège d’exécution limité), en d’autres termes, un code ring3 ne peut pas avoir une instruction réservée à ring0, le kernel va tuer le code en le piégeant comme s’il disait ‘Hey, vous n’avez aucun privilège le domaine de ring0 ‘.

L’autre raison pour laquelle il est écrit en assembleur, est principalement la taille du code et la vitesse native brute, par exemple, un pilote de port série, où l’entrée / sortie est «critique» pour la fonction par rapport au temps, à la latence. , mise en mémoire tampon.

La plupart des pilotes de périphériques (dans le cas de Windows) auraient une chaîne de compilation spéciale ( WinDDK ) capable d’utiliser du code C mais sans lien avec les bibliothèques d’exécution de la norme C standard.

Il existe une seule boîte à outils qui peut vous permettre de créer un pilote dans Visual Studio, VisualDDK . Bien sûr, construire un pilote n’est pas fait pour les faibles, vous obtiendrez une activité induite par le stress en regardant les écrans bleus, les paniques du kernel et en vous demandant pourquoi, en déboguant les pilotes, etc.

Le côté débogage est plus difficile, le code ring3 n’est pas facilement accessible par le code ring3 car les portes sont fermées, il passe par la trappe du kernel (faute de meilleur mot) et si on le lui demande poliment, la porte rest fermée Le kernel délègue la tâche à un gestionnaire résidant sur ring0, l’exécute, quels que soient les résultats renvoyés, est renvoyé au code ring3 et la porte rest fermement fermée. C’est le concept d’analogie selon lequel le code utilisateur peut exécuter du code privilégié sur ring0.

De plus, ce code privilégié peut facilement piétiner l’espace mémoire du kernel et corrompre quelque chose, d’où le panique / bluescreens du kernel …

J’espère que cela t’aides.

Peut-être qu’un pilote ne nécessite pas de fonctionnalités orientées object, alors que le fait que C ait encore des compilateurs un peu plus matures ferait une différence.

Probablement parce que c est souvent plus rapide, plus petit quand il est compilé, et plus cohérent dans la compilation entre les différentes versions du système d’exploitation et avec moins de dépendances. De plus, comme c ++ est vraiment construit sur c, la question est de savoir si vous avez besoin de quoi?

Il y a probablement quelque chose au fait que les personnes qui écrivent des pilotes et des micrologiciels ont l’habitude de travailler au niveau du système d’exploitation (ou inférieur) qui est en c, et sont donc habitués à utiliser c pour ce type de problème.

Il existe de nombreux styles de programmation tels que procéduraux, fonctionnels, orientés object, etc. La programmation orientée object est plus adaptée à la modélisation du monde réel.

Je voudrais utiliser orienté object pour les pilotes de périphériques si elle le suit. Mais, la plupart du temps, lorsque vous programmez des pilotes de périphérique, vous n’auriez pas besoin des avantages de c ++, tels que l’abstraction, le polymorphism, la réutilisation du code, etc.

Eh bien, les pilotes IOKit pour MacOSX sont écrits en sous-ensemble C ++ (pas d’exceptions, de modèles, d’inheritance multiple). Et il y a même une possibilité d’écrire des modules de kernel Linux dans haskell.)

Sinon, C, étant un langage d’assemblage portable, capture parfaitement l’architecture et le modèle de calcul de von Neumann, permettant un contrôle direct de toutes ses particularités et de ses inconvénients (comme le «goulot d’étranglement von Neumann»). C fait exactement ce pour quoi il a été conçu et attrape complètement et parfaitement son modèle d’abstraction cible (à l’exception des hypothèses implicites dans un stream de contrôle unique qui auraient pu être généralisées pour couvrir la réalité des threads matériels) et c’est pourquoi langage. En limitant le pouvoir expressif du langage à de telles bases, on élimine la plupart des détails de transformation imprévisibles lorsque différents modèles informatiques sont appliqués à cette norme de facto. En d’autres termes, C vous permet de restr fidèle aux bases et de contrôler directement ce que vous faites, par exemple lorsque vous modélisez une communauté de comportement avec des fonctions virtuelles que vous contrôlez exactement comme les tables de pointeurs de fonction. et le management. Ceci est en fait utile lorsque l’on considère les caches.

Cela dit, le paradigme basé sur les objects est très utile pour représenter les objects physiques et leurs dépendances. En ajoutant l’inheritance, nous obtenons un paradigme orienté object qui, à son tour, est très utile pour représenter la structure et la hiérarchie du comportement des objects physiques. Rien n’empêche quiconque de l’utiliser et de l’exprimer à nouveau en C, ce qui permet un contrôle total sur la façon dont vos objects seront créés, stockés, détruits et copiés. En fait, c’est l’approche adoptée dans le modèle de périphérique Linux. Ils ont obtenu des “objects” pour représenter des périphériques, une hiérarchie d’implémentation d’object pour modéliser des dépendances de gestion de l’alimentation et des fonctionnalités d’inheritance améliorées pour représenter des familles de périphériques, le tout en C.

Parce que, au niveau du système, les pilotes doivent contrôler chaque bit de chaque octet de la mémoire, un autre langage supérieur ne peut pas le faire ou ne peut pas le faire en mode natif, seul C / Asm réalise ~