Nous écrivons le noyau des guêpes. Ce que vous devez savoir pour écrire un système d'exploitation

Nous écrivons le noyau des guêpes. Ce que vous devez savoir pour écrire un système d'exploitation

30.03.2021

L'abréviation "NT" signifie marketing "Nouvelles technologies", mais dans la documentation du projet, cela signifiait quelque chose de complètement différent. Le fait est que Windows NT a été conçu pour le nouveau, pas encore sorti en 1988, Processeur Intel i860. Son nom de code était "N10" ( NT fr).

La première version - Windows NT 3.1, est sortie 5 ans plus tard, en 1993. À ce stade, il y avait déjà 250 développeurs dans l'équipe.

Windows aujourd'hui

  • 1 milliard d'utilisateurs
  • 140 millions de lignes de code (y compris le code de test et l'outillage)
    Le code Windows est très différent. Certaines parties ont été écrites il y a 20 ans, d'autres n'apparaissaient que dans la version actuelle. Par exemple, le code Web Services on Devices (WSD) dans Windows Vista existe dans sa première version, le code GDI est dans sa phase finale de développement et ne change guère, le code DirectX est déjà bien développé, mais il évolue activement à l'heure actuelle.
  • 8000 développeurs
  • 36 langues de localisation
  • 20 ans de développement

Développement Windows

Il y a 20-30 ans, une seule méthodologie de programmation, "Waterfall", était utilisée. C'est une séquence :

Spécifications → Conception → Mise en œuvre → Tests → Livraison.

Mais cette méthodologie ne fonctionne que pour les petits projets. Pour un produit comme Windows aujourd'hui, différentes méthodologies sont nécessaires :

  • Modèle de cycle de produit
  • Processus de logiciel d'équipe
  • "Programmation extrême"

Toutes ces méthodologies présentent à la fois des avantages et des inconvénients. Selon la taille de l'équipe et le stade de développement du composant, différentes équipes de développement Windows utilisent différentes méthodologies de développement.
Pour Windows, en tant que produit en général, le modèle de cycle de produit est utilisé :

  • Périodes de 3-4 mois
  • A l'intérieur de la période - "cascade"

Le plus grand défi dans le développement d'un produit de cette ampleur est que le développement prend du temps. Au stade initial, les problèmes qui existent à l'heure actuelle et les moyens existants sont résolus. Mais la seule chose qui est permanente, c'est que tout va changer. Au fil des années de développement :

  • Les exigences vont changer
  • Les opportunités vont changer
  • L'horaire de travail va changer
  • Le projet va changer
  • Les utilisateurs vont changer

Malgré le fait que différentes équipes se développent de différentes manières, il existe des règles "universelles":

  • Sortie de versions intermédiaires (jalons, bêta, CTP) pour un large éventail de testeurs
  • Libération des assemblages internes à cycles courts (1 jour)
  • Simplicité et fiabilité de conception
  • Révisions du code personnel et d'équipe
  • Tests unitaires
  • Tests de vérification de construction
  • Tout assemblage intermédiaire doit être de haute qualité (ce qui est écrit doit fonctionner)

Pour ma part, je constate qu'après un mois de travail avec Windows 7 build 6801 comme système d'exploitation principal sur mon ordinateur personnel, j'ai une impression positive de cette version.

L'ensemble du processus de développement de Windows s'articule autour d'un build quotidien :

  • C'est le pouls du produit
  • Le développement ne s'arrête jamais
  • Tests automatisés quotidiens
  • Intégration précoce
  • Responsabilité des développeurs
  • État évident du produit

Il était une fois une seule branche avant code source, et tous les développeurs y ont apporté des modifications directement. Maintenant, l'équipe de développement est si grande que cela ne fonctionne pas. De nombreuses branches sont prises en charge, parmi lesquelles la principale - WinMain. Chaque laboratoire a sa propre branche de développement local dans laquelle s'intègrent les changements. Les changements prouvés sont intégrés dans WinMain au fil du temps.

Cycle de développement quotidien :

  • 15h00 - Modifications autorisées pour l'intégration dans le système de contrôle du code source
  • Construire 6 versions (gratuit / vérifié - x86, x64, IA64)
  • 18h00 - De nouvelles versions sont disponibles pour les tests
  • La nouvelle version est installée sur plusieurs milliers de postes et serveurs pour les tests
  • Test de résistance automatisé
  • 05:00 - Les protocoles de test sont analysés, les échecs sont diagnostiqués
  • 09h00 - Les rapports de synthèse sont automatiquement envoyés aux équipes
  • 09h30 - Réunion consolidée des chefs d'équipe pour définir les objectifs

Tous les participants au projet, y compris les cadres supérieurs, utilisent des versions intermédiaires sur leurs ordinateurs de travail (et généralement à domicile).

Sur quoi Windows est-il écrit ?

  • C, C++, C#, Assembleur (x86, x64, IA64)
    Les assembleurs sont utilisés dans une mesure assez limitée dans des situations où vous ne pouvez pas vous en passer.
  • Visual Studio, Source Insight, build, nmake
  • Dépôt de source - système de contrôle de source
  • WinDbg, KD, NTSD - débogueurs

De nombreux outils internes tels que build peuvent être téléchargés à partir de microsoft.com/whdc/devtools.

Modifications du noyau de Windows 7

Le noyau de Windows 7 a subi les modifications suivantes :

  • Refactorisation
    Pourquoi ne pouvez-vous pas désinstaller le sous-système graphique sous Windows ?
    La réponse à cette question d'un point de vue technique est que le sous-système graphique de Windows n'est pas indépendant, il fait partie du sous-système Win32.
    Windows 7 a refactorisé de nombreux composants de bas niveau pour briser les dépendances. Les utilisateurs ne le remarqueront pas, seules les nouvelles DLL apparaîtront, par exemple, kernel32.dll est divisé en kernel32.dll et kernelbase.dll.
    Ce partitionnement a permis d'allouer un noyau minimal appelé MinWin (20 mégaoctets sur disque).
  • Prise en charge EFI pour x86 et x64 (comme dans Vista SP1)
    De nombreux fabricants tentent de se débarrasser du BIOS au profit de l'EFI.
  • Démarrez à partir du VHD (disque dur virtuel)
  • Initialisation parallèle des appareils et démarrage des services
    Lorsque Windows démarre, la création de l'arborescence des périphériques prend un certain temps. Le gestionnaire PNP doit interroger les pilotes de bus (PCI, USB, FireWire, etc.) pour connaître les périphériques dont ils disposent. Et la plupart du temps le processeur attend que les appareils répondent (ou pas). En effet, afin d'identifier les appareils sur le bus, il faut les interroger. S'ils le sont, ils répondront, mais sinon, vous devez attendre et le processeur est inactif. L'exécution de ces tâches en parallèle réduira les temps de chargement.
  • Suppression du verrou Dispatcher du planificateur et du verrou PFN du gestionnaire de mémoire
    dernières années fréquences d'horloge les processeurs ne grandissent pas, et le développement va vers l'augmentation du nombre d'instructions exécutables parallèles à la fois au niveau d'un cœur et au niveau du système (multicœur). À cet égard, beaucoup de travail a été fait pour améliorer la mise à l'échelle.
    Les deux verrous les plus chauds du noyau, le verrou Dispatcher et le verrou PFN, ont été supprimés.
    Le verrou du répartiteur était utilisé par le planificateur lorsque l'état des threads changeait. Ce verrou a été supprimé et l'état « en attente » du fil a été divisé en plusieurs :
    • En attente : en cours
    • En attente : terminé
    • En attente : Annulé
    Le verrouillage PFN a été utilisé lors de la modification des attributs des pages de mémoire physique. Dans un système multiprocesseur, chaque processeur demandait l'accès à ce verrou, ce qui prenait beaucoup de temps.
  • Prend en charge 256 processeurs logiques
    Auparavant, Windows utilisait un mot machine comme masque d'affinité. Cela a été fait parce qu'il était si facile de trouver des processeurs gratuits - chaque bit représente un processeur. En conséquence, 32 processeurs logiques étaient pris en charge dans un système 32 bits et 64 dans un système 64 bits.
    Dans Windows 7, à la suite de la transition vers le modèle de masque d'affinité de segment, la prise en charge de 256 processeurs logiques est devenue possible. Les processeurs ont commencé à être regroupés en groupes/segments. Chaque groupe peut contenir jusqu'à 64 processeurs. En conséquence, une compatibilité descendante est obtenue, les anciens programmes "voient" uniquement les processeurs d'un groupe et les nouveaux programmes utilisant de nouvelles interfaces fonctionnent avec tous les processeurs du système.
  • Économie d'énergie améliorée : désactivation des sockets de processeur Aujourd'hui, il existe un grave problème d'économie d'énergie, non seulement pour les propriétaires d'ordinateurs portables, mais également pour les propriétaires de centres de données. Aux États-Unis, 2 % de l'électricité est consommée par les centres de données informatiques. Beaucoup d'entre eux éteignent certains de leurs serveurs pendant les périodes de faible activité des utilisateurs (week-ends).
    Il a été constaté qu'il est beaucoup plus rentable de désactiver l'intégralité du socket du processeur qu'un cœur à la fois sur plusieurs, car dans ce cas, vous pouvez également désactiver toute l'infrastructure de prise en charge des sockets (contrôleur de mémoire).

Maintenance Windows, mises à jour

Dans le passé, les mises à jour étaient souvent cumulatives (cumulatives). Cela signifiait que si le code erroné était contenu dans une première mise à jour du composant, les versions ultérieures contiendraient ce code. Mais tous les utilisateurs n'ont pas besoin de toutes les mises à jour, ils ont des configurations différentes.

Maintenant, après la publication (RTM) sur Windows, il existe 2 versions du code source :

  • RTM GDR (Communiqué de Distribution Générale)
    Comprend ces quelques changements qui sont destinés à tout le monde. Principalement des correctifs de sécurité.
  • RTM LDR (Diffusion Limitée)
    Pendant l'installation de la mise à jour Client Windows Update sélectionne la branche dont il a besoin et installe le code à partir de celle-ci.

Création d'une mise à jour de sécurité

Le travail de création d'une mise à jour de sécurité commence par l'identification d'une vulnérabilité. il y a une masse différentes façons détection - équipes de sécurité internes, partenaires de sécurité, développeurs. Lorsqu'une vulnérabilité est détectée, 2 processus parallèles démarrent :

  • Développer un correctif pour toutes les plateformes
  • Rechercher "options"
    Recherche approfondie de vulnérabilités similaires sur toutes les plateformes. Recherche non pas d'un code identique, mais d'un code similaire.

Une fois qu'un correctif est développé, les revues de code commencent. Lorsqu'ils sont terminés, le correctif est intégré au build et le build est envoyé pour test :

  • Tests manuels et automatiques des composants
  • Testez automatiquement la distorsion des formats de fichiers, des composants réseau, etc. (plus d'un million d'options)
  • Tests du système dans son ensemble, y compris les tests de compatibilité descendante

Seuls les correctifs qui répondent à tous les critères de qualité peuvent être publiés sur Windows Update et Centre de téléchargement.

  • En avant>

Le système d'exploitation 0 à 1 est publié sur GitHub et compte plus de 2 000 étoiles et 100 fourches. Comme son nom l'indique, après l'avoir lu, vous pouvez créer votre propre système opérateur- et, peut-être, peu de choses dans le monde des programmeurs peuvent être plus cool.

A travers ce livre, vous apprendrez ce qui suit :

  • Apprenez à créer un système d'exploitation basé sur la documentation technique du matériel. C'est ainsi que cela fonctionne dans le monde réel, vous ne pouvez pas utiliser Google pour des réponses rapides.
  • Comprendre comment les composants informatiques interagissent les uns avec les autres, du logiciel au matériel.
  • Apprenez à écrire du code vous-même. La copie aveugle de code n'est pas une courbe d'apprentissage, vous apprenez vraiment à résoudre des problèmes. Soit dit en passant, la copie aveugle est également dangereuse.
  • Maîtrisez les outils familiers pour le développement de bas niveau.
  • Familiarisez-vous avec le langage assembleur.
  • Découvrez de quoi sont faits les programmes et comment le système d'exploitation les exécute. Nous avons donné un petit aperçu de ce sujet pour les curieux en.
  • Découvrez comment déboguer un programme directement sur le matériel avec GDB et QEMU.
  • Langage de programmation C. Vous pouvez rapidement le maîtriser en suivant.
  • Connaissance de base de Linux. Il suffit d'étudier sur notre site Web.
  • Connaissances de base en physique : atomes, électrons, protons, neutrons, tension.

Ce que vous devez savoir pour écrire un système d'exploitation

La création d'un système d'exploitation est l'une des tâches les plus difficiles de la programmation, car elle nécessite une connaissance approfondie et complexe du fonctionnement d'un ordinateur. Lesquels? Regardons-le ci-dessous.

Qu'est-ce que l'OS

Le système d'exploitation (OS) est Logiciel, qui fonctionne avec le matériel informatique et ses ressources et constitue le pont entre le matériel et les logiciels de l'ordinateur.

Les ordinateurs de première génération n'avaient pas de système d'exploitation. Les programmes des premiers ordinateurs comprenaient le code pour le fonctionnement direct du système, la communication avec les périphériques et les calculs pour l'exécution desquels ce programme a été écrit. En raison de cet alignement, même les programmes simples en termes de logique étaient difficiles à implémenter dans le logiciel.

À mesure que les ordinateurs devenaient de plus en plus diversifiés et complexes, l'écriture de programmes fonctionnant à la fois comme système d'exploitation et comme application est devenue tout simplement gênante. Par conséquent, pour rendre les programmes plus faciles à écrire, les propriétaires d'ordinateurs ont commencé à développer des logiciels. C'est ainsi que sont nés les systèmes d'exploitation.

Le système d'exploitation fournit tout ce dont vous avez besoin pour exécuter des programmes personnalisés. Leur apparition signifiait que désormais les programmes n'avaient plus besoin de contrôler tout le volume de travail de l'ordinateur (c'est un excellent exemple d'encapsulation). Désormais, les programmes devaient fonctionner avec le système d'exploitation, et le système lui-même s'occupait des ressources et fonctionnait avec les périphériques (clavier, imprimante).

En bref sur l'histoire des systèmes d'exploitation

langage C

Comme mentionné ci-dessus, il existe plusieurs langages de programmation de haut niveau pour écrire un système d'exploitation. Cependant, le plus populaire d'entre eux est C.

Vous pouvez commencer à apprendre cette langue à partir d'ici. Cette ressource vous familiarisera avec les concepts de base et vous préparera à des tâches plus complexes.

Learn C the Hard Way est le titre d'un autre livre. En plus de la théorie habituelle, il contient de nombreuses solutions pratiques. Ce tutoriel couvrira tous les aspects de la langue.

Ou vous pouvez choisir l'un de ces livres :

  • Le langage de programmation C de Kernighan et Ritchie ;
  • "C Programming Absolute Beginner's Guide" par Perry et Miller.

Développement d'OS

Après avoir maîtrisé tout ce que vous devez savoir sur l'informatique, le langage d'assemblage et le C, vous devriez lire au moins un ou deux livres sur le développement direct du système d'exploitation. Voici quelques ressources pour ce faire :

Linux à partir de zéro. Voici comment construire la salle d'opération. Systèmes Linux(le manuel a été traduit dans de nombreuses langues, dont le russe). Ici, comme dans le reste des manuels, vous recevrez toutes les connaissances de base nécessaires. En vous appuyant sur eux, vous pouvez vous essayer à créer un système d'exploitation. Pour rendre la partie logicielle du système d'exploitation plus professionnelle, il y a des ajouts au manuel : "

En lisant Habr au cours des deux dernières années, je n'ai vu que quelques tentatives de développement du système d'exploitation (plus précisément: de la part des utilisateurs et (reporté indéfiniment) et (pas abandonné, mais ressemble pour l'instant plus à une description du mode protégé des processeurs compatibles x86 , ce que vous devez sans aucun doute connaître pour écrire un système d'exploitation pour x86) ; et une description du système fini à partir de (mais pas à partir de zéro, bien qu'il n'y ait rien de mal à cela, peut-être même l'inverse)). Pour une raison quelconque, je pense que presque tous les programmeurs système (et une partie de l'application) au moins une fois, ont pensé à écrire leur propre système d'exploitation. À cet égard, 3 OS de la grande communauté de cette ressource semble être un nombre ridicule. Apparemment, la plupart de ceux qui pensent à leur propre système d'exploitation ne vont pas plus loin qu'une idée, une petite partie s'arrête après avoir écrit un chargeur de démarrage, peu d'entre eux écrivent des morceaux du noyau, et seuls les désespérément têtus créent quelque chose qui ressemble à distance à un système d'exploitation (par rapport à avec quelque chose comme Windows / Linux) ... Il y a plusieurs raisons à cela, mais la principale, à mon avis, est que les gens arrêtent le développement (certains n'ont même pas le temps de commencer) à cause du petit nombre de descriptions du processus d'écriture et de débogage du système d'exploitation, qui est assez différent de ce qui se passe lors du développement de logiciels appliqués.

Avec cette petite note, je voudrais montrer que, si vous démarrez correctement, il n'y a rien de particulièrement difficile à développer votre propre système d'exploitation. Sous la coupe se trouve un guide court et assez général sur l'action d'écrire un système d'exploitation à partir de zéro.

Comment ce n'est pas nécessaire commencer
Veuillez ne pas considérer le texte suivant comme une critique explicite des articles de quelqu'un d'autre ou des guides d'écriture du système d'exploitation. C'est juste que trop souvent dans de tels articles sous gros titres, l'accent est mis sur la mise en œuvre d'une sorte de préparation minimale, et il est présenté comme un prototype du noyau. En fait, vous devez penser à la structure du noyau et à l'interaction des parties du système d'exploitation dans son ensemble, et ce prototype doit être considéré comme un "Hello, World!" standard - une application dans le monde des logiciels appliqués. Comme petite excuse pour ces propos, il faut dire qu'en dessous il y a une sous-section "Hello, World!"

Pas besoin d'écrire un bootloader. Des personnes intelligentes ont proposé la spécification Multiboot, implémentée et décrite en détail de quoi il s'agit et comment l'utiliser. Je ne veux pas me répéter, je dirai juste que ça marche, ça facilite la vie, et ça doit être appliqué. Il est d'ailleurs préférable de lire la spécification dans son intégralité, elle est courte et contient même des exemples.

Il n'est pas nécessaire d'écrire complètement le système d'exploitation en langage assembleur. Ce n'est pas si mal, bien au contraire - les programmes rapides et petits seront toujours tenus en haute estime. Tout comme ce langage nécessite beaucoup plus d'efforts de développement, l'utilisation de l'assembleur ne fera que réduire l'enthousiasme et, par conséquent, mettre les sources du système d'exploitation en veilleuse.

Il n'est pas nécessaire de charger une police personnalisée dans la mémoire vidéo et d'afficher quoi que ce soit en russe. Cela n'a aucun sens. Il est beaucoup plus facile et plus polyvalent d'utiliser l'anglais, et de laisser changer la police pour plus tard, en la chargeant à partir de disque dur via le pilote du système de fichiers (cela vous incitera davantage à faire plus que simplement commencer).

Préparation
Pour commencer, comme toujours, vous devez vous familiariser avec la théorie générale afin d'avoir une idée du volume de travail à venir. De bonnes sources sur la question à l'étude sont les livres d'E. Tanenbaum, qui ont déjà été mentionnés dans d'autres articles sur l'écriture d'OS en Habré. Il existe également des articles décrivant systèmes existants, et il existe divers guides / listes de diffusion / articles / exemples / sites avec un parti pris dans le développement d'OS, dont certains liens sont donnés à la fin de l'article.

Après le programme éducatif initial, vous devez vous prononcer sur les principales questions :

  • architecture cible - x86 (mode réel / protégé / long), PowerPC, ARM, ...
  • architecture noyau / système d'exploitation - monolithe, monolithe modulaire, micronoyau, exokernel, divers hybrides
  • langage et son compilateur - C, C++, ...
  • format de fichier du noyau - elf, a.out, coff, binaire, ...
  • environnement de développement (oui, cela joue également un rôle important) - IDE, vim, emacs, ...
Ensuite, vous devez approfondir vos connaissances selon celui choisi et dans les domaines suivants :
  • mémoire vidéo et travail avec elle - la conclusion en tant que preuve de travail est nécessaire dès le début
  • HAL (Couche d'abstraction matérielle) - même si la prise en charge de plusieurs architectures matérielles et qu'il n'est pas prévu de séparer de manière compétente les parties de niveau le plus bas du noyau de la mise en œuvre d'éléments abstraits tels que les processus, les sémaphores, etc. ne sera pas superflu
  • gestion de la mémoire - physique et virtuelle
  • contrôle d'exécution - processus et threads, leur ordonnancement
  • gestion des périphériques - pilotes
  • systèmes de fichiers virtuels - pour fournir une interface unique au contenu de divers systèmes de fichiers
  • API (Application Programming Interface) - comment exactement les applications accéderont au noyau
  • IPC (Interprocess Communication) - tôt ou tard, les processus devront communiquer
Instruments
En tenant compte du langage et des outils de développement choisis, vous devez sélectionner un tel ensemble d'utilitaires et leurs paramètres, qui permettront à l'avenir, en écrivant des scripts, de faciliter et d'accélérer au maximum l'assemblage, la préparation d'images et le lancement d'un machine virtuelle avec un projet. Attardons-nous un peu plus en détail sur chacun de ces points :
  • tous les outils standard sont adaptés à la construction, tels que make, cmake, ... Ici, des scripts pour l'éditeur de liens et des utilitaires (spécialement écrits) pour ajouter un en-tête Multiboot peuvent être utilisés, sommes de contrôle ou à toute autre fin.
  • préparer une image signifie la monter et copier des fichiers. Par conséquent, le format du fichier image doit être sélectionné de sorte que l'utilitaire de montage/copie et la machine virtuelle le prennent en charge. Naturellement, personne n'interdit d'effectuer les actions à partir de ce point soit comme partie finale de l'assemblage, soit comme préparation au lancement de l'émulateur. Tout dépend des outils spécifiques et des options choisies pour leur utilisation.
  • le lancement d'une machine virtuelle ne représente pas de travail, mais il ne faut pas oublier de démonter d'abord l'image (démontage à ce stade, car il n'y a pas de réel sens à cette opération avant de démarrer la machine virtuelle). Aussi, il ne sera pas superflu d'avoir un script pour démarrer l'émulateur en mode debug (s'il y en a un).
Si vous avez terminé toutes les étapes précédentes, vous devez écrire un programme minimal qui se charge en tant que noyau et affiche quelque chose à l'écran. Si des inconvénients ou des lacunes des moyens sélectionnés sont constatés, il est nécessaire de les éliminer (lacunes) ou, dans le pire des cas, de les prendre pour acquis.

Au cette étape vous devez vérifier autant de fonctionnalités des outils de développement que vous prévoyez d'utiliser à l'avenir. Par exemple, charger des modules dans GRUB ou utiliser dans machine virtuelle disque physique / partition / lecteur flash au lieu d'une image.

Une fois cette étape passée avec succès, le véritable développement commence.

Fournir un support d'exécution
Puisqu'il est proposé d'écrire dans les langues haut niveau, il faut veiller à prendre en charge certaines des fonctionnalités de langage qui sont généralement implémentées par les auteurs de packages de compilateur. Par exemple, pour C++, cela inclut :
  • fonction d'allocation dynamique d'un bloc de données sur la pile
  • travailler avec le tas
  • fonction de copie de bloc de données (memcpy)
  • fonction de point d'entrée de programme
  • appels aux constructeurs et aux destructeurs d'objets globaux
  • un certain nombre de fonctions pour travailler avec des exceptions
  • stub pour les fonctions purement virtuelles non implémentées
En écrivant "Hello, World!" l'absence de ces fonctions peut ne pas se faire sentir de quelque manière que ce soit, mais au fur et à mesure que le code est ajouté, l'éditeur de liens commencera à se plaindre des dépendances non satisfaites.

Naturellement, la bibliothèque standard doit être mentionnée tout de suite. Une implémentation complète n'est pas nécessaire, mais un sous-ensemble majeur de la fonctionnalité mérite d'être implémenté. Ensuite, le codage sera beaucoup plus familier et plus rapide.

Débogage
Ne voyez pas ce qu'il dit sur le débogage vers la fin de cet article. En fait, il s'agit d'un problème très grave et difficile dans le développement d'un système d'exploitation, car les outils habituels ne sont pas applicables ici (à quelques exceptions près).

Vous pouvez conseiller les éléments suivants :

  • pour acquis, sortie de débogage
  • assert avec sortie immédiate du "debugger" (voir paragraphe suivant)
  • un semblant de débogueur de console
  • vérifiez si l'émulateur vous permet de connecter un débogueur, des tables de symboles ou autre chose
Sans débogueur intégré au noyau, trouver des bogues a de réelles chances de devenir un cauchemar. Il n'y a donc tout simplement pas d'échappatoire pour l'écrire à un certain stade de développement. Et comme c'est inévitable, mieux vaut commencer à l'écrire à l'avance et ainsi faciliter grandement votre développement et gagner beaucoup de temps. Il est important de pouvoir implémenter le débogueur de manière indépendante du noyau afin que le débogage ait un impact minimal sur le fonctionnement normal du système. Il existe plusieurs types de commandes qui peuvent être utiles :
  • certaines des opérations de débogage standard : points d'arrêt, pile d'appels, sortie de valeurs, impression d'un dump, ...
  • des commandes pour afficher diverses informations utiles, telles que la file d'exécution du planificateur ou diverses statistiques (ce n'est pas aussi inutile qu'il y paraît au premier abord)
  • commandes pour vérifier la cohérence de l'état des différentes structures : listes de mémoire libre/utilisée, tas ou file d'attente de messages
Développement
Ensuite, vous devez écrire et déboguer les principaux éléments du système d'exploitation, qui devraient actuellement assurer son fonctionnement stable et, à l'avenir, une extensibilité et une flexibilité faciles. En plus des gestionnaires de mémoire / processus / (tout le reste), l'interface du pilote est très importante et systèmes de fichiers... Leur conception doit être abordée avec un soin particulier, en tenant compte de toute la variété des types d'appareils / FS. Bien sûr, vous pouvez les modifier au fil du temps, mais c'est un processus très douloureux et sujet aux erreurs (et le débogage du noyau n'est pas une tâche facile), alors rappelez-vous simplement - pensez à ces interfaces au moins dix fois avant de commencer à les implémenter .
Similitude avec le SDK
Au fur et à mesure que le projet se développe, de nouveaux pilotes et programmes doivent y être ajoutés. Très probablement, déjà sur le deuxième pilote (éventuellement d'un certain type) / programme, certaines fonctionnalités communes seront perceptibles (structure de répertoire, fichiers de contrôle de construction, spécification des dépendances entre les modules, code répété dans les gestionnaires de requêtes principaux ou système (par exemple , si les pilotes eux-mêmes vérifient leur compatibilité avec l'appareil) )). Si c'est le cas, c'est un signe de la nécessité de développer des modèles pour différents types de programmes pour votre système d'exploitation.

Il n'y a pas besoin de documentation décrivant le processus d'écriture de tel ou tel type de programme. Mais cela vaut la peine de faire un blanc à partir d'éléments standard. Cela facilitera non seulement l'ajout de programmes (ce qui peut être fait en copiant des programmes existants puis en les modifiant, mais cela prendra plus de temps), mais cela facilitera également leur mise à jour lorsqu'il y a des changements dans les interfaces, les formats ou autre chose. Il est clair que de tels changements ne devraient idéalement pas l'être, mais comme le développement d'un système d'exploitation est une chose atypique, il existe de nombreux endroits pour prendre de mauvaises décisions. Mais la compréhension de l'erreur des décisions prises, comme toujours, viendra quelque temps après leur mise en œuvre.

Actions supplémentaires
Bref, renseignez-vous sur les systèmes d'exploitation (et principalement sur leur appareil), développez votre système (le rythme n'a en fait pas d'importance, l'essentiel est de ne pas s'arrêter du tout et de revenir au projet de temps en temps avec de nouvelles forces et idées) et il est naturel d'y corriger les erreurs (pour trouver lesquelles il faut parfois démarrer le système et "jouer" avec). Au fil du temps, le processus de développement deviendra de plus en plus facile, les erreurs seront moins fréquentes, et vous serez inclus dans la liste des "désespérément têtus", ces quelques-uns qui, malgré une certaine absurdité de l'idée de développer leur propre système d'exploitation, l'a quand même fait.

Un guide pour construire un noyau pour un système x86. Partie 1. Juste le noyau

Écrivons un noyau simple qui peut être chargé avec le chargeur de démarrage x86 GRUB. Ce noyau affichera un message à l'écran et attendra.

Comment démarre un système x86 ?

Avant de commencer à écrire le noyau, comprenons comment le système démarre et transfère le contrôle au noyau.

La plupart des registres du processeur ont déjà certaines valeurs au démarrage. Le registre pointant sur l'adresse des instructions (Instruction Pointer, EIP) stocke l'adresse mémoire à laquelle se trouve l'instruction en cours d'exécution par le processeur. EIP est par défaut 0xFFFFFFF0... Ainsi, les processeurs x86 au niveau matériel commencent à fonctionner à 0xFFFFFFF0. Il s'agit en fait des 16 derniers octets de l'espace d'adressage de 32 bits. Cette adresse est appelée vecteur de réinitialisation.

Désormais, la carte mémoire du chipset garantit que 0xFFFFFFF0 appartient à une partie spécifique du BIOS, pas à la RAM. À ce stade, le BIOS se copie dans la RAM pour un accès plus rapide. L'adresse 0xFFFFFFF0 contiendra uniquement l'instruction de sauter à l'adresse mémoire où la copie du BIOS est stockée.

C'est ainsi que le code du BIOS commence à s'exécuter. Tout d'abord, le BIOS recherche un périphérique à partir duquel démarrer, dans un ordre prédéfini. Vous recherchez un nombre magique qui détermine si le périphérique est amorçable (511e et 512e octets du premier secteur doivent être égaux à 0xAA55).

Lorsque le BIOS trouve un périphérique de démarrage, il copie le contenu du premier secteur du périphérique dans la RAM, en commençant par l'adresse physique 0x7c00; puis navigue jusqu'à l'adresse et exécute le code chargé. Ce code s'appelle chargeur de démarrage.

Le bootloader charge le noyau à une adresse physique 0x100000... Cette adresse est utilisée comme adresse de départ dans tous les grands noyaux sur les systèmes x86.

Tous les processeurs x86 démarrent dans un mode simple 16 bits appelé régime réel... Le chargeur de démarrage GRUB passe en mode 32 bits mode protégé en réglant le bit de poids faible du registre CR0 sur 1 ... Ainsi, le noyau démarre en mode protégé 32 bits.

A noter que dans le cas de Noyau Linux GRUB voit les journaux de démarrage Linux et charge le noyau en mode réel. Le noyau passe automatiquement en mode protégé.

Ce dont nous avons besoin?

  • ordinateur x86 ;
  • Linux;
  • ld (éditeur de liens GNU);

Définir le point d'entrée dans l'assembleur

Autant je ne voudrais pas me limiter à un seul C, autant je dois écrire quelque chose en assembleur. Nous y écrirons un petit fichier qui servira de point de départ à notre noyau. Tout ce qu'il fera est d'appeler une fonction externe écrite en C et d'arrêter le déroulement du programme.

Comment pouvons-nous nous assurer que ce code est exactement le point de départ ?

Nous utiliserons un script d'éditeur de liens qui lie les fichiers objets pour créer l'exécutable final. Dans ce script, nous indiquerons explicitement que nous voulons charger des données à 0x100000.

Voici le code assembleur :

;; kernel.asm bits 32; directive nasm - section 32 bits .text global start extern kmain; kmain est défini dans le fichier c start: cli; les interruptions de bloc mov esp, stack_space; définir l'appel du pointeur de pile kmain hlt; arrêter la section CPU .bss resb 8192 ; 8 Ko pour la pile stack_space :

La première instruction, les bits 32, n'est pas une instruction d'assemblage x86. Il s'agit d'une directive à l'assembleur NASM pour générer du code pour un processeur fonctionnant en mode 32 bits. Dans notre cas, ce n'est pas nécessaire, mais généralement utile.

La section avec le code commence sur la deuxième ligne.

global est une autre directive NASM qui rend les symboles du code source globaux. Ainsi, l'éditeur de liens sait où se trouve le symbole de départ - notre point d'entrée.

kmain est une fonction qui sera définie dans le fichier kernel.c. extern signifie que la fonction est déclarée ailleurs.

Vient ensuite la fonction de démarrage, fonction d'appel kmain et arrêter le processeur avec l'instruction hlt. C'est pourquoi nous désactivons les interruptions à l'avance avec l'instruction cli.

Idéalement, nous voulons allouer de la mémoire et pointer dessus avec un pointeur de pile (en particulier). Cependant, il semble que GRUB l'ait déjà fait pour nous. Cependant, vous allouerez toujours de l'espace dans la section BSS et déplacerez le pointeur de pile à son début. Nous utilisons l'instruction resb, qui réserve le nombre d'octets spécifié. Immédiatement avant d'appeler kmain, le pointeur de pile (esp) est placé à l'emplacement souhaité par l'instruction mov.

Noyau en C

Dans kernel.asm, nous avons fait un appel à la fonction kmain(). Ainsi, notre code "sish" devrait démarrer l'exécution avec kmain() :

/ * * kernel.c * / void kmain (void) (const char * str = "mon premier noyau"; char * vidptr = (char *) 0xb8000; // video mem commence ici. unsigned int i = 0; unsigned int j = 0; / * cette boucle efface l'écran * il y a 25 lignes chacune de 80 colonnes; chaque élément prend 2 octets * / tandis que (j< 80 * 25 * 2) { /* blank character */ vidptr[j] = " "; /* attribute-byte - light grey on black screen */ vidptr = 0x07; j = j + 2; } j = 0; /* this loop writes the string to video memory */ while(str[j] != "\0") { /* the character"s ascii */ vidptr[i] = str[j]; /* attribute-byte: give character black bg and light grey fg */ vidptr = 0x07; ++j; i = i + 2; } return; }

Tout ce que notre noyau fera est d'effacer l'écran et d'imprimer la chaîne "mon premier noyau".

Tout d'abord, nous créons un pointeur vidptr qui pointe vers l'adresse 0xb8000... "Mémoire vidéo" commence à partir de cette adresse en mode protégé. Pour l'affichage du texte à l'écran, nous réservons 25 lignes de 80 caractères ASCII, à partir de 0xb8000.

Chaque caractère n'est pas affiché avec les 8 bits habituels, mais 16. Le premier octet stocke le caractère lui-même et le second - l'octet d'attribut. Il décrit la mise en forme d'un symbole, comme sa couleur.

Pour afficher un caractère s vert sur fond noir, nous écrivons ce caractère dans le premier octet et la valeur 0x02 dans le second. 0 signifie fond noir, 2 signifie couleur de texte verte.

Voici le nuancier :

0 - Noir, 1 - Bleu, 2 - Vert, 3 - Cyan, 4 - Rouge, 5 - Magenta, 6 - Marron, 7 - Gris clair, 8 - Gris foncé, 9 - Bleu clair, 10 / a - Vert clair, 11 / b - Cyan clair, 12 / c - Rouge clair, 13 / d - Magenta clair, 14 / e - Marron clair, 15 / f - Blanc.

Dans notre noyau, nous utiliserons du texte gris clair sur fond noir, notre attribut byte sera donc 0x07.

Dans la première boucle, le programme sort un caractère vide sur toute la zone 80x25. Cela effacera l'écran. Dans la boucle suivante, les caractères de la chaîne terminée par un zéro « mon premier noyau » avec l'octet d'attribut égal à 0x07 sont écrits dans la « mémoire vidéo ». Cela imprimera la chaîne à l'écran.

Pièce de connexion

Nous devons compiler kernel.asm dans un fichier objet en utilisant NASM ; puis utilisez GCC pour compiler kernel.c dans un autre fichier objet. Ensuite, ils doivent être attachés au noyau de démarrage exécutable.

Pour ce faire, nous utiliserons un script de liaison qui est passé à ld en tant qu'argument.

/ * * link.ld * / OUTPUT_FORMAT (elf32-i386) ENTRY (start) SECTIONS (. = 0x100000; .text : (* (. text)) .data : (* (. data)) .bss : (* ( .bss)))

Tout d'abord, nous allons définir format de sortie en tant que format exécutable et associable (ELF) 32 bits. ELF est un format standard fichiers binaires Systèmes Unix d'architecture x86. ENTRÉE prend un argument spécifiant le nom du symbole qui est le point d'entrée. SECTIONS est la partie la plus importante. Il définit le balisage de notre fichier exécutable. Nous définissons comment les différentes sections doivent être connectées et où les placer.

Entre parenthèses après SECTIONS, un point (.) Représente le compteur de position, qui est 0x0 par défaut. Il peut être modifié, c'est ce que nous faisons.

Nous regardons la ligne suivante : .text: (* (. Text)). Un astérisque (*) est un caractère spécial qui correspond à n'importe quel nom de fichier. L'expression * (.Text) signifie toutes les sections .text de tous les fichiers d'entrée.

Ainsi, l'éditeur de liens connecte toutes les sections du code du fichier objet dans une section du fichier exécutable à l'adresse dans le compteur de position (0x100000). Après cela, la valeur du compteur sera égale à 0x100000 + la taille de la section résultante.

De même, tout se passe avec les autres sections.

Grub et Multiboot

Tous les fichiers sont maintenant prêts à créer un noyau. Mais il reste un pas de plus.

Il existe une norme pour démarrer les noyaux x86 à l'aide d'un chargeur de démarrage appelé Spécification multiboot... GRUB ne démarrera notre noyau que s'il répond à ces spécifications.

Après eux, le noyau doit contenir l'en-tête dans ses 8 premiers Ko. De plus, cet en-tête doit contenir 3 champs, soit 4 octets :

  • magique champ : contient un nombre magique 0x1BADB002 pour identifier le noyau.
  • champ drapeaux: nous n'en avons pas besoin, mettez-le à zéro.
  • champ somme de contrôle: si vous l'ajoutez avec les deux précédents, vous devriez obtenir zéro.

Notre kernel.asm deviendra comme ceci :

;; kernel.asm; directive nasm - 32 bits 32 section .text; spécification multiboot align 4 dd 0x1BADB002; magic dd 0x00; flags dd - (0x1BADB002 + 0x00); somme de contrôle. m + f + c doit être égal à zéro démarrage global extern kmain ; kmain est défini dans le fichier c start : cli ; les interruptions de bloc mov esp, stack_space ; définir le pointeur de pile appeler kmain hlt ; arrêter la section CPU .bss resb 8192 ; 8 Ko pour la pile stack_space :

Construire le noyau

Nous allons maintenant créer des fichiers objets à partir de kernel.asm et kernel.c et les lier à l'aide de notre script.

Nasm -f elf32 kernel.asm -o kasm.o

Cette ligne exécutera l'assembleur pour créer le fichier objet kasm.o au format ELF-32.

Gcc -m32 -c kernel.c -o kc.o

L'option -c garantit qu'aucun lien caché ne se produit après la compilation.

Ld -m elf_i386 -T link.ld -o noyau kasm.o kc.o

Cela lancera l'éditeur de liens avec notre script et créera un exécutable appelé noyau.

Configurer grub et démarrer le noyau

GRUB requiert que le nom du noyau corresponde au noyau- ... Alors renommez le noyau. J'ai nommé le mien kernel-701.

Maintenant, placez-le dans le répertoire / botte... Cela nécessitera des droits de superutilisateur.

Dans le fichier de configuration GRUB grub.cfg, ajoutez ce qui suit :

Titre myKernel root (hd0,0) kernel / boot / kernel-701 ro

N'oubliez pas de supprimer la directive hiddenmenu, s'il y en a une.

Redémarrez votre ordinateur et vous verrez également une liste de noyaux avec le vôtre. Sélectionnez-le et vous verrez :

C'est votre cœur ! Ajoutons un système d'E/S.

P.S.

  • Pour toute astuce du noyau, il est préférable d'utiliser une machine virtuelle.
  • Pour exécuter le noyau dans grub2 la configuration devrait ressembler à ceci : menuentry "kernel 7001" (set root = "hd0, msdos1" multiboot / boot / kernel-7001 ro)
  • si vous souhaitez utiliser l'émulateur qemu, utilisez : qemu-system-i386 -kernel kernel

© 2021 hecc.ru - Actualités informatiques