K.C. Argent
Systèmes d'exécution en temps réel intégrés
Systèmes d'exploitation embarqués et temps réel
K.C. Argent
Systèmes opérateur embarqués et temps réel
123
K.C. Wang School of Electrical Engineering press Home Science Universités de l'État de Washington Pullman, HOW États-Unis
ISBN 978-3-319-51516-8 DOI 10.1007/978-3-319-51517-5
ISBN 978-3-319-51517-5
(livre électronique)
Numéro de contrôle de la Bibliothèque du Congrès : 2016961664 © Springer International Publishing AG 2017 Ouvrage mentionné ci-dessus le sujet dans les crédits. Les droits de représentation sont réservés à l'Éditeur, qu'il s'agisse de fournir tout ou partie des éléments à fournir, notamment les droits de traduction, de réimpression, de recyclage d'illustrations, de concert, de diffusion, de reproduction sur microfilms ou par tout autre moyen physique, de transmission ou de stockage d'informations. récupération réelle, adaptation électronique, logiciel informatique ou par une méthodologie différente de bouton similaire maintenant connue ou créée ci-après. L'utilisation de noms descriptifs généraux, de noms enregistrés, de marques commerciales, de marques de service, etc. dans cette publication n'implique pas, même en l'absence d'une mention spécifique, que ces désignations soient exemptées d'une des lois de protection pertinentes à la fois réglementaires et donc libres d'usage général. utiliser. L'éditeur, les livres et les éditeurs susmentionnés peuvent supposer en toute sécurité que les informations juridiques et les informations contenues dans ce livre existent et sont considérées comme vraies et exactes au moment de la publication. Aucun à l'éditeur ni les auteurs ou les éditeurs ne donnent une garantie, expresse ou implicite, en ce qui concerne le matériel contenu dans ce document ou pour toute erreur ou omission qui pourrait avoir été commise. L'éditeur reste neutre avec l'attitude jusqu'à ce que les revendications juridictionnelles dans les cartes publiées et les affiliations institutionnelles. Imprimé sur du papier sans acide Cette mention Springer est publiée par Springer Nature La société cotée en bourse est Jumps International Publishing TAGE L'adresse de la société enregistrée est : Gewerbestrasse 11, 6330 Cham, Allemagne
Dédié à Cindy
Préface
Depuis la publication de mon premier livre sur la conception et la mise en œuvre du système de travail MTX de Springer en 2015, IODIN a reçu des demandes de nombreux rats de bibliothèque enthousiastes sur la façon d'exécuter le système d'exploitation MTX sur leurs appareils mobiles basés sur ARM, comme les iPods ou iPhones, etc. qui m'a motivé à écrire ce get. Le but de la réservation est de fournir une plate-forme appropriée pour l'enseignement et l'apprentissage de la théorie et de la pratique des systèmes d'exploitation embarqués et en temps réel. Il couvre les concepts de base ainsi que les principes des services d'exploitation, il montre également comment les appliquer lors de la conception et de l'utilisation de systèmes d'exploitation complets depuis les systèmes embarqués et en temps réel. Afin de le faire de manière concrète et significative, il utilise une chaîne d'outils ARM pour le développement de programmes, et il utilise des machines virtuelles ARM pour modéliser les principes de conception et les techniques de mise en œuvre. Quant à son contenu technique, ce livre n'est pas destiné aux cours d'entrée de gamme qui apprennent uniquement les concepts et principes des systèmes d'exploitation dépourvus de toute pratique de la programmation. Il est destiné aux cours d'informatique / ingénierie à orientation technique sur nos systèmes embarqués et en temps réel qui mettent l'accent à la fois sur la théorie et la pratique. Le style évolutif du livre, associé à un code source détaillé et à de véritables systèmes d'échantillons de travail complets, le rend plus adapté à l'auto-apprentissage. Entreprendre ce projet de registre possédé s'est avéré être une autre entreprise très exigeante et chronophage, mais j'aimais les défis. Lors de la préparation des manuscrits de commande pour publication, j'ai été béni avec les encouragements et l'aide de nombreuses personnes, y compris plusieurs de mes anciens camarades de classe TaDa EE60. Je voudrais profiter de cette occasion pour remercier l'un d'entre eux. Je suis également reconnaissant à Springer International Publishing AG de m'avoir permis de divulguer gratuitement le code source de ces livres publics, qui sont disponibles sur http://www.eecs.wsu.edu/*cs460/ARMhome pour téléchargement. Un merci spécial à Cindy pour son soutien continu et ses inspirations, qui ont rendu ce volume possible. Enfin et surtout, je tiens à remercier à nouveau ma famille pour m'avoir supporté avec des excuses sans fin d'être un employé de tous les types. Pullman, Washington Octobre 2016
K.C. Argent
viia
Contenu
1
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1 Via Tous Réserver . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Impulsions de ce livre . . . . . . . . . . . . . . . . . . . . 1.3 Objectif et communauté visée . . . . . . . . . . . . . . . 1.4 Caractéristiques uniques de ce livre . . . . . . . . . . . . . . . . . 1.5 Contenu du livre . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6 Utiliser ce livre comme manuel pour les systèmes embarqués . 1.7 Utiliser ce Get comme manuel pour le logiciel d'exploitation . 1.8 Utilisez ce manuel pour l'auto-apprentissage . . . . . . . . . . . . . . . . . . Les références. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
1 1 1 1 2 3 4 5 5 5
2
Architecture et programmation ARM . . . . . . . . . . . . 2.1 Modes du processeur du POIGNET . . . . . . . . . . . . . . . . . . . 2.2 Inscription CPU ARM . . . . . . . . . . . . . . . . . . . . . 2.2.1 Registres génériques . . . . . . . . . . . . . . . . . 2.2.2 Enregistrement d'état . . . . . . . . . . . . . . . . . . 2.2.3 Modifier le mode système POLE . . . . . . . . 2.3 Chaîne d'instructions . . . . . . . . . . . . . . . . . . . . . . 2.4 Instructions POIGNET . . . . . . . . . . . . . . . . . . . . . . . 2.4.1 Indicateurs de condition et technique . . . . . . . 2.4.2 Instructions de la branche . . . . . . . . . . . . . . . . 2.4.3 Opérations arithmétiques . . . . . . . . . . . . . . 2.4.4 Opération de similarité . . . . . . . . . . . . . 2.4.5 Chirurgie logique . . . . . . . . . . . . . . . . 2.4.6 Opérateurs de déplacement de données . . . . . . . . . . 2.4.7 Valeur Immédiate et Barrel Shifter. . . . . 2.4.8 Instructions de multiplication . . . . . . . . . . . . . . . 2.4.9 Instructions de chargement et de stockage . . . . . . . . . 2.4.10 Registre de base . . . . . . . . . . . . . . . . . . . . 2.4.11 Blocage du transfert de données . . . . . . . . . . . . . . . 2.4.12 Opérations de pile . . . . . . . . . . . . . . . . . . 2.4.13 Pile et sous-programmes . . . . . . . . . . . . . . 2.4.14 Interruption logicielle (SWI) . . . . . . . . . . . . 2.4.15 Instructions de transfert PSR. . . . . . . . . . . . 2.4.16 Instructions du coprocesseur . . . . . . . . . . . . 2.5 Chaîne d'outils ARM . . . . . . . . . . . . . . . . . . . . . . . . 2.6 Émulateurs système ARM . . . . . . . . . . . . . . . . . . 2.7 Programmation ARM. . . . . . . . . . . . . . . . . . . . . . 2.7.1 Modèle de programmation d'assemblage ARM 1 2.7.2 Exemple d'apprentissage d'assemblage ARM 2 2.7.3 L'assemblage combiné inclut la programmation en C.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7 8 8 8 9 9 10 10 10 11 12 12 12 13 13 14 14 14 14 14 15 15 15 15 16 16 17 17 19 20
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ix
X
Contenu
2.8
Pilotes de périphérique . . . . . . . . . . . . . . . . 2.8.1 Regelung Data Map . . . . . 2.8.2 Programmation GPIO. . . . . . . 2.8.3 Pilote UART pour E/S série . 2.8.4 Pilote d'affichage LCD couleur . . 2.9 Résumé . . . . . . . . . . . . . . . . . . . . Notre. . . . . . . . . . . . . . . . . . . . . . . . 3
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
27 27 27 29 33 45 46
Traitement des interruptions et des exceptions . . . . . . . . . . . . . . . . . . 3.1 Exceptions ARM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Modes du processeur ARM . . . . . . . . . . . . . . . . . . . 3.1.2 ARM Sauf . . . . . . . . . . . . . . . . . . . . . . . 3.1.3 Tableau des vecteurs d'exceptions . . . . . . . . . . . . . . . . . . 3.1.4 Gestionnaires d'exceptions . . . . . . . . . . . . . . . . . . . . . . 3.1.5 Retour des gestionnaires d'exceptions . . . . . . . . . . . . . 3.2 Interruptions et utilisation des interruptions . . . . . . . . . . . . . . . . . 3.2.1 Types d'interruptions . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.2 Contrôleurs d'interruption . . . . . . . . . . . . . . . . . . . . . 3.2.3 Contrôleurs de perturbation primaires et secondaires. . . . . 3.3 Traitement des interruptions. . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1 Contenu du tableau linéaire . . . . . . . . . . . . . . . . . . . . 3.3.2 Séquence d'interruption matérielle . . . . . . . . . . . . . . . 3.3.3 Contrôle des interruptions dans la navigation. . . . . . . . . . . . . . . 3.3.4 Gestionnaires d'interruptions . . . . . . . . . . . . . . . . . . . . . . . 3.3.5 Distribution d'interruption non imbriquée . . . . . . . . . . . . . . . 3.4 Pilote de minuterie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4.1 WEAR Minuteries polyvalentes 926EJS . . . . . . . . . . . . . . 3.4.2 Programme Timer Rider . . . . . . . . . . . . . . . . . . . . 3.5 Pilote de clavier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5.1 Interface souris-clavier ARM PL050 . . . . . . . 3.5.2 Pilote de clavier . . . . . . . . . . . . . . . . . . . . . . . . 3.5.3 Programme de chauffeur piloté par interruption . . . . . . . . . . . . . . 3.5.4 Programme de l'opérateur frontal . . . . . . . . . . . . . . . . . 3.6 Voiture UART . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6.1 Interface UART PL011. . . . . . . . . . . . . . . 3.6.2 Enregistrements UART . . . . . . . . . . . . . . . . . . . . . . . . 3.6.3 Programme de pilote UART piloté par interruption. . . . . . . . 3.7 Cadeau numérique sécurisé (SD) . . . . . . . . . . . . . . . . . . . . . . . 3.7.1 Protocoles de la carte SB . . . . . . . . . . . . . . . . . . . . . . 3.7.2 Pilote de carte SD . . . . . . . . . . . . . . . . . . . . . . . . 3.7.3 Améliorations du pilote SDC . . . . . . . . . . . . . . . . . . . . 3.7.4 Transfert de preuves multisectoriel . . . . . . . . . . . . . . . . . 3.8 Interruptions de cours . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.8.1 WEAR PL190 Coursed Interrupts Controller (VIC) 3.8.2 Configurer le VIC pour les interruptions vectorielles. . . . . . . . . 3.8.3 Gestionnaires d'interruptions vectorielles . . . . . . . . . . . . . . . 3.8.4 Démonstration des déconnexions vectorielles . . . . . . . . . 3.9 Interruptions Verschachteln. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.9.1 Interruptions mystérieuses . . . . . . . . . . . . . . . . . . . . 3.9.2 Interruptions imbriquées dans ARM . . . . . . . . . . . . . . . . . 3.9.3 Gérer les interruptions imbriquées en mode SYS . . . . . . . . 3.9.4 Démonstration du démarrage des interruptions imbriquées . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47 47 47 47 49 49 50 51 51 51 53 54 54 54 54 55 55 56 56 57 61 61 62 62 63 67 67 68 69 73 74 74 80 82 84 84 85 86 86 87 8 7 88 88 89
Contenu
xii
3.10 Interruptions imbriquées et diapositive de processus . . . . . . . . . . . . . . . . . . . . . . . . . 3.11 Résumés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les références. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
5
Modèles de systèmes natifs . . . . . . . . . . . . . . . . . . . . . . 4.1 Structures de programme hors systèmes embarqués . . . . . . . . . . 4.2 Exemple de super boucle . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Exemples de programmes de super-boucle. . . . . . . . . . . . 4.3 Modèle événementiel . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Lacunes d'un programme Super-Loop . . . . . . 4.3.2 Événements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.3 Horaire événementiel périodique. . . . . . . . . . . . 4.3.4 Logiciel événementiel asynchrone . . . . . . . 4.4 Priorité des événements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5 Modèle de processus. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5.1 Modèle de processus monoprocesseur. . . . . . . . . . . . . . 4.5.2 Modèle de processus multiprocesseur . . . . . . . . . . . . 4.5.3 Processus d'espace d'adressage réel Sélectionnez . . . . . . . . . 4.5.4 Type de processus de salle d'adresse virtuelle . . . . . . . 4.5.5 Modèle de processus statique . . . . . . . . . . . . . . . . . . . 4.5.6 Modèles de processus dynamiques . . . . . . . . . . . . . . . . 4.5.7 Exemple de processus non préemptif. . . . . . . . . . . . 4.5.8 Modèle de processus préemptif . . . . . . . . . . . . . . . 4.6 Modèle de noyau monoprocesseur (UP) . . . . . . . . . . . . . . . . 4.7 Modèle de système d'exploitation monoprocesseur (UP). . . . . . . . . 4.8 Modèle de système multiprocesseur (MP) . . . . . . . . . . . . . . 4.9 Modèle de système en temps réel (RT) . . . . . . . . . . . . . . . . . . 4.10 Méthodologie de conception du logiciel du système fixe . . . 4.10.1 Prise en charge d'un langage de haut niveau pour la planification événementielle . . . . . . . . . . . . . . . . . . . . . . . . 4.10.2 Votre modèle de machine. . . . . . . . . . . . . . . . . . . 4.10.3 Modèle StateChart . . . . . . . . . . . . . . . . . . . . . 4.11 Résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les références. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
92 92 94
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
95 95 95 96 97 97 97 98 101 103 103 103 103 103 103 104 104 104 104 104 104 105 105 105
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
105 105 110 110 111
Gestion des processus dans les systèmes d'imbrication . . . . . . . . . . . . . . . . 5.1 Multitâche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Le concept de processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Multitâche et changement de contexte . . . . . . . . . . . . . . . . . . . . 5.3.1 Programme multitâche simple ADENINE . . . . . . . . . . . . . . . 5.3.2 Changement de contexte. . . . . . . . . . . . . . . . . . . . . . . . . 5.3.3 Démonstration du multitâche . . . . . . . . . . . . . . . . 5.4 Processus fougueux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.4.1 Création de processus dynamique . . . . . . . . . . . . . . . . . . . 5.4.2 Démonstration de processus fougueux . . . . . . . . . . . 5.5 Planification des processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5.1 Terminologie de la planification des processus. . . . . . . . . . . . . . . 5.5.2 Objectifs, politique et algorithmes de planification des processus . 5.5.3 Programmation de processus dans les systèmes enracinés . . . . . . . . 5.6 Synchronisation des processus . . . . . . . . . . . . . . . . . . . . . . . . . . 5.6.1 Chute et réveil . . . . . . . . . . . . . . . . . . . . . . . . 5.6.2 Pilote de périphérique utilisant Veille/Réveil . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
113 113 113 114 114 116 120 121 121 124 124 124 125 125 126 126 127
xii
Contenu
5.7
6
Systèmes embarqués événementiels utilisant la mise en veille/réveil . . . 5.7.1 Démonstration de Event-Driven Embedded Your Utilisation de Veille/Réveil . . . . . . . . . . . . . . . . . . . . . 5.8 Gestion des ressources à l'aide de Sleep/Wakeup . . . . . . . . . . 5.8.1 Lacunes du sommeil/réveil. . . . . . . . . . . . . . 5.9 Sémaphores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.10 Applications des sémaphores . . . . . . . . . . . . . . . . . . . . . . 5.10.1 Clé de sémaphore . . . . . . . . . . . . . . . . . . . . . . . . 5.10.2 Verrou mutex. . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.10.3 Votre gestion par sémaphore . . . . . . . 5.10.4 Attendre les interruptions et les messages . . . . . . . . . . . . 5.10.5 Coopération de procédure . . . . . . . . . . . . . . . . . . . . . 5.10.6 Avantages des sémaphores. . . . . . . . . . . . . . . . . 5.10.7 Précautions d'utilisation des sémaphores . . . . . . . . . . . . . . 5.10.8 Utiliser des sémaphores dans les systèmes embarqués . . . . . . . . 5.11 Autres mécanismes de synchronisation. . . . . . . . . . . . . . . . . 5.11.1 Marqueurs d'occasion dans OpenVMS . . . . . . . . . . . . . . . . . 5.11.2 Variables d'événement dans MVS . . . . . . . . . . . . . . . . . . 5.11.3 ENQ/DEQ dans MVS . . . . . . . . . . . . . . . . . . . . . . 5.12 Formes de synchronisation de haut niveau . . . . . . . . . . . . . . 5.12.1 Variables d'état. . . . . . . . . . . . . . . . . . . . . . 5.12.2 Moniteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.13 Communication de processus . . . . . . . . . . . . . . . . . . . . . . . . . 5.13.1 Mémoire partagée . . . . . . . . . . . . . . . . . . . . . . . . 5.13.2 Tuyaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.13.3 Signaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.13.4 Transmission de messages . . . . . . . . . . . . . . . . . . . . . . . . 5.14 Noyau Netz d'imbrication monoprocesseur (UP) . . . . . . . . . . 5.14.1 UP essentiel non préemptif . . . . . . . . . . . . . . . . 5.14.2 Démonstrations du réseau UP non préemptif . . . . 5.14.3 Noyau UP préemptif . . . . . . . . . . . . . . . . . . . . 5.14.4 Démonstration de la prévention du noyau UP . . . . . . . 5.15 Projet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les références. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
..........
129
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
131 132 133 133 134 134 135 135 135 135 136 136 137 139 140 140 141 141 141 142 142 142 142 147 147 152 152 158 158 164 1 65 167
Gestion de la mémoire dans FORTIFY . . . . . . . . . . . . . 6.1 Espaces d'adressage traités . . . . . . . . . . . . . . . 6.2 Unité de gestion de la mémoire (MMU) dans ARM 6.3 Registres MMU . . . . . . . . . . . . . . . . . . . . 6.4 Accès aux registres MMU . . . . . . . . . . . . . 6.4.1 Autoriser et désactiver la MMU. . 6.4.2 Contrôle d'accès au domaine . . . . . . . . . 6.4.3 Registre de base des clés de traduction . . . 6.4.4 Registre de contrôle d'accès au domaine. . . 6.4.5 Statut de panne enregistré . . . . . . . . . . 6.4.6 Registre d'adresse de défaut. . . . . . . . . . 6.5 Traductions d'adresse de virtualité. . . . . . . . . . . . 6.5.1 Principes de base du tableau des services . . . . . . . . . 6.5.2 Tableau des langues . . . . . . . . . . . . . 6.5.3 Identificateur de niveau un . . . . . . . . . . 6.6 Traduction de la liste des sections . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
169 169 169 170 171 171 172 173 173 173 174 174 175 175 175 176
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
Contenu
xiii
6.7
Traduction de la littérature de page . . . . . . . . . . . . . 6.7.1 Dîner de page de niveau 1 . . . . . . . . . . . . . . . 6.7.2 Descripteurs de page de niveau 2 . . . . . . . . . . . 6.7.3 Traduction concernant les petites références de page . . 6.7.4 Traduction de grandes références de page . . 6.8 Gestion de la mémoire Exemple de prog. . . . . 6.8.1 Pagination à un niveau utilisant des sections de 1 Mo 6.8.2 Pagination à deux niveaux utilisant des pages de 4 Ko . . 6.8.3 Radiomessagerie à un niveau avec espace VA élevé. 6.8.4 Radiomessagerie à deux niveaux sur l'espace VA élevé 6.9 Sommation . . . . . . . . . . . . . . . . . . . . . . . . . . . . Référence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Utilisateur 7.1 7.2 7.3 7.4
7.5
7.6
7.7
7.8
7.9
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
176 176 176 177 178 178 178 183 184 189 190 191
Processus de mode et appels système. . . . . . . . . . . . . . . . . . . . . Processus en mode utilisateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cartographie de l'espace d'adressage virtuel . . . . . . . . . . . . . . . . . . . . . Processus en mode moyen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3.1 Image en mode utilisateur . . . . . . . . . . . . . . . . . . . . . . . . . System Nuclear prend en charge les processus en mode utilisateur. . . . . . . . . . 7.4.1 Bâtiment PROC . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4.2 Réinitialiser le récupérateur. . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4.3 Code noyau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4.4 Script principal de compilation-lien . . . . . . . . . . . . . . . . . . 7.4.5 Démonstration du noyau avec processus en mode utilisateur. . . Système embarqué comprenant les processus d'exécution de l'exploiteur. . . . . . . . . . . 7.5.1 Processus dans le même domaine . . . . . . . . . . . . . . . . 7.5.2 Démonstration de processus dans le même domaine . . . 7.5.3 Transactions avec des domaines individuels . . . . . . . . . . . . . 7.5.4 Démonstration de Batch avec disque RAM de domaines individuels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.6.1 Création d'une image disque RAMS. . . . . . . . . . . . . . . . . . . 7.6.2 Chargeur de fichier image de processus . . . . . . . . . . . . . . . . . . . Gestion des processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.7.1 Création de processus . . . . . . . . . . . . . . . . . . . . . . . . . . 7.7.2 Fin du processus . . . . . . . . . . . . . . . . . . . . . . . 7.7.3 Traiter l'arbre généalogique . . . . . . . . . . . . . . . . . . . . . . . 7.7.4 Attendre la fin de la procédure enfant. . . . . . . . . . . . . 7.7.5 Fork-Exec inclut Unix/Linux . . . . . . . . . . . . . . . . . . . . 7.7.6 Implémentation de Fork . . . . . . . . . . . . . . . . . . . . . 7.7.7 Implémentation d'Exec . . . . . . . . . . . . . . . . . . . . . 7.7.8 Démonstration de Fork-Exec. . . . . . . . . . . . . . . . . . 7.7.9 Simple sh pour l'exécution de la commande . . . . . . . . . . . . . 7.7.10 vfork . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.7.11 Démonstration de vfork . . . . . . . . . . . . . . . . . . . . . Fils. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.8.1 Création de thread . . . . . . . . . . . . . . . . . . . . . . . . . . 7.8.2 Démonstration sans Threads . . . . . . . . . . . . . . . . . . . 7.8.3 Synchronisation des threads . . . . . . . . . . . . . . . . . . . . Système embarqué avec radiomessagerie à deux niveaux. . . . . . . . . . . . . . 7.9.1 Ensemble à 2 niveaux inactif . . . . . . . . . . . . . . . . . . . . . . 7.9.2 Ereignis of Statisches 2-Level Paging . . . . . . . . . . 7.9.3 Radiomessagerie dynamique à 2 niveaux . . . . . . . . . . . . . . . . . . . . 7.9.4 Démonstration de la radiomessagerie dynamique à 2 niveaux . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
193 193 193 194 194 197 197 201 203 209 209 209 209 210 212 212 213 215 216 218 218 219 220 220 221 222 223 225 226 228 2 30 231 231 233 234 234 236 237 238 240
xiv
8
Tableau
7.10 Cartographie de la mémoire KMH . . . . . . . . . . . . . . . . . . . . . . . 7.10.1 KMH par recherche statique à un niveau . . . . . . . . 7.10.2 KMH utilisant la radiomessagerie Statik à deux niveaux . . . . . . . . 7.10.3 KMH utilisant la radiomessagerie énergétique à deux niveaux. . . . . . 7.11 Embeds System prenant en charge les systèmes de stockage . . . . . . . . . . 7.11.1 Créer une image de carte HD. . . . . . . . . . . . . . . . . . . 7.11.2 Formater les partitions des cartes SD pour les systèmes de fichiers . . . . 7.11.3 Créer des accessoires de boucle pour les partitions de carte MD. . . . 7.12 Système embarqué avec système de fichiers SDC . . . . . . . . . . . 7.12.1 Pilotage d'affiches SD à l'aide d'un sémaphore. . . . . . . . . . . 7.12.2 Noyau système utilisant SB File Systematisches . . . . . . . . 7.12.3 En direct du système de magasin SDC . . . . . . . . . . 7.13 Image du noyau de démarrage à partir du SDC . . . . . . . . . . . . . . . . . . . 7.13.1 Programme de démarrage SDC. . . . . . . . . . . . . . . . . . . . 7.13.2 Démonstration du démarrage du noyau à partir de SDC . . . 7.13.3 Démarrage du SDC sans noyau avec pagination dynamique 7.13.4 Démarrage en deux étapes . . . . . . . . . . . . . . . . . . . . . 7.13.5 Démonstration du démarrage en deux étapes . . . . . . . . 7.14 Résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les références. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
241 241 244 245 245 246 247 247 248 248 252 253 253 254 258 259 260 261 262 264
Systèmes d'exploitation fixes à usage général . . . 8.1 Systèmes d'exploitation à usage général . . . . . . . . 8.2 Systèmes d'exploitation embarqués à usage général. 8.3 Portage de GPOS existants vers des systèmes embarqués . 8.4 Développe un GPOS imbriqué pour ARM. . . . . . 8.5 Organisation d'EOS . . . . . . . . . . . . . . . . . . . 8.5.1 Plate-forme matérielle . . . . . . . . . . . . . . 8.5.2 Arborescence des fichiers source EOS . . . . . . . . . . . . 8.5.3 Fichiers du noyau EOS . . . . . . . . . . . . . . . 8.5.4 Capacités d'EOS. . . . . . . . . . . . . . 8.5.5 Séquence d'exécution d'EOS . . . . . . . . . . 8.5.6 Gestion des processus dans EOS . . . . . . . 8.5.7 Code d'assemblage de EO . . . . . . . . . . . 8.5.8 Données du noyau d'EOS . . . . . . . . . . . . . 8.5.9 Fonctions de gestion de processus. . . . . . 8.5.10 Tuyaux . . . . . . . . . . . . . . . . . . . . . . . . 8.5.11 Transmission de messages . . . . . . . . . . . . . . . . 8.5.12 Démonstration de la transmission de messages . . . 8.6 Gestion de la mémoire dans EOS . . . . . . . . . . . . 8.6.1 Carte mémoire de l'EOS . . . . . . . . . . . . 8.6.2 Espaces Entreprise Virtuels . . . . . . . . . . . 8.6.3 Pgdir et tables de pages en mode noyau . . 8.6.4 Traiter les tableaux de pages du mode utilisateur . . . . . 8.6.5 Changer Pgdir pendant le changement de processus . . 8.6.6 Pagination animée . . . . . . . . . . . . . . . . 8.7 Traitement des exceptions et des signes. . . . . . . . . . . 8.7.1 Usinage du signal sous Unix/Linux . . . . . 8.8 Traitement du signal dans EOS . . . . . . . . . . . . . . . 8.8.1 Signaux dans la ressource PROC. . . . . . . . . 8.8.2 Origines des signaux dans EOS . . . . . . . . . . . . 8.8.3 Envoyer le signal au processus . . . . . . . . . . 8.8.4 Modifier le gestionnaire de signaux dans le noyau . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
265 265 265 265 266 266 266 267 267 268 268 269 271 279 282 282 283 285 285 285 285 285 286 286 286 288 288 289 289 289 2 89 290
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Contenu
xv
9
8.8.5 Traitement des signaux dans EOS Main . . . . . . . . . . . . . . . 8.8.6 Dispatch Signal Catchman pour l'exécution en mode utilisateur 8.9 Vos pilotes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.10 Planification de processus dans EOS . . . . . . . . . . . . . . . . . . . . . . . . 8.11 Service de minuterie dans EOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.12 Système de fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.12.1 Niveaux d'opération de fichier . . . . . . . . . . . . . . . . . . . . . . 8.12.2 Opérations d'E/S de fichiers . . . . . . . . . . . . . . . . . . . . . . . . 8.12.3 Système de fichiers EXT2 dans EOS . . . . . . . . . . . . . . . . . . . 8.12.4 Obtention du niveau 1 FS . . . . . . . . . . . . . . . . 8.12.5 Mise en œuvre du SF de niveau 2 . . . . . . . . . . . . . . . . 8.12.6 Mise en œuvre du FS de niveau 3 . . . . . . . . . . . . . . . . 8.13 Blocage de la mémoire tampon d'E/S du périphérique . . . . . . . . . . . . . . . . . . . . . . . . 8.14 Algorithme de gestion du tampon d'E/S . . . . . . . . . . . . . . . . . . . 8.14.1 Sortie du cache de tampon d'E/S. . . . . . . . . . . . 8.15 Interface utilisateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.15.1 Vers INIT utilisateur . . . . . . . . . . . . . . . . . . . . . . . . 8.15.2 Quel programme Get . . . . . . . . . . . . . . . . . . . . . . . . 8.15.3 Le programme sh . . . . . . . . . . . . . . . . . . . . . . . . . . 8.16 Démonstration concernant EOS. . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.16.1 Démarrage EOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.16.2 Traitement des commandes dans EOS . . . . . . . . . . . . . . . . . 8.16.3 Signalisation et gestion des exceptions dans EOS . . . . . . . . . . 8.17 Chapitre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les références. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
290 291 291 292 292 294 294 296 302 304 310 316 318 318 321 321 321 322 322 323 323 323 323 326 327
Multitraitement dans les systèmes embarqués. . . . . . . . . . . . . . . . . 9.1 Multitraitement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2 Exigences du système SMP . . . . . . . . . . . . . . . . . . . . . 9.3 Processeurs ARM MPcore . . . . . . . . . . . . . . . . . . . . . . . 9.4 Processeur ARM Cortex-A9 MPcore. . . . . . . . . . . . . . . . 9.4.1 Cœurs de processeur . . . . . . . . . . . . . . . . . . . . . . . 9.4.2 Pack de contrôle espion (SCU) . . . . . . . . . . . . . . . . 9.4.3 Contrôleur de désactivation de génération (GIC) . . . . . . . . . . 9.5 Démonstration de programmation GIC . . . . . . . . . . . . . . . . . . . . . 9.5.1 Configurez le GIC susmentionné pour router intermittent . . . . . . . . 9.5.2 Explications du code de configuration GIC . . . 9.5.3 Priorité d'interruption plus masques d'interruption . . . . . . . . . 9.5.4 Démonstration de la programmation GIC . . . . . . . . . 9.6 Séquence de démarrage des MPcores ARM . . . . . . . . . . . . . . . 9.6.1 Séquence de démarrage brute . . . . . . . . . . . . . . . . . . . 9.6.2 Séquence de démarrage facilitée par Booter . . . . . . . . . . . 9.6.3 Démarrage SMP sur des machines View . . . . . . . . . . 9.7 Exemples de démarrage ARM SMP . . . . . . . . . . . . . . . . . . . . 9.7.1 Cas de démarrage ARM SMP 1 . . . . . . . . . . . . . 9.7.2 Démonstration dans l'exemple de démarrage SMP 1 . . . . . 9.7.3 Exemple de démarrage SMP ARRIÈRE 2 . . . . . . . . . . . . . 9.7.4 Démonstration du cas de configuration ARM SMP 2 9.8 Régions critiques dans SMP . . . . . . . . . . . . . . . . . . . . . . . 9.8.1 Mise en œuvre dans les régions critiques dans SMP . . . . 9.8.2 Lacunes dans les opérations XCHG/SWAP . . . . 9.8.3 Instructions de synchronisation ARM pour SMP . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
329 329 329 331 331 331 332 332 332 332 339 340 340 340 341 341 341 341 342 346 347 348 349 349 350 350
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
xxv
Contenu
9.9
Primitives de synchronisation dans SMP . . . . . . . . . . . . . . . . 9.9.1 Blocages tournants . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.9.2 Modèle Spinlock . . . . . . . . . . . . . . . . . . . . . . 9.9.3 La démonstration du démarrage SMP inclut Spinlock . . . 9.9.4 Mutex dans SMP . . . . . . . . . . . . . . . . . . . . . . . . 9.9.5 Implémentation de Mutex avec Spinlock . . . . . 9.9.6 Démonstration du démarrage SMP, y compris Mutex Lock 9.10 Minuteries globales et locales . . . . . . . . . . . . . . . . . . . . . . . 9.10.1 Démonstration des horloges régionales pour SMP . . . . . . . 9.11 Sémaphores dans SMP . . . . . . . . . . . . . . . . . . . . . . . . . . 9.11.1 Applications des sémaphores dans SMP . . . . . . . . . 9.12 Verrouillage conditionnel . . . . . . . . . . . . . . . . . . . . . . . . . . 9.12.1 Verrouillage conditionnel . . . . . . . . . . . . . . . . . . . . 9.12.2 Sujet Mutex . . . . . . . . . . . . . . . . . . . . . 9.12.3 Opérations conditionnelles sur les sémaphores . . . . . . . . . . 9.13 Gestion de la mémoire dans SMP . . . . . . . . . . . . . . . . . . . 9.13.1 Modèles de supervision de la mémoire dans SMP. . . . . . . . 9.13.2 Espaces U uniformes . . . . . . . . . . . . . . . . . . . . 9.13.3 Espaces TVA non uniformes . . . . . . . . . . . . . . . . . 9.13.4 Calcul parallèle dans des espaces VA non uniformes . 9.14 Multitâche dans SMP . . . . . . . . . . . . . . . . . . . . . . . . . . 9.15 SMP Kernel on Process Betreuung . . . . . . . . . . . . . . 9.15.1 Le fichier ts.s . . . . . . . . . . . . . . . . . . . . . . . . . . 9.15.2 Le fichier t.c . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.15.3 Fichier Kernel.c . . . . . . . . . . . . . . . . . . . . . . . . . . 9.15.4 Appareil Appareil et interruption du palan dans SMP. . . 9.15.5 Démonstration de la gestion des processus pour SMP . 9.16 Systèmes d'exploitation SMP à usage général . . . . . . . . . . . . 9.16.1 Organisation de SMP_EOS. . . . . . . . . . . . . . . . 9.16.2 Arborescence des enregistrements sources SMP_EOS . . . . . . . . . . . . . . . 9.16.3 Fichiers du noyau SMP_EOS . . . . . . . . . . . . . . . . . . 9.16.4 Gestion des processus dans SMP_EOS . . . . . . . . . . 9.16.5 Protéger le bâtiment de données du noyau dans SMP . . . . . . . 9.16.6 Prévention des interblocages dans SMP . . . . . . . . . . . . . . 9.16.7 Algorithmes UP d'adaptation pour SMP . . . . . . . . . . . . . 9.16.8 Pilote de périphérique et gestionnaires d'interruptions dans SMP. . . 9.17 Démonstration SMP_EOS Votre . . . . . . . . . . . . . . . . . 9.17.1 Séquence de démarrage de SMP_EOS . . . . . . . . . . . . 9.17.2 Capacités de SMP_EOS . . . . . . . . . . . . . . . . 9.17.3 Démonstration de SMP_EOS . . . . . . . . . . . . . . 9.18 Résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les références. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Procédures des opérateurs temps réel embarqués. . . . . . . . . . 10.1 Définitions des RTOS . . . . . . . . . . . . . . . . . . . . . . 10.2 Planificateur de tâches dans RTOS . . . . . . . . . . . . . . . . . 10.2.1 Rate-Monoton Date (RMS) . . . . . 10.2.2 Ordonnancement EDF (Earliest-Deadline-First) . 10.2.3 Date limite-Ordonnancement monotone (DMS). . 10.3 Inversion de priorité . . . . . . . . . . . . . . . . . . . . . . . 10.4 Prévention d'inversion de priorité . . . . . . . . . . . . . . . 10.4.1 Plafond prioritaire . . . . . . . . . . . . . . . . . . . 10.4.2 Héritage prioritaire . . . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
351 351 352 353 353 354 355 357 359 359 360 361 361 361 362 362 363 363 365 368 371 372 373 379 381 384 387 389 389 390 3 90 391 392 394 395 396 397 397 397 398 399 399
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
401 401 401 402 402 403 403 404 404 404
Contenu
xlvii
10.5 Etude des RTOS. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.5.1 FreeRTOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.5.2 MicroC/OS (µC/OS) . . . . . . . . . . . . . . . . . . . . . . 10.5.3 NuttX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.5.4 VxWorks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.5.5 QNX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.5.6 Linux temps réel . . . . . . . . . . . . . . . . . . . . . . . . . 10.5.7 Critique du RTOSS existant . . . . . . . . . . . . . . . . . . 10.6 Société de conception de RTOS . . . . . . . . . . . . . . . . . . . . . . . 10.6.1 Traitement des interruptions . . . . . . . . . . . . . . . . . . . . . . 10.6.2 Gestion des tâches . . . . . . . . . . . . . . . . . . . . . . . . 10.6.3 Planification des tâches . . . . . . . . . . . . . . . . . . . . . . . . . 10.6.4 Outils de synchronisation . . . . . . . . . . . . . . . . . . . . . 10.6.5 Communication de tâche. . . . . . . . . . . . . . . . . . . . . . 10.6.6 Gestion de la mémoire . . . . . . . . . . . . . . . . . . . . . 10.6.7 Système de fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.6.8 Traçage et débogage . . . . . . . . . . . . . . . . . . . . 10.7 RTO monoprocesseur (UP_RTOS) . . . . . . . . . . . . . . . . . . . 10.7.1 Gestion des tâches dans UP_RTOS . . . . . . . . . . . . . . 10.7.2 Problème de synchronisation dans UP_RTOS. . . . . . . . . . . . 10.7.3 Planification des tâches dans UP_RTOS . . . . . . . . . . . . . . . 10.7.4 Communication matière dans UP_RTOS . . . . . . . . . . . . 10.7.5 Le bouclier est une région critique. . . . . . . . . . . . . . . . 10.7.6 Système de fichiers et journalisation . . . . . . . . . . . . . . . . . . . 10.7.7 UP_RTOS avec tâches périodiques statiques et planification Round-Robin . . . . . . . . . . . . . . . . 10.7.8 UP_RTOS avec tâches régulières statiques et prévention de la planification . . . . . . . . . . . . . . . . . 10.7.9 UP_RTOS via les ressources de distribution de tâches dynamiques . 10.8 RTOS multiprocesseur (SMP_RTOS) . . . . . . . . . . . . . . . . . 10.8.1 Noyau SMP_RTOS pour la gestion des tâches . . . . . . . 10.8.2 Adapter UP_RTOS à SMP . . . . . . . . . . . . . . . . . . 10.8.3 Interruptions de verrouillage dans SMP_RTOS . . . . . . . . . . . . . 10.8.4 Empêcher la planification des tâches dans SMP_RTOS. . . . . . 10.8.5 Changement de tâche par SGI . . . . . . . . . . . . . . . . . . . . . . 10.8.6 Planification des tâches par tranches de temps dans SMP_RTOS . . . . . . 10.8.7 Planificateur de tâches préemptif dans SMP_RTOS. . . . . . 10.8.8 Héritier prioritaire . . . . . . . . . . . . . . . . . . . . . . . 10.8.9 Démonstration du système SMP_RTOS . . . . . . . 10.9 Résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les références. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
404 404 405 405 406 407 407 409 412 412 412 412 412 412 412 412 413 413 413 414 414 414 415 415
.........
416
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
426 432 440 440 456 458 461 461 463 465 468 470 474 474
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
477
. . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
À propos de l'auteur
K.C. Wang a obtenu le diplôme BSEE de l'Université nationale de Taiwan en 1960 et le doctorat. diplôme au sein de l'ingénierie électrique de l'Université Northwestern, Evanston, infectée en 1965. Fellow est actuellement professeur de langue d'ingénierie électrique et d'informatique à l'Université de l'État de Washington. Ses enjeux académiques portent sur les méthodes d'exploitation, les systèmes distribués et le calcul parallèle.
xix
1
Induction
1.1
À propos de ce livre
Il s'agit de la conception et de l'einrichtung de systèmes d'exploitation embarqués et en temps réel (Gajski et al. 1994). Il couvre les concepts de base et les bases des systèmes d'exploitation (OS) (Silberschatz et al. 2009 ; Stallings 2011 ; Tanenbaum et Woodhull 2006 ; Wang 2015), l'architecture des systèmes embarqués (ARM Architecture 2016), la programmation des systèmes embarqués (ARM Scheduling 2016), concepts de plantes en temps réel et exigences netz en temps réel (Dietrich et Walker 2015). Il explique comment appliquer la théorie et la pratique de USER à cette conception et à la mise en œuvre de systèmes d'exploitation pour les systèmes embarqués et temps réel.
1.2
Incitations à obtenir un livre
En un printemps, la plupart des systèmes embarqués ont été conceptualisés pour des requêtes extraordinaires. Un système embarqué se compose généralement d'un microcontrôleur d'ampères et de quelques périphériques d'E/S, ce qui est prévu pour surveiller certains capteurs d'entrée en plus de générer des signaux jusqu'à ce que des périphériques externes distants, comme pour allumer des LED ou activer certains commutateurs, etc. Pour cette raison, le contrôle les programmes des premiers systèmes embarqués sont également extrêmement simples. Les personnes sont généralement écrites sous la forme d'une super-boucle ou d'une simple structure d'application pilotée par les événements. Cependant, avec l'augmentation de la puissance de calcul des systèmes embarqués ces dernières années, les systèmes embarqués ont fait un bond en avant considérable à la fois dans la complexité et dans les domaines d'application. En conséquence, les approches traditionnelles de modèle logiciel pour les systèmes embarqués ne sont plus adéquates. En place pour faire face à la complexité sans cesse croissante des systèmes et aux questions de fonctionnalités supplémentaires, l'embarqué nécessite un logiciel plus puissant. À l'heure actuelle, de nombreux services embarqués sont en fait des machines informatiques de grande puissance avec des processeurs multicâbles, des gigaoctets de mémoire et des dispositifs de stockage de plusieurs gigaoctets. De tels systèmes sont destinés à exécuter une large gamme de programmes d'application. Afin d'actualiser pleinement leur possibilité, les systèmes inclus modernes ont besoin du support d'un schéma d'exploitation multifonctionnel. Un bon exemple est l'évolution des téléphones à carburant antérieurs pour les téléphones intelligents actuels. Alors que les premiers étaient conçus pour effectuer la tâche simple de stationner ou de recevoir des appels téléphoniques seuls, les derniers susmentionnés peuvent utiliser des processeurs multicœurs et exécuter des versions adaptées de Linux, comme Android, pour réaliser plusieurs tâches. La tendance actuelle des concepteurs de logiciels de systèmes embarqués évolue clairement dans le sens du développement de systèmes d'exécution multifonctionnels adaptés au futur environnement mobile. Le but de ce livre est de montrer comment appliquer la théorie et la pratique des systèmes d'exploitation pour étendre le système d'exploitation aux systèmes embarqués et temps réel.
1.3
Public objectif et ciblé
L'objectif de ce livre est de déployer un rail adapté à l'enseignement et à l'apprentissage de la théorie et de la coutume susmentionnées des systèmes de services embarqués et temps réel. Il couvre l'architecture des systèmes embarqués, la programmation des systèmes embarqués, les concepts et principes de base des systèmes d'exploitation (OS) et des systèmes temps réel. Il montre comment appliquer ces principes et techniques de programmation à la conception et à la mise en œuvre d'un système d'exploitation authentique pour les systèmes fixes et temps réel. Ce livre est destiné aux étudiants en informatique et aux professionnels de l'informatique qui souhaitent étudier les détails internes susmentionnés des systèmes d'exploitation embarqués et temps réel. Il convient comme formation pour les cours sur les systèmes embarqués et en temps réel avec des programmes d'études My Science / Engineering à orientation technique qui tentent d'atteindre un équilibre entre la théorie et la pratique. L'évolution du livre © Springer International Release A 2017 K.C. Pecker, Services d'exploitation intégrés et en temps réel, DOI 10.1007/978-3-319-51517-5_1
1
2
1
Introduction
Le style, associé à un code d'exemple détaillé et à des systèmes d'échantillonnage fonctionnels complets, le rend particulièrement adapté à l'auto-apprentissage par les passionnés d'informatique. Le livre couvre toute la gamme de la conception de logiciels depuis les systèmes embarqués et en temps réel, allant de simples programmes de contrôle super-boucle et événementiels pour les systèmes monoprocesseurs (UP) aux systèmes d'exploitation complets de multitraitement symétrique (SMP) sur les activités multicœurs. Il convient également à l'étude avancée des systèmes d'exploitation embarqués et temps réel.
1.4
Caractéristiques uniques de ce livre
Ce livre a de nombreuses caractéristiques uniques, qui le distinguent des autres livres. 1. Ce livre est autonome. Il contient tous les produits de base et de base pour étudier les logiciels embarqués, les systèmes temps réel et les produits d'exploitation en général. Il s'agit notamment de l'historique ARM, des instructions ARM pour la programmation (architecture ARM 2016 ; ARM926EJ-S 2008), de la chaîne d'outils pour le développement de programmes (chaîne d'outils ARM 2016), des machines virtuelles (émulateurs QEMU 2010) avec implémentation et test de téléchargement, image d'exécution du programme, appel d'utilisation. expositions, utilisation de la pile d'exécution et liaison des programmes CARBON avec le code d'assemblage. 2. Les interruptions et le traitement des interruptions sont importants pour les systèmes embarqués. Ce livre traite de l'équipement d'interruption et de l'édition des interruptions de manière très détaillée. Celles-ci englobent les interruptions non vectorielles, les interruptions vectorielles (ARM PL190 2004), les interruptions non imbriquées, les arrêts imbriqués (Nesting Interrupts 2011) ainsi que la programmation du contrôleur d'annulation générique (GIC) (ARM GIC 2013) dans ARM MPcore (ARM Cortex-A9 MPCore 2012) basés sur des systèmes. Il montre comment appliquer les principes du traitement des interruptions pour développer un camion de produit piloté par interruption et un système embarqué piloté par événement. 3. Ce livre présente un cadre générique pour le développement de pilotes de périphériques pilotés par interruption, avec une importance sur l'interaction et la synchronisation entre l'assistant d'interruption et les processus. Pour chaque périphérique, il explique les principes de fonctionnement et les techniques Web avant de montrer l'implémentation actuelle du pilote, et il atteste les pilotes de périphérique par des exemples de programmes de travail complets. 4. Le livre montre la conception et la mise en œuvre de l'OPÉRATIONNEL complet pour les procédures embarquées avec escalier incrémental. Tout d'abord, il développe un noyau multitâche simple pour prendre en charge la synchronisation des processus réels de gestion des processus. Ensuite, il intègre le matériel de l'unité de gestion de la mémoire (MMU) (ARM MMU 2008) dans le système pour donner des mappages locaux virtuels et prolonge le noyau simple pour prendre en charge les processus en mode utilisateur et les appels système. Ensuite, il ajoute la planification des processus, le traitement des envois, le système de fichiers et l'interface totale au système, ce qui en fait un système d'exploitation complet. Le style évolutif du livre aide le lecteur à mieux comprendre et taper. 5. Le chapitre 9 traite en détail des systèmes embarqués à multitraitement symétrique (SMP) (Intel 1997). Tout d'abord, il explique les exigences des systèmes SMP et compare l'architecture ARMED MPcore (ARM11 2008 ; ARM Cortex-A9 MPCore 2012) avec l'architecture système SMP d'Intel. Ensuite, il décrit les fonctionnalités SMP des processeurs ARM MPcore, qui incluent le SCU et le GIC depuis le guidage des interruptions et la communication et la synchronisation entre les processeurs par des interruptions générées par logiciel (SGI). Il utilise une série d'exemples de développement pour montrer comment démarrer quels pilotes ARM MPcore et souligne la nécessité d'une synchronisation avec un SMP. Ensuite, il explique les directions WEAPON LDREX / STREX et les barrières de mémoire et les utilise pour installer des verrous tournants, des mutex et des sémaphores pour la synchronisation par lots dans SMP our. Il présente une méthodologie générale de conception du noyau SMP et montre comment demander les principes pour adapter un noyau monoprocesseur (UP) pour SMP. En outre, il montre également comment utiliser des algorithmes équivalents pour la gestion des processus et des ressources afin d'améliorer la concurrence et l'efficacité dans les systèmes SMP. 6. Le chapitre 10 traite des systèmes exploités en temps réel (RTOS). Il introduit la conceptualisation des exigences réelles des systèmes temps réel. Il couvre les différents types d'algorithmes de planification de fonctions dans RTOS et montre comment gérer l'inversion de priorité et l'abonnement aux commandes dans les systèmes en temps réel. Il comprend des études de cas de multiples ROTOS populaires et énonce un ensemble de directives générales pour la construction de RTOS. Je montre la conception à la fois des performances de l'ampère UP_RTOS pour les systèmes monoprocesseurs (UP). Ensuite, il étend l'UP_RTOS en SMP_RTOS pour SMP, qui supporte les interruptions verzahnt, le calendrier préemptif des tâches, l'héritage de priorisation et la synchronisation inter-processeur par SGI. 7. Tout au long du livre, il utilise des systèmes d'échantillons entièrement utilisés pour démontrer la clé de conception et les techniques de mise en œuvre. Il utilise la chaîne d'outils ARM sous Ubuntu (15.10) Linux pour étendre le logiciel pour les systèmes embarqués, et il utilise des machines virtuelles ARM émulées sous QEMU comme plate-forme de verwirklichung et de test.
1.5 Contenu du livre
1.5
3
Contenu du livre
Ce livre est organisé comme suit. Le chapitre 2 couvre l'architecture ARM, les instructions ARM, la programmation ARM et le développement de programmes à exécuter sur des machines virtuelles ARM. Ceux-ci incluent la sélection de la console ARM, les banques d'enregistrement dans différents modes, les instructions et la planification de base dans l'assemblage ARM. Il introduit la chaîne d'outils ARM sous Ubuntu (15.10) Linux et les machines virtuelles ARM émulées sous QEMU. Il voit comment utiliser la chaîne d'outils ARM pour développer des programmes depuis l'exécution avec la machine efficace ARM Versatilepb par une série de produits de programmation. Il explique la convention d'appel de fonction en C et montre comment utiliser le code assembleur de périphériques avec les programmes C. Ensuite, pour faire évoluer un pilote UART simple pour les E/S sur les ports série, plus un dispositif LCD pour afficher à la fois les images graphiques et le texte. Il ou envoie le développement sur une fonction générique printf() pour l'impression formatée sur des périphériques d'édition prenant en charge l'opération de base de caractères d'impression. Le chapitre 3 couvre le traitement des interruptions et des exemptions. Il décrit les modes de fonctionnement des processeurs ARM, les types d'exceptions pressent les vecteurs d'exception. Les informations expliquent les fonctions sont les contrôleurs de sortie plus et les principes de traitement des interruptions en détail. Ensuite, il applique un principe de traitement des interruptions à la structure et à l'implémentation des pilotes de périphériques pilotés par les interruptions. Ces pilotes d'inclusions pour les minuteries, le clavier, les UART et les cartes SD (SDC 2016), et il démontre les pilotes d'équipement par des programmes exemplaires. Il explique quels sont les avantages des interruptions vectorielles par rapport aux interruptions non vectorielles. Il montre comment configurer les contrôleurs d'interruption vectorielle (VIC) pour les interruptions vectorielles, réel montre les données d'interruptions vectorielles par exemple parcourir. Il explique également les principes des interruptions imbriquées et démontre le traitement des interruptions imbriquées par les programmes de cas. Le chapitre 4 couvre les modèles de systèmes embarqués. Tout d'abord, il excuse et démontre le modèle de système de super-boucle simple et souligne vos lacunes. Ensuite, il traite d'un modèle événementiel et de démonstrations d'activités événementielles périodiques et asynchrones par des exemples de programmes. Afin d'aller au-delà des simples modèles de super-boucles et de systèmes événementiels, il justifie la demande de processus ou de tâches utilisés dans les systèmes embarqués. Ensuite, il présente les différents types de modèles de processus, qui sont utilisés comme modèles de développement de systèmes embarqués dans le livre. Enfin, il présente la conception formelle de nos systèmes embarqués fork. Il illustre le modèle Infinite State Machine (FSM) (Katz et Borriello 2005) par une conception complète de l'exemple d'implémentation en détail. Le chapitre 5 traite de la gestion de l'impression. Il introduit le concept d'action de qui et le principe de base du multitâche. L'information démontre la technique du multitâche en raison de la commutation de contexte. Il montre comment créer des processus rapidement et discute des objectifs, de la politique et des algorithmes de date de traitement. Les ordinateurs couvrent la synchronisation des processus et expliquent également divers types de mécanisations de synchronisation des processus, notamment le sommeil / réveil, les mutex et les sémaphores. Il montre comment utiliser la synchronisation de processus pour implémenter des systèmes embarqués pilotés par événements. Il aborde les différents schémas de communication inter-processus, qui incluent la mémoire partagée, les canaux et la transmission de messages. Il explique comment combiner ces concepts et techniques pour implémenter un intérieur monoprocesseur (UP) pour la gestion des processus, et il démontre les exigences du système et les techniques de programmation pour la planification des processus non préemptifs et dissuasifs. Le noyau Who UPWARD sert de base au développement de réseaux d'exploitation complets dans les chapitres suivants. Le chapitre 6 couvre l'unité de gestion des mémoires ARM (MMU) et les mappages d'espace d'adressage virtuel. Il explique en détail la MMU ARM et montre également comment configurer le mappage d'adresses virtuelles requis par la MMU à l'aide de la pagination twain à un niveau et à deux niveaux. En outre, il a également expliqué la distinction entre l'espacement VA faible et les mappages d'espace VA élevés et leurs implications sur les installations du système. Plutôt que de discuter uniquement des normes d'administration de la mémoire, il démontre la diversité des schémas de mappage d'adresses virtuelles en exécutant un logiciel d'exemple complet. Le chapitre 7 couvre les processus en mode utilisateur et les appels utilisateur. Tout d'abord, il étend un noyau de base GOING sur le chapitre 5 pour produire des fonctions de vérification de litige supplémentaires, qui incluent la création de processus dynamique, la terminaison de processus, la synchronisation de processus et l'attente de la fin traitée par une fille. Ensuite, il étend un noyau essentiel pour prendre en charge le fonctionnement en mode utilisateur. Itp montre comment utiliser la gestion totale pour fournir à chaque processus un espace d'adressage virtuel en mode total confidentiel qui sera isolé des autres processus et protégé par le logiciel MMU. Il couvre et illustre différents types de schémas de supervision RAM, qui incluent des sections à un niveau et une pagination statique et dynamique à deux niveaux. Il couvre les concepts avancés des techniques réelles de dossier, exec, vfork également pick. Sont des modules complémentaires, cela montre comme l'utilisation de cartes SD pour conserver les fichiers image du noyau et du mode utilisateur dans un seul fichier SDC. Il montre également comment démarrer le noyau du système par les partitions SDC. Ce chapitre sert de base à la conception et à la mise en œuvre d'un système d'exploitation à usage général pour les systèmes embarqués. Le chapitre 8 présente un système d'exploitation à usage général entièrement fonctionnel (GPOS), désigné par EOS, pour les systèmes intégrés basés sur ARMS monoprocesseur (UP). La suite est un bref résumé de l'organisation et des installations du système AURORA.
4
1
Introduction
1. Images système : L'image du noyau amorçable et les exécutables du mode Exploiter génèrent une table de ressources par la chaîne d'outils ARM sous Ubuntu (15.10) Linux et résident dans un fichier sys (EXT2 2001) sur une partition SDC. Le SDC contient des amorceurs d'étape 1 et d'étape 2 pour démarrer vers le ciel l'image du noyau à partir de la partition SDC. Après le démarrage, quel noyau EOS monte la partition SDC en tant que système de fichiers racine. 2. Processus : Le système prend en charge NPROC = 64 processus et NTHRED = 128 threads par processus, les deux capacités pouvant être augmentées si nécessaire. Chaque cycle de traitement (à l'exception du processus inactif P0) dans le style d'exécution du noyau ou de l'utilisateur. La gestion de la mémoire est des représentations de processus est la pagination dynamique à 2 niveaux. Opération de planification de votre priorité dynamique et tranche de temps. Les ordinateurs assurent la communication inter-processus, les canaux et la transmission des messages. Le noyau EOS prend en charge les fonctions de gestion de méthode de fork, exec, vfork, threads, exit et attend la fin de l'enfant. 3. Pilotes de gadget : Elle contient les pilotes de périphérique pour les périphériques d'E/S les plus couramment utilisés, notamment l'écran LCD, la minuterie, le clavier, l'UART ou le SDC. 4. Système de fichiers : EOS prend en charge un système de fichiers EXT2 entièrement compatible avec le support. Il montre les principes des opérations sur les fichiers, le chemin de contrôle susmentionné et le flux de données de l'espace utilisateur à la salle du noyau jusqu'au niveau du pilote de périphérique. Il montre quelle organisation interne des systèmes de fichiers, réel il définit l'introduction d'un système de fichiers complet en détail. 5. Service de minuterie, exceptions ou traitement du signal : il fournit des fonctions de service de chronomètre et unifie la gestion des exceptions avec le traitement du signal, ce qui permet aux utilisateurs d'installer signal fish pour gérer les exceptions en mode utilisateur. 6. Interface utilisateur : elle permet des connexions multi-utilisateurs à la console et aux terminaux UART. L'interpréteur de commandes sh prend en charge les exécutions de commandes simples avec des redirections d'E/S, ainsi que plusieurs commandes connectées par des canaux. 7. Portail : De l'anlage EOS s'exécute sur une diversité de machines virtuelles ARM sous QEMU, principalement pour plus de commodité. Il devrait en outre fonctionner sur de vraies cartes schaft basées sur ARM, le matériel d'E / S approprié prenant en charge. Portage d'EOS sur certains systèmes populaires basés sur FORTIFY, par ex. Raspberry PI-2 est actuellement en cours. Qui prévoient de le rendre disponible pour les lecteurs à télécharger dès qu'il est prêt. Le chapitre 9 traite du multitraitement dans les systèmes embarqués. Il explique les exigences des systèmes à multitraitement unique (SMP) et l'approche relative du SMP d'Intel égale celle d'ARM. Il répertorie les applicateurs ARM MPcore et décrit les composants réels des processeurs ARM MPcore avancés dans le SMP de démarrage durable. Tous les systèmes basés sur ARM MPcore conditionnent le contrôle global des interruptions (GIC) pour le routage des interruptions et la conversation entre processeurs. Les éléments montrent comment configurer le GIC pour acheminer les interruptions et illustrent la programmation du GIC par des exemples. Il montre comment démarrer les MPcores ARM et explique le besoin de synchronisation par un environnement SMP. Il montre comment utiliser les instructions traditionnelles de test et d'ensemble ou équivalentes pour implémenter les mises à jour atomiques et les régions critiques et noter leurs lacunes. Ensuite, j'ai déclaré les nouvelles fonctionnalités des MPcores ARM avec le support de SMP. Il s'agit notamment des instructions ARM LDRES/STRES et des barres de mémoire. Il montre comment utiliser les fonctionnalités récentes des processeurs ARM MPcore pour implémenter des verrous tournants, des mutex et des sémaphores pour la synchronisation des processus dans SMP. Il définit les verrous tournants conditionnels, les mutex et les hémaphores de la méthode montre pour l'utiliser par interblocage dans les noyaux SMP. Il couvre également les fonctionnalités supplémentaires du SMP requis par ARM MMU. E donné une méthodologie générale pour adapter le noyau du système d'exploitation monoprocesseur jusqu'au SMP. Ensuite, il applique les principes pour développer un SMP_EOS complet par des systèmes SMP embarqués, et il démontre les capacités du système SMP_EOS par des exemples de programmes. Le cours 10 couvre les systèmes d'exploitation en temps réel (RTOS). Y sont introduits les concepts susmentionnés et les exigences des systèmes en temps réel. Il couvre les différents types d'algorithmes de planification du travail dans RTOS, qui incluent RMS, EDF et DMS. Il décrit le problème de priorité inversée en raison de la planification préventive des problèmes et montre comment gérer la réversion de priorité et la préemption de tâche. Il comprend des études de cas de plusieurs systèmes d'exploitation en temps réel populaires et présente un ensemble de directives générales pour la conception de RTOS. Il présente la conception et la mise en œuvre d'un UP_RTOS pour les systèmes monoprocesseur (UP). Ensuite, il étend l'UP_RTOS à un SMP_RTOS, qui prend en charge les interruptions imbriquées, la planification préemptive des tâches, la hiérarchisation de l'héritage et la synchronisation interprocesseur par SGI.
1.6
Utilisez cet achat comme manuel pour les systèmes embarqués
Ce livre convient comme manuel pour les cours à orientation technique sur les solutions natives et en temps réel dans les programmes de sciences/ingénierie en calculatrice, donc un effort pour un équilibre entre la théorie et la pratique. Un cours d'un semestre basé sur ce livre peut inclure les sujets suivants mentionnés ci-dessus.
1.6 Utiliser Is Reserve comme manuel pour les systèmes intégrés
5
1. Introduction aux systèmes enracinés, architecture ARM, programmation ARM de base, chaîne d'outils ARM et développement de logiciels pour les systèmes intégrés, code d'assemblage d'interface avec les programmes CARBON, image d'exécution et utilisation de l'empilement d'exécution, exécution du programme sur l'équipement virtuel ARM, appareil simple pilotage des E/S (Chap. 2). 2. Exceptions et interruptions, produits d'interruptions, conception de la mise en œuvre réelle de la voiture d'instruments pilotée par interruption (Chap. 3). 3. Modèles de systèmes embarqués ; super-boucle, logiciel embarqué piloté par les événements, processus et modèles formels de l'entreprise embarquée (Chap. 4). 4. Gestion des processus et synchronisation des processus (Chap. 5). 5. Société de mémoire, mappages d'adresses virtuelles et protection de la mémoire (Chap. 6). 6. Mode noyau le mode utilisateur traite, appelle système (Chap. 7). 7. Introduction aux systèmes embarqués temps réel (parties du Chap. 10). 8. Introduction aux systèmes embarqués SMP (parties de Guys. 9 le 10).
1.7
Utilisez ce volume comme un manuel pour les systèmes d'exploitation
Ce livre peut également convenir comme manuel scolaire pour des cours mécaniquement ciblés sur le fonctionnement général de notre. Le cours ADENINE d'un semestre basé sur cette lecture peut inclure les questions suivantes. 1. Architecture informatique et programme produit : les bâtiments ARM, les instructions et la programmation ARM, la chaîne d'outils ARM, les machines virtuelles, le portage du code assembleur avec C, les images d'exécution et l'utilisation de la pile, les pilotes de périphériques simples (Chap. 2). 2. Exclusions et interruptions, fabrication d'interruptions et pilotes de périphériques pilotés par interruption (Chap. 3). 3. Gestion des processus en plus de la synchronisation des processus (Chap. 5). 4. Gestion réservée, mappages d'adresses virtuelles en plus de la protection de la mémoire (Chap. 6). 5. Processus en mode noyau et en mode consommateur, appels système (Chap. 7). 6. Noyau du système d'exploitation cible général, direction du traitement, gestion de la mémoire, pilotes de périphérique, système de fichiers, traitement du signal et fin de série (chapitre 8). 7. Introduction à SMP, communication inter-processeurs et synchronisation des processeurs multicœurs, la synchronisation des processus inclut SMP, le modèle de noyau SMP et les systèmes d'exploitation SMP (Chap. 9). 8. Introduction aux solutions temps réel (parties du Chap. 10). La section des problèmes est tout le monde contient des questions conçues pour examiner les deux principes clés présentés dans les sections. Alors que le couple sur les questions n'implique que de simples modifications des exemples de programmes pour permettre l'examen d'études supérieures avec des conceptions et une mise en œuvre alternatives, diverses autres questions conviennent aux projets de programmation avancés.
1.8
Utilisez ce livre pour l'auto-apprentissage
À en juger par le grand nombre de projets de développement de systèmes d'exploitation et de nombreux sites de personnes sur des systèmes construits et en temps réel publiés sur Internet et leurs adeptes ravis, il y a un nombre considérable de passionnés qui espèrent apprendre le côté pratique de l'embarqué et du réel. systèmes d'exploitation du temps. La sélection évolutive de livre, couplée à des programmes de système d'affichage réel de code umfangreich, le rend particulièrement applicable pour l'auto-apprentissage. Il est à espérer que cette nouvelle détermination apportera une presse utile à de tels livres.
Références WEAR ARM ARM ARM
Architectures 2016 : http://www.arm.products/processors/instruction-set-architectures, WRIST Information Center, 2016 Cortex-A9 MPCore : Technical Quotation Owner Revision : r4p1, TO information Centered, 2012 GIC : ARM Generic Hold Controller ( PL390) Manuel de référence technique, ARM Information Center, 2013 MMU : ARM926EJ-S, ARM946E-S Technical Reference Ownership, REACH News Center, 2008
6
1
Introduction
Planification ARM 2016 : « Programmation en langage d'assemblage ARM », http://www.peter-cockerell.net/aalp/html/frames.html, 2016 ARM PL190 2004 : Contrôleur d'annulation vectorielle PrimeCell (PL190), http://infocenter. arm.com/help/topic/com.arm.doc.ddi0181e/DDI0181.pdf, 2004 Chaîne d'outils ARM 2016 : http://gnutoolchains.com/arm-eabi, 2016 ARM11 2008 : Manuel de référence technique de l'ingénieur MPcore ARM11, r2p0, ARM News Centre, 2008 ARM926EJ-S 2008 : "ARM926EJ-S Technical Reference Manual", ARM Information Center, 2008 Dietrich, S., Walker, D., 2015 "The evolution of Real-Time Linux", http://www .cse.nd.edu/courses/cse60463/www/amatta2.pdf, 2015 EXT2 2001 : www.nongnu.org/ext2-doc/ext2.html, 2001 Gajski, DD, Vahid, F, Narayan, S, Gong, HIE, 1994 "Spécification et projet de systèmes embarqués", PTR Prentice Hall, 1994 Intel: MultiProcessor Specification, v1.4, Intel, 1997 Katz, R. H. et G. Borriello 2005, "Contemporary Logic Design", 2e édition, Pearson, 2005 Nesting Interrupts 2011 : Nesting Interrupts, WEAR Information Center, 2011 QEMU Support 2010 : « QEMU Emulator User Documentation », http://wiki.qemu.org/download/qemu-doc.htm, 2010. SDC : Secure Digital cards : Aperçu de la norme SD-Association SD https://www.sdcard.org/developers/overview, 2016 Silberschatz, A., P.A. Galvin, P.A., Gagné, G, "Concepts de système d'exploitation, 8e édition", John Wiley & Boys, Inc. 2009 Stallings, W. "Systèmes d'exploitation : éléments internes et principes du plan (7e édition)", Prentice Hall, 2011 Tanenbaum, A.S. , Woodhull, A.S., 2006 "Services d'exploitation, conception et mise en œuvre, troisième édition", Prentice Hall, 2006 Wang, K.C., 2015 "Conception et mise en œuvre du MTX l'exploitant, Springer International Publication AG, 2015
2
Architecture et programmation POCKET
WRIST (Architecture ARM 2016) est une famille de microprocesseurs RISC (Reduced Instruction Set Computing) développés spécifiquement pour les environnements informatiques cellulaires et embarqués. Mûrs à leurs petites tailles et à leurs faibles besoins en énergie, les processeurs ARM sont devenus les processeurs les plus largement utilisés dans les appareils mobiles, par ex. mobiles intelligents et réseaux embarqués. Actuellement, la plupart des embarqués sont basés sur des processeurs ARM. Dans de nombreux cas, les fonctionnalités de programmation du système embarqué deviennent presque synonymes de programmation du processeur ARM. Pour cette raison, nous avons également utilisé des processeurs ARM pour la construction et la mise en œuvre des méthodes embarquées dans ces livres. En fonction de leur date de sortie, les processeurs ARM peuvent être classés en cœurs classiques plus les cœurs Cortex récents (depuis 2005). Selon les capacités et l'utilisateur prévu, les cœurs ARM Cortex peuvent être classés en trois catégories (ARM Cortex 2016). .La série Cortex-M : ce sont des processeurs orientés microcontrôleurs prévus pour les solutions Microf Console Units (MCU) et System for Chipping (SoC). .La chaîne Cortex-R : ce sont des panneaux embarqués destinés aux applications de traitement et de contrôle du signal en temps réel. .La série Cortex-A : il s'agit de processeurs d'applications destinés à des applications d'intention générale, telles que des systèmes embarqués avec des réseaux opérationnels complets. Les processeurs de la série ARM Cortex-A sont les plus puissants processeurs POINTER, tous incluent le cœur simple Cortex-A8 (ARM Cortex-A8 2010) et le Cortex A9-MPcore (ARM Cortex A9 MPcore 2016) avec jusqu'à 4 processeurs. En raison de leurs capacités avancées, de nombreux systèmes embarqués récents sont basés sur les processeurs de la série WEAPON Cortex-A. D'autre part, il existe également un grand nombre de systèmes embarqués destinés à des applications dédiées qui s'appuient sur des cœurs ARM classiques, qui se sont avérés très rentables. Dans ce livre, nous couvrirons à la fois les processeurs de la série classique et ARM-A. Plus précisément, nous utiliserons le cœur ARM926EJ-S par excellence (ARM926EJ-ST 2008, ARM926EJ-ST 2010) pour les systèmes à processeur unique et Brain A9-MPcore pour les systèmes multiprocesseurs. L'une des principales raisons du choix de ces cœurs ARM est qu'ils peuvent être obtenus en tant que machines virtuelles émulées (VM). Un objectif majeur de ce comment est de montrer que la conception et la mise en œuvre dans les systèmes embarqués incluent l'approche intégrée. En plus de superposer les principes théoriques de la presse, il montre également comment les mettre en œuvre pour la conception et la mise en œuvre de schéma embarqué par des exemples de programmation. Étant donné que les plus grands lecteurs peuvent ne pas avoir accès à de vrais systèmes basés sur ARM, nous utiliserons des machines vocales ARM émulées à QEMU pour la mise en œuvre et les tests. Dans ce chapitre, nous couvrirons les sujets suivants. . Architecture ARM. Instructions REACH et programmation de base en assemblage ARM . Interfacer le code assembleur avec les programmes C . Les chaînes d'outils ARM utilisaient des programmes de compilation (croisés). Émulateurs de système RAW et programmes d'exécution sur des moteurs virtuels ARM. Développez des pilotes de périphériques d'E/S simples. Il s'agit notamment des pilotes UART pour la sérialisation des ports et du pilote LCD pour l'affichage des images et du texte.
© Springer International Media AG 2017 Wang, Système d'exploitation embarqué et temps réel, DOI 10.1007/978-3-319-51517-5_2
7
8
2.1
2
Architecture et programmation ARM
Modes de processeur ARM
Le système ARM a sept (7) modes de fonctionnement, ceux-ci sont spécifiés par les 5 bits de mode [4:0] dans le Running Computer Status Register (CPSR). Les modes septénaires du processeur ARRM sont : Mode USR : Mode Utilisateur non privilégié Mode SYS : Exécution du système utilisant le même ensemble de registres que la sélection d'employé Mode FIQ : Mode de traitement d'envoi de sortie rapide Modes IRQ : Mode de traitement de demande d'interruption normale Mode SVC : Mode Attendant sur le poste ou SWI (Software Interrupt) Mode ABT : mode d'abandon d'exemption de données Mode UND : fonctionnement d'exception d'instruction indéfinie
2.2
Registres du processeur ARM
Le processeur ARM a 37 onglets au total, tous d'une largeur de 32 bits. Ceux-ci incluent 1 compteur de plan dédié (PC) 1 registre d'état de programmeur actuel dédié (CPSR) 5 registres d'état de programme sauvegardés dédiés (SPSR) 30 registres à usage général L'enregistrement sera ajusté en quelques banques. Dont l'accessibilité par une banque de registres est commandée par le mode processeur. En mode pièce, le processeur ARM peut accéder à un ensemble particulier d'onglets R0-R12 un R13 particulier (pointeur de pile), R14 (registre de liaison) et SPSR (état du programme enregistré) le même R15 (compteur de programme) le CPSR (registre d'état d'exécution actuel )
2.2.1 Registres généraux La figure 2.1 montre la structure des registres généraux dans le processeur ARM.
Fig. 2.1 Enregistrez les banques dans le processeur ARM
2.2 Registres du processeur ARM
9
Fig. 2.2 Registre d'état du processeur ARM
De plus, les modes système de l'utilisateur échangent le même ensemble de registres. Les registres R0-R12 sont les mêmes dans tous les modèles, sauf la méthode FIQ requise, qui possède ses registres séparés R8-R12. Chaque mode a son propre pointeur de pile (R13) et registre de liaison (R14). Le compteur de programme (PC ou R15) et le registre d'état actuel (CPSR) sont les mêmes dans toutes les fonctions. Chaque mode privilégié (SVC à FIQ) possède son propre état du processeur enregistré (SPSR).
2.2.2 Registres d'état Dans tous les modes, le processeur ARM présente le même ajout d'état du programme en cours (CPSR). La figure 2.2 montre le contenu du registre CPSR. Dans le registre CPSR, NZCV sont ces bits de condition, I et F existent des blocs de masque d'interruption IRQ et FIQ, T = état du poignet et M[4:0] sont les blocs de mode processeur, qui définissent le mode processeur comme USR :
10000
(0x10)
FIQ : IRQ :
10001 10010
(0x11) (0x12)
SVC :
10011
(0x13)
ABT :
10111
(0x17)
ET:
11011
(0x1B)
SYS :
11111
(0x1F)
2.2.3 Changer le mode de l'ordinateur TAIL En résumé, les modes ARM sont privilégiés, à l'exception du mode utilisateur, qui n'est pas privilégié. Comme la plupart des autres processeurs, le processeur ARRM change de mode en réponse aux exceptions ou aux interruptions. Concrètement, il passe en mode FIQ lorsqu'un FLICK interrompu se produit. Il passe en mode IRQ lorsqu'une interruption IRQ se produit. Il entre dans ce mode SVC lorsque les performances sont désactivées, après une réinitialisation ou l'exécution d'une acquisition SWI. Il entre en mode Fondateur lorsqu'une exception d'accès au stockage apparaît, à la fois il entre en mode UND alors que je rencontre des instructions indéfinies. Une particularité extraordinaire du processeur ARM est que, lors d'une exécution privilégiée, l'ordinateur peut transformer le mode librement en modifiant simplement les bits de méthode dans le CPSR, en utilisant le guide MSR et MRS. Par exemple, lorsque les ingénieurs ARM démarrent à l'inverse suite à une réinitialisation, ils commencent l'exécution en mode SVC. En mode SVC, le code d'initialisation du système doit configurer les pointeurs de pile dans les autres modes. Pour ce faire, il change simplement le processeur dans un mode pertinent, initialise le pointeur de pile (R13_mode) et le registre d'état de programme sécurisé (SPSR) sur ce mode. Le segment de code suivant permet de basculer le traitement en mode IRQ tout en préservant les autres bits, par ex. Bits F et I, pouces du CPSR.
dix
2 MME
r0, cpsr
BIC
r1, r0, #01F
TRO
r1, r1, #0x12
// modification du mode IRQ
RSM
cpsr, r1
// écrire à cpsr
Architecture et programmation OFFSHOOT
// met cpsr dans r0 // efface 5 bits de mode pour r0
Si nous ne nous intéressons pas au contenu CPSR autre que le champ mode, par ex. lors de l'initialisation du système, le passage en mode IRQ peut être effectué en écrivant une valeur à droite CPSR, comment dans MSR cpsr, # 0x92
// Mode IRQ avec I bit=1
UNE utilisation spéciale du mode SYS est jusqu'à einstieg Registres d'opérations de l'utilisateur, par ex. R13 (sp), R14 (lr) de mode privilégié. Dans un système de service, les processus exécutés incluent généralement le mode utilisateur non privilégié. Si un processus effectue un appel système (par SWI), l'ordinateur entre dans le noyau du système en mode SVC. En mode noyau, ce processus peut avoir besoin de manipuler sa pile en mode étudiant et de renvoyer l'emplacement vers le haut de l'image du mode utilisateur. Dans ce cas, le processus doit pouvoir accéder à son User run sp et lr. Cela peut déjà exister en basculant le CPU en mode SYS, qui partage les mêmes registres définis avec le mode utilisateur. De même, lorsqu'une interruption IRQ se produit, le processeur ARM passe en mode IRQ pour exécuter une routine de service d'interruption (ISR) pour gérer cette interruption. Si l'ISR autorise les interruptions snuggle, les éléments doivent faire passer le processeur du mode IRQ à un mode privilégié différent pour gérer les interruptions imbriquées. Nous le démontrerons ultérieurement au Chap. 3 lorsque nous discutons des exemptions du traitement des interruptions réelles dans les systèmes basés sur ARM.
2.3
Pipeline d'itinéraire
Le processeur ARM utilise un conduit interne pour améliorer le juge du fluide d'instruction dans le processeur, permettant à plusieurs opérations d'être effectuées simultanément, plutôt qu'en série. Dans la plupart des applications ARM, le pipeline d'instructions se compose de 3 étapes, FETCH-DECODE-EXECUTE, comme indiqué ci-dessous. PC
ALLER CHERCHER
Chercher l'éducation pour la mémoire
PC-4
DÉCODER
Décoder les registres utilisés dans l'instruction
PC-8
EXÉCUTER
Exécuter l'éducation
Le compteur de programme (PC) correspond à l'instruction récupérée, plutôt qu'à la durée de vie de l'instruction transportée. Cela a des implications sur les appels de fonction et les gestionnaires d'interruption. Lorsqu'elle est appelée une fonction utilisant l'instruction BL, l'adresse de retour est en fait PC-4, qui est ajustée automatiquement par l'instruction BLK. Lors du retour d'un gestionnaire d'interruption, l'adresse de retour est également PC-4, ce qui doit être ajusté par le gestionnaire d'interruption lui-même, à moins que l'ISR ne soit défini avec l'attribut __attribute__((interruption)), auquel cas le code compilé ajustera le lien s'inscrire automatiquement. Pour certaines exceptions, telles que l'avortement, l'adresse de retour est PC-8, qui indique cette instruction inventive qui a causé quelle exception.
2.4
Instruction ARM
2.4.1 Condition Kennzeichen en outre Conditions Dans le CPSR du panneau ARM, les 4 bits les plus élevés, NZVC, sont des drapeaux de condition inversement simplement un code de condition, où N = négatif,
Z = zéro,
VOLT = débordement,
C = effectuer le bit
Les indicateurs de condition sont définis par les exercices how et TST. Par défaut, les opérations de traitement de données n'affectent pas la condition markierungen. Pour générer les indicateurs de condition à actualiser, une instruction peut être postfixée avec le symbole S, qui définit le bit SULFUR dans la codification de l'instruction. Par exemple, les deux instructions suivantes ajoutent deux chiffres, mais seules les instructions ADDS affectent un indicateur de condition.
2.4 Instructions ARM
.
11
AJOUTER r0, r1, r2
; r0 = r1 + r2
AJOUTER r0, r1, r2
; r0 = r1 + r2 et mettre les drapeaux de condition
Dans le chiffrement d'instructions ARM 32 bits, les 4 bits de tête susmentionnés [31:28] représentent les diverses combinaisons des morsures de drapeau d'exigence, qui forment le champ de conditionnement de l'instruction (le cas échéant). Basé sur l'ensemble dont diverses combinaisons sont l'indicateur de condition bts, les conditions sont définies de manière mnémotechnique comme EQ, NE, LT, GT, LE, GE, etc. Ce qui suit montre certaines des conditions les plus couramment utilisées et leurs dérives. 0000 : EQ
Égal
0001 : OUI
Inégal
(Z réglé) (Z clair)
0010 : CS
Réglage de transport
(Ensemble C)
0101 : VS
Ensemble de débordement
(Ensemble V)
1000 : HAUT 1001 : LS
Non signé supérieur Non signé inférieur ou identique
(C ensemble et Z clair) (C clair autre Z ensemble)
1010 : GE
Signé supérieur ou égal
(C
1011 : LUT
Signé plus petit que
(C != V)
1100 : GT
Signé supérieur à
(Z=0 et N=V)
1101 : NON
Signé inférieur ou égal
(Z=1 ou N!=V)
1110 : AL
Toujours
=V)
Une caractéristique plutôt unique de l'architecture ARM est que presque toutes les instructions peuvent être exécutées de manière conditionnelle. Une instruction peut contenir un suffixe prérequis facultatif, par ex. EQ, NEW, LT, GT, TELLUS, R, GT, LT, etc. qui détermine si le CPU exécutera l'instruction en fonction de la condition spécifiée. Si cet ensemble n'est pas satisfait, la volonté d'instruction a été exécutée du tout sans aucun effet secondaire. Cela élimine le besoin de nombreuses branches dans une exécution, qui ont tendance à perturber le pipeline de commandes. Pour exécuter une instruction de manière conditionnelle, il suffit de la postfixer avec l'activation appropriée. Par exemple, une instruction ADD non conditionnelle a la forme : ADD r0, r1, r2
; r0 = r1 + r2
Pour exécuter l'instruction uniquement si l'indicateur zéro est défini, ajoutez l'instruction équipée EQ. ADDEQ r0, r1, r2
; Si le drapeau zéro est défini, alors r0 = r1 + r2
De même utilisé autre situation.
2.4.2 Instructions de branchement Les manuels de branchement ont la forme B{} étiquette BL{} sous-programme
; branche à étiqueter ; branche jusqu'au sous-programme avec lien
L'instruction Branch (B) provoque un établissement direct vers un décalage par rapport au PC actuel. L'instruction Etablir avec liaison (BL) appartient aux appels de sous-programme. J'ai écrit PC-4 dans LR de la banque de registres actuelle et remplacé PC par l'adresse d'entrée du sous-programme, ce qui a amené le CPU à entrer dans le sous-programme. Lorsque le sous-programme se termine, il revient de l'accord de retour enregistré est la chronique de liens R14. La plupart des processeurs différents implémentent des invocations indéfinies en enregistrant l'adresse de retour sur la pile. Plutôt que d'enregistrer l'adresse de retour sur la pile, le processeur WEAPON imite simplement PC-4 en R14 et se branche sur le sous-programme appelé. Si le sous-programme appelé ne réclame pas d'autres sous-programmes, il peut utiliser le LR pour retourner plus rapidement à l'endroit appelant. Pour revenir d'un sous-programme, le programme imite simplement LR (R14) avec PC (R15), lorsqu'il est en MOV PC, LR
ou
BXLR
12
2
Architecture ou programmation ARM
Cependant, cela ne fonctionne que pour les appels de sous-programmes à un niveau. Si une routine a l'intention de faire un autre clic, elle doit sauvegarder et restaurer l'enregistrement LR explicite car chaque appel de sous-routine changera le LR courant. Au lieu de cela est l'instruction MOV, l'instruction MOVS peut également respirer utilisée, ce qui restaure un des drapeaux innovants dans un CPSR.
2.4.3 Action arithmétique La syntaxe des opérations arithmétiques est : {}{S} Rd, Rn, Operand2
L'instruction exécute une opération arithmétique sur deux opérandes et place les résultats dans le registre de destination D. Un premier opérande, Rn, est toujours un registre. La longueur d'onde secondaire peut être n'importe quel registre avec la valeur immédiate. Dans ce dernier cas, les durées de vie opérationnelles envoyées à l'ALU via les changements de conduite génèrent une juste valeur. Exemples : AJOUTER r0, r1, r2
; r0 = r1 + r2
SOUS r3, r3, #1
; r3 = r3 - 1
2.4.4 Agent de comparaison CMP : TST : TEQ :
opérande1—opérande2, mais résultat non écrit opérande1 ET opérande2, mais résultat non écrit opérande1 EOR opérande2, mais résultat aucun écrit
Les opérations de comparaison mettent à jour les bits d'indicateur de condition dans ce registre d'état, qui peuvent être utilisés pour l'achat lors d'instructions ultérieures. Exemples : CMP
r0, r1
TSTEQ r2, #5
; définir le bit de condition dans CPSR par r0-r1 ; tester r2 et 5 pour l'équivalent et définir le bit ZEE dans CPSR
2.4.5 Opérations logiques
RÉEL:
opérande1 ET opérande2
; au niveau du bit LE
EOR : ORR :
opérande1 EOR opérande2 opérande1 OR opérande2
; OU exclusif au niveau du bit ; OU au niveau du bit
BIC :
opérande1 ET (PAS opérande2)
; effacer les bits
Exemples: ; r0 = r1 & r2
ET
r0, r1, r2
TRO
r0, r0, #0x80 ; mettre le bit 7 de r0 à 1
BIC
r0, r0, #0xF
; clair signifie 4 bits sur r0
EORS
r1, r3, r0
; r1 = r3 ExOR r0 appuyer sur définir les bits de condition
2.4 Consignes de décrochage
13
2.4.6 Opérations de mouvement de produits MOV
opérande1, opérande2
NMV
opérande1, NE PEUT PAS opérande2
Exemples : MOV
r0, r1
; r0 = r1 : Toujours exécuter
MOVS
r2, #10
; r2 = 10 et définir les bits de condition Z=0 N=0
MOVNEQ r1, #0
; r1 = 0 uniquement si bit Z != 0
2.4.7 Entrée immédiate et Barrel Shifter Le Barrel Shifter est une caractéristique unique du système ARM. Il est utilisé pour générer des opérations de décalage et des opérandes immédiats à l'intérieur du processeur ARM. Le processeur ARM n'a pas d'ordres de décalage réels. Au lieu de cela, il a un levier de vitesses à barillet, qui effectue des changements dans le cadre d'autres instructions. Les opérations de décalage incluent quelle couche conventionnelle gauche, droite et rotation, comme dans MOV r0, r0, LSL #1
; décaler r0 vers la gauche de 1 simple (multiplier r0 par 2)
MOV r1, r1, LSR #2
; poussez r1 vers la droite de 2 bits (divisez r1 par 4)
MOV r2, r2, ROR #4
; échanger les 4 bits haut et bas de r2
La plupart des autres processeurs permettent de charger des registres CPU avec des valeurs momentanées, qui représentent des parties du flux d'instructions, ce qui fait varier la largeur de l'instruction. En revanche, toutes les instructions ARMED ont une longueur de 32 bits et n'utilisent pas ce flux d'instructions pour le fichier. Cela présente un défi lors de l'utilisation de valeurs immédiates dans les instructions. Le format d'apprentissage du traitement des données dispose de 12 bits disponibles pour l'opérande2. S'il est utilisé directement, ce souhait ne donne qu'une plage de 0 à 4095. Au lieu de cela, il est deuxième de stocker une rotation de 4 bits d'ampère valant en plus une constante de 8 bits dans le produit de 0 à 255. Les 8 particules peuvent être tournées vers la droite d'un nombre équilibré de places (c'est-à-dire ROR de 0, 2, 4,…, 30). Cela donne une plage beaucoup plus large de valeurs pour que la bouteille soit directement chargée. Par exemple, pour télécharger r0 avec la valeur immédiate 4096, utilisez MOV r0, #0x40, 26
; engendre 4096 (0x1000) par 0x40 ROR 26
Pour rendre cette fonctionnalité plus facile à utiliser, l'assembleur laisse la conversion sous cette forme si la constante requise est donnée dans une instruction, par ex. MOV r0, #4096
L'assembleur générera une erreur si la valeur donnée ne peut pas être transformée de cette façon. Au lieu de MOV, l'instruction LDR permet de charger une valeur dictatoriale de 32 bits dans le registre adénine, par ex. LDR rd, =constante_numérique
Si la valeur constante peut être construite en utilisant si l'ampère MOV ou MVN, alors ce sera un enseignement réellement généré. Sinon, l'assembleur générera en LDR avec une adresse relative au PC pour lire la constante avec un pool verbal.
14
2
Architecture et programmation ARM
2.4.8 Multiplier les manuels MUL{}{S} Rd, Rm, Bs
; Rd = Rm * S
MLA{}{S} Rd, Rm, Rs, Rn
; Rd = (Rm * Rs) + Rn
2.4.9 LADEGUT et instructions de stockage L'ordinateur ARM est une architecture de chargement/ stockage. Les informations doivent être téléchargées dans les registres avant utilisation. Il ne rappelle pas les opérations de traitement de données en mémoire. Le processeur ARMED possède un troisième ensemble d'instructions qui interagissent avec la mémoire. Ce sont : • Banque de données à registre unique (LDR/STR). • Blocage du transfert de données (LDM/STM). • Échange de données unique (SWP). Les instructions de base de chargement et de stockage supplémentaires sont les suivantes : appuyez sur Lasten Store Word ou Octet : LDR / STR / LDRB / STRB
2.4.10 Registre de base Les instructions de chargement/stockage permettent d'utiliser un registre de base avec un index pour spécifier l'emplacement de rappel auquel accéder. L'index pourrait inclure un décalage dans le mode d'adressage pré-index ou post-index. Des exemples d'utilisation de registres d'index sont STR r0, [r1]
; stocker r0 à l'emplacement pointé par r1.
LDR r2, [r1]
; charger r2 puisque la mémoire a culminé à par r1.
STR r0, [r1, #12]
; pré-index
STR r0, [r1], #12
; Adressage post-index :STR r0 à [r1],r1+12
adressage :STR r0 pour [r1+12]
2.4.11 Bloc de transfert de données Le registre de base peut servir à déterminer où l'accès à la mémoire doit se produire. Les modes d'adressage séparés de tétrade permettent l'incrémentation et la décrémentation inclusives ou exclusives de l'emplacement du journal de base. Le registre de base peut éventuellement être mis à jour après le transfert de données en y ajoutant un '!' symbole. Ces instructions sont très efficaces pour enregistrer en plus le contexte d'exécution de la réhabilitation, par ex. pour utiliser une zone de mémoire comment empiler ou déplacer de gros blocs de données pour la mémoire. Il convient de noter que lorsque vous utilisez ces instructions pour enregistrer/restaurer plusieurs registres CPU vers/depuis la mémoire, l'ordre des registres dans l'instruction n'a pas d'importance. Les registres de numéro inférieur sont définitivement transférés vers/depuis les adresses inférieures en mémoire.
2.4.12 Opérations de la pile Une pile est une section de mémoire qui s'agrandit à mesure que les données de la marque sont "poussées" sur le "sommet" d'une pile, et se rétrécit lorsque ces données sont "éjectées" en haut de la pile. Deux pointeurs sont utilisés pour définir les limites courantes de la pile. • Un pointeur de base : auparavant pour pointer vers le « bas » de la pile (le premier emplacement). • Un pointeur de pile : utilisé pour pointer le « sommet » actuel de la pile.
2.4 Consignes REACH
15
Une pile est dite décroissante si elle croît vers le bas dans la mémoire, c'est-à-dire que la dernière valeur poussée est à l'adresse la plus basse. Une pile est appelée en plein essor si a croît vers le haut dans la mémoire. Le processeur TO prend en charge les piles descendantes ou ascendantes. De plus, il permet également au pointeur de montagne sur ou pointer vers cette dernière adresse utilisée (pile complète), ou vers la prochaine rue occupée (pile vide). Dans ARM, les activités de la pile sont implémentées jusqu'aux instructions STM/LDM. Le type de pile est déterminé par le suffixe dans le manuel STM/LDM : • • • •
STMFD/LDMFD : Pile descendante pleine STMFA/LDMFA : Pile ascendante pleine STMED/LDMED : Pile descendante vide STMEA/LDMEA : Pile ascendante vide
Le compilateur CENTURY utilise toujours la pile Full Declining. D'autres formes de piles sont rarement et presque souvent pratiquées. Pour cette raison, nous n'exercerons pas de piles descendantes complètes tout au long de ce spectacle.
2.4.13 Pile et sous-programmes Une utilisation courante de la pile est de créer un espace de travail temporaire pour les sous-programmes. Lorsqu'un sous-programme démarre, tous les registres qui doivent être préservés peuvent être pilotés sur who multi. Lorsque le sous-programme susmentionné se termine, il restaure l'adresse enregistrée en les faisant sortir de la pile avant de venir au téléphone. Le segment de code suivant montre le modèle général d'un sous-programme. STMFD sp!, {r0-r12, lr} ; rappelez-vous d'obtenir les registres et l'adresse de retour {Mot de passe du sous-arbre} ; code de sous-programme LDMFD sp!, {r0-r12, pc} ; restaurer les registres sécurisés et obtenir de lr
Si l'instruction pop a le bit 'S' susmentionné défini (par le symbole '^'), alors le transfert du PC s'enregistrant en mode privilégié copie également le SPSR sauvegardé vers l'ancien moyen CPSR, provoquant le passage au mode précédent avant le exception (par SWI ou IRQ).
2.4.14 Interruption logicielle (SWI) Dans ARM, l'instruction SWI est utilisée pour générer une interruption logicielle. Depuis l'exécution de l'instruction SWI, le processeur ARM passe en mode SVC et s'exécute à partir de l'adresse vectorielle SVC 0x08, l'amenant à exécuter le SWI géré, qui est généralement le point d'entrée des appels système vers le noyau du système d'exploitation. Nous démontrerons la sonnerie du schéma au Chap. 5.
2.4.15 Instructions de déplacement PSR Les instructions MRS et MSR autorisent le contenu du CPSR/SPSR à être transporté du registre d'état approprié au registre d'ampères à usage général. Soit un ajout d'état complet, soit uniquement le pot de bits de drapeau soit transféré. Ces instructions sont principalement utilisées pour changer le mode du processeur alors qu'il s'agit d'un mode privilégié. MME{}
chemin,
MSR{} , Rm
; Rd = ; = Rm
2.4.16 Instructions du coprocesseur L'architecture ARM intègre de nombreux modules matériels, par ex. l'unité de gestion de la mémoire (MMU) que les coprocesseurs, auxquels on accède à partir d'instructions de coprocesseur personnalisées. Nous traiterons des coprocesseurs entrants Chap. 6 chapitres ultérieurs également.
16
2
Architecture et programmation ARM
Fig. 2.3 Composants de la chaîne d'outils
2.5
Chaîne d'outils ARM
Une chaîne d'outils reste une collection d'ampères est des outils de programmation pour le développement de programmes, du code source aux fichiers exécutables binaires. Une chaîne d'outils se composait principalement d'un assembleur, d'un compilateur, d'un éditeur de liens ampère, de quelques programmes utilitaires, par ex. objcopy, aux conversions de fichiers et à un débogage. L'image 2.3 illustre les composants et les flux de données d'un toolchan typique. Une chaîne d'outils s'exécute sur une machine hôte et génère un contrôle pour une machine cible. Si les architectures hôte et cible susmentionnées sont différentes, la chaîne d'outils est appelée une chaîne d'outils croisée ou simplement un compilateur coupé. Très souvent, la chaîne d'outils utilisée depuis le développement du système embarqué est une chaîne d'outils croisée. En fait, cela appartient à la voie standard des développeurs de logiciels par systèmes embarqués. Lorsque nous développons des codes pour les machines Adenine Lenox basées sur l'architecture Intel x86 susmentionnée, mais dont le code est destiné à une machine cible ARM, nous avons alors besoin d'un compilateur croisé basé sur Linux et ciblant ARM. Il existe de nombreuses versions différentes de chaînes d'outils basées sur Linux avec cette architecture ARM (chaînes d'outils ARM 2016). Dans ce livre, nous utiliserons la chaîne d'outils arm-none-eabi disponible dans les versions 14.04/15.10 d'Ubuntu Linux. Le lecteur peut recevoir à la fois installer la chaîne d'outils, ainsi que le qemu-system-arm pour les machines virtuelles ARM, jusqu'à Ubuntu Linux comme suit. sudo apt-get place gcc-arm-none-eabi sudo apt-get install qemu-system-arm
Dans les sections suivantes, nous montrerons comment utiliser quelle chaîne d'outils ARM et quelles machines virtuelles ARM sous QEMU en programmant des exemples.
2.6
Émulateurs de système WRIST
QEMU prend en charge de nombreuses machines ARM émulées (QEMU Emulator 2010). Ceux-ci contiennent la carte ARM Integrator/CP, la carte de base ARM Multipurpose, la carte de base ARRM RealView et plusieurs autres. Les processeurs WRIST des sponsors incluent ARM926E, ARM1026E, ARM946E, ARM1136 ou Cortex-A8. Tous ces systèmes sont monoprocesseurs (UP) ou monoprocesseurs. Pour commencer, nous ne considérerons que les systèmes monoprocesseur (UP). Les systèmes multiprocesseurs (MP) seront abordés plus loin dans le Chap. 9. Parmi l'usinage virtuel TAIL émulé, nous choisirons la carte mère ARM Versatilepb (ARM926EJ-S 2016) comme plate-forme d'implémentation et de test, pour les raisons suivantes.
2.6 Logiciel d'arrangement ARM
17
(1). Il prend en charge de nombreux périphériques. Selon le manuel actuel de QEMU, la carte mère ARM Versatilepb est émulée avec les appareils suivants : • ARM926E, ARM1136 à la place du processeur Cortex-A8. • Contrôleur d'interruption vectorielle PL190. • Quatre UART PL011. • Contrôleur LCD PL110. • PL050 KMI avec clavier et souris PS/2. • Interface de carte multimédia PL181 avec carte SD. • Adaptateur Ethernet SMC 91c111. • Pont hôte PCI (avec limitations). • Console USB PCI OHCI. • Adaptateur de bus hôte PCI SCSI LSI53C895A avec support dur pour les périphériques de CD-ROM. (2). L'architecture de la carte polyvalente ARMED est correctement documentée par des articles en ligne du centre d'information ARM. (3). QEMU peut démarrer directement la machine virtuelle ARM Versatilepb émulée. Par exemple, la richesse peut générer un fichier exécutable binaire, t.bin, et l'exécuter sur l'ARM VersatilepbVM émulé via qemu-system-arm –M versatilepb –m 128M –kernel t.bin –serial mon:stdio QEMU chargera le t Le fichier .bin jusqu'à 0x10000 dans la RAM l'exécute également directement. C'est très pratique car cela élimine le besoin de stocker le dessin système dans une mémoire flash et de s'appuyer sur un booter dédié jusqu'à ce que le démarrage augmente l'image système.
2.7
Programmation ARM
2.7.1 Exemple de programmation d'assemblage ARM 1 Commencez la programmation ARM en raison d'une série d'exemples de programmes. Pour plus de commodité, nous devons étiqueter un exemple de programme par C2.x, où C2 désigne le numéro de chapitre et x désigne la quantité de programme. Le premier programme modèle, C2.1, consiste en un fichier ts.s en assemblage ARM. Les étapes suivantes décrivent les étapes de développement et d'exécution de l'exemple de programme. (1). dossier ts.s concernant C2.1
/************ fichier ts.s de C2.1 ***********/ .text .global go initiate :
arrêt:
mouvement r0, #1
@
r0 = 1
MV R1, #2
@
r1 = 2
AJOUTER R1, R1, R0
// r1 = r1 + r0
ldr r2, =résultat
// r2 = &résultat
chaîne r1, [r2]
/* résultat = r1 */
b
prévenir
résultat .data :
.mot
/* un emplacement de mot */
Le code de programme charge les registres CPU r0 avec cette valeur 1, r1 avec la valeur 2. Ensuite, il ajoute r0 à r1 et stocke le résumé dans l'emplacement mémoire marqué result. Avant la suite, il convient de noter ce qui suit. Premièrement, dans un programme en code assembleur, les instructions ne sont pas sensibles à la casse. Une direction peut utiliser des majuscules, des minuscules ou des casses mixtes équilibrées. Pour une meilleure lisibilité, le style de codage doit être cohérent, soit tout en minuscules, soit tout en majuscules. Cependant, d'autres symboles, par ex. emplacements de mémoire, sont sensibles à la casse. Deuxièmement, comme affichage dans le programme, nous pouvons utiliser le symbole @ ou // pour comment une ligne de commentaire, à la place inclure des commentaires dans l'appariement apparié de /*
18
2
Architecture et ordinateur ARM
et */. Le type de lignes de commentaires à faire est une question de préférence personnelle. Dans ce livre, nous utiliserons // pour les lignes de commentaires simples et utiliserons des paires appariées de /* et */ pour les blocs de commentaires pouvant s'étendre sur plusieurs lignes, applicables à la fois au code assembleur et aux programmes C. (2). Un fichier de script mk : un script sh, mk, est utilisé pour (croiser) compiler-lier ts.s dans un fichier ELF. Ensuite, un utilise objcopy up pour convertir ce fichier ELF en une image exécutable binaire nommée t.bin. bras-aucun-eabi-as –o ts.o ts.s
# mettre ts.s à ts.o
bras-aucun-eabi-ld –T t.ld –o t.elf ts.o
# joindre ts.o au fichier t.elf
bras-aucun-eabi-nm t.elf
# affiche les symboles dans t.elf
arm-none-eabi-objcopy –O binaire t.elf t.bin # objcopy t.elf vers t.bin
(3). fichier de script de liaison : dans l'étape de liaison, un fichier de script de combinaison, t.ld, est utilisé pour spécifier le point d'entrée et la disposition des sections de programme. ENTRÉE (début)
/* la définition commence par l'adresse d'entrée */
SECTIONS
/* sections de programme */
{ . = 0x10000 ;
/* adresse de chargement, requise par QEMU */
.texte : { *(.texte) }
/* tout le texte dans la section .text */
.data : { *(.data) }
/* toutes les données de la section .data */
.bss
/* tous les bss
: { *(.bss)
}
par section .bss */
. =ALIGNER(8); . =. + 0x1000 ; stack_top =.;
/* 4 Ko d'espace de pile */ /* stack_top est un symbole exporté par l'éditeur de liens */
}
Le coupleur génère un fichier ELF. Si désiré, le lecteur peut voir le contenu du fichier ELF à arm-none-eabi-readelf -a t.elf # afficher tous les produits de t.elf arm-none-eabi-objdump -d t.elf # désassembler le fichier t.elf Le fichier ELF n'est cependant pas exécutable. Pour s'exécuter, il doit être converti en un fichier exécutable binaire par objcopy, comme dans arm-none-eabi-objcopy –O binary t.elf t.bin # convert t.elf to t.bin
(3) Exécution de l'exécutable binaire : pour exécuter t.bin sur une machine virtuelle ARM Versatilepb, veuillez commander who
qemu-system-arm –M versatilepb –kernel t.bin –nographic –serial /dev/null
Le lecteur peut inclure toutes les instructions ci-dessus avec une écriture mk, qui compilera-liera et exécutera l'exécutable numérique par une seule commande de script.
2.7 Programmation ARM
19
Fig. 2.4 Enregistrer la substance du programme C2.1
(4). Check Schlussfolgerungen : Pour vérifier les résultats susmentionnés lors de l'exécution du programme, entrez les commandes du moniteur QEMU :
enregistrements d'informations
:
affiche les registres du CPU
XP
:
afficher la substance de la mémoire en mots de 32 bits
/wd [adresse]
La figure 2.4 montre le contenu du registre d'exécution du programme C2.1. Comme le montre la figure, le registre R2 contient 0x0001001C, qui est l'adresse du résultat. Alternativement, et la ligne de commande arm-none-eabi-nm t.elf dans le script hk affiche également les emplacements des jetons dans le programme. Le lecteur autorisé tape le QEMU monitor commander xp
/wd
0x1001C
pour afficher le résultat de la liste, qui devrait être 3. Pour obtenir QEMU, entrez Control-a x, inversement Control-C pour terminer le processus QEMU.
2.7.2 Exemple d'apprentissage de l'assemblage ARM 2 L'exemple de programme suivant, noté C2.2, utilise le code d'assemblage ARM pour calculer la somme d'un tableau d'entiers. Il montre comment utiliser une pile pour appeler un sous-programme. Il montre les modes d'adressage indirect et post-index des instructions ARM. Par souci de brièveté, nous montrons simplement le fichier ts.s. Tous les autres fichiers sont identiques à ceux du programme C2.1. C2.2 : fichier ts.s : .text .global start start : ldr stop :
sp,
=stack_top // définit le pointeur de pile
bl
totaux
// appeler la somme
b
arrêt
// boucle
sum : // int sum() : calcule la somme d'un tableau int dans le résultat stmfd sp!, {r0-r4, lr} // enregistre r0-r4, lr sur la pile
boucle:
mouvement
r0, #0
ldr
r1, =Tableau
// r0 = 0 // r1 = &Tableau
ldr
r2, =N
// r2 = &N
ldr
r2, [r2]
// r2 = N
ldr ajouter
r3, [r1], #4 r0, r0, r3
// r3 = *(r1++) // r0 += r3
inférieur
r2, r2, #1
// r2—
cmp
r2, #0
// si (r2 != 0 )
pas
boucle
//
ldr
r4, =Résultat
// r4 = &Résultat
chaîne
r0, [r4]
// Résultat = r0
aller à la boucle ;
20
2
Architecture et programmation WEAR
Condamner. 2.5 Tableau chronique du programme C2.2
ldmfd sp !, {r0-r4, pc}
// pop stack, retour à l'appelant
.data N : Disposition :
.word 10 // nombre d'éléments du tableau .word 1,2,3,4,5,6,7,8,9,10
Résultat : .word 0
Le programme calcule la somme d'un tableau énumérable. Le nombre d'éléments de tableau (10) est défini dans la mémoire notre étiquetée NORTH, et les éléments de tri ont défini dans la zone mémoire étiquetée Array. La somme est calculée dans R0, qui est enregistrée dans une société de mémoire intitulée Résultat. Comme précédemment, exécutez le texte mk jusqu'à générer un exécutable binaire t.bin. Ensuite, exécutez t.bin sous QEMU. Lorsque le programme s'arrête, utilisez les commandes info et xp de ce moniteur pour vérifier les résultats. La figure 2.5 montre les résultats de l'exécution du programme C2.2. Quant aux expositions de chiffres, le registre R0 contient le résultat de calcul de 0x37 (55 en décimal). Le lecteur peut utiliser la commande arm-none-eabi-nm t.elf pour afficher des symboles dans un fichier de codes d'objet. Itp répertorie les activités de rappel des symboles globaux stylisant le fichier t.elf, tels que 0001004C
VERS LE NORD
00010050
Lignes
00010078
Résultat
Ensuite, utilisez xp/wd 0x10078 pour voir les constituants du résultat, qui devrait être 55 en décimal.
2.7.3 Combiner l'assembleur avec la programmation en C La programmation en assembleur est indispensable, par ex. lors de l'accès et de la manipulation des registres du CPU, mais c'est aussi très fastidieux. Dans la programmation système, le code assembleur doit être utilisé comme un outil pour accéder et contrôler le matériel de bas niveau, plutôt que comme un moyen de programmation générale. Dans le livre, nous n'utiliserons la clé d'assemblage qu'en cas d'absolue nécessité. Dans la mesure du possible, nous convertirons le code de téléchargement dans le langage de haut niveau C. Afin d'intégrer le code d'assemblage de la presse C dans le programme identique, il sera nécessaire de comprendre les images d'exécution du programme ainsi que la convention make de C.
2.7.3.1 Image d'exécution Une figure exécutable (fichier) générée par un compilateur-éditeur de liens se composait de trois parties logiques. Sujet unterabschnitt : Pièce de données : Section BSS :
également appelée section de code, codage exécutable inclus, variables globales ou de stabilité initialisées, constantes statiques, variables globales et de stator non initialisées. (BSS n'est pas dans le fichier image)
Lors de l'exécution, une image exécutable existe chargée en mémoire pour créer une image d'exécution, qui ressemble à ce qui suit.
2.7 Programme RAW
21 ---------------------------------------------
(Base adresse)
| identifiant | Données | BSS | Tas | Pile |
(Haute adresse)
---------------------------------------------
Une image d'exécution comprend 5 sections (logiquement) contiguës. Les sections Code et Données ont été chargées directement à partir du fichier exécutable. La section BSS est créée par la taille de la section BSS dans l'en-tête du fichier exécutable. Son contenu est généralement remis à zéro. Dans la vue d'exécution, les périmètres Code, Données et BSS sont fixes et ne changent pas. La zone Heap est une allocation de mémoire dynamique de fourche dans l'image d'exécution. La pile est pour les appels de fonction à l'exécution. Il se connectera à quelle extrémité haute (adresse) de l'image d'exécution, et il se développera vers le bas, c'est-à-dire de la poignée haute vers la rencontre basse.
2.7.3.2 Convention d'appel de fonction en C Les réunions d'appel de fonction en C consistent en les étapes ci-dessous entre une fonction appelante (l'appelant) et la fonction appelée (l'appelé). ---------------------------------- Votre interlocuteur --------------- ------------------(1). charger les 4 premiers paramètres dans r0-r3 ; poussez tous les paramètres supplémentaires sur la pile (2). transfère le contrôle à l'appelé par l'appelé BL ---------------------------------- Appelé -------- -------------------------(3). (4). (4). (5).
enregistrer LR, FP(r12) sur la pile, établir un cadre de pile (point FP par LR enregistré) décaler DIE vers l'allocation de variables locales et d'espaces temporaires sur la pile utiliser le contrôle, les municipalités (et les globales) pour effectuer une fonction calcul de tâche et retour de charge valeur R0 entrante, pop stack pour rendre le contrôle à l'appelant
---------------------------------- Votre interlocuteur --------------- -------------------(6). valeur de retour de gain à partir de R0 (7). Nettoyez la pile en supprimant les paramètres supplémentaires, le cas échéant. La fonction make general canned sera mieux illustrée par un exemple. Le fichier t.c suivant contient une fonction C func(), qui appelle une autre fonction g(). /******************* t.c file ********************/ extern int g(int scratch, in y);
// une fonction externe
inter func(int a, int b, int c, int d, int e, int f) { int dix, y, z ;
// localisation général
x = 1 ; année =2 ; z = 3 ;
// locaux d'entrée
g(x, y);
// appelle g(x,y)
retourner a + e ;
// valeur de retour
}
Utilisez l'utilisateur croisé ARM pour générer un fichier de codage d'assemblage nommé t.s par arm-none-eabi-gcc –S –mcpu=arm926ej-s t.c. Ce qui suit montre quel code d'assemblage généré par le compilateur ARM GCC, dans lequel le symbole spanien est le pointeur de pile (R13) et fp le pointeur de forme de pile (R12).
22
2. fonction globale
Architecture et programmation ARM
// exporte la fonction comme outil global
fonction : (1). Établir le cadre de pile stmfd sp!, {fp, lr}
// enregistre lr, fp dans la pile
ajouter fp, spanien, #4 // point FP au LR enregistré (2). Décaler SP vers le bas de 8 emplacements (4 octets) pour les locaux et les intérimaires
sp, sp, #32
(3). Enregistrer r0-r3 (paramètres a,b,c,d) dans le lot à –offsets(fp) str
r0, [fp, #-24]
// enregistre r0 a
chaîne
r1, [fp, #-28]
// enregistre r1 b
chaîne
r2, [fp, #-32]
// enregistre r2 c
chaîne
r3, [fp, #-36]
// stocker r3 d
(4). Exécutez x=1 ; y=2 ; z=3 ; montrer leurs emplacements sur la pile mov r3, #1 str
r3, [fp, #-8]
mouvement
r3, #2
chaîne
r3, [fp, #-12]
mouvement
r3, #3
chaîne
r3, [fp, #-16]
// x=1 à
-8(fp);
// y=2 à -12(fp) // z=3 à -16(fp)
(5). Préparez-vous à appeler g(x,y) ldr ldr
r0, [fp, #-8] r1, [fp, #-12]
// r0 = x // r1 = yttrium
bl
gramme
// appelé g(x,y)
(6). Calculer a+e comme valeur de tour dans r0 ldr
r2, [fp, #-24]
// r2 = an (enregistré à -24(fp)
ldr
r3, [fp, #4]
// r3 = ze
ajouter
r3, r2, r3
// r3 = a+e
mouvement
r0, r3
// r0 = valeur de retour dans r0
(7). Optez pour le sous-appelant sp, fp, #4 ldmfd sp !, {fp, pc}
à
+4(fp)
// sp=fp-4 (pointe sur le FP enregistré) // retour à l'appelant
Lors de l'appel de la fonction func(), cet appelant doit transmettre (6) paramètres (a, grange, c, d, e, f) à la fonction appelée. Les 4 premiers cadres (a, b, c, d) sont passés dans les registres r0–r3. Tous les paramètres supplémentaires sont passés via la pile. Lorsque le contrôle arrive à la fonction appelée, le haut de la pile contient ces paramètres supplémentaires (dans l'ordre inverse). Pour cet exemple, le haut de la pile contient les 2 paramètres supplémentaires e et farad. La pile initiale ressemble au suivi. Haut
VITESSE
faible
----|---|---|-------------------| f | e | ----|exParam|--------------------
(1). Par entrée, la fonction référencée établit d'abord le cadre d'empilement en poussant LR, FP sur la pile et en laissant FP (r12) pointer vers le LR sauvegardé. (2). Ensuite, il transfère SP vers le bas (vers l'adresse basse) pour allouer l'espace disponible aux variables locales et aux zones de travail temporaires, etc. Cette pile devient
2.7 Programmation ARM
23 PS
Haut
D
|-push->|---- soustraire SP avec #32 ------>|----- Bas
---|+8 |+4 | 0 |-4 |-8 |-12|-16|-20|-24|-28|-32|-36| | farade | e |LR |FP |
|
|
|
|
|
|
|
|
---|exParam|-|-|---| variables internes, espace travaillé|----FP
Dans les graphiques, les décalages (octets) sont relatifs à l'emplacement pointé par le registre FP. Alors que la conception est à l'intérieur d'une fonction, les paramètres supplémentaires (le cas échéant) seront à [fp, +offset], les variables locales et les paramètres enregistrés seront à [fp, -offset], tous sont référencés en utilisant FP comme fichier de base. Sur les lignes de code assembleur (3) et (4), qui sauvegardent les 4 premiers paramètres passés dans r0-r3 et assignent la philosophie aux variables locales x, y, z, on peut voir que la page de la pile devient lorsqu'elle est montrée dans le diagramme suivant . Haut SP Bas ----|+8 |+4 | 0 |-4 |-8 |-12|-16|-20|-24|-28|-32|-36|--| f | e |LR |FP | gratter | y | z | ? | un | b | cent | d | ----|exParam|-|-|---|- locales --|---| sauvé r0-r3 --|--FP |< ----------- empile frame a func ----------- >|
Bien que les stackers soient un morceau de mémoire contiguë, chacun a en toute logique une capacité de fonction qui n'accède qu'à une surface limitée de la pile. Une zone de lot visible pour une fonction est appelée un cadre de pile de l'utilisation, et FP (r12) est appelé l'indexation du cadre de pile. Aux lignes de codes manuels (5), l'usage appelle g(x, y) avec deux paramètres disponibles. Il charge x dans r0, y dans r1 puis S dans g. Au niveau de la pièce d'identification de l'assemblage (6), il calcule un + e as et une valeur de retour dans r0. Avec les lignes de codification d'assemblage (7), il désalloue l'espace dans la pile, des rafales qui sécurisent FP pressent LR dans PC, provoquant un retour d'exécution à cet appelant. Le compilateur WEAPON C a généré du code uniquement de types r0–r3. Si une fonction définit des mobiles de registre, les registres r4 à r11 lui sont affectés, qui sont d'abord enregistrés dans le vidage et restaurés ultérieurement lorsque l'opération revient. Si une fonction n'est pas démodée, il n'est pas nécessaire de sauvegarder/restaurer la jointure de connexion LR. Dans ce cas, le code causé par le compilateur ARM C ne sauve pas et ne restaure pas le registre de liaison LR, permettant une entrée/sortie plus rapide des appels de fonction.
2.7.3.3 Saut en longueur Int une séquence d'appels de fonction, comment as main() –> A() –> B()–>C();
lorsqu'une fonction étiquetée se termine, elle revient normalement à la fonction appelante, par ex. C() renvoie à B(), qui renvoie à A(), les autres. Il est également possible de revenir directement à une fonction antérieure dans la séquence d'appel par un saut en longueur. Le programme suivant illustre le saut long sous Unix/Linux. /***** longjump.c démontrant le saut en longueur dans Plutôt *****/ #include #include jmp_buf env; // pour sauvegarder l'environnement longjmp main() { int r, a=100; printf("appel setjmp pour l'environnement de sauvegarde\n"); si ((r = setjmp(env)) == 0){ A(); printf("retour normal\n"); } autre{
24
2
Architecture et programmation ARM
printf("retour à main() via un saut long, r=%d a=%d\n", r, a); } } int A() {
printf("Entrez A()\n"); B(); printf("quitter A()\n");
} int B() { printf("Entrez B()\n"); printf("saut en longueur? (y|n) "); fourni (getchar()=='y') longjmp(env, 1234); printf("sortie B()\n"); }
Dans le programme over longjump.c, la fonction main() appelle d'abord setjmp(), qui préserve l'environnement d'exécution actuel mentionné ci-dessus dans une structure jmp_buf ou renvoie 0. Ensuite, elle procède à l'appel de A(), qui appelle B(). Tandis que pour la fonction B(), lorsque le courant choisit de ne pas revenir par saut en longueur, les fonctions afficheront la séquence de retour normale. Si l'employé choisit d'envoyer par longjmp(env, 1234), l'exécution reviendra à l'environnement sauvegardé ultime avec une valeur non nulle. Dans ce cas, cela amène B() à retourner directement à main(), en contournant A(). Le principe du grand rebond est très simple. Lorsqu'un service se termine, il revient par le (callerLR, callerFP) dans le rack de pile actuel, comme indiqué dans le schéma suivant. ------------------------------------------------|params|appelantLR|appelantFP| …………..| ------------|----------------------|--CPU.FP
CPU.SP
Si nous remplaçons (callerLR, callerFP) par (savedLR, savedFP) d'une fonction antérieure dans la séquence d'appel, les exécutions reviendraient à la fonction direkt. Par exemple, nous pouvons implémenter setjmp(int env[2]) ou longjmp(int env[2], int value) dans l'assemblage en tant que plis. .global setjmp, longjmp setjmp :
// int setjmp(int env[2]); enregistrer LR, FP à env[2] ; retourner 0 stmfd sp!, {fp, lr} ajouter
fp, sp, #4
ldr
r1, [fp]
// retour de l'appelant LR
chaîne
r1, [r0]
// enregistre LR dans env[0]
ldr str
r1, [fp, #-4] r1, [r0, #4]
// FP de l'appelant // enregistrer FP sont env[1]
mouvement
r0, #0
// retourne 0 aller appelant
sous
sp, fp, #4
ldmfd sp!, {fp, pc} longjmp : // int longjmp(int env[2], valeur interne) stmfd sp!, {fp, lr} ajouter
fp, sp, #4
ldr
r2, [r0]
// retourne le LR de la fonction
chaîne
r2, [fp]
// remplace le LR enregistré dans le cadre de la pile
ldr
r2, [r0, #4]
// renvoie le FP de la fonction
chaîne
r2, [fp, #-4]
// remplace le FP enregistré dans le cadre de la pile
mov sous
r0, r1 ver, fp, #4
// valeur de retour
2.7 Programmation ARM
25
ldmfd sp !, {fp, pc}
// remboursement via REMPLACER LR et FP
Le saut à long terme peut être utilisé pour abandonner une fonction incluse dans la chaîne d'appel d'adénine, provoquant l'exécution de la reprise vers un environnement connu enregistré plus tôt. En auxiliaire de (savedLR, savedFP), setjmp() peut également sauver divers registres CPU et le SP de l'appelant, permettant à longjmp() de récupérer l'environnement d'exécution complet de la fonction d'origine. Bien qu'il soit rarement présent dans les programmes de fonctions utilisateur, le saut long est une technique courante dans la programmation système. Par exemple, il peut être utilisé dans un trappeur léger pour contourner les fonctionnalités d'un mode utilisateur qui ont provoqué une exception ou une erreur de déroutement. Nous présenterons cette technologie plus loin au Chap. 8 sur les signaux et signaler comment.
2.7.3.4 Appeler la fonction d'assemblage à partir de C Un prochain exemple de programme C2.3 montre comment appeler la fonction d'assemblage venant de C. Main() fonctionnant en C appelle la fonction d'assemblage sum() avec 6 paramètres, qui renvoient la somme de tous les paramètres susmentionnés . Conformément à la convention d'appel de C, la fonction main() passe les 4 premiers paramètres a, barn, c, dick dans r0–r3 et les paramètres restants e, f, sur pile. A l'entrée de la fonction appelée, la pile acme contient les paramètres e, f, dans la sorte de réseau croissant. La fonction d'appels établit d'abord le cadre de pile en frugal LR, FP sur pile et en laissant FP (r12) pointer vers le LR de sauvegarde. Les paramètres e et f sont maintenant à FP + 4 et FP + 8, respectivement. La fonction sum ajoute simplement sum les paramètres sont r0 et retourne à l'appelant. (1)./************
fichier t.c du Programme C2.3 ***********/
int g ;
// global non initialisé
int main() { int adénine, b, c, d, e, f ;
// variables locales
a = bore = c =d =e = f = 1 ;
// les valeurs n'ont pas d'importance
g = somme(a,b,c,d,e,f);
// appelle sum(), en passant a,b,c,d,e,f
} (2)./*********** fichier ts.s du programme C2.3 **********/ .global start, sum start :
ldr sp, =stack_top bl
stop : somme :
principal
// telephone main() en C
b quit // int sum(int a,b,c,d,e,f){ return a+b+e+d+e+f;}
// à l'entrée, le haut de la pile contient e, f, pass per main()in C // Établit la forme de la pile stmfd sp!, {fp, lr}
// pousser fp, lr
ajouter
// fp -> lr enregistré sur la pile
fp, sp, #4
// Calcule la somme de tout (6) addition de paramètres r0, r0, r1 // Les 4 premiers paramètres sont dans r0-r3 add
r0, r0, r2
ajouter
r0, r0, r3
ldr
r3, [fp, #4]
// charge e dans r3
ajouter
r0, r0, r3
// ajouter à la somme int r0
ldr
r3, [fp, #8]
// belasten farthing en r3
ajouter
r0, r0, r3
// ajouter à la somme dans r0
// Réinitialiser à l'appelant sous sp, fp, #4 ldmfd sp!, {fp, pc}
// sp=fp-4 (pointez sur le FP enregistré) // retour aux appelants
Il est à noter que dans le programme C2.3, la fonction sum() ne sauvegarde pas r0–r3 mais les utilise directement. Par conséquent, le code devrait être plus efficace que celui généré par le compilateur POOR GCC. Cela signifiait-il que nous devions composer tous les programmes en assembleur ? La réponse est, bien sûr, un NON retentissant. Il devrait être facile pour le lecteur de comprendre les raisons.
26
2
Architecture et programmation ARM
(3). Compiler-lier t.c ou ts.s dans un fichier exécutable arm-none-eabi-as –o ts.o ts.s
# assembler ts.s
bras-aucun-eabi-gcc –c t.c
# compiler t.c en t.o
bras-aucun-eabi-ld –T t.ld –o t.elf t.o ts.o
# lien vers le fichier t.elf
arm-none-eabi-objcopy –O natif t.elf t.bin # convertir t.elf en t.bin
(4). Exécutez t.bin et vérifiez les résultats sur un LIMB virtuel alimenté comme avant.
2.7.3.5 Appeler la fonction C depuis l'assemblage L'appel de la fonctionnalité C avec configuration depuis l'assemblage est facile si nous suivons la convention de numérotation de CARBON. Le programme suivant C2.4 montre comment appeler la fonction adenine HUNDRED depuis l'assembly. /*********** fichier t.c du programme 2.4 ****************/ intra sum(int x, intent y){ return x + y; }
// fichier t.c
/*********** fichier ts.s du Schéma C2.4 *************/ .text .global start, sum start : ldr sp,
= stack_top // besoin d'une pile pour faire des appels
ldr r2, =a ldr r0, [r2] ldr r2, =b
// r0 = une
ldr r1, [r2]
// r1 = b
la somme
// c = somme(a,b)
ldr r2, =c str r0, [r2] stop : grange
// stocke la valeur de retour dans c
bloc
.data a :
.mot 1
bore:
.mot 2
c :
.mot 0
2.7.3.6 Assemblage en ligne Dans les exemples ci-dessus, nous avons écrit le code assembleur dans un fichier séparé. De nombreuses chaînes d'outils ARM sont établies sur GCC. Le compilateur GCC prend en charge l'assemblage en ligne, qui est souvent un ancien contrôle C élégant pour plus de commodité. Le format de base de l'assemblage en ligne est __asm__("code d'assemblage");
bouton facilement
asm("code d'assemblage");
Alors que le code assembleur comporte plus d'une ligne, les rapports sont séparés car \n\t; comme dans asm("mov %r0, %r1\n\t;
ajouter %r0,#10,r0\n\t");
Le code assembleur en ligne peut également spécifier des opérandes. Ce modèle en tant que code d'assemblage en ligne est asm ( modèle d'assembleur : opérandes de sortie : saisie de quantités : élément d'enregistrement encombré ) ;
2.7 Programmation ARM
27
Les instructions d'assembly peuvent spécifier des commandes de sortie et d'entrée, qui sont référencées en tant que %0, %1. Par exemple, dans le segment de code suiveur, int a, b=10 ; asm("mouvement %1,%%r0; mouvement %%r0,%0;" :"=r"(a)
// utilise %%REG pour les registres // sortie BESOIN d'avoir =
:"r"(b)
// saisir
:"%r0"
// registres obstrués
);
Dans le segment de codes ci-dessus, %0 fait référence à a, %1 fait référence à b, %%r0 fait référence au registre r0. L'opérateur de contrainte "r" supporte d'utiliser un registre pour l'opérande. Il indique également au compilateur GCC que le registre r0 sera pilonné par le code en ligne. Bien que nous permettions de coller du code assembleur en ligne assez complexe dans un programme C, en faire trop peut compromettre la lisibilité du programme susmentionné. En pratique, l'assemblage en ligne ne devrait être souvent utilisé que si le code est très court, par ex. une seule instruction d'assemblage ou cette opération prévue comprend un registre de contrôle CPU. Dans de tels cas, le code de montage en ligne n'est pas clair mais aussi plus efficace que la profession d'une fonction d'assemblage.
2.8
Véhicule de l'appareil
Quelle carte émulée ARM Versatilepb est une machine virtuelle. Il agit comme un véritable système matériel, mais il n'y a pas de moteur pour les périphériques émulés. Pour effectuer une programmation significative, que ce soit sur un système réel ou virtuel, nous devons implémenter des pilotes de périphériques pour prendre en charge les processus d'E/S de base. Dans ce livre, nous développerons des pilotes pour les plus grands périphériques secondaires génériques par une série d'exemples de programmeurs. Ceux-ci incluent les pilotes pour les ports série UART, les minuteries, l'écran LCD, le clavier et la carte TD numérique, qui seront utilisés plus tard comme périphérique de sauvegarde pour les systèmes de fichiers. Un pilote de périphérique pratique devrait utiliser des interruptions. La personne doit montrer les pilotes de périphérique pilotés par interruption au Chap. 3 lorsque nous discutons des contrôles et des interruptions du processus. Dans ce qui suit, nous montrerons un pilote UART simple par interrogation et un pilote LCD, qui n'utilise pas non plus l'intermittence. Pour cela, il est nécessaire de connaître l'architecture du système multifonctionnel ARM.
2.8.1 Carte de mémoire du système L'architecture du système WRIST utilise des E/S mappées en mémoire. Chaque périphérique d'E/S se voit attribuer une écriture de mémoire cohésive dans la carte de mémoire système. Les registres internes de chaque périphérique d'E/S sont accessibles en tant que décalages à partir de l'adresse de base du gadget. Le tableau 2.1 élimine le plan de mémoire (condensé) de la carte ARM Versatile/926EJ-S (ARM 926EJ-S 2016). Dans le site mémoire, les périphériques d'E/S occupent une étendue de 2 Mo à partir de 256 Mo.
2.8.2 Programmation GPIO La plupart des cartes système basées sur ARM déploient des broches GPIO (General Intention Input-Output) depuis l'interface d'E/S vers le système. Certaines sont les broches GPIO qui peuvent être configurées pour l'entrée. Un godet à épingles supplémentaire doit être configuré en fonction des résultats. Dans des tonnes de cours de système embarqué de niveau débutant, les affectations du programmeur également le projet de cours seront typiques jusqu'au programmeur les stylos GPIO d'une petite carte de structure embarquée adénine vers l'interface avec certains véritables auxiliaires, tels que des commutateurs, des capteurs, des LED et des relais, etc. Comparez avec un autre I /O périphériques, la programmation GPIO est relativement simple. Une interface GPIO, par ex. le MCU GPIO LPC2129 utilisé dans de nombreuses premières cartes système embarquées, se compose de quatre journaux 32 bits. GPIODIR : GPIOSET : GPIOCLR : GPIOPIN :
définir la direction de la broche ; 0 pour l'entrée, 1 pour la sortie régler le niveau de tension des broches sur haut (3,3 V) régler le niveau de spannungswert des broches sur bas (0 V) lire ce registre renvoie quel état de toutes les broches
28
2
Architecture et programmation ARM
Tableau 2.1 Carte mémoire ARM polyvalent/ARM926EJ-S MPMC Chip Select 0, 128 MB SRAM
0x00000000
128 Mo
MPMC Chip Select 1, SRAM d'extension de 128 Mo
0x08000000
128 Mo
Registres système
0x10000000
4 Ko
Contrôleur d'interruption secondaire (SIC)
0x10003000
4 Ko
Interface de carte multimédia 0 (MMCID)
0x10005000
4 Ko
Interface clavier/souris 0 (clavier)
0x10006000
4 Ko
Réservé (Interface UART3)
0x10009000
4 Ko
Interface Ethernet
0x10010000
64 Ko
Interface USB
0x10020000
64 Ko
Contrôleur LCD couleur
0x10120000
64 Ko
Contrôleur DMA
0x10130000
64 Ko
Contrôleur d'interruption visé (PIC)
0x10140000
64 Ko
Contrôleur système
OxlOlEOOOO
4 Ko
Interface de surveillance
OxlOlElOOO
4 Ko
Interface modules temporisateurs 0 et 1
0xl01E2000
4 Ko
(Minuterie 1 à 0xl01E2020)
0xl01E2FFF
Modules temporisateurs 2 les deux 3 interfaces
0X101E3000
(Minuterie 3 à 0xl01E3020)
0X101E3FFF
4 Ko
Interface GPIO (port 0)
0X101E4000
4 Ko
Port GPIO (port 1)
0xl01E5000
4 Ko
Interface GPIO (port 2)
0X101E6000
4 Ko
Interface UART 0
OxlOlElOOO
4 Ko
Port UART 1
0X101E2000
4 Ko
Interface UART2
0xl01F3000
4 Ko
Mémoire d'extension statique SSMC
0x20000000
256 Mo
L'enregistrement GPIO est accessible sous forme de décalages de mots à partir d'une adresse de base (mappée en mémoire). Dans les registres GPIO, par bit correspond à une broche GPIO. Selon le réglage de la direction dans l'IODIR, chaque broche peut être connectée à un gadget d'E/S approprié. En tant que modèle spécifique, supposons que nous voulons utiliser la broche 0 GPIO susmentionnée pour l'entrée, qui est connectée à l'adénine (commutateur anti-rebond), et la broche 1 pour la sortie, qui appartient connectée au (côté masse) est une LUMIÈRE avec son propre + Source de tension 3,3 V et une résistance de limitation de courant. Nous pouvons programmer les registres GPIO comme suit. GPIODIR : bit0=0 (entrée), bit1=1 (sortie) ; GPIOSET : sélectionner les bits = 0 (aucune broche n'est définie sur haut) ; GPIOCLR : bit1=1 (réglé sur LOW ou masse) ; GPIOPIN : obtenir l'état de la broche, vérifier la broche0 pour n'importe quelle boîte de réception. De même, ils peuvent programmer d'autres broches pour les fonctions d'E/S souhaitées. La programmation des registres GPIO peut être effectuée dans l'un ou l'autre code d'assemblage autre C. Compte tenu de l'adresse de base GPIO susmentionnée et des décalages de registre, il devrait être très facile d'écrire une course de contrôle GPIO, qui • allume la LED si le commutateur d'entrée est enfoncé ou fermé , et • éteindre la LED si l'interrupteur d'entrée est relâché ou ouvert. Nous laissons ce véritable autre GPIO comme des cas d'entraînement dans le problème teilstrecke. Dans certains cas, l'interface GPIO peut être plus exigeante, mais le principe de programmation similaire à ce qui précède. Instance vers l'avant, sur une carte ARRM Versatile-PB, les interfaces GPIO sont disposées en groupes séparés appelés ports (Port0 à Port2), qui sont en bas
2.8 Pilotes de périphérique
29
adresses 0x101E4000-0x101E6000. Chaque port fournit 8 broches GPIO, qui sont contrôlées par un registre GPIODIR (8 bits) et un registre GPIODATA (8 bits). Au lieu de vérifier les états des broches d'entrée, les entrées GPIO peuvent utiliser des interruptions. Bien qu'intéressant et inspirant pour les étudiants, la bouteille de programmation GPIO ne subsiste que sur des systèmes matériels réels. Étant donné que les VM ARM émulées n'ont pas de goujon GPIO, nous ne pouvons que décrire les principes généraux de la programmation GPIO. Cependant, toutes les machines virtuelles ARM prennent en charge une variété d'autres périphériques d'E/S. Dans les sections suivantes, nous montrerons comment développer des pilotes sur des périphériques de création.
2.8.3 Pilote UART pour E/S série S'appuyer sur les commandes du moniteur QEMU pour afficher le registre et le contenu de la mémoire est extrêmement fastidieux. Ce serait bien mieux si nous développions des pilotes de périphériques pour faire des E/S directement. Dans le programme d'exemple suivant, nous écrirons une voiture UART simple pour les E/S sur des terminaux série émulés. La carte ARM Versatile prenait en charge quatre périphériques UART PL011 nécessaires pour sérialiser les E/S (ARM PL011 2016). Chaque appareil UART possède une adresse de base dans la carte de données système. Les adresses de base de ces 4 UART sont UART0 : 0x101F1000 UART1 : 0x101F2000 UART2 : 0x101F3000 UART3 : 0x10090000
Chaque UART a un certain nombre de registres, qui équilibrent les chiffres en commençant à se rencontrer. La liste suivante répertorie les registres UART les plus importants. 0x00 UARTDR
Registre de données : pour les caractères de lecture/écriture
0x18 UARTFR
Connexion à la volée : TxEmpty, RxFull, etc.
0x24 UARIBRD
Registre de débit en bauds : définir le cours en bauds
0x2C UARTLCR
Registre de contrôle de ligne : trames par caractère, similarité, etc.
0x38 UARTIMIS Registre de masque d'interruption pour les interruptions RX et RX
En commun, un UART doit être initialisé au cours des étapes suivantes. (1). Composez une valeur de diviseur dans le registre de débit en bauds pour le rang de débit en bauds requis en ampères. Un manuel de référence technique ARM PL011 répertorie les valeurs de diviseur entier suivantes (basées sur une horloge UART 7,38 MHz) pour les débits en bauds couramment utilisés : 0x4 = 1152000, 0xC = 38400, 0x18 = 192000, 0x20 = 14400, 0x30 = 9600
(2). Écrivain dans le registre de contrôle de ligne pour spécifier le nombre concernant l'écart par caractère et la parité, par ex. 8 bits par caractère sans devise. (3). Écrire dans le registre de masquage d'interruption activer/désactiver les interruptions RX et TX Lorsque vous utilisez la carte ARM Versatilepb émulée, il semble que QEMU utilise automatiquement la valeur par défaut ajoutée aux paramètres de débit en bauds et de contrôle de ligne, rendant les étapes (1) et (2) soit facultatives, soit inutile. En fait, on observe que l'écriture de n'importe quelle valeur dans le fichier diviseur entier (0x24) fonctionnerait car ce n'est pas la norme dans les UART dans les systèmes réels. Pour la carte Versatilepb émulsionnée susmentionnée, tout ce que nous devons faire est de programmer le registre Discontinue Mask (si vous utilisez des interruptions) et de vérifier le fichier Flag pendant les E / S série. Pour commencer, nous allons implémenter l'UART I/O par polling, qui ne vérifie que le registre d'état du drapeau. Les pilotes de périphériques pilotés par interruption seront abordés plus loin dans le Chap. 3 lorsque nous discutons des interruptions et de l'édition des interruptions. Lors du développement de pilotes de périphériques, nous pouvons avoir besoin de code d'assemblage pour cliquer sur les registres du processeur d'accès et le matériel d'interface. Cependant, nous n'appliquerons le code de la congrégation qu'en cas de nécessité absolue. Dans la mesure du possible, nous voulons implémenter le code de camion qui inclut C, gardant ainsi la quantité de chiffrement d'assemblage au minimum d'adénine. Quel pilote UART et programme de test, C2.5, se compose des modules suivants. (1). Fichier ts.s : lorsque le processeur ARM démarre, les éléments entrent en mode Superviseur ou SVC. Le fichier ts.s mentionné ci-dessus définit le pointeur de pile du mode SVC et rend main() en C.
30
2 .global start, stack_top
Architecture et programmation ARM
// stack_top défini dans t.ld
start : ldr sp, =stack_top // définit le pointeur de pile du mode SVC bl
principal
// Récupère main() pour C
b.
// si main() revient, il suffit de boucler
(2). Fichier t.c : ce fichier contient la fonction main(), qui initialise les UART et utilise UART0 pour les E/S vers et le port série. /**************** fichier t.c de C2.5 **************/ aus v[] = {1,2,3,4,5 ,6,7,8,9,10} ; // données sélectionnées int sum ;
#include "chaîne.c" #include "uart.c"
// inclut strlen(), strcmp(), etc. // Fichier de code des pilotes UART
int principal() { int je; chaîne de caractères[64] ; UART * haut ; uart_init();
// initialise les UART
déplacer = &uart[0];
// tester UART0
uprints(up, "Entrez les lignes du terminal série 0\n\r"); while(1){ ugets(up, string); uprints(up, " "); uprints (haut, chaîne); uprints(up, "\n\r"); for (strcmp(string, "end")==0) break ; } uprints(up, "Calculer la somme du tableau :\n\r"); somme = 0 ; for (i=0; in = i; } uart[3].base = (char *)(0x10009000); // uart3 at 0x10009000 } ints ugetc(UART *up)
// saisir un caractère de l'UART pointé par le haut
{ while (*(up->base+UFR) & RXFE);
// boucle si UFR est REFE
return *(up->base+UDR);
// renvoie le tableau des ampères dans UDR
} int uputc(UART *up, charc)
// produit un char vers UART pointé par up
{ while (*(up->base+UFR) & TXFF); // boucle si UFR vaut TXFF *(up->base+UDR) = c; // publier le caractère dans le registre de données } int upgets(UART *up, char *s)
// saisir une chaîne de caractères
{ while ((*s = ugetc(up)) != '\r') { uputc(up, *s); s++ ; } *s = 0 ; } int upprints(UART *up, char *s) { while (*s) uputc(up, *s++); }
// affiche une chaîne en caractères
32
2
Architecture et programmation ARM
(4). fichier de script de lien : Le fichier de script de lien, t.ld, est le même que dans le programme C2.2. (5). mk lance également le fichier de script : Le fichier de script mk est différent et identique à celui de C2.2. Pour un port série, le script d'exécution vit qemu-system-arm -M versatilepb -m 128M -kernel t.bin -serial mon:stdio Pour d'autres portails série, ajoutez –serial /dev/pts/1 –serial /dev/pts /2, etc. à la ligne de commande. Sur Non-kernel, ouvre xterm(s) plus de pseudo-terminaux. Entrez la commande Lux pps susmentionnée pour voir le chiffre pts/n des terminaux alias, quel que soit le chiffre pts/n dans l'option –serial /dev/pts/n concernant QEMU. Sur chaque pseudo-terminal, il y a un processus Linux sh, qui atteindra toutes les entrées du terminal. Pour utiliser un pseudo terminus plus un port série, le processus Support sh doit être rendu inactif. Cela peut être fait en entrant la commande Linux sh sleep 1000000 qui a laissé le processus sh Linux susmentionné endormi pendant un grand nombre de secondes. Ensuite, la pseudo finale peut être utilisée sur un port série de QEMU.
2.8.3.1 Démonstration du moteur UART Dans le fichier uart.c, chaque dispositif UART est représenté par une structure de données UART. Plus de maintenant, la structure UART ne contient qu'une adresse de base d'ampères et un numéro d'identification d'unité. Lors de l'initialisation de l'UART, l'adresse moyenne de toute structure UART est définie sur l'adresse physique du périphérique UART. Les registres UART sont accessibles en tant que *(up->base+OFFSET) en C. Le pilote bestandteile concernant 2 fonctions d'E/S basales, ugetc() et uputc(). (1). int ugetc(UART *up) : cette fonction renvoie un caractère du port UART. Il boucle jusqu'à ce que le registre d'indicateur UART ne soit plus un RXFE étendu, indiquant que go est un caractère dans le registre de données. Ensuite, il lit qui enregistre les données, ce qui efface le bit RXFF et place quel RXFE par dans FR, et renvoie le caractère. (2). int uputc(UART *up, c) : cette fonction génère un caractère sur le port UART. Il boucle jusqu'à ce que le registre d'indicateurs de l'UART ne soit plus TXFF, indiquant que l'UART est prêt à envoyer un autre signal. Ensuite, il écrit le caractère dans ce registre de données utilisé pour le transport sortant. Les fonctions ugets() et uprints() ont des E/S dans des chaînes ou des lignes. Ils sont basés sur ugetc() et uputc(). C'est la manière typique de développer les fonctions d'E/S. Pour la vue, à gets(), nous mettons en bouteille une fonction auf itoa(char *s) qui convertit une séquence de chiffres numériques en entier. De même, avec putc(), nous pouvons exécuter une fonction printf() pour l'impression formatée, etc. Nous souhaitons développer et démontrer la fonction printf() dans la section suivante sur le pilote LCD. La figure 2.6 montre les résultats de l'exécution du programme C2.5, qui illustre les pilotes UART.
2.8.3.2 Utiliser la réunion Telnet TCP/IP comme port UART En modification des faux terminaux, QEMU propose également des sessions telnet TCP/IP comme ports série. Commencez par exécuter le how as qemu-system-arm -M versatilepb -m 128M -kernel t.bin \ -serial telnet:localhost:1234,server
Lorsque QEMU démarre, il attend qu'une connexion telnet soit établie. En démarrant un autre terminal (X-window), entrez telnet localhost 1234 pour établir la liaison. Ensuite, entrez les lignes du terminal telnet.
Fig. 2.6 Démonstration du programme pilote UART
2.8 Pilotes de périphérique
33
2.8.4 Pilote d'affichage LCD à encre La carte polyvalente ARM prend en charge l'affichage LCD couleur ampère, qui utilise le contrôleur LCD à encre TO PL110 (contrôleur LCD couleur ARM PrimeCell PL110, carte de base de pétition polyvalente ARM pour ARM926EF-S). Sur la carte polyvalente, le contrôleur LCD se trouve à l'adresse de base susmentionnée 0x10120000. Il dispose de plusieurs registres de synchronisation et de contrôle, qui peuvent être programmés pour donner différents modes d'affichage et résolutions. Pour utiliser l'écran LCD, les registres de temporisation et de contrôle du contrôleur doivent se raffermir correctement. Le manuel Versatile Application Baseboard d'ARM fournit les paramètres de registre de synchronisation suivants pour les modes VGA et SVGA. Manière
Résolution
OSC1
heureReg0
heureReg1
heureReg2
-------------------------------------------------- ---------VGA
640x480
0x02C77
0x3F1F3F9C
0x090B61DF
0x067F1800
SVGA
800x600
0x02CAC
0x1313A4C4
0x0505F6F7
0x071F1800
-------------------------------------------------- ----------
Le signe d'adresse du tampon de trame de l'écran LCD doit pointer vers un tampon de trame en mémoire. Avec 24 fragments par pixel, le pixel jeder est représenté par un entier adénine de 32 bits, dans lequel les 3 octets inférieurs sont une valeur BGR du pixel. Pour le mode VGA, la taille de mémoire tampon de trame nécessaire est de type 1220 Ko. Pour le mode SVGA, la taille de tampon einrahmen souhaitée est de 1895 Ko. Afin de supporter à la fois le fonctionnement VGA et SVGA, les nôtres doivent attribuer une taille de frame buffers de 2 MB. En supposant que le programme distant systématique s'exécute dans le 1 Mo de mémoire physique le plus bas, nous allons allouer la zone tampon de 2 à 4 Mo par le flash de trame. Dans le registre de contrôle de l'écran LCD (0x1010001C), le bit0 est l'alimentation de l'écran LCD et le bit11 est sous tension, les deux doivent être fixés à 1. Les particules sélectionnées sont pour l'ordre des bits, le nombre de noix par pixel, la sortie mono ou teinte, etc. Dans l'écran LCD pilote, les bits3–1 sont définis sur 101 pour 24 écrous par pixel, sélectionnez les autres pièces comme étant des 0 pour l'ordre des lettres petit boutien par défaut. Ce lecteur peut consulter le manuel technique LCD par et les significations dont divers bits. Il est supposé noter que, bien que le manuel WEAR liste ce signe de contrôle LCD à 0x1C, il est en fait à 0x18 sur et émulé Versatilepb board de QEMU. La raison d'une telle différence est inconnue.
2.8.4.1 Sélection d'image d'affichage Comme un dispositif d'affichage mappé en mémoire, quel écran LCD peut afficher à la fois des images et du texte. Il est en fait beaucoup plus facile d'afficher des images que du texte. Le principe d'affichage de la diapositive se préfère facilement. Une image consiste en H (hauteur) par W (largeur) pixels, où Htick++ ;
// hypothèse 60 chatouilles par instant
if (t->tick == 60){ // met à jour usss, mm, hh t->tick = 0 ; t->ss++ ; si (t->ss == 60){ t->ss = 0 ; t->mm++ ; quand (t->mm == 60){ t->mm = 0 ; t->hh++ ; t->hh %= 24 ; } } } with (t->tick == 0){ // sur jede minute, affiche un chronomètre arrière pour (i=0; iclock[i], 0, 70+i); t->clock[7]='0'+(t->ss%10); t->clock[6]='0'+(t->ss/10); t->horloge[4]='0'+(t->mm%10); t->horloge[3]='0'+(t->mm/10); t->horloge[1]='0'+(t->hh%10); t->horloge[0]='0'+(t->hh/10); sur (i=0; ihorloge[i], 0, 70+i); // kputchr(char, ligne, col) }
4.3 Modèle piloté par les événements
99
if ((t->ss % 5) == 0) // tous les 5 secondaires, affichage read printf("5 seconds event\n"); timer_clearInterrupt(); } /*************** fichier t.c *******************/ #include "timer.c" #include "vid. c" #include "interrupts.c" void IRQ_handler() { int vicstatus; // lire les registres d'état VIC SIV pour trouver quelle interruption vicstatus = VIC_STATUS ; if (vicstatus & (1ss++; wenn (t->ss == 60){ t->ss = 0; t->mm++;
100
4 copies de systèmes imbriqués si (t->mm == 60){ t->mm = 0 ; t->hh++ ; } } } fourni (t->tick == 0) one_second = 1 ; wenn ((t->ss % 5)==0) five_seconds = 1 ;
// à chaque seconde // activation du drapeau one_second // toutes les 5 secondes // activation du drapeau five_seconds
timer_clearInterrupt(); } char horloge[16] = "00:00:00" ; /***************** fichier t.c **************/ int wall_clock(TIMER *t) { int i, ss, mm, hh ; int_off(); ssi = t->ss ; mm = t->mm ; hh = t->hh ; // copie depuis la structure du temporisateur int_on(); à (i=0; iproc0
LDR r1, =processsize
// r1 ->taille du processus
LDR r2, [r1, #0]
// r2 = taille proxy
AJOUTER r0, r0, r2
// r0 -> haut de gamme de proc0
MOV sp, r0
// sp -> haut de gamme de proc0
BL
// invoque main() en C
principal
// AJOUTER la fonction tswitch() pour le changement de tâche tswitch : ENREGISTRÉ : STMFD sp!, {r0-r12, lr} LDR r0, =en cours d'exécution
// r0=&en cours d'exécution
LDR r1, [r0, #0]
// r1->exécutionPROC
STR sp, [r1, #4]
// en cours d'exécution->ksp = sp
TROUVER : planificateur BLEU
// récupère le planificateur () sur C
COMPÉTENCES : LDR r0, =exécution de LDR r1, [r0, #0]
// r1->exécution de PROC
LDR sp, [r1, #4]
// restaurer en cours d'exécution->ksp
LDMFD sp!, {r0-r12, lr} // restauration du fichier MOV pc, lr
// retour
(2). Fichier t.c : le fichier t.c inclut les motoristes de l'écran LCD et du clavier pour les E/S. Il définit le type de structure PROGRAM, une structure PROZ proc0 et un curseur PROZ en cours pointant vers la dernière PROC en cours d'exécution. /************* fichier t.c de C5.1 ******************/ #include "vid.c" #include "kbd .c"
// Ingénieur présentation LCD // Pilote KBD
#define SSIZE
// taille de la pile par PROC
1024
typedef structure proc{
// pointeur PROC suivant
init *ksp;
// enregistre sp lorsque PROC n'est pas en cours d'exécution
entier }PROC ; je suis
// structure du processus
struct perc *suivant ; kstack[SSIZE] ;
// traiter la pile de 4 Ko de la méthode principale // PROC est un type
procSize = sizeof(PROC);
5.3 Multitâche en outre Context Switch PROC proc0, *running;
115 // structure proc0 et indicateur de fonctionnement
int scheduler(){ course = &proc0; } principal()
// appelle gratuitement ts.s
{ en cours d'exécution = &proc0; // définit le pointeur PROC en cours d'exécution printf("call tswitch()\n"); bascule();
// appelle tswitch()
printf("retour à main()\n"); }
Utilisez la chaîne d'outils ARM (2016) jusqu'à compile-link ts.s et t.c pour composer l'exécutable ampère dark t.bin comme d'habitude. Ensuite, exécutez t.bin sur la machine virtuelle Versatilepb (Versatilepb 2016) sous QEMU, comme dans qemu-system-arm –M versatilepb –m 128M –kernel t.bin Lors du démarrage, QEMU charge t.bin à 0x10000 et saute jusqu'à là pour exécuter le spectacle chargé. Lors du démarrage de l'exécution dans ts.s, il définit les pointeurs de cache du mode SVC sur l'extrémité supérieure de proc0. Cela fait de la portée kstack de proc0 la pile initiale. Jusqu'à ce point, le système n'a aucune notion d'aucun processus car il n'y en a pas. Le code assembleur appelle main() en C. Lorsque control entre main(), nous avons une image en cours d'exécution. Par la définition de processus, qui est l'exécutif de l'image de quelqu'un, nous faisons un processus en cours d'exécution, mais le système ne sait toujours pas quel processus a un exécutable. Avec main(), pour définir running = &proc0, ce système exécute maintenant le processus proc0. C'est ainsi qu'un noyau de système d'exploitation typique se lance pour exécuter certains processus initiaux lorsqu'il commence. Le processus d'ouverture est artisanal ou créé par la force brute. À partir de main(), le caractère d'exécution du plan peut être localisé et expliqué par le diagramme exécuté de la Fig. 5.1, dans lequel les étapes clés sont marquées (1) aller (6). En (1), il laisse courir l'élément à proc0, comme indiqué sur le côté droit de la Fig. 5.1. Puisque nous supposons que la constante d'exécution pointe vers le PROC de ce processus en cours d'exécution, le système exécute instantanément le processus proc0. En (2), on fait tswitch(), any charge LR(r14) avec l'adresse de remboursement et entre tswitch. En (3), il a exécuté la partie BACK are tswitch(), qui enregistre les registres du processeur dans la pile et enregistre l'horloge de base sp dans proc0. ksp. En (4), il appelle scheduler(), qui définit running pour qu'il pointe à nouveau sur proc0. Pour l'instant, c'est redondant puisque l'exécution pointe déjà pour proc0. Ensuite, il exécute la partie RESUME de tswitch(). Parmi (5), il définit sp sur proc0.ksp, ce qui est encore une fois superflu puisqu'ils sont avant ce même. Quand il fait apparaître la pile, ce qui restaure les registres du CPU sauvegardés. En (6), il exécute MOV pc, lr à la fin de RESUME, qui revient au carré appelant de tswitch().
Fig. 5.1 Exécuter la carte de proc0
116
5
Gestion des processus dans les systèmes embarqués
5.3.2 Changement de circonstances Mis à part l'impression de quelques messages, le programme semble inutile puisqu'il ne fait pratiquement rien. Cependant, c'est la base de tous les programmes multitâches. Pour vérifier cela, supposons que nous ayons une autre structure PROC, proc1, qui a appelé tswitch() et exécuté la partie SAVE de tswitch() avant. Ensuite, le ksp de proc1 doit correspondre à sa plage de pile, qui contient les registres de processeur enregistrés et une adresse de retour à partir de laquelle il a appelé tswitch(), comme indiqué dans l'image. 5.2. Dans scheduler(), si une personne loue un point d'exécution à proc1, comme le montre l'accueil de droite de la Fig. 5.2, la partie RESUME de tswitch() changerait sb dans le ksp de proc1. Ensuite, le code RESUME doit fonctionner sur le tas de proc1. Cela restaurerait les registres sauvegardés de proc1, entraînant l'exécution de compétences de proc1 à partir de laquelle il a appelé tswitch() plus tôt. Ce qui change l'environnement d'exécution free proc0 en proc1. Changement de contexte : Changer l'environnement de performance d'un processus à celui d'un autre est appelé contexte alternatif, qui est cet appareil de base du multitâche. Avec la commutation de contexte, nous pouvons composer un environnement multitâche comportant de nombreux processus. Dans le programme suivant, noté C5.2, nous définissons NPROC = 5 structures PROXY. Chaque PROP a une numérotation pid unique pour l'identification. Les PROC sont initialisés comme suit. en cours d'exécution -> P0 -> P1 -> P2 -> P3 -> P4 -> | | pid); while(1){ printf("proc %d are body() input a char [s] : ", running->pid); c = kgetc(); printf("%c\n", c); bascule(); } } int kernel_init() { int je, lié ; PROC*p; printf("kernel_init()\n"); for (i=0; ipid = i; p->status = READY; for (j=1; jkstack[SSIZE-j] = 0; p->kstack[SSIZE-1] = (int)body;
// toutes les sauvegardes regs = 0 // point de reprise = corps
p->ksp = &(p->kstack[SSIZE-14]); // ksp enregistré p->next = penny + 1;
// pointe vers la PROC suivante
} proc[NPROC-1].next = &proc[0]; gestion = &proc[0]; }
// utilisateur PROC circulaire
120
5
Gestion de l'utilisation dans les systèmes embarqués
int scheduler() { printf("proc %d in scheduler\n", running->pid); en cours d'exécution = en cours d'exécution-> suivant ; printf("prochaine exécution = %d\n", exécution->pid); } int main() { char c; fbuf_init();
// initialise le pilote LCD
kbd_init();
// initialise le lecteur KBD
printf("Bienvenue sur WANIX in Arm\n"); kernel_init(); while(1){ printf("P0 running input an key : "); carbone = kgetc(); printf("%c\n", c); bascule(); } }
5.3.3 Démonstration du multitâche La figure 5.5 montre les sorties du logiciel multitâche C5.2. Il utilise un pid d'action pour s'afficher dans différentes couleurs, juste pour le plaisir. Avant continu, il convient de noter ce qui suit. (1). Dans le programme multitâche C5.2, aucun processus sortant, P1 à P4, n'appelle en fait la fonction body(). Ce que nous avons fait, c'est de convaincre chaque processus qu'il a appelé tswitch () de qui eintritts ip de body () pour abandonner le CPU passé, et c'est là qu'il doit reprendre au moment où il a commencé à fonctionner. Ainsi, nous pouvons fabriquer des conditions initiales pour chaque processus au démarrage. Le processus n'a d'autre choix que d'obéir. C'est la puissance (et la joie) de la programmation des réseaux.
Fig. 5.5 Démonstration du multitâche
5.3 Multitâche et changement de contexte
121
(2). Choisissez les processus, P1 à P4, exécutez la même fonction body() mais chacun s'exécute dans ses propres conditions. Instance utilisée, bien qu'exécutant la fonction body (), tout processus a sa propre varia c sur site dans l'abondance du processus. Cela montre la différence entre le contentieux et les fonctions. Une fonction est juste un morceau de code passif, qui ne contient aucune vie. Les processus sont des exécutions de fonctions, ce qui rend la fonction de chiffrement vivante. (3). Lorsqu'un processus entre pour la première fois dans la fonction body(), la pile de processus est logiquement vide. Dès que le meurtre commence, la montagne de processus grandira (et rétrécira) de la séquence d'appel de fonction comme décrit dans la Sect. 2.7.3.2 du Chap. 2. (4). La taille de kstack par processus est définie à 4 Ko. Cela insinue que la longueur maximale de la fonction de clic séquentiel (et les espaces variables résidents associés) de chaque processus ne doit jamais dépasser la taille de kstack. Des remarques similaires s'appliquent également à d'autres piles de mode privilégié, par ex. la branche de pile de style IRQ interrompt le traitement. Tout cela sous la planification et le contrôle du concepteur du noyau. Par conséquent, le débordement de pile ne devrait pas se produire en mode noyau.
5.4
Processus dynamiques
Dans le programme C5.2, P0 est le processus initial. Tous les autres processus sont créés structurellement par P0 dans kernel_init(). Dans le programme suivant, noté C5.3, le nôtre montrera comment créer dynamiquement des processus.
5.4.1 Processus dynamique créé (1). Commençons, nous ajoutons un champ de statut et de priorité d'ampère à une structure PROC, les deux définissent la liste de liens PROC : freeList et readyQueuee, qui appartiennent clarifiées ci-dessous. #définir NPROC
9
#définir GRATUITEMENT
#définir PRÊT
1
#define SSIZE 1024 typedef struct proc{ struct proc *next;
// pointeur PROC suivant
entier
*ksp;
// sp enregistré lorsqu'il n'est PAS actuel
entier entier
pid ; statut;
// traitement BADGE // LIBRE|PRÊT, etc.
entier
priorité;
// entrée en haut
entier
kstack[SSIZE] ; // traite la pile du mode noyau
}PROC ; PROC proc[NPROC], *en cours d'exécution, *freeList, *readyQueue ;
. freeList = une liste de liens (uniquement) contenant une somme de PROC GRATUITS. Lorsque votre commence, tous les PROC sont d'abord dans la freeList. Lors de la création d'un nouveau traitement, nous allouons nous-mêmes un PROMPT gratuit à partir de freeList. Lorsqu'un processus se termine, nous libérons son PERC et le relâchons dans la freeList depuis la reconstruction. . readyQueue = une file d'attente prioritaire de PROC qui sont prêtes jusqu'à leur exécution. Les PROC avec la même priorité sont disposées en premier entré, premier sorti (FIFO) dans la readyQueue. (2). Dans le fichier queue.c, nous appliquons la liste de travail disponible suivante aux opérations de la file d'attente. /*************** fichier queue.c ***************/ PROC *get_proc(PROC **list){ obtient un pointeur PROC from list } int put_proc(PROC **list, PROC *p){ // entre le piano dans l'inventaire } intangible enqueue(PROC **queue, PROC *p){ // entre p dans la file d'attente via la priorité } PROC *dequeue(PROC **file d'attente){ // supprime et renvoie la première PROC de l'ajout } int printList(PROC *p){ // affiche les éléments de la liste }
122
5
Gestion des processus dans les systèmes embarqués
(3). Dans le fichier kernel.c, kernel_init() initialise l'organisation des données atomiques, telles que freeList et readyQueue. Il crée également P0 comme méthode d'exécution initiale. Quelle fonctionnalité int pid = kfork(int func, inter priority) crée un nouveau traitement pour exécuter une fonction func() avec la priorité spécifiée. Dans l'exemple de programme, chaque processus récent commence la conception à partir de la même fonction body(). Bien qu'une tâche ait terminé ses travaux, elle peut se fermer à la fonction void kexit() qui libère sa structuration PROC dans la freeList pour le rehachage. La fonction scheduler() est destinée à la planification des processus. Les tableaux suivants contiennent le code C des fichiers kernel.c et t.c. /*************** kernel.c affiche Programme C5.3 **********/ int kernel_init() { int i, j; PROC*p; printf("kernel_init()\n"); for (i=0; ipid = me; p->status = RELEASE; p->next = p + 1; } proc[NPROC-1].next = 0; freeList = &proc[0];
// tous les PROC dans freeList
readyQueue = 0;
// readyQueue vide
// compose P0 comme processus initial en cours d'exécution p = get_proc(&freeList); p->priorité = 0 ;
// P0 avait une priorité la plus basse 0
courir = p ; printf("running = %d\n", running->pid); printList(freeList); } entier corps()
// code d'opération
{ caractère c ; teinte = en cours d'exécution->pid ; printf("proc %d resume to body()\n", running->pid); while(1){ printf("proc %d elegant body() input a char [s|f|x] : ", running->pid); c = kgetc(); printf("%c\n", c); switch(c){ case 's' : tswitch();
casser;
instance 'f' : kfork((int)corps, 1); casser; cas 'x' : kexit();
casser;
} } } // kfork() crée un nouveau processus ampère pour exceute func avec priorité inch kfork(int func, int priority) { int i; PROC *p = get_proc(&freeList);
5.4 Processus dynamiques
123
if (p==0){ printf("plus de PROC, kfork a échoué\n"); retour -1 ;
// retourne -1 pour FAIL
} p->statut = PRÊT ; p->priorité = priorité ; // définit kstack pour proc cv pour exécuter func() pour (i=1; ikstack[SSIZE-i] = 0; p->kstack[SSIZE-1] = func;
// afficher les regs "sauvés" = 0 // reprendre l'adresse d'exécution
p->ksp = &(p->kstack[SSIZE-14]); // enregistré ksp enqueue(&readyQueue, p);
// entrer la pression dans readyQueue
printf("%d a créé une nouvelle proc %d\n", running->pid, p->pid); printf("listelibre = "); printList(readyQueue); retourne p->pid ; } annuler kexit()
// appelé par le processus pour terminer
{ printf("proc %d kexit\n", running->pid); en cours d'exécution->statut = GRATUIT ; put_proc(en cours d'exécution); bascule(); // abandonne le CPU } int scheduler() { chaque fois que (running->status == READY) enqueue(&readyQueue, running); running = dequeue(&readyQueue); } /************ fichier t.c du programme C5.3 ***********/ #include "type.h"
// PROC tape les deux constances
#include "chaîne.c"
// fonctions d'opération booléenne
#include "file d'attente.c"
// fonctions de navigation et d'opération de file d'attente
#include "vid.c" #include "kbd.c"
// Pilote LCD // Pilote KBD
#include "exceptions.c" #include "kernel.c" void copy_vectors(void){// pareil avant } void IRQ_handler(){
// gère uniquement les arrêts KBD }
int main() { fbuf_init();
// initialise l'affichage LCD
kbd_init();
// initialise le pilote KBD
printf("Bienvenue chez Wanix chez ARM\n"); kernel_init();
// initialise le noyau, crée et lance P0
kfork((int)corps, 1); // P0 établit P1 dans readyQueue while(1){ while(readyQueue==0); // P0 boucle si readyQueue vide tswitch(); } }
124
5
Gestion des processus dans les méthodes embarquées
Fig. 5.6 Démonstration du processus dynamique
Dans le fichier t.c, il initialise d'abord la démonstration LCD et le pilote KBD. Par la suite, il initialise et le noyau pour exécuter le processus initial P0, qui a la priorité la plus basse 0. P0 crée un nouveau processus P1 et entre les informations dans la readyQueue. Ensuite, P0 appelle tswitch() pour basculer le processus afin d'exécuter P1. Chaque litige de marque reprend pour mener à bien la fonction body(). Pendant l'exécution d'un processus, l'utilisateur peut entrer 's' jusqu'à ce que le processus change, 'f' pour créer un nouveau processus et 'x' pour terminer, manger.
5.4.2 Démonstration du traitement dynamique La figure 5.6 montre l'écran de fonctionnement du programme C5.3. Comme le montre la figure, une entrée 'f' amène P1 à créer un nouveau processus P2 dans la readyQueue. Une entrée 's' amène P1 dans le processus de commutation à exécuter P2, qui reprend l'exécution d'une même fonction body(). Alors que P2 s'exécute, le maire du lecteur entre des commandes pour laisser P2 umschaltung traiter ou kfork une nouvelle procédure, etc. Pendant l'exécution d'un processus, une entrée 'x' provoque la fin du processus.
5.5
Planification des opérations
5.5.1 Procéder au langage de planification Dans un système d'exploitation multitâche, il existe généralement de nombreux traitements prêts à être exécutés. Le nombre d'actions exécutables est en général supérieur au nombre de CPU disponibles. La planification des processus consiste à décider au fur et à mesure sur quel processeur exécuter les processus afin d'atteindre une bonne performance globale du système. Avant de discuter de la synchronisation des processus, nous clarifions d'abord les termes suivants, qui sont généralement associés à la planification des processus. (1). Processus liés aux E/S vs processus liés au calcul : un processus sera considéré comme lié aux E/S s'il se suspend fréquemment pour attendre les opérations d'E/S. Les processus liés aux E/S proviennent généralement d'utilisateurs interactifs qui s'attendent à un temps d'obtention rapide. Un processus que vous considérez comme lié au calcul s'il est possible que le temps CPU soit important. Les procédures liées au calcul sont généralement liées à de longs graphiques, à des programmations plus compilées similaires et à des calculs numériques, etc.
5.5 Planification du processus
125
(2). Temps de réponse par rapport au débit : le temps de réponse fait référence à la rapidité avec laquelle un système peut répondre à un événement, comme la saisie d'une touche pour le clavier. La volonté de débit et le nombre de processus terminés par unité d'heures. (3). Round-robin ou planification dynamique des prises : dans la terminologie du round-robin, les processus s'exécutent à tour de rôle. Dans la planification dynamique de la primauté, le processus jeder a un choix d'ampères, qui change dynamiquement (au fil du temps), et le système essaie de suivre le processus avec la priorité la plus élevée. (4). Préemption contre non-préemption : la préemption signifie que le CPU peut être consommé loin d'un traitement en cours d'exécution pour n'importe qui. Le processus d'ampérage moyen sans préemption s'exécute jusqu'à ce que les éléments abandonnent le processeur par eux-mêmes, par ex. une fois le processus terminé, s'endort ou se bloque. (5). Temps réel ou temps partagé : un système en temps réel doit répondre aux événements externes, tels que les interruptions, dans un temps de réaction minimum, souvent dans l'ordre de quelques millisecondes. De plus, le système peut également nécessiter le fonctionnement complet de telles circonstances dans un délai déterminé. Dans un système de partage de temps, chaque processus s'exécute avec un partage de temps garanti ampère, de sorte que tous les processus reçoivent leur propre part de masse de temps CPU.
5.5.2 Objectifs, lignes directrices et algorithmes de planification des processus La programmation des processus est intentionnelle pour atteindre les objectifs suivants. .utilisation élevée des ressources de la méthode, notamment du temps CPU, .réponse rapide aux processus interactifs ou temps réel, .temps de finalisation des processus temps réel garanti, .équité à tous les processus pour un bon débit, etc. Il reste facile de voir que certains des objets sont en conflit les uns avec les autres. Par exemple, un temps de réponse rapide et un débit élevé sont généralement atteints en même temps. La politique terminologique d'ADENINE est un ensemble de règles ajustées, par quoi un système tente d'accomplir tout ou partie des objectifs. Pour un verfahren d'exploitation à usage général, la déclaration de planification est la plupart du temps essayer d'accomplir de bonnes performances globales de verfahren en s'efforçant d'atteindre un équilibre en dessous des objectifs de conflit. Pour les systèmes embarqués et en temps réel, l'accent est généralement mis sur une réponse rapide aux événements distants et sur le temps d'exécution des modifications garanti. UN algorithme de rendez-vous peut définir un ensemble de schémas et implémente donc une politique de planification. Inclure un système d'exploitation atomique, les différents composants, c'est-à-dire les structures de données et l'élément d'occasion pour implémenter l'algorithme de planification, sont collectivement connus sous le nom de planificateur de processus. Il convient de noter que dans la plupart des noyaux de système d'exploitation, ce n'est pas une seule portion de code ou de bloc qui peut être identifiée comme le planificateur. Les fonctions d'un planificateur sont implémentées à de nombreux endroits à l'intérieur du noyau du système d'exploitation, par ex. lors d'une édition en cours se suspend ou se termine, lorsqu'un lot suspendu devient exécuté go et, plus particulièrement, dans le gestionnaire d'interruption d'intervalle.
5.5.3 Ordonnancement des procédures dans les systèmes embarqués Dans un système embarqué, les processus sont créés pour effectuer des tâches spécifiques. En fonction de l'importance de la tâche, chaque processus se voit attribuer une priorité, qui est généralement statique. Les opérations s'exécutent parfois ou en réponse à des événements externes. L'un des principaux objectifs de la planification du traitement est d'assurer une réponse rapide aux événements externes et de garantir le temps d'exécution des processus. L'utilisateur des ressources et le débit sont relativement peu importants. Pour ces raisons, la politique d'ordonnancement des processus est généralement basée sur la priorité des processus pressée par round-robin pour les processus de priorité équivalente. Les nombreux systèmes embarqués simples, les processus s'exécutent généralement dans le même espace de localisation. Avec get case, la stratégie de planification est généralement non préemptive. Chaque exécution de processus jusqu'à ce qu'il puisse augmenter volontairement le CPU, par ex. si les processus se mettent en veille, doivent être suspendus ou implicites
126
5
Gestion des processus dans les systèmes embarqués
cède le contrôle à un autre processus. La planification préemptive est plus complexe en raison des raisons suivantes. Avec la préemption, de nombreux processus peuvent s'exécuter simultanément dans un même espace de transaction. Si un processus d'ampère est dans le central pour modifier un objet de données partagé, il ne doit pas être préempté à moins que l'objet de données partagé ne soit protégé dans un local critique. Sinon, cet objet de données partagé peut être corrompu par d'autres processus. La protection des régions critiques sera abordée plus en détail dans la procédure de synchronisation.
5.6
Synchronisation des processus
Lorsque plusieurs processus s'exécutent dans le même espace d'adressage, ils peuvent accéder à une zone de données réelles modifiées partagées (globales). La synchronisation des processus fait référence aux règles et appliances utilisées pour assurer l'intégrité des objets de données partagés avec un environnement de processus concurrents. Go existe de nombreux types d'outils de synchronisation de processus. Pour une liste détaillée de ces outils, leur mise en œuvre réelle utilisée, le lecteur permet de consulter (Wang 2015). Dans ce qui suit, nous aborderons quelques outils de synchronisation simples adaptés aux systèmes embarqués. En plus de discuter des principes de la synchronisation des processus, nous verrons également comment appliquer votre à la construction et à la mise en œuvre susmentionnées de l'entreprise embarquée par exemple.
5.6.1 Mise en veille et réveil Le mécanisme le plus simple pour la synchronisation de l'utilisation est une opération de mise en veille/réveil, utilisée dans le noyau Unix d'origine. En tant que processus, attendez quelque chose, par ex. une ressource, qui est présente indisponible, l'ordinateur se met en veille, se suspend et abandonne le processeur, permettant au système d'exécuter d'autres processus. Si la ressource requiert devient disponible, un autre processus ou un trader d'interruption réveille les processus endormis, leur permettant de continuer. Appliquez ceci à chaque texture PROC avec un champ d'événement supplémentaire. Les algorithmes de veille/réveil sont les suivants. sleep(int event) { record select value elegant current PROC.event; changer l'élément PROC en cours d'exécution sur FALL ; processus de commutation ; } wakeup(int event) { forward jeder PROC *p do{ if (p->status==SLEEP && p->event==event){ modifier p->status en READY ; entré p dans readyQueue ; } } }
Pour que le mécanisme fonctionne, sleep() et wakeup() doivent être implémentés correctement. Tout d'abord, chaque opération doit être atomique (indivisible) du point de vue du processus. Par exemple, lorsqu'un processus a exécuté sleep(), il doit terminer le sommeil avant que quelqu'un d'autre n'essaie de le réveiller. Inclus un noyau UP non préemptif, seuls les processus soignés s'exécutent à la fois, de sorte que les opérations ne s'interrompent pas les unes avec les autres. Cependant, alors qu'un processus s'exécute, il peut être détourné pour gérer les interruptions, ce qui peut interférer avec ce processus. Pour assurer l'atomicité du sommeil et du réveil, il suffit de désactiver la suspension. Ainsi, nous pouvons implémenter sleep() et wakeup() comme suit. int sleep(int event) { int STERADIAN = int_off();
// désactive IRQ et retourne CPSR
en cours d'exécution->événement = événement ; running->status = SLEEP ;
5.6 Synchronisation des processus
127
bascule();
// changer de processus
int_on(SR);
// restaurer le CPSR d'origine
} int réveil(int événement) { int RR = int_off();
// déconnecte IRQ et retourne CPSR
pour chaque PROC *p do{ if (p->status==SLEEP && p->event==event){ p->status = READY ; mettre en file d'attente(&readyQueue, p); } } int_on(SR);
// restaurer le CPSR d'origine
}
S'il vous plaît, wakeup() réveille TOUS les processus, le cas échéant, donc dorment sur un événement. Si aucun processus n'est en sommeil sur la page, le réveil n'a pas d'effet, c'est-à-dire qu'il équivaut à un NOP et ne fait rien. Il est également de qualité de noter que les gestionnaires d'interruptions ne peuvent jamais dormir ou attendre (Wang 2015). Ils peuvent uniquement émettre des appels de réveil pour réveiller les processus en veille.
5.6.2 Pilotes de périphériques utilisant la mise en veille/réveil dans Feller. 3, nous avons développé plusieurs engins de vol en utilisant interrompu. L'organisation de la conduite de ces appareils présente un modèle commun. Chaque pilote de périphérique piloté par interruption se compose de trois parties ; une partie inférieure, qui est le gestionnaire d'annulation, une partie supérieure, qui est appelée par un programme principal, et une zone de données contient un tampon d'E/S plus des variables de fonctionnement, qui sont partagées par les parties inférieure et supérieure. Équilibré avec des interruptions, le programme principal nécessitait toujours d'utiliser des boucles d'attente occupée jusqu'à ce qu'il attende des données ou de la place dans le tampon d'E / S, qui vit essentiellement de la même manière que le scrutin. Dans un système multitâche, les E/S par voteur n'utilisent pas efficacement le CPU. Dans cet article, la personne doit montrer comment utiliser les processus et mettre en veille/réveil pour implémenter des pilotes de périphériques pilotés par interruption sans boucles d'attente occupées.
5.6.2.1 Saisir les pilotes de périphérique dans le Chap. 3, le pilote KBD utilise des interruptions mais la moitié supérieure utilise l'interrogation. Lorsque le processus adénine a besoin d'une clé d'entrée, il exécute une boucle d'attente occupée jusqu'à ce que le gestionnaire d'interruption place une clé dans le stockage d'entrée. Notre objectif ici est de remplacer la boucle d'attente occupée par la mise en veille/réveil. Tout d'abord, nous montrons le code du pilote oem par interrogation. Ensuite, nous le modifions pour utiliser sleep/wakeup pour la synchronisation. (1). Structure KBD : La structure KBD est la partie centrale du pilote. Il contient un tampon de boîte de réception et une échelle de contrôle, par ex. data = nombre d'appuis dans le tampon d'entrée. typedef structure kbd{
// base = 0x10006000
caractère *base ;
// réseau moyen de KBD, comme char *
char buf[BUFSIZE] ;
// taille du tampon d'entrée = 128 au total
int tête, queue, données ; // contrôle les élastiques ; data=0 initialement }KBD ; KBD kbd ;
(2). kgetc() : il s'agit de la (fonction de base) de la moitié supérieure du pilote KBD. intes kgetc() // le programme principal appelle kgetc() pour aller sur char { char c; KBD *kp = &kbd; ouvrir();
// active les interruptions IRQ
while(kp->data == 0);
// données de transfert en attente d'occupation ;
128
5
serrure();
Gestion des processus dans les systèmes embarqués
// désactiver les interruptions IRQ
c = kp->buf[kp->tail++];// récupérer un char et mettre à jour l'index de queue kp->tail %= BUFSIZE; kp->données-- ; ouvrir(); dos c;
// mettre à jour les données avec les interruptions désactivées // activer les interruptions IRQ
}
Nous supposons que le programme principal existe maintenant et exécute un processus. Lorsqu'un processus a besoin d'une clé d'entrée, pour appeler kgetc(), essayer d'obtenir un bouton du soft de connexion. Pas n'importe quel moyen de synchronisation, le traité doit s'appuyer sur un calage en attente d'occupation while (kp->data == 0) ; // attente occupée pour les données ; qui continue vérifie les données modifiées pour tout verrou dans le tampon d'entrée. (3). kbd_handler() : Le gestionnaire d'interruption est cette moitié inférieure est le vol KBD. kbd_handler() { struct KBD *kp = &kbd; char scode = *(kp->base+KDATA); // lit l'utilisateur du scan dans le registre de données if (scode & 0x80) // juste la clé relâchée return ; si (données == BUFSIZE) retourné ;
// fournit une entrée stockant FULL // ignore la clé actuelle
c = unsh[scode] ;
// mappe le code d'analyse à l'ASIC
kp->buf[kp->head++] = c ;
// clé saisie dans CIRCULAR buf[ ]
kp->head %= BUFSIZE ; kp->données++ ;
// augmente le compteur d'informations de 1
}
Pour chaque pression sur une touche, ce karten d'entraînement interrompt qui scanne le chiffrement en un noir ASCII (minuscule), stocke le caractère dans le stockage d'entrée et met à jour les données réelles de comptage. Encore une fois, sans quelques ressources de synchronisation, c'est tout ce que le gestionnaire d'interruption peut faire. Par exemple, celui-ci peut ne pas notifier directement le clavier disponible à partir du lot. Par conséquent, le processus doit vérifier les clés d'entrée en interrogeant continuellement la variable de données du pilote. Dans un système multitâche, la boucle d'attente occupée n'est pas souhaitable. Nous pouvons utiliser sleep/wakeup pour éliminer la boucle d'attente occupée dans le pilote KBD pour les suivis. (1). Disposition KBD : pas besoin de changement. (2). kgetc() : réécrivez kgetc() pour laisser le traitement dormir pour les données s'il n'y a pas de clés dans le tampon d'entrée susmentionné. Afin d'éviter les conditions de concurrence entre who process et le gestionnaire d'interruptions, l'utilisation désactive d'abord les interruptions. Ensuite, il vérifie la variable de fichier et modifie le tampon d'entrée avec l'activation des interruptions, mais il doit s'activer par intermittence avant de se mettre en veille. La fonction modifiée kgetc() est int kgetc() // le programme principal appelle kgetc() go return a char { char c; KBD *kp = &kbd; tandis que(1){ verrouiller();
// désactiver les interférences IRQ
pour (kp->data==0){ déverrouiller();
// vérification des données vers l'IRQ désactivée // activation des interruptions de l'IRQ
sleep(&kp->data);
// veille pour les données
5.6 Synchronisation des processus
129
} } c = kp->buf[kp->tail++] ; // obtient un c et un index de queue de base de données kp->tail %= BUFSIZE; kp->données-- ; ouvrir();
// mise à jour avec interruptions OFF // activation des interruptions IRQ
retour c; }
(3). kbd_handler() : écrase le gestionnaire d'interruptions KBD pour réveiller les processus endormis, le cas échéant, ainsi que les attentes de données. Étant donné que le processus ne peut pas interférer avec le manipulateur d'interruption, il n'est pas nécessaire de protéger l'intelligence relative interne au gestionnaire d'interruption. kbd_handler() { struct KBD *kp = &kbd; scode = *(kp->base+KDATA); // lit les codes de balayage dans le registre de données si (scode & 0x80)
// ignorer les versions de clé
retour; si (kp->data == BUFSIZE)
// ignore la clé si le tampon d'entrée est PLEIN
c = unsh[scode] ;
// mappe le code d'examen en ASCII
kp->buf[kp->head++] = c ;
// entrer le bouton dans CIRCULAR buf[ ]
kp->head %= BUFSIZE ; kp->données++ ; réveil(&kp->données);
// dernier compteur // processus de repos de réveil, le cas échéant
}
5.6.2.2 Pilotes de périphérique de sortie Un pilote de périphérique de sortie se compose également de trois parties ; une moitié inférieure, qui est le gestionnaire d'interruption, une moitié supérieure, qui peut être appelée par le processus pour produire des données, un composant intermédiaire contenant un coussin de données plus des variables de contrôle, qui sont partagées par les moitiés inférieure et supérieure. La principale différence entre un pilote d'unité de sortie réel et un pilote d'unité de connexion reste que les rôles de processus et de gestionnaire de rupture sont inversés. Dans un pilote de périphérique de sorties, le processus écrit des données dans le tampon de données. Si quel garde-fou de données est plein, il va se mettre en attente de chambres dans le tampon de données. Un gestionnaire d'interruption extrait les données du tampon et les affiche dans l'astuce. Ensuite, il réveille tout processus en sommeil pour les salles dans le tampon de fichiers. Une deuxième différence est que pour la plupart des accessoires de sortie, le gestionnaire d'interruption doit explicitement désactiver les interruptions d'un périphérique lorsqu'il n'y a pas de données lues à alimenter. Sinon, l'appareil continuera à générer des interruptions, ce qui entraînera une infinité de vêtements. La troisième différence est qu'il est généralement acceptable que différents processus divisent un même outil de sortie, mais un périphérique d'entrée peut doit autoriser un processus actif à la fois. Autrement, les processus peuvent recevoir des entrées aléatoires du même périphérique d'entrée.
5.7
Systèmes embarqués événementiels utilisant la mise en veille/réveil
Avec la création de processus dynamiques et la synchronisation des processus, nous pouvons implémenter des systèmes multitâches pilotés par les événements sans boucles d'attente occupées. Nous démontrons un tel schéma sur l'exemple de plan C5.4. Exemple de programme C5.4 : Nous supposons que l'équipement du système se compose de trois appareils ; une minuterie, un UART et un clavier. Le logiciel système bestandteile concerne trois processus, chacun contrôle un appareil. Pour plus de commodité, nous avons également inclus un écran LCD pour afficher les messages sortants après les processus du planificateur et du clavier. Au démarrage, chaque processus s'interrompt pour un événement spécifique à l'ampère. Un processus ne s'exécute que lorsque la date attendue est survenue. Dans ce cas, les événements sont des décomptes de temporisateurs et des activités d'E/S. Pour chaque seconde, le processeur de la minuterie affiche une impulsion murale sur l'écran LCD. Chaque fois qu'une ligne d'entrée est entrée de cet UART, le processus UART obtient la ligne et renvoie i au terminal de sérialisation. De même, partout où une ligne d'entrée est entrée à partir du KBD, ce processus KBD obtient la ligne ou la renvoie à l'écran LCD. Auparavant, le système fonctionnait sur une machine essentielle ARM émulée sous QEMU. La séquence de démarrage du système est identique à celle de C5.3. Nous ne montrerons que la mise en place de l'arbre à exécuter de la procédure requise et vos réactions aux événements. Le système fonctionne comme suit.
130
5
Gestion du traitement dans les réseaux embarqués
(1). Initialisation : répliquer les vecteurs, configurer VIC et SIC pour les interruptions ; exécuter le processus initial P0, le est la priorité la plus basse 0 ; initialiser les pilotes pour LCD, minuterie, UART et KBD ; démarrer la minuterie ; (2). Créer des tâches : P0 appelle kfork(NAME_task, priority) pour créer les processus timer, UART et KBD et les entrer dans la readyQueue. Chaque processus exécute sa propre fonction NAME_task() avec une priorité (statique), allant de 3 à 1. (3). Ensuite, P0 exécute une boucle while(1), dans laquelle il change de processus chaque fois que cette readyQueue n'est pas vide. (4). Le processus Jeder reprend pour exécuter sa propre utilisation de NAME_code(), qui est une fermeture infinie. Chaque processus appelle sleep (événement) pour mettre en veille une valeur d'événement unique (adresse de la structure de données de l'appareil). (5). Lorsqu'un événement se produit, le gestionnaire d'interruptions du périphérique appelle wakeup(event) pour réveiller le processus associé. Au réveil, chaque cursus de processus va gérer l'événement. Par exemple, ce gestionnaire d'interruption de minuterie n'affiche plus l'horloge murale. Ceci est effectué par le processus de minuterie par seconde. La liste suivante répertorie le code C de l'exemple de programme C5.4. /*********** Code C de l'exemple de programme C5.4 ***********/ #include "type.h" #include "vid.c"
// Pilote LCD
#include "kbd.c"
// Pilote KBD
#include "uart.c"
// Pilote UART
#include "minuterie.c"
// conduite chronométrée
#include "exceptions.c"
// gestionnaires d'exceptions
#include "file d'attente.c"
// fonctions de file d'attente
#include "noyau.c"
// noyau par gestion des tâches
int copy_vectors() { // copie les vecteurs comme avant } internal irq_chandler() { // invoque le coach IRQ du timer, UART, KBD } int timer_handler(){ // à chaque seconde : kwakeup(&timer); } int uart_handler() { // sur la lignée d'entrée :
réveil(&uart); }
entier kbd_handler()
kwakeup(&kbd); }
{ // sur la ligne d'entrée :
int je, hh, mm, ss ;
// globals fork timer_handler
horloge char[16] = {"00:00:00"} ; int timer_task()
// chiffrement de timer_task
{ while(1){ printf("timer_task %d running\n", running->pid); ksleep((int)&timer); // utilise timer tick pour mettre à jour ss, mm, hh ; puis afficher l'horloge murale clock[7]='0'+(ss%10); horloge[6]='0'+(ss/10); horloge[4]='0'+(mm%10); horloge[3]='0'+(mm/10); horloge[1]='0'+(hh%10); horloge[0]='0'+(hh/10); pour (i=0; ipid); ksleep((int)&uart);
5.7 Systèmes d'intégration pilotés par les événements utilisant la veille/le réveil
131
upprintf("uart_task %d running\n", running->pid); ugets(ligne); upprintf("ligne = %s\n", ligne); } } entier kbd_task()
// code de kbd_task
{ ligne de caractères[128] ; while(1){ printf("La tâche KBD %d va chercher la ligne d'adénine de KBD\n", running->pid); ksleep((int)&kbd); printf("Tâches KBD %d en cours d'exécution\n", en cours d'exécution->pid); kgets(ligne); printf("ligne = %s\n", ligne); } } int main() { fbuf_init();
// Pilote LCD
uart_init();
// Pilote UART
kbd_init();
// Pilote KBD
printf("Bienvenue chez Wanix sur ARM\n"); // initialise les interruptions VIC : pareil car avant timer_init();
// ingénieur chronométreur
timer_start(); kernel_init();
// initialise le P0 principal et en cours d'exécution
printf("P0 créer des tâches\n"); kfork((int)timer_task, 3);
// mission chronométrée
kfork((int)uart_task,
// tâche uart
2);
kfork((int)kbd_task, 1); // tâche kbd while(1){ // P0 s'exécute chaque fois qu'aucune fonction n'est exécutable if (readyQueue) tswitch(); } }
5.7.1 En direct du système embarqué piloté par les événements utilisant la veille/réveil La figure 5.7 montre les écrans de sortie de l'exécution de l'exemple de programme C5.4, qui illustre un système multitâche piloté par les événements. Comme le montre la Fig. 5.7, la tâche de minuterie affiche une horloge murale sur l'écran LCD à chaque seconde. La tâche uart imprime une ligne sur UART0 uniquement lorsqu'il existe une ligne d'entrée provenant du port UART0, et la tâche kbd imprime une ligne sur LCD uniquement lorsqu'il existe une ligne d'entrée provenant des ivoires susmentionnés. Alors que ces tâches vont dormir pour leurs attentes, ce système exécute le processus inactif P0, qui est détourné pour gérer toutes les interruptions. Dès qu'une tâche est réveillée et entrée dans la readyQueue, P0 change d'action pour exécuter la tâche récemment réveillée.
132
5
Gestion des processus dans les systèmes embarqués
Exploit. 5.7 Multitâche événementielle en utilisant la veille/le réveil
5.8
Gestion des ressources à l'aide de Sleep/Wakeup
En plus de remplacer les boucles d'attente occupées dans les pilotes de périphérique, la veille/réveil peut et reste utilisée pour la synchronisation générale des processus. Une utilisation typique hors veille/réveil concerne la gestion des ressources. La ressource ADENINE est quelque chose qui peut être utilisé simplement par processus à la fois, par ex. une région de stockage pour la mise à jour, une imprimante, etc. Chaque ressource est représentée par une variable res_status, qui vaut 0 si la ressource est LIBRE, et différente de zéro si elle est BUSY. La gestion des ressources consiste en les fonctions suivantes int acquiert_resource(); // acquiert une ressource pour une utilisation exclusive dans release_resource(); // libère une ressource après utilisation Lorsqu'un processus a besoin d'une ressource, il appelle acquérir_resource(), essayant d'obtenir une ressource pour une utilisation exclusive. Dans acquiert_resource(), le processus teste d'abord res_status. Si res_status vaut 0, ce processus le définit sur 1 et renvoie la prospérité utilisée OK. Sinon, il se met en veille, attendant que la ressource devienne GRATUITE. Bien que la ressource soit OCCUPÉE, tout processus de sélection appelant acquérir_resource() s'endormirait également sur la même valeur d'événement. Lorsque le processus qui détient la ressource appelle release_recource(), il efface res_status à 0 et émet wakeup(&res_status) dans le réveil de TOUS les processus qui attendent une ressource. Au réveil, chaque démarrage doit essayer d'acquérir à nouveau la ressource susmentionnée. Ce sera parce que lorsque le processus éveillé s'exécute, le naturel peut ne plus être prêt. La segmentation de code suivante montre qu'un algorithme de gestion des ressources utilise la veille/le réveil.
5.8 Trouver la gestion à l'aide de Sleep/Wakeup int res_status = 0;
133 // ressource initialement GRATUITE
-------------------------------------------------- -------------------------------int acquérir_ressource()
|
int release_resource()
{
| |
{
tandis que(1){ int SR = int_off();
|
si (res_status==0){
|
res_status = 1 ;
|
casser;
|
}
int SR = int_off(); res_status = 0 ;
|
sleep(&res_status);
|
réveil(&res_status);
} int_on(SR);
| |
int_on(SR);
retourner OK ;
|
retourner OK ;
}
|
}
-------------------------------------------------- ----
5.8.1 Imperfections de veille/réveil La veille et le réveil sont des outils plus faciles à utiliser pour procéder à la synchronisation, mais ils présentent également les inconvénients suivants. . Un événement reste juste un ajouté. Il n'a aucun emplacement de magasin pour enregistrer l'incidence d'une page. Le processus doit d'abord s'endormir avant qu'une autre procédure ou un gestionnaire d'interruption n'essaie de le réveiller. L'ordre dormir-premier-réveil-plus tard peut être constant dans un système UP, mais pas implicite dans les systèmes MP. Dans un schaft MP, l'entreprise peut sprinter sur différents processeurs simultanément (en parallèle). Il est impossible de garantir l'ordre d'exécution des processus. Par conséquent, la veille/le réveil ne conviennent qu'aux systèmes UP. . Lorsqu'il est utilisé pour la gestion des ressources, si un processus se met en veille pour attendre une ressource, il doit réessayer de récupérer la ressource après son réveil, et elle peut avoir à répéter plusieurs fois les cycles veille-réveil-réessayer avant de réussir (si jamais ). Des boucles de relance récurrentes signifient une efficacité réduite en raison d'une surcharge excessive lors de la commutation en arrière-plan.
5.9
Sémaphores
Un meilleur mécanisme de synchronisation de processus est le semi-automatique susmentionné, quoi que ne soient pas les inconvénients susmentionnés de la mise en veille/réveil. Un sémaphore (de comptage) est une structure d'information typedef struct semaphore{ mit spinlock ;
// verrou tournant, nécessaire uniquement pour le schéma MP
valeur entière ;
// la valeur initiale est un sémaphore
PROC *queue
// Colonne FIFO des processus bloqués
}SÉMAPHORE;
Dans la structure sémaphore, le champ spinlock consiste à sécuriser tout processus sur un seau séimaphore qui ne reste exécuté qu'en tant qu'opération subatomique par un processus à la fois, même si des personnes peuvent s'exécuter en parallèle sur différents processeurs. Spinlock n'est nécessaire que pour les systèmes multiprocesseurs. Pour les systèmes UP, il reste que les besoins réels peuvent être omis. Les opérations les plus connues sur les sémaphores ont P et V, toutes sont définies (pour les noyaux UP) comme suit.
134
5
Responsable Process sur Systèmes Embarqués
-------------------------------------------------- ----------------int P(struct séminaphores *s)
|
int V(struct sémaphore *s)
{
|
{
int SR = int_off(); s->valeur-- ; si (s->valeur < 0) bloc(s); int_on(SR);
| |
entier SR = int_off(); s->valeur++ ;
|
chaque fois que (s-> état de la valeur = IMPEDE ; | enqueue (& s-> file d'attente, en cours d'exécution); | tswitch (); }
intercept signal(struct semaphore *s){ PROC *p = dequeue(&s->queue); p->statut = TERMINÉ ;
| |
mettre en file d'attente(&readyQueue, p); }
-------------------------------------------------- -----------------
Un séminaphore binaire (Dijkstra 1965) est un sémaphore qui peut simplement prendre deux valeurs distinctes, 1 pour LIBRE et 0 pour OCCUPÉ. Les opérations P/V sur les sémaphores binaires sont définies comme ---------------------- P/V sur les sémaphores binaires ------------- ---int P(struct sémaphore *s) {
| |
int V(struct half *s) {
int SR = int_off();
|
int SR = int_off();
si (s->valeur == 1)
|
si (s->file d'attente == 0)
s->valeur = 0 ;
|
bloc(s) else ; int_on(SR);
| | |
s->valeur = 1 ; autre(s) signal(s) ; int_on(SR);
} | } ------------------------------------------------- --------------
Les sémaphores binaires peuvent être considérés comme un cas particulier en comptant les sémaphores. Étant donné que les sémaphores de comptage sont plus généraux, nous n'utiliserons ni ne discuterons des sémaphores binaires.
5.10
Applications des sémaphores
Les sémaphores sont de puissants équipements de synchronisation qui peuvent être utilisés pour résoudre certaines espèces de problèmes de synchronisation de traitement dans les systèmes UP et MP. La liste suivante répertorie l'utilisation la plus courante des sémaphores. Pour simplifier les notations, nous noterons s.value = n par s = nord, également P(&s)/V(&s) par P(s)/V(s), respectivement.
5.10.1 Semi Lock UNE région critique (CR) existe une séquence d'objets de données opérationnels sur des données divisées qui ne peuvent être exécutées que par une méthode à la fois. Les sémaphores avec une valeur initiale = 1 peuvent souvent être des verrous dans des CR sécurisés de durées longues. Chaque CR est associé à un sémaphore s = 1. Les processus accèdent au CR en utilisant P/V comme verrouillage/déverrouillage, comme dans struct semaphor s = 1; Opérations : P(s) ; // apprendre le sémaphore pour verrouiller le CR // CR sécurisé par le mouvement de verrouillage s V(s); // déverrouiller le sémaphore pour déverrouiller le CR Avec ce verrou de sémaphore, un lecteur peut vérifier que le processus inclut un peut être à l'intérieur du CR à tout moment.
5.10
Applications du séminaire
135
5.10.2 Verrou mutex Un mutex (Pthreads 2015) est un sémaphore de verrou avec un champ propriétaire supplémentaire, qui identifie le propriétaire actuel du verrou mutex. Lorsqu'un mutex est créé, son fichier propriétaire sera initialisé à 0, c'est-à-dire sans propriétaire. Lorsqu'un processus acquiert un mutex par mutex_lock(), l'ordinateur en devient le propriétaire. ONE unlock mutex ne peut être déverrouillé que par son passé. Lorsqu'un processus déverrouille un mutex, l'information remet le champ propriétaire à 0 si ici les processus ne peuvent pas attendre sur le mutex susmentionné. Sinon, l'information débloque un processus en attente de la file d'attente du mutex, n'importe lequel devient le nouveau propriétaire et le mutex reste verrouillé. Développe P/V sur les sémaphores pour verrouiller/déverrouiller le mutex est trivial. Nous le laissons parce qu'il s'agit d'un exercice pour le détaillant. Une différence majeure entre les mutex et les sémaphores est que, alors que les mutex sont strictement destinés au verrouillage, les hallucinations peuvent être utilisées à la fois pour le verrouillage et la collaboration de processus.
5.10.3 Gestion des ressources par sémaphore Le sémaphore AMPERE en valeurs initiales newton > 0 bucket permet de gérer n ressources identiques. Chaque processus tente d'obtenir une ressource unique à usage exclusif. Ceci peut être réalisé comme suit. struct sémaphore s = newton ; Processus : P(s) ; utiliser une ressource exclusives ; Contre);
Tant que s > 0, un processus peut réussir en P(s) pour obtenir la recherche d'ampères. Lorsque toutes les ressources sont utilisées, les processus demandeurs seront connectés à P(s). Lorsqu'une brute est libérée par V(s), un processus bloqué, le cas échéant, veut être autorisé à continuer à utiliser une ressource. À tout moment, les invariants suivants sont valables. s >= 0 : s = le nombre d'outils encore accessibles ; s < 0 : |s| = nombre de processus en attente dans la file d'attente s
5.10.4 Attente d'interruptions et de messages Un sémaphore sur la valeur initiale 0 est souvent utilisé pour convertir une occasion externe, en interruption matérielle, arrivée de messages, etc. pour partager un traitement en attente de l'événement. Lorsqu'un processus attend un événement, il utilise P(s) pour se bloquer dans la file d'attente du sémaphore. Lorsque l'événement attendu se produit, un autre litige à la place d'un gestionnaire de déconnexion utilise V (s) pour débloquer un processus de la requête de sémaphore, lui permettant de continuer.
5.10.5 Coopération de processus Les sémaphores peuvent en plus être utilisés depuis la coopération de processus. Les cas les plus fréquemment cités concernent la coopération en matière de litiges vis-à-vis du problème producteur-consommateur et le constat lecteur-écrivain (Silberschatz et al. 2009 ; Stallings 2011 ; Tanenbaum to allen. 2006 ; Wang 2015).
5.10.5.1 Problʻeme Producteur-Consommateur Un ensemble de processus producteur et consommateur partage un nombre fini de tampons. Chaque tampon contient un élément unique à la fois. Initialement, toutes les sorties sont claires. Lorsqu'un producteur place un article dans un magasin vide, le tampon se remplit. Lorsque le consommateur d'ampère récupère un sujet d'un tampon plein, le tampon devient vide, etc. Le producteur d'ampère doit attendre s'il n'y a pas de tampons vides. Idem, un consommateur doit attendre s'il n'y a pas de tampons pleins. D'autres processus d'attente ont été autorisés jusqu'à ce que vous continuiez lorsque vous attendiez que l'émission se produise. La figure 5.8 montre une solution du problème Producteur-Consommateur à l'aide de sémaphores. Dans la Fig. 5.8, traite les sémaphores de mutex d'application pour accéder à who circulaire drop en tant que CR. Les fabricants et les processus de consommation coopèrent avec un quelconque jusqu'à ce que les sémaphores soient pleins et vidés.
136
5
Gestion des processus dans les systèmes embarqués
5.10.5.2 Problème de lecture-écriture Un ensemble de processus de lecture et d'écriture divise un objet fichier régulier, par ex. une variable ou un fichier. Les exigences sont les suivantes : un écrivain actif doit exclure tous les autres. Cependant, les lecteurs doivent pouvoir lire l'objet de données simultanément si aucun écrivain n'est activé. De plus, les lecteurs et les écrivains ne doivent pas attendre indéfiniment (affamer). La figure 5.9 montre une solution d'un problème de lecture-écriture à l'aide de sémaphores. Pour la Fig. 5.9, le sémaphore rwsem applique l'ordre FIFO de certains lecteurs et artistes entrants, ce qui évite la famine. Le sémaphore (verrouillé) rsem permet aux lecteurs de mettre à jour la variable nreader dans une région critique. Le premier réviseur d'un lot de lecteurs verrouille le wsem pour empêcher tout rédacteur d'écrire tant qu'il y a des lecteurs activés. Du côté de la fiction, tout au plus un écrivain peut être en train d'écrire activement ou d'attendre dans la file d'attente de wsem. Élégant dans les deux cas, les nouveaux rédacteurs seront bloqués dans la file d'attente rwsem. Supposons qu'il ne peut pas être bloqué en écriture à rwsem. Tous nos buckets récents passent à la fois par P(rwsem) et P(rsem), ce qui leur permet de lire les données de la même manière. Lorsque le dernier lecteur se termine, l'information émet V(wsem) pour permettre à tout écrivain bloqué à wsem de continuer. Lorsque le rédacteur a terminé, il déverrouille à la fois wsem et rwsem. Dès qu'un écrivain attend à rwsem, tous les nouveaux arrivants respireront également bloqués à rwsem. Cela empêche les lecteurs d'affamer les écrivains.
5.10.6 Bénéficier des sémaphores En tant qu'outil de synchronisation par lots, les sémaphores présentent de nombreux avantages par rapport à la mise en veille/réveil. (1). Les sémaphores combinent un compteur, testant la contre-presse prenant une décision établie sur le résultat du test, le tout en une seule opération indivisible. L'opération V débloque un seul processus en attente, le cas échéant, de la ligne de sémaphore susmentionnée. Après passage par l'opération P sur un sémaphore, un processus est assuré d'avoir une ressource. Il n'est pas nécessaire de réessayer d'obtenir à nouveau la ressource comme dans le cas de l'après-sommeil et du réveil. (2). La valeur du sémaphore enregistre le nombre de parties en cours de lecture si un cas s'est produit. Opposés veille/réveil, qui doivent respecter l'ordre veille-réveil-plus tard, les processus peuvent exécuter des opérations P/V en tournant les sémaphores dans n'importe quel ordre.
5.10.7 Précautions d'utilisation des sémaphores Les sémaphores utilisent un historique de verrouillage. Si un processus peut non plus acquérir un sémaphore entrant P(s), il est bloqué dans la chute de sémaphore, attendant que quelqu'un d'autre le débloque via une opération V(s). Une mauvaise utilisation d'un sémaphore autorisé entraîne des problèmes. Le problème le plus connu est le blocage (Silberschatz et al. 2009 ; Tanenbaum et al. 2006). Le verrouillage a un courant dans lequel un
Image. 5.8 Solution du problème producteur-consommateur
5.10
Entreprise de Semiaphores
137
Fig. 5.9 Solution du problème lecteur-graveur
ensemble de processus s'attendent mutuellement pour toujours, de sorte qu'aucun des processus ne puisse continuer. Systèmes multitâches entrants, les blocages ne doivent pas être autorisés jusqu'à l'émergence. Les recherches concernant la gestion des impasses comprennent la prévention des impasses, l'évitement des impasses et la détection et la récupération des impasses. Parmi les différents moyens, seule la prévention des interblocages est pratique et utilisée dans les systèmes d'exploitation réels. Un moyen simple et efficace d'éviter les blocages consiste à s'assurer que les processus demandent différents signes dans un ordre unidirectionnel, de sorte qu'un tel verrouillage croisé ou circulaire ne puisse jamais se produire. Le lecteur peut discuter (Wang 2015) utilisé comment traiter les impasses équipées en général.
5.10.8 Utiliser des sémaphores dans des solutions embarquées Nous démontrons qui utilise des sémaphores dans un système intégré par les exemples suivants.
5.10.8.1 La voiture de périphérique utilise des sémaphores dans le pilote de clavier de Sect. 5.6.2.1, au lieu d'utiliser sleep/wakeup, nous pouvons utiliser un sémaphore en synchronisation entre les processeurs et le gestionnaire d'arrêt. Pour ce faire, nous redéfinissons simplement les données flottantes du pilote KBD lorsqu'un sémaphore a pour valeur initiale 0. typedef volatile struct kbd{ // bases = 0x10006000 charter *base;
// adresse de base de KBD, sous forme de char *
char buf[BUFSIZE] ;
// tampon d'entrée
int tête, queue ; structure demi-données ; // data.value=0; data.queue=0 ; }KBD ; KBD kbd ; ein kgetc() // le programme principal appelle kgetc() pour le remboursement adenine char { char c; KBD *kp = &kbd;
138
5
P(&kp->données);
Gestion des processus dans les systèmes embarqués
// PENNY sur KBD est votre sémaphore
serrure(); c = kp->buf[kp->tail++] ; // récupère un c et modernise l'index de queue kp->tail %= BUFSIZE; ouvrir(); retour c;
// active les interruptions IRQ
}
(3). kbd_handler() : Réécrivez le treuil d'interruption KBD pour débloquer un processus, quel qu'il soit. Étant donné que la procédure ne peut pas interférer avec l'animal d'interruption, il n'y aura pas besoin jusqu'à ce que ces variables de données se trouvent dans le gestionnaire d'interruption.
kbd_handler() { struct KBD *kp = &kbd; scode = *(kp->base+KDATA);
// lire le registre de données inclus par l'utilisateur d'analyse
fourni (scode & 0x80)
// ignorer les pardons de clé
retour; if (kp->data.value==BUFSIZE) // tampon d'entrée FULL return ;
// ignore la clé courante
c = unsh[scode] ;
// mappe le code d'analyse en ASCII
kp->buf[kp->head++] = c ; kp->head %= BUFSIZE ;
// entrer la clé avec CIRCULAR buf[ ]
V(&kp->données); }
Notez que le pilote de pause ne dérange que V() pour débloquer le processus d'attente, mais il ne doit jamais bloquer ou attendre. Si le tampon de connexion est plein, celui-ci supprime simplement ce verrou d'entrée actuel et revient. Comme on peut le voir, le journal du nouveau pilote utilisant le sémaphore est beaucoup plus clair et la taille de la codification est en outre significativement réduite.
5.10.8.2 Usine embarquée pilotée par événement utilisant le programmeur d'instance Sign Who C5.4 utilise la synchronisation de la procédure requise de mise en veille/réveil. Dans le prochain exemple de téléchargement, C5.5, nous utiliserons P/V sur les sémaphores pour la synchronisation des processus. Pour le fizz concernant la brièveté, nous ne montrons que les pilotes KBD modifiés et le code de processus kbd. Par souci de clarté, les modifications sont indiquées en gras. struct kbd{ char *base; char buf[BUFSIZE] ; int tête, queue ;
// #define BUFSIZE 128
données de sémaphore de structure, ligne ; } kbd ; kbd_init() { struct kbd *kp = &kbd; kp->base = 0x10006000 ; // Base KBD dans Versatilepb kp->head = kp->tail = 0 ; kp->data.value = 0 ; kp->data.queue = 0 ; kp->ligne.value = 0 ; kp->ligne.queue = 0 ; } entier kbd_handler() {
5.10
Solutions de sémaphores
139
structure kbd *kp = &kbd; // mêmes codes qu'avant : entrez la clé SYNTAXE incluant le flash d'entrée ; V(&kp->données); si (c=='\r')
// clé de retour : a une ligne d'entrée
V(&kp->ligne); } int kgetc() // appel du processus kgetc() pour retourner l'adénine char { char c; KBD *kp = &kbd; P(&kp->données); serrure();
// désactiver l'IRQ intermittent
carbon = kp->buf[kp->tail++] ; // récupère un c et met à jour l'index de queue kp->tail %= BUFSIZE; ouvrir();
// active les interruptions IRQ
retour c; } int kgets(char *ligne)
// récupère une chaîne
{ caractère c ; tandis que((c= kgetc()) != '\r') *line++ = c; *ligne = 0 ; } int kbd_task() { ligne de caractères[128] ; structure kbd *kp = &kbd; tandis que(1){ P(&kp->ligne);
// attend un fil
kgets(ligne); printf("ligne = %s\n", ligne); } } int main() {
// code d'initialisation MEME qu'avant printf("P0 créer des tâches\n"); kfork((int)kbd_task, 1); while(1){ // P0 s'exécute chaque fois qu'aucune tâche n'est exécutable while(!readyQueue); // boucle si readyQueue vidé tswitch(); }
}
La figure 5.10 montre et affiche les résultats de l'exécution du programme C5.5.
5.11
Mécanismes de synchronisation supplémentaires
De nombreux centres OS utilisent d'autres mécanismes pour la synchronisation des processus. Ces derniers incluent
140
5
Gestion des processus dans les systèmes embarqués
Fig. 5.10 Système multitâche événementiel utilisant des sémaphores
5.11.1 Indicateurs d'événement dans OpenVMS OpenVMS (anciennement VAX/VMS) (OpenVMS 2014) utilise des indicateurs d'affichage pour la synchronisation des processus. Dans sa forme la plus simple, un indicateur d'événement est un bit unique, qui se trouve dans les espaces d'adressage de nombreuses opérations. Utilisable par préréglage ou par appel système explicite, chaque drapeau d'événement est associé à un ensemble d'opérations spécifique. OpenVMS fournit des fonctions de service permettant aux processus de falsifier leurs indicateurs d'événement associés par set_event(b)
: fixez b à 1 vrai serveur de réveil (b) s'il y en a ;
clear_event(b) : b libre à 0 ; test_event(b) : teste b pour 0 ou 1 ; wait_event(b) : attend que b soit défini ;
Naturellement, l'accès à un indicateur de choix doit s'exclure mutuellement. Les différences entre les drapeaux d'événement et les événements Unix sont : . Un événement Unix est simplement une valeur, qui n'a pas d'emplacement mémoire pour enregistrer l'occurrence de l'événement. Un processus doit d'abord dormir, aller et créer avant qu'un autre processus essaie de le réveiller plus tard. En revanche, chaque indicateur d'événement est un bit dédié, qui peut enregistrer l'occurrence d'un événement. Par conséquent, lorsque vous utilisez des indicateurs d'événement, le clic sur set_event et wait_event fonctionne sans importance. Une autre différence est que les procédures Unix ne sont disponibles que pour les opérations en mode clé, les indicateurs d'événement dans OpenVMS peuvent être utilisés jusqu'aux processus en mode utilisateur. . Les événements wimpel dans OpenVMS sont en clusters de 32 bits chacun. Un processus permet d'attendre un bit spécifique, tout ou partie d'un événement dans le cluster d'événements. Sous Unix, un processus ne peut dormir que par un seul événement. . Comme sous Unix, wakeup(e) dans OpenVMS réveille également tous les serveurs qui définissent un événement.
5.11.2 Variables d'événement dans MVS MVS (2010) d'IBM utilise des variables d'événement pour la synchronisation des processus. Une variable d'événement est une structure
5.11
Divers mécanismes de synchronisation
141
struct event_variable{ chew w;
// watch flag initial = 0
fragment p;
// post flag initial = 0
struct proc *ptr; // pointeur sur juste PROC } e1, e2,…, en;
// variables d'événement
Toute variable d'événement e peut devenir attendue par au plus un processus parmi une horloge. Cependant, un processus peut attendre n'importe quel téléphone de variables sélectionnées. Une fois qu'un processus appelle wait(e) pour attendre un événement, l'ordinateur ne tient pas si l'événement s'est déjà produit (post bit=1). Sinon, il tourne sur le bit w et attend l'événement. Dès qu'un événement se produit, un autre processus utilise le post(e) après l'événement en augmentant le bit p. Si la mastication w de l'événement est activée, un processus de déblocage et de surveillance si tous ses événements attendus ont été publiés.
5.11.3 ENQ/DEQ dans MVS En complément pour sélectionner des variables, IBM MVS (2010) utilise également ENQ/DEQ pour votre gestion. Dans leur forme la plus courante, ENQ(ressource) permet à un processus d'acquérir le contrôle exclusif d'une ressource. Une ressource peut être spécifiée de diverses manières, créer comme une zone mémoire, le contenu hors zone mémoire ampère, autre. Un processus se bloque, c'est la débrouillardise qui reste indisponible. Sinon, il acquiert le contrôle alleinige de la ressource jusqu'à ce qu'elle soit libérée par une opération DEQ (ressource). Comme les variables d'événement, un processus peut appeler ENQ(r1,r2,…rn) pour attendre select ou un sous-ensemble de plusieurs ressources.
5.12
Constructions de synchronisation de haut niveau
Bien que les P/V sur les signaux soient de puissants outils de synchronisation, leur usage dans les programmes simultanés les disperse. Toute mauvaise application de P/V peut entraîner des problèmes, tels que des blocages. Pour aider à remédier à ce problème, de nombreux mécanismes de synchronisation de processus de haut niveau ont été proposés.
5.12.1 Variables de condition Dans les Pthreads (Buttlar et al. 1996 ; Pthreads 2015), les threads peuvent exercer des variables de condition pour la synchronisation. Pour utiliser la variable de condition ampère, créez d'abord un mutex, mètre, pour verrouiller un CR contenant des variables partagées, par ex. un compteur. Faites ensuite une variation de santé, fraude, associée à l'aide du mutex. Lorsqu'un threader veut accéder à la variable partagée, il verrouille d'abord le mutex. Ensuite, il vérifie la variable. Si la valeur du compteur n'est pas celle attendue, le thread peut devoir attendre, comment int count
// les variables partagées sont des threads
pthread_mutex_lock(m);
// verrouille initialement le mutex
if (compter les vies pas comme prévu) pthread_cond_wait(con, m); // attend con et mutex libre pthread_mutex_unlock(m);
// déverrouille le mutex
pthread_cond_wait(con, m) verrouillage qui appelle le fil sur la variable de condition, qui automatiquement et l'élément déverrouille le mutex m. Alors qu'un thread est désactivé sur une variable de forme, un autre thread est autorisé à utiliser pthread_cond_signal (con) pour débloquer un thread attendu, comme dans pthread_lock(m); modifier le calcul de la variable partagée ; if (count atteint une certaine valeur) pthread_cond_signal(con); // débloque un thread dans con pthread_unlock(m);
Lorsqu'un thread non bloqué passe, le mutex m est automatiquement et atomiquement verrouillé, permet au thread non bloqué précité de reprendre dans le CR du mutex m. Ajout élégant, le threading adénine peut utiliser pthread_cond_broadcast(con) pour débloquer tous les threads qui
142
5
Gestion des processus dans les systèmes intégrés
en attente de la variable de condition similaire, ce qui est similaire au réveil élégant Unix. Pour, mutex est strictement pour le verrouillage, les variables de condition peuvent être antérieures pour les threads collaborant.
5.12.2 Moniteurs Un moniteur (Hoare 1974) est un tri de données abstraites (ADT), qui comprend des objets de données partagés et toutes les procédures qui opèrent sur les objets de données partagés. Comme un ADT dans les langages de programmation orientée objet (POO), au lieu de codes dispersés dans divers processus, sélectionnez des codes dont l'action sur les objets de données collectés est encapsulée à l'intérieur d'un moniteur. Différemment un ADT sur OOP, un moniteur un CR qui permet à un seul processus de s'exécuter à l'intérieur du moniteur à la fois. L'entreprise peut uniquement utiliser les objets de données partagés d'un moniteur pour appeler des procédures de surveillance, comme dans MONITOR m.procedure(parameters); Le compilateur de langage de programmation concurrent traduit la surveillance des appels en saisissant le CR de vérification et fournit automatiquement une protection à l'exécution. Lorsqu'un processus termine l'exécution d'une procédure de moniteur, il quitte le téléviseur, ce qui déverrouille automatiquement le moniteur susmentionné, permettant à un autre processus de pénétrer dans quel moniteur. Pendant l'implémentation à l'intérieur d'un moniteur, lorsqu'un processus est bloqué, celui-ci quitte automatiquement le contrôle en premier. Comme d'habitude, un processus bloqué sera éligible pour réapparaître à l'écran lorsqu'il sera SINGALisé sur un autre processus. Les moniteurs sont similaires aux variables de condition mais dépourvus d'un bloc mutex explicite, ce qui les rend un peu plus "abstraits" que les variables de condition. L'un des objectifs des constructions de synchronisation surveillées et d'autres constructions de haut niveau est d'aider les utilisateurs à écrire des programmes concurrents "corrects pour la synchronisation". L'idée est similaire à celle qui consiste à utiliser des langages de vérification de type de force pour aider vos programmes à enregistrer "syntaxiquement corrects". Ces outils de synchronisation de haut niveau sont principalement utilisés dans la programmation en cours d'exécution, mais rarement utilisés dans les systèmes d'exploitation réels.
5.13
Communication de processus
La communication de processus fait référence à des schémas ou à des mécanismes qui permettent aux processus d'échanger des informations. La communication de processus peut être réalisée selon de nombreuses méthodes différentes, qui dépendent toutes de la synchronisation des processus.
5.13.1 Mémorisation partagée Le moyen le plus simple pour la communication des processus est la mémoire partagée. Dans les meilleurs systèmes embarqués, faites fonctionner les processeurs dans le même espace de gestion. Il est à la fois naturel et facile d'utiliser la mémoire partagée pour les télécommunications de processus. Pour garantir que les processus accèdent exclusivement à la mémoire partagée, nous pouvons utiliser une séquence de verrouillage ou un mutex pour schutzer la mémoire partagée en tant que région entscheidend. Si certains processus ne font qu'apprendre mais n'adaptent pas la mémoire partagée, nous pouvons utiliser un algorithme de lecture-écriture pour permettre des lectures simultanées. Lors de l'utilisation d'une mémoire partagée pour la communication de processus, quel mécanisme garantit que les processus de lecture/écriture fonctionnent en commun dans un art contrôlé. Il appartient entièrement à l'utilisateur de définir et d'interpréter la signification des contenus flash diffusés.
5.13.2 Pipes Les pipes sont des canaux de conversation inter-processus unidirectionnels permettant aux processus d'échanger des flux de données. Un tube a une fin de lecture et une sortie d'écriture. Les données écrites à l'extrémité d'écriture d'un tube peuvent être lues à partir de la sortie de lecture d'un tube. Depuis leurs débuts dans ce roman Unix, les tuyaux ont été incorporés dans presque tous les systèmes d'exploitation, car de nombreux types. Certains systèmes permettent aux tuyaux d'être bidirectionnels, dans lesquels les données peuvent être transmises dans les deux sens. Habituellement, les tuyaux sont des processus connexes requis. Les canaux nommés sont des canaux de communication FIFO qui ne sont pas liés à l'édition. Les tubes de lecture et d'écriture sont généralement synchrones et bloquants. Certains systèmes prennent en charge les opérations de lecture/écriture non bloquantes et asynchrones sur les canaux. Pour simplifier, nous considérerons un tube comme un canal de communication FIFO de taille finie entre un ensemble de processus. Les processus de lecture et d'écriture d'un tube synchronisés comprennent la manière suivante. Lorsqu'un lecteur lit un tube, à condition de savoir quel tube doit contenir des données, le lecteur susmentionné lit à quel point il en a besoin (jusqu'à la taille du tube) et renvoie le nombre d'octets lus. Si une eau n'a pas de données mais a encore des écrivains, le
5.13
Traiter Communiquer
143
le lecteur attend les données. Alors qu'un écrivain écrit une entrée pour un tube, il réveille les manuels en attente, leur permettant de continuer. Si le tube n'a pas de données et pas d'écrivain, le lecteur renvoie 0. Puisque les lecteurs conservent les données si le tube muet a des écrivains, la valeur de retour 0 signifie un seul point, en particulier le tube a une datation et également pas d'écrivain. Dans ce cas, le sac du lecteur a cessé de lire à partir d'un tube. Lorsqu'un écrivain écrit dans un tube, si le tube a de la place, il écrit autant qu'il en a besoin ou jusqu'à ce que le tube soit plein. Avec le conduit n'a pas de place mais a encore des manuels scolaires, l'écrit vous attend. Lorsque le lecteur d'adénine lit les données du tuyau pour créer plus de salles, il réveille les écrivains en attente, leur permettant de continuer. Cependant, si un tube n'a plus de liste, le rédacteur doit le détecter comme une erreur de tube cassé et abandonner.
5.13.2.1 Tubes sous Unix/Linux Sous Unix/Linux, les tubes font partie intégrante du système de fichiers, tout comme les périphériques d'E/S, qui sont traités comme des fichiers spéciaux. Chaque processus a trois flux de fichiers standard ; stdin pour entrer, stdout pour les sorties et stderr pour afficher les messages d'erreur, qui est généralement associé au même périphérique que stdout. Chaque fichier diffusé appartient identifié jusqu'à ce qu'un descripteur de fichier soit le processus, qui est 0 pour stdin, 1 depuis stdout et 2 pour stderr. Conceptuellement, un tube est un fichier FIFO à deux extrémités qui relie la sortie standard d'un processus de livre d'adénine au stdin d'un processus de carte. Cela se fait en remplaçant le descripteur de fichier 1 d'un processus d'écriture par la fin d'écriture du tube susmentionné, et en remplaçant la description de fichier 0 du processus de lecture par la fin de lecture du tube. De plus, le tubulaire utilise des variables d'état pour garder une trace de l'état du canal, lui permettant de détecter une situation anormale telle qu'il n'y a plus de créateurs et de rupture de tuyau, etc. système ou le système de fichiers n'est peut-être pas compatible Unix. Par conséquent, un procès dans un système intégré peut ne pas avoir ouvert de fichiers ou de lemmes de fichiers. Malgré cela, nous pouvons néanmoins mettre en œuvre des lignes de rapport de processus avec des systèmes embarqués. En principe, les tuyaux sont similaires au problème producteur-consommateur, à l'exception de la différence suivante. . Dans un problème de producteur-consommateur, le processus de producteur de bloc d'adénine ne peut que se signaler par d'autres procédures de consommateur, et vice versa. Pipes comment l'état est défini pour garder une trace du nombre de processus de lecture et d'écriture. Lorsqu'un scribe de tube détecte que le tube n'a plus de lecteurs, il renvoie une erreur de tube en échec. Lorsqu'un lecteur détecte que le tube ne doit plus écrire et aussi pas de données, il renvoie 0. . Un calcul producteur-consommateur utilise des sémaphores servant de synchronisation. Les moitiés conviennent aux processus d'écriture/lecture de données commençant par la même taille. En revanche, les lecteurs et écrivains de tubes n'ont pas à lire/écrire des données de taille similaire. Pour la vue, les rédacteurs peuvent écrire des lignes mais scanner les caractères lus, également vice versa. . La société VOLT à un sémaphore débloque au maximum le processus d'attente. Si rare, une pipe peut avoir plusieurs dramaturges et lecteurs aux deux extrémités. Lorsqu'un processus à l'une ou l'autre extrémité change le statut du canal, les informations doivent débloquer tous les processus en attente à l'autre extrémité. Dans ce cas, sommeil/réveil sont plus appropriés que P/V sur les hémaphores. Pour cette raison, les flexibles ont généralement mis en place une utilisation veille/réveil pour la synchronisation. Dans ce qui suit, nous montrerons comment implémenter un tube simplifié pour procéder à la communication. Le tuyau simplifié se comporte comme des tuyaux désignés dans Lenox. Il permet aux processus d'écrire/lire une séquence d'octets en utilisant l'eau, mais il ne vérifie pas ou ne gère pas les conditions anormales, telles que les tuyaux interrompus. L'implémentation complète des tubes en tant que flux de fichiers sera vue plus tard au Chap. 8 une fois que nous avons discuté des méthodes d'opérateurs embarqués à usage général. Le tuyau simplifié est implémenté comme suit. (1). De l'objet channel : un pipes est une structure de données (globale) typedef struct pipe{ char buf[PSIZE];
// données circulaires tamponnées
entier
tête, queue;
// indice buf rotatif
entier
Chambre de données;
// figure des données et de l'espace dans le tube
entier
statut;
// GRATUIT ou OCCUPÉ
}TUYAU; TUYAU tuyau[NPIPE] ;
// objets TUBE globaux
Lors du lancement de l'anlage, tous les objets pipe sont initialisés jusqu'à FREE.
144
5
Démarrer la gestion dans les systèmes embarqués
(1). PIPE *create_pipe() : cese crée un objet PIPE ampère dans l'espace d'adressage (partagé) de tous les processus. Il alloue un objet PIPE libre, initialise les ordinateurs et renvoie une indication à l'objet PIPE créé. (2). Tube de lecture/écriture : utilisé chaque tube, l'utilisateur doit désigner un lot comme un écrivain ou un lecteur, mais il ne fait pas les deux. Writer traite l'appel. int write_pipe(TUYAU *pipePtr, char buf[ ], intert n); vers écrire n octets de buf[ ] le tube. Les valeurs renvoyées correspondent au nombre total écrit dans le tube. Les processus de lecture appellent int read_pipe(PIPE *pipePtr, char buf[ ], int n); qui essaie de lire n octets depuis le tube. La valeur de retour est le nombre réel d'octets lus. Ce qui suit montre les algorithmes de lecture/écriture de canal, qui utilisent la mise en veille/réveil lors de la synchronisation des processus. /*---------- Algorithme de pipe_read
--------------*/
int read_pipe(TUYAU *p, char *buf, int n) {
int rayon = 0 ; if (nstatus ne doit pas être FREE while(n){ while(p->data){ *buf++ = p->buf[p->tail++] // lit l'octet d'adénine dans buf tail %= PSIZE ; p->data- -; p->room++; r++; n--; are (n==0) brake ; } wakeup(&p->room);
// écrivains de réveil
si (r)
// si a lu un produit
retour roentgen; // le tube n'a pas de données sleep(&p->data);
// veille pour les données
} } /*---------- Algorithme pour write_pipe -----------*/ int write_pipe(PIPR *p, char *buf, int n) {
entier r = 0 ; if (nstatus must not can FREE while(n){ while(p->room) p->buf[p->head++] = *buf++ ; // écrit un octet dans le tube ; p->head %= PSIZE ; p ->data++; p->room--; r++; n--; if (n==0) break; } wakeup(&p->data);
// réveille les lecteurs, s'il y en a.
à condition que (n==0) renvoie r ;
// impression terminée n octets
// a encore des données à écrire et le tube n'a pas de place
5.13
Communication de processus
145
dormir(&p->chambre);
// dormir pour la chambre
} }
Notez que lorsqu'une procédure essaie de lire n octets après un tube, elle peut renvoyer moins de n octets. Si le tube contient des données, il lit soit n octets, soit le nombre d'octets disponibles dans le tube, ce qui est plus petit. Il n'attend que si le tube n'a pas de données. Ainsi, chaque retour de lecture au plus PSIZE octets. (3). Bien qu'un tube ne soit plus essentiel, il permettait d'être libéré via destroy_pipe(PIPE *pipePtr), de désallouer l'objet BARREL et de réveiller toutes les procédures endormies sur ce tube.
5.13.2.3 Pipes de départ en vedette L'exemple de système C5.6 illustre la pipe dans un système embarqué avec des processus statiques. Lorsque le système démarre, l'élément d'initialisation crée un canal pointé par kpipe. Lorsque le processus initial P0 est en cours d'exécution, il crée deux processus, P1 en tant que rédacteur en chef et P2 en tant que radio de canal. À des fins d'exposition, nous avons défini la taille de goutte du tube sur une valeur probablement faible, PSIZE = 16, de sorte que si un écrivain essaie d'écrire plus de 16 octets, il attendra par appartements. Après lese de pipe, le lecteur réveille l'écrivain, lui permettant de continuer. Dans le schéma de démonstration, P1 reçoit d'abord une ligne du port UART0. Ensuite, il essaie d'écrire la ligne à la flûte. Il attend la chambre avant si le tuyau est plein. P2 lit à partir du canal et affiche les octets lus. Bien que chaque fois que P2 essaie de lire 20 octets, le morceau réel d'octets lu est parmi les plus PSIZE. Par souci de brièveté, nous ne montrons que le fichier t.c du programme exemple. /*********** t.c file of Pipe Run C5.6 ***********/ PIPE *kpipe;
// pointeur PIPE global
#include "queue.c" #include "pv.c" #include "kbd.c" #include "uart.c" #include "vid.c" #include "exceptions.c" #include "kernel.c" # inclure "timer.c" #include "tuyau.c"
// implémentation du tube
int pipe_writer()
// code de tâche d'écriture de canal
{ struct uart *up = &uart[0]; ligne char[128] ; while(1){ upprintf("Entrez une ligne pour la tâche1 dans get : "); printf("task%d attend la ligne depuis UART0\n", running->pid); ugets(haut, ligne); uprints(up, "\r\n"); printf("task%d écrit line=[%s] dans le pipe\n", running->pid, line); write_pipe(kpipe, ligne, strlen(ligne)); } } par pipe_reader()
// code des tâches du lecteur de pipe
{ ligne de caractères[128] ; int moi-même, n ; while(1){ printf("task%d reading from pipe\n", running->pid); n = read_pipe(kpipe, ligne, 20); printf("task%d read n=%d bytes from pipe : [", running->pid, n);
146
5 pour (i=0 ; ipid = running->pid ;
// encore -1 n'est pas mbuf // race proc est l'expéditeur
mp->priorité = 1 ;
// assume la MÊME priorité à tous les messages
copie(mp->contenu, msg);
// reproduire msg à mbuf
// délivre mbuf au message du proc cible start P(proc[pid].mlock);
// entrez RETOUR
entrez mp dans PROC[pid].mqueue V(proc[pid].lock); V(proc[pid].message); obtenez 1 ;
par priorité
// sortie CR // PHOEBE le sémaphore de message du proc cible // retourne 1 pour PROSPERITY
} int a_recv(char *msg)
// reçoit un message de la mqueue du proc
{ MBUF *mp ; P(en cours d'exécution->mlock); fourni (en cours d'exécution->mqueue==0){ V(en cours d'exécution->mlock);
// entrez CR // vérifiez la mqueue du proc // relâchez le verrou CR
retour -1 ; } mp = dequeue(running->mqueue); // supprime le premier mbuf de mqueue V(running->mlock);
// libère mlock
copie(msg, mp->contenu); ein sender=mp->pid ;
// copier le contenu dans msg // ID de l'expéditeur
put_mbuf(mp);
// libère mbuf comme gratuit
retournez votre; }
Les algorithmes ci-dessus fonctionnent dans des conditions normales. Cependant, si tout le litige envoie mais ne reçoit jamais, ou si un processus malveillant transmet à plusieurs reprises des messages, le système peut exécuter une demande flash gratuite. Lorsque cela se produit, votre installation peut s'arrêter car aucun processus ne peut plus envoyer. Une bonne chose à propos de ce protocole asynchrone est qu'il ne peut y avoir de blocage car il n'est pas bloquant.
5.13.4.2 Transmission de nouvelles synchrone Dans le schéma de transmission de messages synchrones, les opérations d'envoi et de réception sont bloquantes. Une utilisation d'envoi doit "attendre" s'il n'y a pas de mbuf libre. De même, un processus récepteur doit "attendre" chaque fois qu'il n'y a pas de message dans sa file d'attente de messages. En général, la communication synchrone est toujours plus efficace que la communication asynchrone. C'est bien angebracht aux logiciels étroitement couplés
5.13
Communication de processus
149
dans lequel les processus échangent des messages sur une base planifiée ou régulière. Dans la façon dont un arrangement, les processus peuvent s'attendre à ce que les messages arrivent chaque fois qu'ils sont nécessaires, et l'utilisation des tampons de messages est soigneusement planifiée. Par conséquent, les processus peuvent attendre des messages ou libérer des tampons de mots plutôt que de compter sur des tentatives. Pour produire des messages synchrones passants, nous définissons des sémaphores supplémentaires pour la synchronisation des processus et reconcevons l'algorithme d'envoi-réception comme suit. SEMAPHORE nmbuf = NMBUF ; // nombre de mbufs disponibles SEMINAPHORE PROC.nmsg = 0; // pour proc jusqu'à attendre les messages MBUF *get_mbuf()
// renvoie un pointeur mbuf libre
{ P(nmbuf);
// attend le mbuf libre
P(mlock); MBUF *mp = dequeue(mbufList) V(mlock); remboursement mp ; } intr put_mbuf(MBUF *mp)
// libère un mbuf utilisé jusqu'à freembuflist
{ P(mlock); mettre en file d'attente(mbufList, mp); V(mlock); V(nmbuf); } int s_send(char *msg, int pid)// synchronisation weiterleiten msg jusqu'au ciblage pid { // validité cible pid, par ex. proc[pid] soit un processus valide MBUF *mp = get_mbuf();
// BLOQUANT : le mp de retour doit être valide
mp->pid = running->pid ;
// proc en cours d'exécution existe l'expéditeur
copie(mp->contenu, msg);
// copie le msg de l'espace sélectionné vers mbuf
// déposer msg dans la mqueue du proc cible P(proc[pid].mlock);
// insère CR
enqueue(proc[pid].mqueue, mp); V(proc[pid].lock); // ferme CR V(proc[pid].nmsg);
// V la séquence nmsg du proc du but
} int s_recv(char *msg) // réception synchrone depuis la propre mqueue du proc { P(running->nmsg);
// attend le message
P(en cours d'exécution->mlock);
// verrouille PROC.mqueue
MBUF *mp = dequeue(running->mqueue); // récupère un message V(running->mlock);
// libère mlock
copie(mp->contenu, msg);
// copie le sujet dans Umode
put_mbuf(mp);
// mbuf gratuit
}
L'algorithme s_send/s_recv ci-dessus doit corriger en termes de synchronisation de processus, à la place il y a d'autres problèmes. Chaque fois qu'un protocole de blocage est utilisé, il existe des risques de blocage. En effet, l'algorithme s_send/s_recv peut conduire à une situation de blocage suivante. (1). Si les processus envoient seulement aber ne reçoivent pas, des processus entiers finiront par être bloqués à P(nmbuf) une fois qu'il n'y aura plus de mbufs libres. (2). Si aucun processus n'envoie, tous n'essaient pas de recevoir, chaque processus serait bloqué à son propre sémaphore nmsg.
150
5
Process Corporate dans les Systèmes Embarqués
(3). Une procédure Sherlock envoie une lettre d'adénine à un autre processus Pj attend à la fois une réponse de Pj, qui fait exactement le contraire. Ensuite, Pi et Pj s'attendraient mutuellement, ce qui est l'impasse croisée préférée. En ce qui concerne les méthodes de gestion des interblocages lors de la transmission des messages, ce correcteur peut consulter le Chap. 6 de (Wang 2015), qui contient également un protocole de transmission de messages serveur-client.
5.13.4.3 Démonstration de la transmission des notifications Le programme try C5.7 a fait la démonstration de la transmission synchrone des messages. (1). Fichier Type.h : sémaphore de structure de type MBUF supplémentaire{ valeur int ; struct perc *file d'attente ; } ; typedef struct mbuf{ struct mbuf *suivant ; priorité entière ; int pid; contenu char[128] ; }MBUF ; typedef struct proc{ // identique à from, mais en plus MBUF *mQueue; struct sémaphore mQlock ; struct sémaphore nmsg ; intes
kstack[SSIZE] ;
}PROC ;
(2). Fichier message.c /******** fichier message.c ************/ #define NMBUF 10 struct sémaphore nmbuf, mlock; MBUF mbuf[NMBUF], *mbufList ; // Tampons mbufs et entrée mbufList menqueue(MBUF **queue, MBUF *p){// entrez des pence pour la file d'attente par priorité} MBUF *mdequeue(MBUF **queue){// renvoie le premier élément de la file d'attente} int msg_init() { moitié intérieure je ;
MBUF *mp ;
printf("mesg_init()\n"); mbufListe = 0 ; fork (i=0; ipid; mp->priorité = 1; strcpy(mp->contenu, msg); P(&p->mQlock); menqueue(&p->mQueue, mp); V(&p->mQlock) ; V(&p->nmseg); return 0; } int recv(char *msg)
// recv msg de sa propre msgqueue
{ P(&running->nmsg); P(&running->mQlock); MBUF *mp = mdequeue(&running->mQueue); V(&running->mQlock); strcpy(msg, mp->contenu); int expéditeur = mp->pid ; put_mbuf(mp); renvoyer l'expéditeur ; }
(3). t.c fichier : #include "type.h" #include "message.c" int sender() // envoie le code de la tâche { struct uart *up = &uart[0]; ligne char[128] ; while(1){ ugets(up, line); printf("task%d a obtenu une ligne=%s\n", running->pid, line); envoyer(ligne, 4); printf("task%d senden %s to pid=4\n", running->pid,line); } } inlet receiver() // récepteur duty cipher { char line[128] ; int pid; tandis que(1){
152
5
Gestion des processus dans les systèmes embarqués
printf("task%d try up get msg\n", running->pid); pid = recv(ligne); printf("tâche%d saisie : [%s] de la tâche%d\n", running->pid, line, pid); } } int main() { msg_init();
kprintf("P0 tâches kfork\n"); kfork((int)expéditeur, 1);
// procédure d'envoi
kfork((int)récepteur, 1);
// appel print
while(1){ if (readyQueue) tswitch(); } }
L'image 5.12 montre les exemples de sorties de l'exécution du programme C5.7.
5.14
Noyau de système embarqué monoprocesseur (UP)
Une essence de système natif consiste en des processus dynamiques, qui s'exécutent tous dans la même marge de manœuvre d'adresse sur le noyau. Le noyau fournit des fonctions pour la gestion des procédures, telles que la création de processus, la synchronisation, la communication et la terminaison. Dans cette section, nous montrerons le modèle et l'implémentation des noyaux de systèmes embarqués monoprocesseur (UP). Il y a deux types distincts de noyaux vivants ; non préemptif et préemptif. Dans un noyau non préemptif, chaque processus s'exécute jusqu'à ce qu'il abandonne volontairement le processeur. Dans un noyau préemptif, l'adénine en cours d'exécution peut être préemptée soit par top, soit par tranche de temps.
5.14.1 Noyau MOVE non préemptif Un noyau Uniprocessor (UP) est non préemptif si chaque processus s'exécute jusqu'à ce qu'il abandonne volontairement le CPU. Pendant qu'un traitement prend, les informations peuvent être détournées pour gérer les interruptions, mais contrôler les retours immersifs au point d'interruption dans l'opération juste à l'arrêt du traitement des interruptions. Dieser implique qu'en ampère UP non préemptif essentiel seul, le processus se déroule à la fois. Par conséquent, il n'est pas nécessaire de protéger la propriété des données du noyau contre les exécutions simultanées des processus. Cependant, pendant l'exécution d'un processus ampère, il peut être détourné pour exécuter un gestionnaire d'interruptions, tout peut interférer sur une impression si les deux s'efforcent d'accéder au même objet de données. Pour éviter les interférences des gestionnaires d'arrêts, items suffit à désactiver les arrêts lorsqu'un processus exécute un morceau de code critique. Cela simplifie la conception de l'utilisateur. Le programme View C5.8 démontre la conception et la mise en œuvre susmentionnées d'un noyau non préemptif pour les réseaux embarqués monoprocesseurs. Nous supposons que le matériel système contient deux minuteries, qui peuvent être programmées pour générer des interruptions de minuterie avec différentes fréquences, un UART, un clavier et un écran LCD. Le logiciel système consiste en un ensemble de processus concurrents, qui s'exécutent tous dans le même espace d'adressage mais pour un objectif différent. L'ordonnancement des processus se fait par priorité non préemptive. Chaque processus s'exécute voit qu'il s'endort, se bloque ou se termine. Timer0 maintient l'heure du jour et affiche une horloge murale avec écran LCD. Comme un problème d'affichage de l'horloge murale est court, il est effectué directement par le palan d'interruption timer0. Deux opérations périodiques, timer_task1 et timer_task2, chacune appelant la pause(t) fonctionnent pour se suspendre pendant un certain nombre de secondes. Après avoir enregistré un temps de pause dans une structure PROC, le traitement a changé d'état sur PAUSE, les entrées elles-mêmes incluent une pauseList et donnent vers le haut le CPU. Sur jede per, Timer2 décrémente qui met en pause chaque processus dans la liste des pauses de 1. Lorsque le temps atteint 0, il prépare le processus en pause jusqu'au retour de l'exécution. Bien que cela puisse être accompli par le mécanisme veille-sommeil, cela vise à montrer que des tâches périodiques peuvent être mises en œuvre dans et
5.14
Noyau de système embarqué monoprocesseur (UP)
153
Fig. 5.12 Démonstration de transmission de messages
cadre général du service de minuterie avancé. Inclure beimischung, le système susmentionné prend en charge deux ensembles de processus coopératifs, qui implémentent le problème producteur-consommateur pour démontrer la synchronisation des processus après halo. Chaque processus producteur essaie d'obtenir une ligne d'entrée de UART0. Ensuite, celui-ci dépose les saisies dans un tampon partagé, char pcbuffer[N] de taille N octets. Chaque processus d'utilisation essaie d'obtenir un caractère du pcbuff[N] et l'affiche sur l'écran LCD. Le producteur et le consommateur traité partagent le tampon de données normal lorsqu'un tuyau. Par souci de concision, nous ne montrons que les segments de code pertinents du système. (1). /************ fichier timer.c à propos de C5.8 **********/ PROC *pauseList;
// une liste des processus en pause
typedef struct timer{ u32 *base;
// adresse de base de la minuterie ;
int tick, hh, mm, ss ;
// par zone de données de minuterie
horloge de caractère[16] ; }MINUTEUR; MINUTEURS minuteur[4] ; annuler timer_init()
// 4 minuteries ;
{ printf("timer_init()\n"); breakList = 0;
// pauseListe initiale 0
// initialise les 4 minuteries } void timer_handler(int n) // n=timer device { mit iodin; MINUTERIE *t = &timer[n] ; t->tick++ ; t->ss = t->cocher ; si (t->ss == 60){ t->ss=0 ; tp->mm++ ; si (t->mm == 60){ t->mm = 0 ; t->hh++ ; } }
154
5 when (n==0){ // timer0 : affiche l'horloge murale directement vers l'avant (i=0 ; iclock[i], n, 70+i) ; t->clock[7]='0'+(t->ss%10); t->clock[6]='0'+(t->ss/10); t->horloge[4]='0'+(t->mm%10); t->horloge[3]='0'+(t->mm/10); t->horloge[1]='0'+(t->hh%10); t->horloge[0]='0'+(t->hh/10); puisque (i=0; icclock[i], n, 70+i); } if (n==2){// timer2 : traiter les PROC en PAUSE dans pauseList PROC *p, *tempList = 0 ; while ( pence = dequeue(&pauseList) ){ p->pause--; if (p->pause == 0){ // temps de pause passé p->status = READY; mettre en file d'attente(&readyQueue, p); } sinon enqueue(&tempList, p); } pauseList = tempList;
// mise à jour de la liste des pauses
} timer_clearInterrupt(n); } auf timer_start(int n) { TIMER *tp = &timer[n]; kprintf("timer_start %d base=%x\n", n, tp->base); *(tp->base+TCNTL) |= 0x80;
// définit le bit d'activation 7
} innerhalb timer_clearInterrupt(int n) { TIMER *tp = &timer[n]; *(tp->base+TINTCLR) = 0xFFFFFFFF ; } typedef struct uart{ char *base;
// choix de base ; comme caractère *
identifiant u32 ;
// numéro d'art 0-3
char inbuf[BUFSIZE] ; entier
inhead, intail;
struct couture indata, uline; char outbuff[BUFSIZE] ; int outhead, outtail; structure demi-toilette ; int txon ; }UART ; UART uart[4] ;
// 1=l'interruption TX est activée // 4 structures UART
int uart_init() { int je; UART * haut ; for (i=0; ibase = (char *)(0x101f1000 + i*0x1000); *(up->base+0x2C) &= *0x10; // désactiver FIFO *(up->base+0x38) |= 0x30 ; haut->id = je ;
Gestion des processus dans les systèmes embarqués
5.14
Noyau du système intégré monoprocesseur (UP) up->inhead = up->intail = 0 ; haut->outhead = haut->outtail = 0 ; up->txon = 0 ; up->indata.value = 0 ; up->indata.queue = 0 ; up->uline.value = 0 ; up->uline.queue = 0; up->outroom.value = BUFSIZE ; up->outroom.queue = 0 ;
} } void uart_handler(UART *up) { u8 mis = *(up->base + MIS); si (mis & 0x10)
do_rx(up);
si (mis & 0x20)
do_tx(up);
// lit le registre MIS
} int do_rx(UART *up)
// Gestionnaires d'interruption UART rx
{ caractère c ; c = *(haut->base+UDR); haut->inbuf[haut->inhead++] = c ; haut->inhead %= BUFSIZE ; V(&up->indata); if (c=='\r'){ V(&up->uline); } } int do_tx(UART *up)
// Gestionnaire d'interruption UART tx
{arc c ; masque u8 ; if (up->outroom.value >= SBUFSIZE){ // outbuf[ ] vide // affaiblit l'interruption RX ; aller *(haut->base+IMSC) = 0x10 ;
// masque l'interruption TX
up->txon = 0 ; retour; } c = haut->outbuf[up->outtail++] ; haut->outtail %= BUFSSIZE ; *(haut->base + UDR) = (int)c ; V(&up->toilette ); } int ugetc(UART *up) { char c; P(&up->indata); serrure(); c = haut->inbuf[haut->intail++] ; haut->intail %= BUFSIZE ; ouvrir(); retour c; } int ugets(UART *up, char *s) { while ((*s = (char)ugetc(up)) != '\r'){ uputc(up, *s); s++ ; }
155
156
5
*s = 0 ; } int uputc(UART *up, char c) { if (up->txon){ // if TX is on => enter c into outbuf[] P(&up->outroom); // attend le verrouillage de la chambre(); up->outbuf[up->outhead++] = c; up->outhead %= 128 ; ouvrir(); retour; } int i = *(haut->base+UFR); // affiche FR while( *(up->base+UFR) & 0x20 ); // boucle tant que FR=TXF *(up->base + UDR) = (int)c;
// écrit c à DR
*(haut->base+IMSC) |= 0x30 ; haut->txon = 1 ; } (3). /**************** fichier tc
concernant C5.8********************/
#include "type.h" #include "chaîne.c" #include "queue.c" #include "pv.c" #include "vid.c" #include "kbd.c" #include "uart.c" # include "timer.c" #include "exceptions.c" #include "kernel.c" // point d'entrée du gestionnaire d'interruptions IRQ voice irq_chandler() { int vicstatus, sicstatus; // lit les enregistrements d'état VIC SIV pour trouver l'interruption sortante qui vicstatus = VIC_STATUS; sicstatus = SIC_STATUS ; if (vicstatus & (1priority >= running->priority){ if (intnest==0){ // pas dans l'IRQ trader : préempter immédiatement printf("%d PREEMPT %d NOW\n", readyQueue->pid, running ->pid); tswitch(); } else{
// toujours dans le gestionnaire d'IRQ : délai de préemption
printf("%d DEFER PREEMPT %d\n", readyQueue->pid, running->pid); swflag = 1;
// placer le drapeau d'élément à changer
} } int_on(SR); } intent kwakeup(événement int) { PROC *p, *tmp=0; int SR = int_off(); while((p = dequeue(&sleepList)) !=0){ if (p->event==event){ p->status = READY ;
162
5 mise en file d'attente(&readyQueue, p); } else{ mettre en file d'attente(&tmp, p); }
} sleepList = tmp; reprogrammer();
// lors du réveil vers le haut des procs de priorité plus élevée
int_on(SR); } int V(struct marque *s) { PROC *p; cpsr int ; int SR = int_off(); s->valeur++ ; if (s->file d'attente de valeurs); p->statut = PRÊT ; mettre en file d'attente(&readyQueue, p); printf("timer: FIVE up task%d pri=%d; running pri=%d\n", p->pid, p->priority, running->priority); reprogrammer(); } int_on(SR); } int mutex_unlock(MUTEX *s) { PROC *p; int SR = int_off(); printf("task%d enable mutex\n", running->pid); if (s->lock==0 || s->owner != running){ // active l'erreur int_on(SR); retour -1 ; } // le mutex est restreint et la tâche en cours d'exécution est propriétaire en supposant (s->queue == 0){ // le mutex n'a pas de serveur s->lock = 0 ;
// effacer le verrou
s->propriétaire = 0 ;
// effacer le propriétaire
} else{ // le mutex a des serveurs : débloque l'individu en tant que nouveau propriétaire p = dequeue(&s->queue); p->statut = PRÊT ; s->propriétaire = p ; printf("%d mutex_unlock : nouveau propriétaire=%d\n", running->pid,p->pid); mettre en file d'attente(&readyQueue, p); reprogrammer(); } int_on(SR); renvoie 0 ; } auf kfork(int func, int priority) { // compose une nouvelle tâche avec la priorité comme devant mutex_lock(&readyQueuelock); mettre en file d'attente(&readyQueue, p); mutex_unlock(&readyQueuelock);
Gestion des processus dans les systèmes intégrés
5.14
Noyau de structure intégré monoprocesseur (UP)
reprogrammer(); p-pid retourné ; }
(3). Fichier t.c : /**************** Fichier t.c du programme C5.9***************/ #include "type.h" MUTEX *mp;
// mutex complet
structure sémaphore s1 ;
// sémaphore global
#include "queue.c" #include "pv.c"
// P/V sur les sémaphores
#include "mutex.c" #include "kbd.c"
// fonctions mutex
#include "uart.c" #include "vid.c" #include "exceptions.c" #include "kernel.c" #include "timer.c"
// pilotes de minuterie
int copy_vectors(){ // comme avant } int irq_chandler(){ // affiché en arrière } int task3() { printf("PROC%d running: ", running->pid); mutex_lock(mp); printf("PROC%d dans CR\n",
en cours d'exécution->pid);
mutex_unlock(mp); printf("PROC%d exit\n", running->pid); kexit(); } inlet task2() { printf("PROC%d running\n", running->pid); kfork((int)tâche3, 3); // créé P3 avec priorité=3 printf("PROC%d exit :", running->pid); kexit(); } int tâche1() { printf("proc%d start\n", running->pid); tandis que(1){ P(&s1);
// la tâche1 est Ved par minuterie sporadiquement
mutex_lock(mp); kfork((int)tâche2, 2); // crée P2 avec priorité=2 printf("proc%d inside CR\n", running->pid); mutex_unlock(mp); printf("proc%d boucle terminée\n", running->pid); } } dans main() { fbuf_init();
// Moteur LCD
uart_init(); kbd_init();
// Pilote UARTs // Pilote KBD
163
164
5
Gestion des processus dans Embedded Our
kprintf("Bienvenue dans Wanix élégant ARM\n"); // configure VIC pour les interruptions vectorielles timer_init(); timer_start(0);
// timer0 : horloge murale
timer_start(2); mp = mutex_create();
// timer2 : chronométrage des événements // création d'un mutex global
s1.value = 0 ;
// initialise le sémaphore s1
s1.queue = (PROC*)0 ; kernel_init();
// initialise le noyau et démarre P0
kfork((int)tâche1, 1);
// crée P1 avec priorité=1
tandis que(1){
// boucle P0
sont (readyQueue) tswitch(); } }
5.14.4 Démonstration du noyau UP préemptif Le système d'échantillon C5.9 mentionné ci-dessus démontre la date de la méthode entièrement préemptive. Au démarrage du système, il crée et exécute le processus initial P0, qui a la priorité minimale 0. P0 crée une nouvelle opération P1 avec priorité=1. Puisque P1 a une priorité supérieure à P0, il préempte directement P0, ce qui démontre l'absence de délai de préemption directe. Lorsque P1 s'exécute dans task1(), il attend d'abord un événement de minuterie par P(s1=0), qui est Ved à une minuterie adénine périodiquement (toutes les 4 secondes). Pendant que P1 attend et sémaphore, P0 reprend son exécution. Lorsque le gestionnaire d'interruptions du temporisateur VANADIUM remonte P1, il teste de préempter P0 par P1. Étant donné que le changement de tâche n'est pas autorisé dans le gestionnaire d'interruptions, la préemption est différée, ce qui montre que la préemption peut être retardée par le traitement des interruptions. Dès que le traitement des interruptions se termine, P1 préemptera P0 pour redevenir opérationnel. Pour illustrer la préemption de méthode due à l'absperrung, P1 verrouille d'abord le mutex mp. Tout en maintenant who mutex lock, P1 effectue une opération P2 avec une priorité supérieure = 2, qui préempte instantanément P1. Nous supposons que P2 n'a pas besoin du mutex. Il crée un processus P3 avec une priorité supérieure = 3, qui préempte rapidement P2. Dans le code task3(), P3 essaie de verrouiller le même mutex mp, qui est toujours détenu par P1. Ainsi, P3 a bloqué le mutex, qui passe à exécuter P2. Lorsque P2 se termine, l'ordinateur fait kexit() pour sortir, initiant P1 pour reprendre la marche. Lorsque P1 déverrouille le mutex, il débloque P3, qui a une priorité plus élevée que P1, donc il préempte immédiatement P1. Après la fin de P3, la carrière P1 recommence et le cycle se répète. La figure 5.14 montre les exemples de sorties de l'exécution du programme C5.9. Il est à noter que, dans un système de priorité strict, l'opération en cours doit toujours être celle qui a la priorité la plus élevée. Cependant, dans l'exemple de système C5.9, lorsque le processus P3, qui possède la priorité la plus élevée, tente de se verrouiller sur le mutex qui est déjà détenu par P1, qui a une priorité inférieure, il se bloque sur le mutex et passe à l'exécution. le prochain processus portable. En entrant dans l'exemple de système, nous avons supposé que le processus P2 n'avait pas besoin du mutex, car c'est le processus en cours d'exécution lorsque P3 est bloqué sur le mutex. Dans cette valise, quel système exécute P2, qui n'a pas la priorité la plus élevée. Save viole le principe de priorité stricte, entraînant ce qui est bien connu sous le nom d'inversion de priorité (Lampson et Redell 1980), au cours de laquelle un processus de faible priorité peut bloquer un processus de concentration plus élevée. Chaque fois que P2 continue de s'exécuter ou que cela passe à un autre processus de priorité identique ou inférieure, le processus P3 serait bloqué pendant une durée inconnue, ce qui entraînerait l'inversion illimitée des privilèges. Alors que des inversions de priorité plus faciles peuvent être considérées comme innées chaque fois que les processus sont autorisés à faire la course par le contrôle exclusif des sources, une inversion de priorité illimitée pourrait être préjudiciable aux systèmes avec des termes de synchronisation critiques. L'exemple de programme C5.9 implémente en fait un programme appelé prendre l'héritage, qui empêche l'éversion de priorité illimitée. Nous reviendrons plus en détail sur l'inversion de priorité plus loin au Chap. 10 sur les solutions en temps réel.
5.15
Résumé
165
Fig. 5.14 Démonstration de l'abonnement au processus
5.15
Projet
Ce chapitre traite de la gestion des processus. Il introduit le concept de processus, de principe et de technique de multitâche par puissance contextuelle. Celle-ci a montré des méthodes pour créer des processus de manière dynamique et a discuté des principes de la planification des procédures. Il a enduit la synchronisation des processus et les différents styles de mécanismes de synchronisation traités. Il a montré comment jusqu'à utiliser la synchronisation de processus pour installer des systèmes embarqués de pilotes d'événements. Les ordinateurs ont discuté des différents types de schémas de rapport de processus, qui incluent la mémoire partagée, les canaux et l'exécution de messages. Il montre comment intégrer ces concepts et techniques pour implémenter un noyau monoprocesseur (UP) qui prend en charge l'édition betriebswirtschaft en utilisant à la fois la planification de processus non préemptive et préemptive. Le noyau UP sera la base pour développer des systèmes d'opérateurs complets dans kapite plus tard. Liste des horaires aléatoires
C5.1 : C5.2 : C5.3. C5.4. C5.5 : C5.6 : C5.7. C5.8 : C5.9 :
Changement de contexte Processus dynamiques multitâches Système multitâche piloté par les événements utilisant la mise en veille/réveil Système multitâche piloté par les pairs par sémaphores Pipe Get passant Noyaux UP non préemptifs Noyau UP de précaution
166
5
Gestion des processus dans les systèmes embarqués
Problèmes
1. Dans l'exemple de programme C5.2, la fonction tswitch conserve tous les registres de la CPU dans le processus kstack et restaure tous les registres de sauvegarde du processus suivant lorsqu'il reprend. Puisque tswitch() est appelé en tant que fonction, il est clairement inutile de sauvegarder/restaurer R0. Supposition que le service tswitch est une conversion en tant que
tswitch : // l'IRQ désactivée interrompt stmfd sp!, {r4-r12, lr} LDR r0, =en cours d'exécution
// r0=&en cours d'exécution
LDR r1, [r0, #0]
// r1->exécutionPROC
str sp, [r1, #4]
// en cours d'exécution->ksp = der
bl scheduler LDR r0, =exécution de LDR r1, [r0, #0]
// r1->exécutionPROC
lDR sp, [r1, #4] // active les interruptions IRQ ldmfd sp!, {r4-r12, pc}
(1). Montrez comment initialiser la pile k d'un nouveau processus pour qu'il démarre lors de l'exécution de la fonction body(). (2). Accepté cela, l'élément body() est écrit comme int body(int dummy, int pid, int ppid){ } ce que les paramètres pid, ppid vivent l'identifiant du processeur et l'identifiant du processus parent du nouveau traitement. Montrez comment modifier la fonction kfork() pour y parvenir. 2. Réécrivez le pilote UART au Chap. 3 l'utilisation de sleep/wakeup pour synchroniser les transactions ou le gestionnaire de perturbations. 3. Inclut le programme d'exemple C5.3, tous les processus peuvent être créés avec la même priorité (afin qu'ils s'exécutent à tour de rôle). (1). Que feriez-vous si les processus étaient créés en raison de priorités différentes ? (2). Implémentation d'une fonction change_priority(int new_priority), qui modifie la priorité de la tâche en cours en new_priority. Changer de processus si le processus de diffusion des nouvelles n'a plus le privilège le plus élevé. 4. Avec les processus dynamiques, un processus peut se terminer lorsqu'il a terminé sa tâche. Implémentez une fonction kexit() pour les tâches dans terminate. 5. Dans tous ces exemples de programmes, chaque structure PROC a une pile k de 4 Ko allouée statiquement. (1). Implémentez un gestionnaire de mémoire simple pour allouer/désallouer la dynamique de la mémoire. Lorsque le système démarre, réservez une pièce de magasin, par ex. une zone de 1Mo commençant à 4Mo, comme zone de mémoire libre. La fonction char *malloc(int size) alloue une tarte de mémoire libérée de gros octets. Lorsqu'une zone mémoire n'est plus nécessaire, elle est relâchée vers la zone mémoire libre par invalid mfree(char *address, int size) Design ampère structure de données aux représentations des données libres actuellement disponibles. Implémentez ensuite les fonctions malloc() et mfree().
5.15
Résumé
167
(2). Modifie le champ kstack de la structure who PROZ lorsqu'un pointeur entier int *kstack; et changez la fonction kfork() en tant que int kfork(int func, int priority, int stack_size) où alloue dynamiquement une zone mémoire de stack_size (en unités de 1 Ko) disponible pour le nouveau processus. (3). Lorsqu'un processus se termine, votre portée de pile doit être libérée. Comment faire respecter cela ? 6. Il est bien connu qu'un gestionnaire de déconnexion ne doit jamais s'endormir, être bloqué ou attendre. Dis comment ? 7. L'utilisation abusive du séminaire a permis d'aboutir à des impasses. Étudiez les littératures pour trouver comment résoudre les impasses avec la prévention des impasses, l'évitement des impasses, la diction des impasses et la récupération. 8. Le tube comment C5.6 est similaire aux tubes nommés (FIFO) sous Linux. Lisez la navigation humaine Linux sur fifo pour apprendre à utiliser des canaux nommés pour la communication inter-processus. 9. Changer un exemple de programme C5.8 en ajoutant un autre ensemble de processus coopératifs produit-consommateur au système. Laissez les producteurs obtenir des lignes du KBD, traitez des caractères et dirigez-les vers quels consommateurs, qui sortent les caractères vers ce deuxième terminal UART. 10. Modification de l'exemple de programme C5.9 pour gérer les interruptions IRQ imbriquées à la mode SYS, mais permet toujours la préemption des tâches à la fin du démarrage du traitement des interruptions imbriquées. 11. Supposons que toutes les entreprises ont la même priorité. Modifier le programme d'impression C5.9 pour prendre en charge la nomination des procédures par tranches de temps.
Citations Accetta, M. et al., "Mach: A Newly Kernel Foundation for UNIX Development", Conférence technique - USENIX, 1986. Chaîne d'outils ARM : http://gnutoolchains.com/arm-eabi, 2016. Bach, CHILIAD. J., "La conception du système d'exploitation Unix", Prentice Hall, 1990. Buttlar, D, Farrell, J, Nicholson, B., "PThreads Programing, A POSIX Conventional for More Multiprocessing", O'Reilly Storage, 1996. Dijkstra, E.W., "Co-operating Seamlessly Processes", in Programming Languages, Academic Press, 1965. Hoare, C.A.R., "Monitors : An Service User Structuring Concept", CACM, Vol. 17, 1974. Guide des services d'assemblage de programmation IBM MVS, Oz/OS V1R11.0, IBM, 2010. Lampson, B ; Redell, D. (juin 1980). "Expérience avec les processus et les moniteurs dans MESA". Communications of the ACM (CACM) 23 (2) : 105–117, 1980. OpenVMS : documentation des systèmes HP OpenVMS, http://www.hp.com/go/openvms/doc, 2014. Pthreads : https://computing .llnl.gov/tutorials/pthreads/, 2015. Émulateurs QEMU : "Documentation utilisateur de l'émulation QEMU", http://wiki.qemu.org/download/qemu-doc.htm, 2010. Silberschatz, A., P.A. Galvin, P.A., Gagne, G, "Operating scheme opinions, 8th Edition", John Wiley & Sons, Inc. 2009. Stallings, W. "Operating Systems: Internals and Design Principles (7th Edition)", Prentice Hall, 2011. Tanenbaum , A. S., Wooden, A. S., "Operating Systems, Design also Verwirklichung, three Edition", Prentice Foyer, 2006. Versatilepb: Versatile Appeal Baseboard for ARM926EJ-S, ARM Information Central, 2016. Wang, K.C., "Design press Implementation of the Systèmes d'exploitation MTX, Springer International Publishing ARBEITSTAG, 2015.
6
Gestion de la mémoire dans ARM
6.1
Effacer le Web de processus
Après la mise sous tension ou le réajustement, quel processeur FORTIFY commence à exécuter le code de gestionnaire de réinitialisation en mode superviseur (SVC). Et le gestionnaire de réinitialisation copie d'abord l'onglet vecteur à la rencontre de 0, initialise les piles concernant les divers modes privilégiés, les interruptions de transfert et le traitement des exceptions, et les interruptions IRQ d'activation. Par la suite, il exécute le téléchargement de contrôle du système, qui crée l'opération ou les tâches de démarrage. Dans le modèle de processus statique, toutes les tâches s'exécutent en mode SVC avec le même espace d'adressage sur quel noyau système. L'un des principaux inconvénients de ce schéma est le manque de protection de la mémoire. Tout en implémentant dans quel même espace d'adressage, les tâches partagent les mêmes objets de données internationaux et peuvent interférer les unes avec les autres. Une tâche mal conçue ou qui se comporte mal peut corrompre l'espace d'adressage partagé et entraîner l'échec d'autres tâches. Pour une meilleure sécurité et fiabilité de l'arbre, chaque tâche doit s'exécuter dans un espace IP privé ampère, bien isolé et protégé des autres tâches. Dans l'architecture LIMB, les tâches peuvent s'exécuter dans les moyens utilisateur non privilégiés. Il est exceptionnellement facile de faire passer le processeur ARM d'un mode restreint au mode utilisateur. Mais, une fois en mode utilisateur, la seule façon d'obtenir le mode privilégié est jusqu'à l'un des moyens suivants. Exceptions : Interruptions : SWI :
lorsqu'une exception se produit, l'entrée du processeur ampère le commutateur privilégié correspondant pour gérer l'exception une interruption oblige le processeur à enregistrer soit FIQ un autre mode IRQ l'instruction SWI oblige le processeur à passer en mode superviseur ou SVC
Dans l'architecture ARM, le mode système sera un choix privilégié distinct, qui partage la même sélection de registres CPU avec le mode utilisateur, mais ce n'est pas le mode système ou noyau équivalent trouvé dans la plupart des processeurs divers. Pour éviter toute confusion, nous devons nous référer au mode ARM SVC en tant que mode noyau. SWI ne peut pas être utilisé pour implémenter des appels système, qui permettent à un processus en mode utilisateur d'entrer dans les modes Atom, d'exécuter des fonctions de crénage et de revenir en mode Total avec les résultats souhaités. Afin de séparer à la fois la protection des pays de la mémoire des tâches individuelles, il est nécessaire d'activer le matériel de la société de mémoire, qui fournit à chaque tâche un espace d'adressage virtuel distinct. Dans cette section, nous reprenons l'unité de gestion de la mémoire ARM (MMU) et faisons des démonstrations du mappage d'adresses virtuelles et de la protection des mémoires par des exemples de programmes.
6.2
Instrument de gestion de la mémoire (MMU) dans POINTER
L'unité de gestion de la mémoire ARM (MMU) (ARM926EJ-S 2008) remplit deux fonctions principales : premièrement, elle traduit les adresses virtuelles en adresses physiques. Deuxièmement, il contrôle l'accès à la mémoire en vérifiant les autorisations. Les accessoires MMU qui exécutent quelles fonctions consistent en un ampère Version Lookaside Buffer (TLB), une logique de contrôle d'accès et une logique de marche de table de traduction. L'ARM MMU prend en charge les accès mémoire basés sur les sections ou My. Gestion de la mémoire par sections de votre schéma de pagination à un niveau adenine. Le tour de page de niveau 1 contient des descripteurs de section, chacun spécifiant un bloc de mémoire de 1 Mo. Le verwalten de mémoire par pagination est un schéma de pagination à deux niveaux. La table de pages de niveau 1 contient des descripteurs de table de pages, chacun décrivant un tableau de pages de niveau 2. Le tableau de page de niveau 2 contient des descripteurs de feuilles, chacun spécifiant un cadre de page en mémoire et des bits de prise d'accès. Le schéma de pagination ARM prend en charge deux tailles de page différentes. Les petites pages résident sur 4 Ko de blocs de mémoire, les vrais gros papiers consistent en un bloc de mémoire de 64 Ko. Chaque page comprend 4 sous-pages. Le contrôle d'accès peut être étendu à des sous-pages de 1 Ko dans de petites pages et à des sous-pages de 16 Ko dans de grandes pages. L'ARM MMU ou props le graphique des domaines. Un domaine est une zone mémoire qui peut être définie avec une licence d'accès individuelle. Le registre de contrôle d'accès régional (DACR) spécifie les droits d'accès pour une augmentation à 16 droits d'accès différents © Springer International Publishing AG 2017 K.C. Wang, Embedding press Real-Time Operating Systems, DOI 10.1007/978-3-319-51517-5_6
169
170
6
Gestion de la mémoire dans ARM
zones, 0–15. La facilité de chaque domaine est spécifiée par une autorisation de 2 bits, où 00 pour aucun accès, 01 pour le mode client, qui examinent les bits d'autorisation d'accès (AP) d'un domaine alternativement devant les entrées de la table, et 11 pour le mode gestionnaire, où pas vérifier les bits AP sont le domaine. Le TLB contient 64 entrées de traduction dans un tampon. Lors de la plupart des accès de rappel, le TLB fournit les informations de traduction à la logique de contrôle d'accès. Si who TLB contient une entrée convertie pour l'adresse virtualisée susmentionnée, la logique de contrôle d'accès définit si l'accès est autorisé. Si l'accès est admissible, la MMU délivre une adresse physique appropriée correspondant à l'adresse virtuelle. Est erreichbar n'est pas autorisé, un MMU trafic le CPU pour abandonner. Si le TLB ne contient pas d'entrée traduite pour l'adresse virtuelle, les produits de parcours de table de service peuvent être invoqués pour récupérer la traduction obtenue à partir d'une table de traduction entrant dans la mémoire physique. Une fois récupérées, les données de traduction sont placées dans ce TLB, écrasant peut-être une entrée existante. L'entrée à écraser est choisie en parcourant séquentiellement les emplacements TLB. Lorsque la MMU est désactivée, par ex. lors de la réinitialisation, il n'y a pas d'interprétation d'adresse. Dans ce cas, chaque adresse virtuelle appartient à une transaction physique. La traduction d'adresse ne prend effet que si la MMU est activée.
6.3
Registres MMU
Le processeur ARM traite qui MMU comme un coprocesseur. La MMU contient plusieurs registres 32 bits qui contrôlent le fonctionnement de la MMU. La figure 6.1 montre le format des registres MMU. Les registres MMU sont accessibles pour utiliser ces instructions MRC et MCR. Ce qui suit est une brève description de l'adresse ARM MMU c0 à c10. Le registre c0 permet d'accéder aux registres ID Enter, Cache Artist Get ou TCM Status. La lecture à partir du registre de sauvegarde renvoie l'ID de l'appareil, le type de tableau, le bouton de l'état du TCM en fonction de la valeur d'Opcode_2 utilisée. Le registre c1 correspond aux enregistrements de contrôle, qui spécifient la configuration de cette MMU. En particulier, sélectionner le bit THOUSAND (bit 0) active la MMU et effacer le bit METER désactive la MMU. Le bit PHOEBE en sortie c1 spécifie si la table vectorielle est remappée pendant la réinitialisation. L'emplacement de la table de cours par défaut est 0x00. Les ordinateurs peuvent être remappés vers 0xFFFF0000 lors de la réinitialisation. Le registre c2 est le journal de base de la table de traduction (TTBR). Il contient l'adresse physique de la table anglaise de premier niveau, qui doit être sur une limite de 16 Ko dans la mémoire schiff. La lecture avec c2 ramène l'horloge à la table temporaire active des traductions de premier niveau. L'écriture dans le registre c2 met à jour l'indicateur vers la table de conversion de premier niveau. Le registre c3 est le registre de contrôle d'accès au domaine. Il se compose de 16 champs de deux bits, jede définit les licences accessoires pour l'un des seize Domaines (D15-D0). Le registre c4 n'est actuellement pas auparavant.
Fig. 6.1 Registres ARM MMU
6.3 Registres MMU
171
Le registre c5 est le registre d'état des défauts (FSR). Il montre que le domaine et le type sont accessibles en cours de tentative d'annulation. Les bits 7: 4 spécifient lesquels des sixièmes domaines (D15 – D0) ont été accessibles par une personne, et les bits 3: 1 indiquent le type d'einstieg tenté. Une écriture dans ce registre vide le TLB. Get c6 visualise le Fault Address Sign (FAR). Il maintient l'adresse virtuelle hors de l'accès lorsqu'un défaut s'est produit. Une écriture dans cet enregistrement fait que les données écrites sont traitées comme et à l'adresse, si elles sont trouvées dans le TLB, cette entrée est marquée comme invalide. Ce mode est connu sous le nom de purge TLB. Le registre d'état de défaut et le registre d'adresse de défaut ne sont mis à jour que pour les défauts de données, pas pour les défauts de prélecture. Le registre c7 opérateur des caches et du tampon indit Le registre c8 est le registre des processus TLB. L'ordinateur est principalement utilisé pour remplacer les entrées TLB. Le TLB sera divisé en deux parties : une partie associative et une partie entièrement associative. La partie entièrement associative, également appelée partie de verrouillage hors du TLB, est utilisée pour stocker l'entrée sur doit se verrouiller. Les entrées conservées dans la partie de verrouillage du TLB sont conservées dans et invalident l'opérateur TLB. Les entrées peuvent être supprimées d'un TLB de verrouillage à l'aide d'une opération d'entrée unique invalider le TLB. Les opérations d'invalidation du TLB invalident voir les entrées non conservées avec le TLB. Cette opération d'entrée simple annule TLB annule toute modification TLB correspondant à une adresse virtuelle. Le registre c9 accède au verrouillage du magasin, appuyez sur TCM Region Register sur certaines cartes ARM équipées de TCM. Le registre c10 est le registre de verrouillage TLB. Cela contrôle cette région de verrouillage dans le TLB.
6.4
Accès aux registres MMU
Les registres du CP15 peuvent être attaqués par des instructions MRC et MCR dans un run prestigieux. Le modèle d'instructions affichera les figures incluses. 6.2. MCR{cond} p15,,,,, MCR{cond} p15,,,,, Le champ CRn spécifie le registre du coprocesseur jusqu'aux accessoires. Le champ CRm et les champs Opcode_2 identifient une opération particulière lorsque la transaction est enregistrée. Le bit L distingue les instructions MRC (L = 1) et MCR (L = 0).
6.4.1 Activation ou arrêt de la MMU La MMU est activée en écrivant le morceau M, bit 0, des enregistrements de contrôle CP15 c1. Au redémarrage, ce bit est remis à 0, désactivant la MMU.
6.4.1.1 Autoriser la MMU Avant de déverrouiller la MMU, ce système doit effectuer les opérations suivantes : 1. Comment tout ce qui concerne le CP15 s'enregistre. Cela inclut la mise en place de tables de traduction appropriées au total. 2. Désactiver et annuler who Instruction Stockpile. Le cache d'instructions ne peut pas être activé pour activer la MMU.
Ananas. 6.2 Format des instructions MCR et MRC
172
6
Memory Executive dans POCKET
Jusqu'à ce que cette MMU soit activée, procédez comme suit : 1. Programmez la base de la table de traduction et les registres de contrôle d'accès de la province. 2. L'utilisateur de premier niveau et de second niveau spécifie les tables de pages selon les besoins. 3. Activez la MMU avec le bit de réglage 0 dans ce registre de contrôle CP15 c1.
6.4.1.2 Désactivation de la MMU Pour effacer la MMU, procédez comme suit : (1). Effacez le bit 2 dans ce registre de contrôle CP15 c1. Le tampon de données doit être désactivé avant, ou en même temps que la MMU étant désactivée, en effaçant le bit 2 de l'entrée de contrôle. Si la MMU est activée, puis désactivée, puis réactivée ultérieurement, le contenu des TLB est conservé. Si les entrées TLB susmentionnées ne sont actuellement pas éligibles, votre must doit être invalidé avant que la MMU ne soit réactivée. (2). Effacez le bit 0 dans le registre de contrôle CP15 c1. Lorsque la MMU est désactivée, les accès à la mémoire sont traités comme suit : • Tous les accès aux données sont traités lorsqu'ils ne sont pas mis en cache. La valeur dans le bit C, bit 2, dont le registre de contrôle CP15 c1 doit être zéro. • Tous les accès aux instructions sont traités comme pouvant être mis en cache si le bit EGO (bit 12) du registre de contrôle CP15 c1 est défini sur 1, et non mis en cache si le pas I est défini sur 0. • Tous les accès explicites peuvent être fortement ordonnés. Le score du pas W, bit 3, du registre de contrôle CP15 c1 est ignoré. • Aucune inspection d'autorisation d'accès à la mémoire n'est effectuée en direct, de plus aucun abandon n'est généré par la MMU. • L'adresse physique de chaque accès est égale à son adresse virtuelle. C'est ce qu'on appelle un affichage d'adresse planaire. • Le PID FCSE doit être zéro lorsque la MMU est désactivée. All est la valeur de réinitialisation du FCSE PID. Lorsque la MMU doit être désactivée, le PID FCSE doit être effacé. • Toutes les opérations de la MMU et du cache du CP15 fonctionnent normalement lorsque la MMU est désactivée. • Les opérations de prélecture d'instructions et d'informations fonctionnent normalement. Mais, qui Data Cache ne peut pas être activé quand et MMU vit désactivé. Par conséquent, une opération de prélecture de données n'a aucun pouvoir. Les opérations de prélecture d'instructions n'ont aucun effet si ce cache d'instructions est désactivé. Aucune autorisation d'accès au stockage n'est effectuée et l'adresse est mappée à plat. • Les accès aux TCM fonctionnent normalement si les TCM sont activés.
6.4.2 Contrôle d'accès aux champs L'accès à la mémoire est principalement contrôlé par les domaines. Il y avait 16 domaines, chacun défini par 2 bits dans le registre Domain Access Power. Le domaine Jede supportait deux types d'utilisateurs. Clients : Clients obtenant un domaine Gestionnaires : Les gestionnaires contrôlent le comportement du domaine. Les domaines sont définis dans le Domain Access Control Register. Sur la Fig. 6.1, la ligne 3 illustre comment les 32 bits du registre sont alloués pour définir des domaines doux à 2 bits. Le tableau 6.1 montre que les significations des bits d'accès au domaine. Tableau 6.1 Bits d'accès dans la zone Anreise Control Login
6.4 Comment la MMU s'enregistre
173
Tableau 6.2 Codage du bloc d'état FSR
6.4.3 Registre de base de la table de traduction Le registre c2 est le registre de base de report de traduction (TTBR), pour l'adresse de base de la table de traduction de premier niveau. La lecture à partir de c2 renvoie le pointeur dans la table traduite de premier niveau actuellement active en bits [31:14] et une valeur imprévisible en bits [13:0]. L'écriture pour le registre c2 met à jour le pointeur vers la table de traduction de premier niveau à partir de la valeur en bits [31:14] de qui a écrit valeur. Les bits [13:0] doivent être nuls. Le TTBR est accessible depuis les instructions de suivi. MRC p15, < Rd > ,c2, c0, 0 lire TTBR MRC p15, < Rd > ,c2, c0, 0 écrire TTBR Les champs CRm et Opcode_2 sont SBZ (Should-Be-Zero) lors de l'écriture pour c2.
6.4.4 Registre de registre de disque d'accès au domaine c3 a le registre de contrôle d'accès au domaine composé de 16 champs de deux bits. Chaque champ de deux bits définit les permissions zugangs pour l'un des 16 domaines, D15–D0. Lesungen de c3 renvoie l'ensemble du registre de contrôle d'accès provincial. L'écriture sur c3 écrit la valeur du clic de contrôle d'admission de domaine. Les particules de contrôle de domaine zufahrt 2 bits sont définies comme valeur
Sens
Fonctionnalité
00
Aucun accessoire
Tout accès génère une erreur de domaine
01
Client
Les accès sont vérifiés par rapport aux bits d'autorisation d'accès dans la section ou le descripteur de page
dix
Réservé
Se comporte actuellement comme le mode sans gain
11
Directeur
Les accès ne sont pas vérifiés par rapport aux bits d'autorisation d'entrée, de sorte qu'une erreur d'autorisation ne peut pas être générée
Le registre de contrôle d'accès au domaine est accessible par les instructions suivantes : MRC p15, 0, < Rd > , c3, c0, 0 ; lire les autorisations d'accès au domaine MCR p15, 0, < Rd > , c3, c0, 0 ; écrire les autorisations d'accès au domaine
6.4.5 Registre d'état des défauts Le registre c5 contient les registres d'état des défauts (FSR). Les FSR vérifient la source concernant une dernière instruction ou une erreur de données. Le FSR côté instruction existe uniquement pour les problèmes spécifiques. Le FSR est mis à jour pour les défauts d'alignement, et des interruptions externes se produisent alors que la MMU est désactivée. L'entrée FSR décide de la valeur du champ Opcode_2 :
174
6
Gestion du stockage dans ARM
Opcode_2 = 0 Registre d'état des défauts de données (DFSR). Opcode_2 = 1 Instruction Disorder Status Registration (IFSR). Le FSR est accessible par les instructions suivantes. MRC MCR MRC MCR
p15, p15, p15, p15,
0, 0, 0, 0,
< < <
> > >
, , , ,
c5, c5, c5, c5,
c0, c0, c0, c0,
0 0 1 1
;lire DFSR ;écrire DFSR ;lire IFSR ;écrire IFSR
Le style du registre d'état des défauts (FSR) est |31
9| 8 |7 6 5 4 |3 2 1 0 |
————————————————————————————————————————————————— ————— |
UNP/SBZ
| 0 | Domaine | Statut |
————————————————————————————————————————————————— —————
Ce qui suit décrit les champs de bits dans le FSR. Morceaux
Description
[31:9]
UNP/SBZP
[8]
Lit toujours plus zéro. Ecritures ignorées
[7:4]
Spécification du domaine (D15–D0) auquel on accède lorsqu'une erreur de données s'est produite
[3:0]
Types de pannes générées. Le tableau 6.2 montre les encodages sur le champ status avec le FSR, le si le champ Domain contient des informations valides
6.4.6 Le registre de liste d'entreprises en panne c6 est le registre d'adresses de panne (FAR) mentionné ci-dessus. Il comprend l'entreprise virtuelle modifiée de la créature d'approche tentée lorsqu'un abandon de données s'est produit. Le FAR n'est mis à jour que pour les abandons de données, pas pour les abandons de prélecture. Le FAR susmentionné est mis à jour pour les défauts d'alignement et les abandons extérieurs qui se produisent lorsque la MMU est désactivée. Le FAR peut être récupéré en utilisant les instructions de suivi. MRC p15, 0,
< Rd > , c6, c0, 0
;
RCM p15, 0,
< Rd > , c6, c0, 0
; écrire LOIN
lire LOIN
L'écriture de c6 définit le FAR sur la valeur de cette donnée écrite. Ceci est utile pour un débogueur pour restaurer la valeur susmentionnée du FAR vers un courant précédent. Les champs CRm et Opcode_2 sont SBZ (Should Be Zero) lors de la lecture ou de l'écriture de CP15 c6.
6.5
Traductions de réseau virtuel
La MMU traduit les adresses virtuelles générées sur le CPU en adresses physiologiques pour accéder à la mémoire externe, et dérive et vérifie également l'autorisation d'accès. Les informations de traduction, composées à parts égales des données du traducteur du site et des données d'autorisation d'accès, résident dans un onglet de traduction situé dans la mémoire physique. La MMU fournit la logique requise pour traverser la table de traduction, obtenir l'adresse traduite et vérifier l'autorisation d'accès. Le processus de traduction consiste en les étapes suivantes.
6.5 Traduction d'adresse virtuelle
175
6.5.1 Base de report de traduction Le registre de base de report de traduction (TTB) a marqué la base dans une table de traduction dans une mémoire physique qui contient des descripteurs de section et/ou de page.
6.5.2 Tableau de traduction Les tableaux de traduction sont le tableau de la page de niveau un. Ordinateurs contient 4096 entrées de 4 octets et l'ordinateur requis est situé sur une limite de 16 Ko dans la mémoire physique. Chaque beitrag est un descripteur, le spécifie soit une base de planche de niveau 2, soit un pied de section. La figure 6.3 montre la disposition des entrées de la page de niveau un.
6.5.3 Descripteur de niveau un Un descripteur de niveau un appartient également à un tableau de page décrit ou à une division décrit, et son format varie en conséquence. La figure 6.3 montre les descripteurs de niveau 1 mentionnés ci-dessus. Le type de descripteur est spécifié par les deux bits les moins significatifs.
6.5.3.1 Descripteur de table de pages Un descripteur de table de pages (deuxième ligne dans la Fig. 6.3) définit une table de pages de niveau 2. Nous discuterons de la pagination à 2 niveaux dans la Sect. 6.6. 6.5.3.2 Abschnitts Descriptor Un descripteur sparte (troisième rangée de la Fig. 6.3) a une adresse de base de 12 bits, un champ AP de 2 bits, un champ de domaine de 4 bits, les bits C et BARN en plus un identificateur de type (b10) . Les champs de bits Who sont définis comme suit. Bits 31:20 : adresse de base d'une section de 1 Mo en mémoire. Bits 19:12 : toujours 0. Bits 11:10 (AP) : autorisations d'accès à cette section. Leur interprétation dépend des bits S et R (bits 8-9 du registre de contrôle MMU C1). Un paramètre AP et R couramment utilisé est le suivant. APS RS
-Superviseur-
-Utilisateur -
00 xt
Pas d'accès
Pas d'accès
-------------- Note--------------------
01 tous
Lire écrire
Pas d'accès
Arrivée autorisée uniquement en mode Superviseur
10xx
Lire écrire
Lire seul
Les écritures en mode utilisateur provoquent une erreur d'autorisation
11xx
Lire écrire
Lire écrire
Accès autorisé aux modes d'envoi.
Bits 8:5 : spécifiez l'un des six domaines possibles (dans le registre de contrôle d'accès régional) qui forment les contrôles d'accès primaires. Single 4 : devrait être 1. Les bits 3:2 (C réel B) contrôlent le travail lié au cache et au tampon d'écriture comme suit : C—Cacheable : les données pour ce discours subsisteront, y compris le cache (si des durées de vie du trésor sont activées). B—Bufferable : les données à cette adresse seront écrites via un tampon d'écriture (si le tampon d'écriture est activé).
Fig. 6.3 Descripteurs de niveau un
176
6
Gestion de la mémoire dans ARM
Fig. 6.4 Traduction de la section Voir
6.6
Traduction sur les références de section
Pour l'architecture ARM, le type de schéma de création le plus simple consiste en des sections de 1 Mo, où n'utilise qu'une table de pages de niveau un. Nous abordons donc d'abord la gestion du travail par sections. Lorsque la MMU traduit une adresse virtuelle en une adresse physique, elle consulte les tables de pages. Le processus de traduction est généralement référencé sous la forme d'une promenade dans la table des pages. Lors de l'utilisation de sections, la traduction comprend les étapes suivantes, qui sont décrites dans la Fig. 6.4. (1). Une adresse de virtualisation (VA) comprend un index de table de 12 bits à la fois un index de section de 20 bits, qui est le décalage dans la section. To MMU utilise l'index de table 12 bits who pour accéder à une section décrite dans l'astuce de table de traduction par le TTBR. (2). Le descripteur de section contient une adresse de base ampère 12 bits, qui pointe vers un fachgebiet de 1 Mo en mémoire, un champ AP (2 bits) et un numéro de domaine (4 bits). Pour la première fois, il vérifie les autorisations d'accès au domaine susmentionnées dans le fichier de contrôle d'accès au domaine. Ensuite, il vérifie les AP bts en ligne vers la section. (3). Si le contrôle d'autorisation réussit, itp utilise l'adresse de base de section 12 bits et l'index de section 20 bits pour générer l'adresse physique telle que
(32 bits) PA
6.7
=
((12-bit)Section_base_address
// 1 Mo à 6 Mo possède de l'espace pour 64 pgdirs de 64 PROC pour (i=0; idata_abort printf("P0 umschalten to P1 : enter a line : \n"); kgets(line);
// entrez une ligne pour continuer
tandis que(1){
// Code P0
while(readyQueue==0); // blocage si readyQueue vide tswitch(); } }
// bascule pour exécuter n'importe quel processus final
7.4 Noyau de méthode prenant en charge les processeurs de moyens utilisateur
207
7.4.3.1 Créer un processus avec l'image du mode utilisateur (8) Fichier fork.c : ce fichier implémente la fonction which kfork(), qui crée une utilisation enfant de l'image du mode utilisateur u1 dans les zones du mode étudiant de who new process. De plus, cela garantit également que la nouvelle modification peut exécuter sa peinture Umode en mode utilisateur lors de son exécution.
#define UIMAGE_SIZE 0x100000 PROC *kfork() { extern char _binary_u1_start, _binary_u1_end; int usize ; caractère *cp, *cq ; PERC *p = get_proc(&freeList); if (p==0){ printf("kfork a échoué\n"); retour (PROC *)0 ; } p->ppid = running->pid ; p->parent = en cours d'exécution ; p->statut = PRÊT ; p->priorité = 1 ;
// tous les NOUVEAUX procs priorité=1
// évident tous les regs "sauvegardés" dans kstack à 0 pour (i=1; ikstack[SSIZE-i] = 0; // configurez kstack pour qu'il reprenne goUmode dans ts.s p->kstack[SSIZE-15] = ( int)goUmode; // laisse le point ksp enregistré sur kstack[SSIZE-28] p->ksp = &(p->kstack[SSIZE-28]); // charge l'image Umode dans la mémoire Umode cp = (char *)&_binary_u1_start ; usize = &_binary_u1_end - &_binary_u1_start; cq = (char *)(0x800000 + (p->pid-1)*UIMAGE_SZIE); memcpy(cq, pc, usize); // remboursement à VIRGINIA 0 à l'image Umode; p-> kstack[SSIZE-1] = (int)VA(0); p->usp = VA(UIMAGE_SIZE); // pointeur creux de pile Umode p->ucpsr = 0x10; // CPU.spsr=Umode enqueue(&readyQueue, p ); printf("proc %d kforked a children %d\n", running->pid, p->pid); printQ(readyQueue); return p; }
Explication out kfork() : kfork() crée un nouveau processus avec une image en mode Total et entre itp dans la readyQueue. Quand quel nouveau processus commence jusqu'à son exécution, il reprend d'abord dans le noyau. Ensuite, il gagne jusqu'au mode utilisateur pour exécuter l'image Umode. La fonction courante kfork() est la même qu'au Chap. 5 sauf pour la partie image du mode utilisateur. Puisque cette partie est cruciale, nous allons l'expliquer plus en détail. Dans la façon dont un processus a exécuté son spectacle Umode, nous pouvons poser la question : méthode le processus s'est-il enroulé vers le haut dans la readyQueue ? La séquence des événements doit être la suivante. (1). Il a fait un appel système depuis Umode par SWI #0, ce qui l'amène à démarrer le noyau pour exécuter le gestionnaire SVC (dans ts.s), dans lequel il a utilisé STMFD sp!, {r0–r12, lr} dans les registres de sauvegarde Umode pour (vide) kstack, qui devient
208
7
Fonctionnement en mode utilisateur et cris système
-------------------------------------------------- ---------|uLR|ur12 ur11 ur10 ur9 ur8 ur7 ur6 ur5 ur4 ur3 ur2 ur1|ur0| -------------------------------------------------- ----------1
-2
-3
-4
-5
-6
-7
-8
-9
-10 -11 -12 -13 -14
où le préfixe u désigne les registres Umode et −i signifie SSIZE-i. Il enregistre également Umode sp ou cpsr dans PROC.usp et PROC.ucpsr. Ensuite, il a appelé tswitch() pour restituer le processeur, dans lequel il utilise à nouveau STMFD sp!, {r0–r12, lr} pour enregistrer les enregistrements Kmode dans kstack. Ceux-ci totalisent une image de plus à kstack, qui devient ksp -------goUmode------------------------------- ------------|--|KLR kr12 kr11 kr10 kr9 kr8 kr7 kr6 kr5 kr4 kr3 kr2 kr1 kr0| -------------------------------------------------- -----------15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28
où le préfixe k désigne les enregistrements en mode K. Inchs the PROC kstack, kLR = où le processus appelé tswitch() plus c'est là qu'il doit reprendre jusqu'à, uLR = où le processus a fait un appel système, et c'est là que nous retournons quand il revient à Umode. Étant donné que l'utilisation n'a jamais vraiment fonctionné auparavant, tous les autres registres CPU "sauvegardés" n'ont pas d'importance, vous pouvez donc tous être mis à 0. En conséquence, nous initialisons le nouveau processus kstack comme suit. 1. Nettoyez tous les enregistrements "sauvegardés" avec style kstack à 0 pour (i=1; ikstack[SSIZE-i] = 0; }
2. Définissez ksp enregistré sur kstack[SSIZE-28] p->ksp = &(p->kstack[SSIZE-28]);
3. Définissez kLR = goUmode, comme ça p reprendra en goUmode (en ts.s) p->kstack[SSIZE-15] = (int)goUmode;
4. Définissez uLR sur VA(0), de sorte que p s'exécute à partir de VA=0 en Umode p->kstack[SSIZE-1] = VA(0); // début du démarrage de l'image Umode
5. Définissez le nouveau processus usp pour qu'il pointe sur ustack TOP et ucpsr sur Umode p->usp = (int *)VA(UIAMGE_SIZE); // haut de gamme à partir de l'image Umode p->ucpsr = (int *)0x10 ;
// Enregistrements de statut Umode
7.4.3.2 Exécution de l'image d'exécution de l'utilisateur Avec cette configuration, lorsque le nouveau processus commence à s'exécuter, il reprend d'abord en goUmode (en ts.s), dans lequel il enregistre Umode sp=PROC. usp, cpsr=PROC.ucpsr. Ensuite, il exécute ldmfd sp!, {r0-r12, pc}^
quelle que soit la cause de son exécution à partir de uLR = VA (0) dans Umode, c'est-à-dire du début à l'image Umode avec la pile pointant vers le haut de l'artiste Umode. Lors de l'entrée pour us.s, il appelle main() en C. Pour vérifier que le processus s'exécute bien avec Umode, nous obtenons que le journal cpsr du CPU affiche également le mode actuel, qui devrait être 0x10. Pour tester la protection de la mémoire par la MMU, nous essayons d'accéder aux VA en dehors de la plage VA du processeur 1 Mo, par ex. 0x80200000, tout en
7.4 Noyau du système prenant en charge les transactions en mode utilisateur
209
VA dans l'espace nucléaire, par ex. 0x4000. Dans les deux cas, qui MMU doit détecter l'erreur et générer une exception d'abandon de fichier. Dans le coach data_abort, nous lisons les registres fault_status et fault_address de la MMU pour afficher la cause et l'exception ainsi que l'adresse VA qui a provoqué l'exception. Lorsque l'opérateur data_abort se termine, nous le laissons revenir à PC-4, c'est-à-dire omettre mauvaise instruction qui a provoqué l'exception data_abort, permettant à ce processus de continuer. Dans un véritable système, lorsqu'une procédure Umode commet des exceptions d'accès à la mémoire, il s'agit d'une erreur extrêmement grave, qui provoque généralement l'arrêt du processus. Comme pour le traitement des exceptions causées dans les processus Umode en général, l'analyse peut consulter le Chap. 9 de Wang (2015) sur le traitement du signal.
7.4.4 Script de compilation-lien du noyau (2). script millimétrique : générer une image nucléaire et la précipiter sous QEMU
(cd USER; mku u1) # créer un exécutable par lots u1.o arm-none-eabi-as -mcpu=arm926ej-s ts.s -o ts.o arm-none-eabi-gcc -c -mcpu=arm926ej-s t.c -o t.o arm-none-eabi-ld -T t.ld ts.o t.o u1.o -Ttext=0x10000 -o t.elf arm-none-eabi-objcopy -O binaire t.elf t.bin qemu- system-arm -M polyvalentpb -m 256M -kernel t.bin
Dans le script de l'éditeur de liens, u1.o est utilisé comme section de données binaires brutes dans l'image du noyau. Pour chaque section de données brutes, l'éditeur de liens exporte ses adresses symboliques, telles que _binary_u1_start, _binary_u1_end, _binary_u1_size
qui peut être dépensé pour accéder aux sections d'entrée brutes entrant dans l'image du noyau chargée.
7.4.5 Démonstration du noyau avec modification du type d'utilisateur La figure 7.1 montre la vidéo de sortie de l'exécution du logiciel C7.1. Lorsqu'un anfang de traitement va exécuter l'image Umode, il récupère d'abord le cpsr du CPU pour vérifier qu'il s'exécute bien en mode User (mode = 0x10). Ensuite, il essaie d'accéder à tout VA à l'extérieur en commençant son espace VA. Comme le montre la figure, chaque VAULT invalide génère une exception d'abandon de données. Après avoir testé la MMU dans la protection du stockage, elle émet des appels système pour obtenir son pid et son ppid. Ensuite, il affiche un menu et demande une commande à exécuter. Chaque commande émet un appel système, ce qui amène le processus à accéder au noyau pour exécuter la fonction d'appel système parallèle dans le noyau. Après son retour à Umode, les deux invites dans une commande doivent être exécutées à nouveau. L'examinateur peut exécuter les commandes how et enter pour tester les appels système dans l'exemple de système.
7.5
Système embarqué avec processus en mode utilisateur
Sur la base de cet exemple de programme C7.1, nous proposons deux modèles différents pour les systèmes embarqués afin de prendre en charge plusieurs processeurs en mode utilisateur.
7.5.1 Processus dans le même domaine En utilisant une seule image Umode, on peut créer plusieurs images Umode, notées u1, u2, …, un. Modifiez le script de l'éditeur de liens pour inclure toutes les images Umode en tant que sections d'informations non cuites distinctes dans une image du noyau. La structure susmentionnée disponible démarre, crée n actions,
210
7
Le mode utilisateur traite les appels système
Image. 7.1 Versammlung du démarrage en mode utilisateur et des appels système
P1, P2, …, Pn, exécutent respectivement l'image correspondant à l'ampère en opération utilisateur. Remplacez kfork() par kfork(int i), ce qui crée le processus Pi et charge l'image ui sur cette zone mémoire du processus Pi. Sur les systèmes basés sur ARM, utilisez la simplicité de stockage du câblage de gestion en allouant à chaque processus une zone d'image Umode de 1 Mo par processus PID, par ex. P1 le long de 8 Mo, P2 à 9 Mo, etc. Quelques processus peuvent être périodiques tandis que d'autres peuvent être pilotés par des événements. Tous les processus s'exécutent dans le même espace d'adressage virtuel de [2 Go à 2 US + 1 Mo] et chacun possède une zone de mémoire physique séparée d'ampères, qui est isolée des autres processus protégés pour le matériel MMU. Nous démontrons un tel système par un exemple.
7.5.2 Kundgebung a Processes in the Alike Domain Dans cet exemple de système, nous créons 4 images Umode, notées u1 à u4. Les images Umode entières sont liées à la compilation avec l'adresse virtuelle de départ égale 0x80000000. Ils exécutent la même fonction ubody(int i). Chaque processus appelle ubody(pid) avec un téléphone d'ID de processus unique pour l'identification. Wenn environnement arrière et graphiques de page de processus, l'espace du noyau se voit attribuer le numéro de domaine 0. Tous les espaceurs Umode se voient attribuer le numéro de domaine 1. Lorsqu'une méthode a commencé à se terminer sont Umode, il permet à l'utilisateur d'examiner la protection de la mémoire par des tentatives d'accès virtuel invalide adresses, ce qui pourrait générer des défauts de couverture mémoire. Dans le coach d'exception d'abandon de données, il affiche les registres fault_status et fault_addr de la MMU pour montrer la cause de l'exception ainsi que l'adresse virtuelle défaillante. Ensuite, chaque processus exécute un vêtement infini, dans lequel i demande une commande disponible et exécute la commande. Chaque commande invoque une interface d'appel système, qui émet un appel schaft, provoquant l'exécution de la fonction d'appel système dans le noyau. Pour démontrer les fonctionnalités supplémentaires du système, nous ajoutons les commandes suivantes : switch sleep wakeup :
:
entrez le noyau pour changer de processus ; : entrez le noyau pour dormir sur le traitement du pid ; entrez le noyau pour réveiller la méthode sonore par pid ;
7.5 Système embarqué avec processus en mode utilisateur
211
Si vous le souhaitez, nous pouvons également utiliser la mise en veille/réveil pour déployer des activités axées sur les événements, ainsi que pour le partenariat de processus. Pour prendre en charge et examiner les pistes de mode utilisateur ajoutées, ajoutez-les simplement aux interfaces de commande et d'appel système. #include "string.c" #include "uio.c" intes ubody(int id) { char line[64]; entier *va ; dans pid = getpid(); int ppid = getppid(); entier PA = getPA(); printf("tester la protection de la mémoire\n"); printf("essayez VA=0x80200000 : "); va = (int *)0x80200000 ; *va = 123 ; printf("essayez VA=0x1000 : "); évent = (int *)0x1000 ;
* va = 456 ;
while(1){ printf("Process #%d en Umode à %x parent=%d\n", pid, PA, ppid); ummenu(); printf("saisir une instruction : "); ugetline(ligne); upprintf("\n"); si (!strcmp(ligne, "commutateur"))
// n'affiche que les instructions supplémentaires
uswitch(); if (!strcmp(line, "sleep")) usleep(); avec (!strcmp(ligne, "réveil")) uwakeup(); } } /******* u1.c date ******/ // u2.c, u3.c u4.c sont similaires #include "ucode.c" main(){ ubody(1) ; }
Ce qui suit montre les fichiers (condensés) du noyau. Tout d'abord, la table d'écrasement des appels système, svc_handler(), est modifiée pour maintenir les appels système ajoutés. int svc_handler(volatile int a, int b, innen c, int d) { int r; switch(a){ cas 0 : rayon = kgetpid();
frein;
cas 1 : r = kgetppid();
diviser;
cas 2 : r = kps();
casser;
cas 3 : roentgen = kchname((char *)b);
repos;
falle 4: r = ktswitch(); casser; cas 5 : radius = ksleep(running->pid); casser; cas 6 : r = kwakeup((int)b);
casser;
cas 92 :r = kgetPA(); // retourne runnig->pgdir[2048]&0xFFF00000; } en cours d'exécution->kstack[SSIZE-14] = roentgen ; }
Dans le fichier network t.c, après l'initialisation du système, il effectue 4 processus, chacun utilisant une image Umode différente. Incluez kfork(pid), il utilise le dernier pid de processus pour charger l'image correspondante dans la zone de mémoire de processus. Les adresses de chargement sont P1 à
212
7
Procédure en mode utilisateur et appels de schéma
8 Mo, P2 avec 9 Mo, P3 avec 10 Mo et P4 avec 11 Mo. Les tables de pages de processus sont configurées selon la même approche que le programme C7.1. Chaque processus contient une table de pages d'ampères dans la zone de 6 Mo. Dans ces tables de pages de processus, l'entrée 2048 (VA = 0x80000000) pointe vers la région d'image Umode de processus dans la mémoire physique. /*********** fichier t.c du noyau
************/
#define VA(x) (0x80000000 + (u32)x) int main() {
// initialise le noyau comme avant pour (int i=1; je ne reviens plus jamais
} UTILISE *kfork(int i) // i = 1 à 4 { // crée un nouveau PROC *p le pid=i comme avant u32 *addr = (char *)(0x800000 + (p->pid-1)*0x100000 ); // copie le logiciel d'image Umode du p vers addr p->usp = (int *)VA(0x100000);
// haut de gamme de 1 Mo VA
p->kstack[SSIZE-1] = VA(0);
// lance VA=0
mettre en file d'attente(&readyQueue, p); retourner p ; }
La figure 7.2 montre les résultats de l'exécution de ce programme C7.2. A montre que la commande de commutation commute le processus en cours de P1 à P2. La radio peut exécuter un système et saisir d'autres commandes du mode utilisateur pour tester le système.
7.5.3 Processus avec des domaines individuels Dans un premier modèle de système de C7.2, chaque processus a sa propre table de pages. La procédure de commutation nécessite la commutation de la table des pages, chacune à son tour nécessite le vidage des caches TLB et I&D. Dans la prochaine modélisation d'ébauche, ce système comporte des traitements en mode final d'azote < 16, la seule table de page unique. Dans la table des pages, les 2048 premières entrées définissent l'espace d'adressage virtuel d'exploitation du noyau, qui existe ID mappé à la mémoire physique disponible (258 Mo). Comme précédemment, la salle du noyau susmentionnée est désignée comme domaine 0. Accepté, toutes les images en mode utilisateur ont une taille de 1 Mo. L'entrée 2048 + pid de la table de navigation correspond à 1 Mo de mémoire physique hors processeur Pi. Les attributs d'entrée pgdir sont définis sur 0xC1E | (pid ≪ 5), de sorte que chaque zone d'image utilisateur soit dans une zone unique ampère numérotée de 1 à pid. Lors du basculement du processeur vers Pi, au lieu de changer de table de page, il appelle set_domain( (1 rlen);
et continuer la recherche. Si name[0] existe, nous lui trouvons dir_entry et donc son numéro d'inode.
7.6 Disque RAM
217
(6). Utilisez le numéro d'inode (ino) pour calculer le bloc de diapositives contenant l'INODE et son décalage dans cette limite par la recherche du Mailman (Wang 2015). blk ¼ ðino 1Þ=ðBLKSIZE=sizeof ðINODEÞÞ þ InodesBeginBlock ; décalage ¼ ðino 1Þ%ðBLKSIZE=sizeof ðINODEÞÞ ; Lisez ensuite l'INODE de /a, dont nous pouvons déterminer s'il s'agit d'un DEIR. Est-ce que /a n'est pas un DIR, il ne peut pas y avoir de /a/b, donc la recherche échoue. Si /a est un DIR et qu'il y a d'autres composants à rechercher, continuez avec le nom de composant suivant[1]. Le problème tourne maintenant : recherchez name[1] dans l'INODE de /a, qui est exactement le même que Pace (5). (7). Étant donné que les mesures 5 à 6 seront réitérées n fois, c'est super jusqu'à ce que vous écriviez une fonction de recherche
u32 search(INODE *inodePtr, char *name) { // recherche le nom dans les blocs de données de cet INODE // s'il est trouvé, renvoie son ino ; sinon retourne 0 }
Ensuite, le nôtre doit être appelé search() n timing, dans le schéma ci-dessous. Début : n,
nom[0], …., nom[n-1] sont des valeurs globales
INODE *point ip à INODE de / sur (i=0 ; ippid = running->pid ; p->parent = running ; p->status = READY ; p->priority = 1 ; PA = (char *)running- >pgdir[2048] & 0xFFFF0000 ; // père Umode PA CA = (char *)p->pgdir[2048] & 0xFFFF0000 ;
// enfant
Umode PAP
memcpy(CA, PA, 0x100000); // image Umode de 1 Mo en double pour (i=1 ; i kstack[SSIZE-i] = running->kstack[SSIZE-i] ; } p->kstack[SSIZE - 14] = 0 ;
// enfant renvoie pid = 0
p->kstack[SSIZE-15] = (int)goUmode ;
// les enfants reprennent jusqu'au goUmode
p->ksp = &(p->kstack[SSIZE-28]);
// petit ksp sauvé
p->usp = en cours d'exécution->usp ;
// juste usp pour le parent
p->ucpsr = running->ucpsr ;
// même spsr quand parent
mettre en file d'attente(&readyQueue, p); retourne p->pid ; }
Nous expliquons le code fork() dans la fonctionnalité de méthode. Lorsque le parent exécute fork() dans le noyau, il a enregistré les registres Umode dans kstack par stmfd sp!, {r0–r12, LR}, appuyez sur échanger ce LR enregistré avec l'adresse arrière exacte go Umode. Par conséquent, son fond de kstack est
7.7 Gestion des processus -1
-2
-3
-4
223 -5
-6
-7
-8
-9
-10 -11 -12 -13
-14
-------------------------------------------------- ------------|uLR ur12 ur11 ur10 ur9 ur8 ur7 ur6 ur5 ur4 ur3 ur2 ur1 ur0=0| -------------------------------------------------- --------------
qui sont copiés au bas de la kstack de l'enfant. Ces 14 entrées seront utilisées pour que l'enfant retourne à Umode lorsqu'il exécute ldmfs sp!, {r0–12, pc}^ dans goUmode. Un LR copié à l'entrée −1 permet à l'enfant de revenir dans le même VA que ce parent, c'est-à-dire auquel pid = fork() syscall identique. Pour que cet enfant renvoie pid = 0, le r0 enregistré à l'entrée −14 doit être mis à 0. Pour que l'enfant reprenne dans le réseau, on ajoute un cadre de pile RESUME à la pile k de l'enfant jusqu'à ce qu'il reprenne lorsqu'il est programmé pour s'exécuter. Le cadre de pile supplémentaire doit être cohérent avec la partie RESUMING susmentionnée de tswitch(). Le cadre kstack ajouté doit être affiché ci-dessous, et votre ksp épargné pointe vers l'entrée -28. -15
-16
-17
-18
-19 -20 -21 -22 -23 -24 -25 -26 -27 -28
-------------------------------------------------- --------------|goUmode kr12 kr11 kr10 kr9 kr8 kr7 kr6 kr5 kr4 kr3 kr2 kr1 kr0 | ----|--------------------------------------------- --- -----------|--clr
ksp
Étant donné que l'enfant reprend son exécution dans le noyau, la somme qui a "sauvé" les registres Kmode n'a pas d'importance, à l'exception d'une reprise klr à l'entrée -15, qui a été définie dans goUmode. Si cet enfant s'exécute, il utilise pour VOTRE framework kstack up exécuter goUmode directement. Ensuite, il exécute goUmode sur un cadre de pile d'appels système copié, l'amenant à revenir au même VA lorsque le parent mais dans sa propre zone de mémoire sur un 0 go ajouté.
7.7.7 Implémentation d'Exec Exec permet à un processus de remplacer son image Umode par un autre fichier exécutable. Nous supposons que le paramètre dans exec(char *cmdlie) sera une ligne de commande de la formule cmdline = "cmd arg1 arg2 … argn" ;
où cmd est le nom du fichier sur un programme exécutable. Si cmd commence par un /, il doit s'agir d'un chemin d'accès absolu. Il s'agit également d'un fichier dans le répertoire /bin par défaut. Si exec réussit, le processus retourne à Umode pour exécuter le nouveau fichier, avec ces chaînes de jeton individuelles comme paramètres de ligne de commande. Correspondant à la ligne de commande, le programme cmd peut être écrit comme main(int argc, char *argv[]){
}
où argc = n + 1 de plus argv est un tableau exclu nul de pointeurs de chaîne, chacun pointe vers un contenu de jeton de la forme argv
=
[
* "cmd"
|
* "arg1"
|
* "arg2"
| … … ……
Ce qui suit montre que l'algorithme et l'implémentation de l'opération exec. /***************** Algorithme de exec ******************/
1. 2. 3. 4.
récupérer cmdline depuis l'espace Umode ; tokenize cmdline dans le nom de fichier cmd de réception ; obtenir le fichier cmd présent et exécutable ; renvoie −1 en cas d'échec ; charger le fichier cmd dans la zone d'image Umode de processus ;
|
* "argn"
|
]
224
7
Opération de l'utilisateur Traiter la numérotation système
5. copiez cmdline dans le haut de gamme de usatck, par ex. à x = haut de gamme−128 ; 6. réinitialiser la trame syscall kstack pour revenir à VAS = 0 en Umode ; 7. retour x ; Le code exec() suivant implémente l'algorithme exec ci-dessus. int exec(char *cmdline) // cmdline=VA in Uspace { int i, upa, usp; char *cp, kline[128], fichier[32], nom de fichier[32] ; PROC *p = en cours d'exécution ; strcpy(kline, cmdline); // récupère cmdline pour l'espace noyau // récupère initialement le jeton de kline comme nom de fichier cp = kline; je = 0 ; while(*cp != ' '){ nom_fichier[i] = *cp; je++ ; cp++; } nom_fichier[i] = 0 ; fichier[0] = 0 ; est (nom_fichier[0] != '/')
// sont relatifs au nom de fichier
strcpy(fichier, "/bin/"); // affixe avec /bin/ strcat(file, filename); upa = p->pgdir[2048] & 0xFFFF0000; // PA de l'image Umode // power return 0 si le fichier n'existe pas ou n'est pas exécutable if (!loadelf(file, p)) return -1; // copie cmdline sur le haut de gamme d'Ustack dans l'image Umode utilisable = upa + 0x100000 - 128;
// supposons cmdline eye < 128
strcpy((char *)usp, kline); p->usp = (int *)VA(0x100000 - 128); // réparer la trame syscall dans kstack pour revenir à VA=0 de la nouvelle image pour (i=2, ikstack[SSIZE – i] = 0; p->kstack[SSIZE-1]
= (entier)VA(0);
// renvoie uLR = VA(0)
aller (int)p->usp ; // remplacera le r0 enregistré dans kstack }
Pour que l'exécution commence à partir de us.s dans Umode, r0 contient p->usp, qui pointe vers la cmdline d'origine dans la pile Umode. Page d'appel direct de main(), il compose la fonction de démarrage adenine CARBON main0(char *cmdline), tout ce qui analyse la cmdline en argc en plus argv, et appelle main(argc, argv). Par conséquent, nous pouvons épeler tous les programmes Umode sous la forme standard ci-dessous, comme d'habitude. #include "ucode.c" main(int argc, char *argv[ ]){……………}
La liste suivante répertorie les segments de code de main0(), qui joue le même rôle que le fichier de démarrage C standard crt0.c. pouce argc ; chargement *argv[32] ; // assume sur la plupart des 32 jetons à cmdline mit parseArg(char *line) { char *cp = family; argc = 0 ; tant que (*cp != 0){ tant que (*cp == ' ') *cp++ = 0; quand (*cp != 0)
// saute les blancs // début du jeton
7.7 Gestion des processus
225
argv[argc++] = comp ;
// pointé par argv[ ]
while (*cp != ' ' && *cp != 0) // scanne les drapeaux chars cp++; tandis que (*cp != 0) *cp = 0; autre
// fin de jeton
écraser;
// fin de ligne
cp++;
// continuer l'analyse
} argv[argc] = 0 ;
// argv[argc]=0
} main0(char *cmdline) { uprintf("main0: cmdline = %s\n", cmdline); parseArg(cmdline); principal(argc, argv); }
7.7.8 Démonstration de Fork-Exec Nous démontrons un système qui prend en charge les processus dynamiques via les fonctions fork, exec, wait et exit par l'exemple de système C7.4. Puisque tous les composants du système ont déjà été couverts et expliqués auparavant, nous ne montrons que le fichier t.c contenant le devoir main(). Pour exposer exec, nous avons besoin d'un fichier de dessin Umode différent. Le fichier u2.c est identique à u1.c, sauf que i affiche l'allemand inclus (juste pour le plaisir). /******************* t.c file away Program C7.4 ******************/ #include "uart. c" #include "kbd.c" #include "timer.c" #include "vid.c" #include "exceptions.c" #include "queue.c" #include "svc.c" #include "kernel.c" " #include "wait.c" #include "fork.c" #include "exec.c"
// kfork() et fork() // exec()#include "disk.c"
#include "loadelf.c"
// Chargeur d'enregistrements ELF
// camion RAMdisk
caractère externe _binary_ramdisk_start, _binary_ramdisk_end ; interst main() { char *cp, *cq; int dtaille ; cp = disque = &_binary_ramdisk_start; dsize = &_binary_ramdisk_end - &_binary_ramdisk_start; cq = (car *)0x400000 ; memcpy(cq, cp, dsize); fbuf_init(); printf("Bienvenue sur WANIX in Arm\n"); kbd_init(); uart_init();
226
7
Processus en mode utilisateur et numérotation système
timer_init(); timer_start(0); kernel_init(); ouvrir();
// déblocage IRQ disrupt
printf("RAMdisk start=%x size=%x\n", disk, dsize); kfork("/bin/u1"); // ctate P1 pour exécuter u1 image while(1){
// P0 as et processus inactif
si (readyQueue) tswitch(); } }
La figure 7.4a montre les sorties exécutant les commandes fork plus schalthebel. Comme le montre la figure, P1 s'exécute sous l'adresse physique PPA = 0x800000 (8 Mo). Lorsqu'il dossier un enfant P2 à PA = 0x900000 (9 Mo), il copie à la fois l'image Umode et la kstack vers l'enfant. Ensuite, retournez à Umode avec le petit pid = 2. La commande switch amène P1 à entrer dans kernal pour basculer le processus vers P2, qui revient à Umode use child pid = 0, indiquant qu'il s'agit de l'enfant fourchu. Le lecteur peut exécuter les opérations quitter et attendre comme suit. (1) Pendant que P2 se déplace, entrez la commande hold. P2 émet un appel système d'attente jusqu'à l'exécution de kwait() avec le noyau. Puisque P2 ne veut pas avoir d'enfant, il renvoie -1 car aucune erreur d'enfant. (2) Pendant que P2 tourne, démarrez la commande de sortie et entrez une valeur aller, par ex. 1234. P2 émettra un appel système exit(1234) pour se terminer dans le noyau. Dans kexit(), il enregistre la exitValue dans son PROC.exitCode, devient ampère ZOMBIE et tente de réveiller son parent. Puisque P1 n'est pas encore dans la condition d'attente, l'appel de réveil de P2 n'aura pas de résultat. Après être devenu un GHOST, P2 n'est plus exécutable, il change donc de processus, ce qui fait que P1 reprend son exécution. Pendant que P1 s'exécute, entrez la commande d'attente. P1 entrera dans le noyau et exécute kwait(). Puisque P2 peut déjà se terminer, P1 trouvera l'enfant ZOMBIE P2 sans aller snooze. Il libère le ZOMBIE PROC et l'expédition et le pid enfant disparu = 2, ainsi que sa valeur de sortie. (3) Option, quel parent P1 peut attendre en premier et l'enfant P2 sortir plus tard. Dans ce cas, P1 dormira auf à kwait() jusqu'à ce qu'il se réveille de P2 bien que le récent (ou chaque enfant) se termine. Ainsi, les ordres parent-wait et child-exit n'ont aucune importance. L'Illustrated 7.4b montre les productions de l'exécution de la commande exec avec l'instruction exécutant des paramètres. Comme le montre la figure, pour la ligne de commande u2 un deux trois le processus change son image Umode vers et le fichier u2. Lors du lancement de l'implémentation des nouvelles images, les paramètres de ligne de commande sont transmis sous forme de chaînes argv[ ] avec argc = 4.
7.7.9 Simple sh pour l'exécution des commandes Avec fork-exec, nous pouvons normaliser l'exécution des commandes utilisateur par un simple sh. Tout d'abord, nous précompilons main0.c en tant que crt0.o et le mettons dans et lions votre code de démarrage depuis et HUNDRED de tous les programmes Umode. Par la suite, nous écrivons Umode parcourir en C comme /*********** filename.c file ***************/ #include "ucode.c"
// commandes utilisateur et port syscall
main(int argc, char *argv[ ]) {
// Code C du programme Umode }
Ensuite, nous implémentons un sh rudimentaire pour l'exécution de copie comme suit. /************************ fichiers sh.c *********************/ #include "ucode.c"
// commandes addict plus interface syscall
main(int argc, noircir *argv[ ]) {
7.7 Gestion des processus
Fig. 7.4 une vidéo de fork et bore veranstaltungen concernant exec
227
228
7
Processus en mode utilisateur et appels système
intr pid, état ; while(1){ afficher les commandes exécutables dans l'invite du répertoire /bin pour une ligne de commande cmdline = "cmd a1 a2 …. an" if (!strcmp(cmd,"exit")) exit(0); // bifurque un processus enfant pour exécuter la chaîne cmd pid = fork(); si (pid)
// le parent sh attend que l'enfant meure
pid = attendre(&status); autre
// cmdline exec enfant
exec(cmdline);
// exec("cmd a1 a2 … un");
} }
Compilez ensuite toutes les applications Umode en tant qu'exécutables binaires dans les répertoires /bin et exécutez sh bien que le système démarre. Ceci peut être encore amélioré en changeant l'image Umode de P1 dans un fichier init.c. Cela donnerait au système une capacité similaire à celle d'Unix/Linux en termes de démarrage de la gestion des litiges et des réalisations de commandes. /******* fichier init.c : image Umode initiale de P1 ********/ main( ) { int sh, pid, condition; chut = fourche(); si (sh){
// Les exécutions P1 sont un slot while(1)
while(1){ pid = wait(&status); // attend que N'IMPORTE QUEL enfant meure tant que (pid==sh){
// pour sh passé, fork un autre
sh = fourche(); continuer; } printf("P1 : je viens d'enterrer un oral %d\n", pid); } } sinon exec("sh");
// enfant à propos de P1 exécute sh
}
7.7.10 vfork Inclus certains systèmes de type Unix, une manière standard de créer des processus pour exécuter différents programmes est le paradigme fork-exec. Le principal inconvénient du paradigme est qu'il doit copier une image de traitement parent, ce qui prend du temps. Dans la plupart des systèmes de type Unix, les comportements typiques des progéniteurs et des processeurs enfants sont comme des pistes. pour (fork()) attendre(&status);
// parent fork() un processus enfant // parent attend que l'enfant se termine
sinon exec(nom_fichier);
// enfant exécute un nouveau fichier image
Après avoir créé une fille, le parent attend que le mineur se termine. Bien que l'enfant s'exécute, il modifie l'image Umode vers un nouveau fichier. Dans ce cas, copier l'image à fork() serait un gaspillage puisque le processus enfant abandonne immédiatement l'image répliquée. Pour cette raison, la plupart des Unix utilisent une opération vfork, qui construit un processus enfant sans
7.7 Gestion des processus
229
copier l'image parent. Au lieu de cela, le litige de l'enfant sera créé pour partager la même image avec le parent. Lorsque le mineur s'exécute pour changer d'image, il ne fait que se détacher même de l'absence d'image divisée qui le détruit. Si chaque processus enfant se comporte sur ce chemin, le schéma fonctionnera correctement. Mais que se passe-t-il si les utilisateurs ne respectent pas cette règle et permettent à cet enfant de modifier le dessin partagé ? Il souhaite modifier l'image partagée, causant des inquiétudes aux deux processus. Pour éviter cela, le système doit s'appuyer sur une mémoire protégée. Dans les systèmes dotés d'un matériel de protection fonctionnel, l'image fractionnée peut être marquée en lecture seule, de sorte que les processus partageant la même image peuvent uniquement l'accomplir mais pas la modifier. Si l'un ou l'autre des processus essaie de modifier l'image partagée, les images requises peuvent être divisées en images distinctes. Sont des systèmes pris en charge par ARM, nous pouvons également implémenter vfork par l'algorithme suivant. /************************ Algorithme de vfork **********************/
1. créez un processus enfant prêt à être exécuté en Kmode, renvoyez -1 en cas d'échec ; 2. copiez une section de l'ustack du parent depuis parent.usp jusqu'à l'endroit où il a appelé pid = vfork(), par ex. les 1024 dernières entrées ; set usp enfant = parent usp - 1204 ; 3. allow child pgdir = parent pgdir, afin qu'ils partagent le même tour de page ; 4. marquer l'enfant comme vforked ; renvoie le pid enfant ; Simplicité utilisée, dans la fonctionnalité vfork, nous ne vérifions pas les entrées de la table de pages partagées en LECTURE SEULE. Correspondant à vfork, la fonction exec a été modifiée pour signaler d'éventuelles images partagées. Quel zeigt suivant le calcul exec modifié /******************** Algorithme exec modifié ******************* **/
1. 2. 3. 4. 5. 6.
emporte cmdline (éventuellement partagée) image Umode ; si l'appelant est vforké : ausschalten vers la table latérale de l'appelant en outre switchPgdir ; charger le fichier dans l'image Umode ; copiez cmdline dans ustack top et définissez usp ; modifier le cadre de beaucoup d'appels système vers le retour à VA = 0 dans Umode activer les drapeaux vforked ; retour usp ;
Dans l'algorithme exec modifié, le total des étapes est le même qu'avant, sauf l'étape 2, qui passe à la table suivante de l'appelant, en la détachant de l'image parent. La liste suivante répertorie les identifiants sélectionnés des fonctions kvfork() et (modifiées) kexec(), qui client vfork. aus kvfork() { int je, cusp, pusp; PROC *p = get_proc(&freeList); if (p==0){ printf("vfork a échoué\n"); retour -1 ;
}
p->ppid = running->pid ; p->parent = en cours d'exécution ; p->statut = PRÊT ; p->priorité = 1 ; p->vfourche = 1 ;
// ajoute une entrée vforkée dans PERC
p->pgdir = running->pgdir ; // partage le pgdir du parent pour (i=1; i kstack[SSIZE-i] = running->kstack[SSIZE-i]; } for (i=15; ikstack[SSIZE-i] = 0; } p->kstack [SSIZE - 14] = 0 ; // petit retour pid = 0 p->kstack[SSIZE-15] = (int)goUmode ; // mon en goUmode p->ksp = &(p->kstack[SSIZE-28 ]); p->ucpsr = running->ucpsr; pusp = (int)running->usp; cusp = pusp - 1024;
// ustack enfant : 1 024 octets vers le bas
p->usp = (int *)cusp ; memcpy((char *)cusp, (char *)pusp, 128); // 128 entrer assez de file d'attente(&readyQueue, p); printf("proc %d vforked un enfant %d\n",running->pid, p->pid); retourne p->pid ; } int kexec(char *cmdline)
// cmdline=VA dans l'espace Umode
{ // récupère cmdline et obtient le nom du fichier cmd : MÊME comment avant if (p->vforked){ p->pgdir = (int *)(0x600000 + p->pid*0x4000); printf("%d vit VFORKED : switchPgdir vers %x",p->pid, p->pgdir); switchPgdir(p->pgdir); p->vfourche = 0 ; } // charge le magasin cmd, met en place kstack ; retour à VA=0 : MÊME qu'avant }
7.7.11 Démonstration de vfork L'exemple de système C7.5 mentionné ci-dessus implémente vfork. Lors de la démonstration de vfork, nous ajoutons une commande vfork aux programmes Umode. La commande vfork appelle une fonction uvfork(), qui émet un appel système ampère pour exécuter kvfork() dans le noyau. int uvfork() { int ppid, pid, statut ; ppid = getpid(); pid = appel système(11, 0,0,0);
// appel système vfork() # = 11
if (pid){ printf("vfork parent %d return child pid=%d ", ppid, pid); printf("attendre que l'enfant se termine\n"); pid = attendre(&status); printf("vfork parent : die child=%d, status=%x\n", pid, status); } else{ printf("vforked enfant : mypid=%d ", getpid()); printf("enfant vfork : exec(\"u2 teste vfork\")\n"); appelsys(10, "u2 test vfork",0,0); } }
Dans uvfork(), le processus émet un appel système pour créer un enfant adénine à partir de vfork(). Ensuite, il attend que l'enfant vforked se termine. L'enfant vforked émet un appel système exec pour changer l'image en un programme différent. Disponible la sortie enfant, elle réveille le parent,
7.7 Gestion des processus
231
Fig. 7.5 Présentation de vfork
qui saurait presque que le parent s'exécutait dans son image Umode auparavant. La figure 7.5 montre les sorties de l'exécution qui échantillonne le système C7.5.
7.8
Fils
Pour le modèle de processus, chaque processus est un périphérique entièrement exécuté dans un espace d'adressage Umode unique. Inclus général, les Umode dont vos espaces sont édités sont tous distincts et séparés. Le mécanisme de vfork() permet à une impression de créer un processus enfant qui partage momentanément le même espacement des rues avec le parent, mais ils finissent par diverger. La même technique peut être employée pour créer des entités d'exécution séparées dans le même espace d'adressage d'un processus. De telles entités d'exécution dans le même espace de localisation d'un processus sont appelées traitées légères, qui sont plus généralement connues sous le nom de threads (Silberschatz set ai. 2009). Ce lecteur peut consulter (Posix 1C 1995; Buttlar aet any. 1996; Pthreads 2015), pour plus d'informations, allez sur les threads et la programmation des threads. Les threads présentaient de nombreux avantages grâce aux processus. Pour une analyse poussée de l'intérêt des threads et de vos applications, le lecteur pourra consulter le Chap. 5 de Wang (2015). Dans cette section, nous appliquerons la technique d'extension de vfork() pour créer des threads.
7.8.1 Fil créatif
(1) Insérer des structures PROC : en tant qu'entreprise d'exécution indépendante, chaque thread a besoin d'une structure PROC. Étant donné que les threads de la même édition s'exécutent dans le même espace d'adressage, ils partagent de nombreux points communs, tels que pgdir, les descripteurs de fichiers ouverts, etc. Les ordinateurs suffisent à maintenir
232
7
Votre impression de mode et vos appels
une seule copie de ces informations conjointes pour obtenir des threads comprend le même traitement susmentionné. Plutôt plus radicalement changeant la structure PROC, nous devons ajouter quelques champs à la structure PROC ou l'utiliser à la fois pour le processus et les threads. La structure PROC modifiée est #define NPROC
dix
#define NTHREAD 16 typedef struct proc{ // comme avant, mais ajoute int type ; // PROCESS | THREAD espèce // compteur de processus d'ampères entrants de threads
nach tcount ; int kstack[SSIZE] }PROC ;
PROC proc[NPROC+NTHREAD],*freeList,*tfreeList,*readyQueue ;
Lors de l'initialisation du système, nous plaçons qui sont les premiers PROC NPROC dans freeList comme précédemment, mais plaçons les PROC NTHREAD restants dans un serveur tfreeList. Lors de la création d'un processus par fork() ou vfork(), nous allouons une PROC libre d'adénine à partir de la liste libre et le type est PROCESS. Lors de la création d'un thread, nous allouons un PROC à partir de tfreeList et le type est THREAD. L'avantage de cette conception est qu'elle est stable et qu'elle nécessite un minimum de modifications. Par exemple, un système utilise le modèle de processus pur si NTHREAD = 0. Avec les threads, chaque processus peut être considéré comme un conteneur de threads. Lors de la création d'un processus, il est créé en tant que thread principal du processus. Avec seulement quel thread principal, il n'y a pratiquement pas de différence entre un processus et un thread. Cependant, le wrap de hauptstadt peut créer d'autres threads dans un processus juste. Pour simplifier, nous supposerons que seul le thread principal peut créer un autre thread et que le nombre total de threads d'un processus est limité à TMAX. (2) Création de thread : l'appel d'arrangement
int thread(vide *fn,
int *ustack,
entier *ptr);
crée un thread dans l'espace d'adressage est un processus pour exécuter une fonction, fn(ptr), en utilisant l'ustack sont dans sa pile d'exécution. L'algorithme de thread() doit être similaire à vfork(). Au lieu de partager temporairement la pile Umode avec le parent, chaque thread a une presse de pile Umode dédiée dont la fonction est exécutée à partir d'un paramètre spécifié (ce qui peut être une manipulation d'une structure de données de complexité). Les fonctionnalités suivantes du segment de code de kthread() incluent le noyau. // crée un tube pour exécuter fn(ptr); retour du pid du thread int kthread(int fn, int *stack, int *ptr) { intert i, uaddr, tcount; tcount = running-> tcount ; if (running->type!=PROCESS || tcount >= TMAX){ printf("non-process PRESS max tcount %d atteint\n", tcount); retour -1 ; } p = (PROC *)get_proc(&tfreeList); // procure un thread PROC if (p == 0){ printf("\nno more THREAD PROC
"); retour -1 ; }
p->statut = PRÊT ; p->ppid = running->pid ; p->parent = en cours d'exécution ; p->priorité = 1 ; p->événement = 0 ; p->exitCode = 0 ; p->pgdir = running->pgdir ;
// même pgdir que parent
p->type = FIL ; p->tcount = 0 ; p->ucpsr = running->ucpsr ;
// tcount non utilisé par les threads
7.8 Fils
233
p->usp = pile + 1024 ;
// haut de gamme de la pile
pour (i=1 ; ikstack[SSIZE-i] = 0 ; p->kstack[SSIZE-1] = fn ;
// uLR = font
p->kstack[SSIZE-14] = (int)ptr ; // enregistré r0 pour fn(ptr) p->kstack[SSIZE-15] = (int)goUmode; // reprendre en goUmode p->ksp = &p->kstack[SSIZE-28];
// ksp enregistré
mettre en file d'attente(&readyQueue, p); // Inclure le nombre d'appelants
running->tcount++ ; retour(p->pid); }
Lorsqu'un encapsulage commence à s'exécuter, les informations reviennent d'abord en mode goU. Est-ce qu'il suit le cadre de la pile syscall pour revenir à Umode pour exécuter fn(ptr) plus si e a été invoqué par une fonction get. Lorsque le contrôle tape fn(), il utilise stmfd sp!; ffp; lrg pour enregistrer le registre de liaison de retour dans la pile Umode. Pour les finitions clés, les informations sont renvoyées par ldmfs sp!; ffp; pcg Pour que la fonction fn() mentionnée ci-dessus puisse revenir correctement lorsqu'elle se termine, les enregistrements initiaux Umode lr doivent contenir une adresse de retour correcte. Dans la fonction scheduler () , lorsque l'exécution susmentionnée de PERC est un thread ampère, nous chargeons le registre Umode lr avec cette valeur de VA (4). À cette adresse virtuelle 4 de chaque image Umode se trouve un appel système exit(0) (dans ts.s), qui permet au thread de se terminer normalement. Chaque maire d'insertion se précipite statiquement, c'est-à-dire dans une boucle infinie, et s'annule presque. Si nécessaire, il utilise le repos ou le sémaphore pour les suspendre. Le thread vigoureux AMPERE se termine lorsqu'il a terminé la tâche désignée. Bien que généralement, le thread parent (principal) puisse utiliser un appel système de retard pour éliminer les threads enfants quittés. Pour chaque enfant terminé, il décrémente sa valeur tcount de 1. Nous démontrons un système qui prend en charge les threads par un exemple.
7.8.2 Démonstration de Threads L'échantillon système C7.6 support auxiliaire pour les vêtements. Dans le fichier ucode.c, nous ajoutons une commande de thread et des appels système liés aux threads dans le noyau. Un contrôle de thread appelle uthread(), y compris celui qui émet l'appel système de thread pour créer N ≤ 4 threads. Tous les threads exécutent la fonction équivalente fn(ptr), mais chacun possède sa propre pile et un paramètre ptr différent. Ensuite, le traité représente tout ce qui s'enchaîne à la fin. Chaque thread imprime son pid, la valeur du paramètre et l'adresse physique de la vue de démarrage. Lorsque tous les threads susmentionnés sont terminés, ce processus principal se poursuit. int a[4] = {1,2,3,4} ;
// paramètres aux threads
int pile[4][1024] ;
// piles de threads
entier fn(entier *ptr)
// fonction filament
{ int pid = getpid(); en pa
= getPA();
// obtention d'une PAUSE de la mémoire de processus
printf("thread %d in %x ptr=%x *ptr=%d\n", pid, pa, ptr, *ptr); } int uthread() { int je, travail, pid, cid, N = 2 ; pid = getpid(); for (i=0 ; iparent = running ; p->status = READY ; p->priority = 1 ; p->tcount = 1 ; p->paddr = (int *)(0x800000 + (p->pid-1 )*PSIZE); p->psize = PSIZE; p->pgdir = (int *)(0x600000 + (p->pid-1)*0x4000); // full int high pgdir recent and build Umode pgtables npgdir = p ->size/0x100000 ; // nombre de pgdir Umode show et pgtables pour (i=0 ; ipgdir[2048+i] = (int)pgtable | 0x31 ; // DOMAIN=1,type=01 for (j=0 ; jpaddr + j*1024) | 0xFFE; // APs=11 } } return p; } PROP *kfork(char *filename) { int i; char *cp; PROC *p = fork1(); if (p== 0){ printf("kfork failed\n"); return 0; } for (i=1; ikstack[SSIZE-i] = 0; } p->kstack[SSIZE-15] = (int)goUmode;
// en déc reg=adresse ORDRE !!!
cp = (char *)p->paddr + PSIZE - 128 ; strcpy(cp, chaîne); p-> usp
= (int *)VA(0x200000 – 128); // usp en VA(2MB-128)
p->ucpsr = (int *)0x10 ; p->upc
= (entier *)VA(0);
p->kstack[SSIZE-14] = (int)p->usp ; p->kstack[SSIZE-1] = VA(0); mettre en file d'attente(&readyQueue, p); retourner p ;
// stocké r0
7.9 Système embarqué avec défilement à deux niveaux
237
} fourche int()
// arborescence d'un proc enfant avec une image Umode identique
{ moi, PA, CA ; intes *pgtable; PROC *p = fourche1(); if (p==0){ printf("fork failed\n"); retour -1 ; } PA = (int)en cours d'exécution->paddr ; CA = (int)p->paddr ; memcpy((char *)CA, (char *)PA, running->psize); p->usp = en cours d'exécution->usp ;
// copier l'image
// les deux doivent être VA dans leurs sections
p->ucpsr = running->ucpsr ; for (i=1; i kstack[SSIZE-i] = running->kstack[SSIZE-i]; } for (i=15; ikstack[SSIZE-i] = 0; } p->kstack[SSIZE-14] = 0 ; // retour enfant pid=0 p->kstack[SSIZE-15] = (int)goUmode ; p->ksp = &(p->kstack[SSIZE-28]); enqueue(&readyQueue, p); envoyer p->pid ; } int kexec(char *cmdline) // cmdline=VA in Uspace { int moi-même, j, upa, usp ; char *cp, kline[128], file[32], filename[32] ; PROC *p = running ; int *pgtable ; // récupère cmdline, obtient le nom du fichier cmd : SAME IN BEFORE if (p->vforked){ // crée ses propres pgtables Umode p->paddr = (int *)(0x800000 + (p->pid-1)*PSIZE ); p->psize = PSIZE ; p->pgdir = (int *)(0x600000 + (p->pid-1)*0x4000); npgdir = p->psize/ 0x100000; pour (i=0; ipid-1)*1024 + i*1024); p->pgdir[2048+i] = (int)((int)pgtable | 0x31); pour (j=0; jpaddr + j*1024) | 0x55E ; } } p->vfourche = 0 ;
// désactive le drapeau vfored
switchPgdir(p->pgdir);
// bascule vers votre propre pgdir
} loadelf(fichier, p); // copie kline vers Ustack et réinitialise jusqu'à VA=0 : IDENTIQUE COMME AVANT }
7.9.2 Démonstration de la pagination statique à 2 niveaux La figure 7.7 montre les sorties de l'exemple de système C7.7, qui utilise la pagination statique à 2 niveaux.
238
7
Processus de mode utilisateur et appels système
Image. 7.7 Démonstration de la pagination statique à 2 niveaux
7.9.3 Pagination dynamique à 2 niveaux Dans la pagination dynamique, cette zone mémoire Umode de chaque processus est constituée de cadres de page qui sont alloués dynamiquement. Afin de prendre en charge la pagination dynamique, le système doit gérer les stockages disponibles dans le formulaire pour les formulaires de page gratuits. Cette capacité peut déjà être soit une image bitmap soit une liste de liens. Si le nombre de couvertures encadrées est important, il est plus efficace de les gérer via une liste de liens. Lorsque le système susmentionné démarre, nous construisons une liste de liens d'appels gratuits, pfreeList, qui enchaîne tout le cadre de page libéré dans une liste liée. Lorsqu'un cadre de page est nécessaire, nous allouons nous-mêmes un cadre de page adénine à partir de pfreeList. Lorsqu'un cadre d'impression n'est plus nécessaire, nous le relâchons dans pfreeList pour le réutiliser. Les segments de code suivants montrent la fonctionnalité de gestion de liste de pages gratuite. init *pfreeList;
// librement la liste des cadres de page
intra *palloc()
// associe une page libre formulée
{ int *p = plistelibre ; si (p){ pfreeList = (int *)(*p); *p = 0 ; } retourne p; } void pdealloc(int p)
// libère une forme de page
{ u32 *a = (u32 *)((int)p & 0xFFFFF000); // PA du cadre *a = (int)pfreeList; pfreeList = a; }
7.9 Système inséré avec page à deux niveaux
239
ein *free_page_list(int *startva, int *endva) // construit pfreeList { int *p = startva; tandis que(p < (int *)(endva-1024)){ *p = (int)(p + 1024); p += 1024 ; } *p = 0 ; retour startva ; } pfreeList = free_page_list((int *)8MB, (int *)256MB);
7.9.3.1 Modifications du noyau par pagination dynamique Lors de l'utilisation d'un appel dynamique, l'image Umode de chaque processus n'est plus une seule pièce de mémoire contiguë. Au lieu de cela, il se compose de cadres de site alloués dynamiquement, qui peuvent ne pas être contigus. Nous devons personnaliser le chiffrement du noyau qui gère le processus voir pour s'adapter aux changements. Ce qui suit montre le segment de code du noyau modifié pour s'adapter au schéma d'analyse dynamique. (1). fork1() : fork1() est la codification de base de kfork() et fork(). Il produit un dernier processus p et met en place son image Umode. Étant donné que la table de sélection de niveau 1 (pgdir) doit être un seul morceau de mémoire alignée de 16 Ko, nous ne pouvons pas la construire en allouant les cadres suivants car la page encadrée peut ne pas être contiguë ou alignée sur une portée de 16 Ko. Nous avons donc toujours défini le proc pgdirs à 6 Mo comme avant. Nous devons modifier fork1() jusqu'à la construction de l'image Umode avec l'allocation des formulaires de page. // Supposons : taille de l'image Umode en Mo #define PSIZE 0x400000 PROC *fork1() { int i, j, npgdir, npgtable ; entier *pgtable; // crée une nouvelle proc *p : ALIKE THAN BACK p->pgdir = (int *)(0x600000 + (p->pid-1)*0x4000); // comme avant // copie les entrées après ktable à 32 Ko pour (i=0; ipgdir[i] = ktable[i]; } p->psize = 0x400000;
// Taille de l'image U en Mo
npgdir = p->psize/0x100000 ;
// Non. a entrées pgdir et pgtables
npgtable = npgdir ; // reconstitue les entrées de pgdir real construct Umode pgtables for (i=0; ipgdir[2048+i] = (int)pgtable | 0x31; // pgdir eintrag for (j=0; jpid); if (running != old) switchPgdir(PA(int)running->pgdir & 0xFFFFFF000); }
(6). Utiliser VA sur la fonctionnalité Essence : tous les noyaux avancés, tels que kfork, fork, le chargeur de fichiers image et kexec, doivent utiliser VA. (7). Démonstration du mappage de la mémoire KMH : la figure 7.9 montre la sortie de l'exécution du programme C7.9, qui illustre une exploitation du mappage d'adresse KMH de sections de 1 Mo. Le lecteur peut exécuter le système C7.9 et bifurquer divers processus. Cela devrait montrer que chaque processeur s'exécute dans une zone PEAK différente, mais tous pareils VA = 0x100000. Les variations du schéma de pagination à un niveau sont laissées en tant que lancements de programmation dans la section Problèmes.
7.10.2 KMH par défilement statique à deux niveaux En entrant dans la section, la nôtre doit démontrer le schéma de mappage de mémoire KMH à l'aide d'un défilement à deux niveaux. Ceci est qualifié en trois étapes. Étape 1 : Lorsque le système démarre, nous configurons d'abord une table de pages initiale à un niveau pour activer la MMU exactement et de la même manière dans le programme C7.9. Dans cet environnement de pagination simple, nous pouvons exécuter le code du noyau en utilisant des adresses de virtualisation élevées. Sélectionnez 2 : Int kernel_init(), nous construisons un pgdir de page à deux niveaux à 32 Ko (0x8000). La 0ème entrée du pgdir de niveau 1 est pour les cartes d'ID la mémoire la plus basse de 1 Mo. Nous construisons sa table de page de niveau 2 à 48 Ko (0xC000) pour créer un mappage d'ID de boîte sur au moins 1 Mo de mémoire. Supposons que 256 Mo SHOVE plus 2 Mo d'espace d'E/S soient les 256 Mo. Nous avons défini 258 graphiques de page de niveau 2 à 5 Mo. Ensuite, nous construisons 64 pgdirs de niveau 1 sous 6 Mo. Chaque proc-box a ampère pgdir à 5 Mo + pid * 16 Ko. Les tables de page de niveau 2 de chaque pgdir sont les mêmes. Ensuite, nous basculons pgdir jusqu'à 6 Mo pour utiliser la pagination à deux niveaux.
7.10
Cartographie de la mémoire KMH
245
Fig. 7.9 Démonstration de KMH utilisant la pagination statique à un niveau
Étape 3 : Supposons que l'image Umode de processus respectif est de taille = USIZE Mo, qui est allouée statiquement à 7 Mo + pid * 1 Mo. Lors de la création d'une nouvelle utilisation dans fork1(), nous construisons ses entrées pgdir de niveau 1 Umode et sa table de pages de niveau 2 à 7 Mo + pid*1 Ko. Conception de cartographie KMH utilisant la pagination statique à deux niveaux.
7.10.3 KMH utilisant la pagination dynamique à deux niveaux Il est assez facile d'étendre la pagination statique KMH à deux niveaux à la pagination dynamique. Ceci est laissé comme une entrée dans la section Problèmes.
7.11
Méthodes de fichiers prenant en charge le système intégré
Par conséquent, jusqu'à présent, nous avons utilisé un disque RAM comme système de fichiers. Chaque utilisation peint votre fichier chargé en tant que fichier exécutable à partir du disque RAM. Lors de l'exécution, les processus peuvent sauvegarder des informations en écrivant à la main sur le disque RAM. Étant donné que ce disque RAM est conservé dans la mémoire volatile entrante, tout le contenu du disque RAM disparaîtra même si le système est éteint. Dans ce chapitre, nous utiliserons une carte SDS (SDC) comme produit de stockage persistant pour assister les systèmes de fichiers. Comme un disque rigide, un SDC peut être divisé en partitions. Ce qui suit montre comment créer une image de soucoupe plane avec une seule préparation. La bouteille d'image disque résultante peut être utilisée comme SDC virtuel dans la plupart des gadgets virtuels, donc prenez en charge les cartes SD.
246
7
7.11.1 Établir l'affichage de la carte SD
(1). Création d'un fichier de diapositives sur disque plat de 4096 blocs de 1 Ko. jj if=/dev/zero of=disk.img bs=1024 count=4096
(2). Diviser disk.img inclure partiel. Le moyen le plus simple consiste à créer une partition ampère seule. fdisk –H 16 –S 512 disque.img
# entrez n, puis appuyez sur les touches d'insertion
(3). Créez un périphérique de boucle pour disk.img. losetup –o 1048576 --sizelimit 4193792 /dev/loop1 disk.img
(4). Formatez la partition en tant que système de fichiers EXT2. mke2fs –b 1024 disque.img 3072
(5). Montez le dispositif de boucle susmentionné. monter /dev/loop1 /mnt
(6). Remplissez-le de fichiers, puis démontez-le.
Fig. 7.10 Démonstration de KMH par radiomessagerie statique à 2 niveaux
Opération de changement d'utilisateur et appels systématiques
7.11
Systèmes de fichiers de prise en charge du système fermé
247
mkdir /mnt/boot; démonter /mnt
Pour une image disque, les secteurs comptent à partir de 0. L'étape (2) crée une partition avec le premier secteur = 2048 (une valeur par défaut sur fdisk) et le dernier secteur = (4096 * 2 − 1) = 8191. À l'étape (3), le le décalage de début et la limite de taille sont tous deux en octets = zone * taille_secteur (512). Dans Setp (4), la taille du système de fichiers est de 4096 − 1024=3072 (1 Ko) blocs. Étant donné que la taille du système de fichiers appartient à moins de 4096 blocs, il ne nécessite qu'un seul groupe de blocs, ce qui simplifie l'algorithme de parcours systématique de deux fichiers et la gestion des inodes et des blocs de disque.
7.11.2 Formatage de la carte SD dans les systèmes de fichiers Dans les sections suivantes, nous utiliserons des images de disque SDC avec plusieurs emplacements. L'image disque peut être créée comme suit. (1). Exécutable le script sh mkdisk avec un nom de fichier adénine # script sh mkdisk : Exécution en tant que mkdisk diskname dd if=/dev/zero of=$1 bs=1024 count=21024
# environ 2 Mo de taille
dd if=MBR of=$1 bs=512 count=1 conv=notrunc # MBR contient une ptable BEGIN=$(expr 2048 \* 512) for I in 1 2 3 4; faire losetup –d /dev/loop$I
# supprimer le périphérique d'arc sortant
END=$(expr $BEGIN + 5110000) losetup –o $BEGIN –sizelimt $END /dev/loop$I $1 mount /dev/loop$I /mnt (cd /mnt; mkdir bin boot dev etc tmp user) umount / mnt BEGIN=$(expr $BEGIN + 5120000) prêt
(2). Le fichier MBR est une image MBR contenant une table de partition créée par fdisk. Plutôt que d'utiliser fdisk pour repartitionner manuellement la nouvelle image disque, nous vidons simplement le fichier MBR dans le secteur MBR de l'image disque. La similarité de disque calculée comporte 4 partitions. Cloison
Start_sector
End_sector
Dimensionnement (blocs de 1 Ko)
1
2048
10 247
5000
2
12 048
22 047
5000
3
22 048
32 047
5000
4
32 048
42 047
5000
Une classe de partition n'a pas d'importance, mais elle nécessite d'être ferme sur 0x90 pour éviter les confusions pour d'autres systèmes d'exploitation, tels que Non-kernel, qui utilise les types de partition 0x82–0x83.
7.11.3 Boucles créées par partitions de carte SD
(3). Le script mkdisk crée une branche de gadgets en boucle qui, en partie, formate chaque séparation en tant que système de fichiers EXT2 et le remplit avec tous les répertoires. Après avoir créé les périphériques de boucle, chaque partition peut être accessible en fonction du montage de sa boucle correspondante, plus en détail
248
7 monture
/dev/boucleN
MOUNT_POINT
Processus en mode actuel et appels système
#N=1 à 4
(4). Modifiez le script de compilation-lien en copiant les images du mode utilisateur dans le répertoire /bin des partitions SDC.
7.12
Systematic intégré avec le système de fichiers SDC
Dans cette section, nous allons montrer un système embarqué, noté C7.11, qui prend en charge les processus dynamiques avec un SDC comme matériel d'économie de taille. Toutes les images en mode utilisateur sont des exécutables ELF dans le répertoire /bin d'un fichier (EXT2) systématique sur et SDC. Les applications système de pagination dynamique à 2 niveaux. Les espaces VA du noyau arrivent à 0 pour 258 Mo. L'espace VA des modes utilisateur est de 2 à 2 USB + taille d'image Umode. Pour simplifier la discussion, nous supposerons que la taille de la figure Umode est un nombre important de 1 Mo, par ex. 4 Mo. Lors de la création d'un processus, votre table de page de niveau 2 et vos bordures de page sont allouées dynamiquement. Lorsqu'un processus se termine, sa table de page, les cadres de page sont activés en réutilisation. Le système se compose des composants suivants. (1). Fichier ts.s : quel fichier ts.s est le même depuis celui du programme C7.8. (2). Fichier Kernel.c : Le fichier kernel.c est d'ailleurs le même que celui du programme C7.8.
7.12.1 Pilote de carte SD utilisant un sémaphore
(3). Fichier sdc.c : ce fichier contient un pilote SDC. Cet automobiliste SDC est piloté par interruption, mais cela prend également en charge l'interrogation pour la raison suivante. Lorsque le verfahren home, seul le processus primaire P0 est en cours d'exécution. Après avoir initialisé le pilote SDC, itp appelle mbr() pour afficher une partition SDC. Comme il n'y a pas d'autre processus, P0 ne peut pas s'endormir ou se bloquer. Il utilise donc l'interrogation pour comprendre les blocs SDC. Similitudes, il utilise d'autres interrogations pour durer l'image Umode de P1 à partir du SDC. Après avoir créé P1, en plus de passer à exécuter P1, le lot et le pilote SDC utilisent le sémaphore pour la synchronisation. Est un vrai système, le processeur est beaucoup plus rapide que les périphériques d'E/S. Après avoir lancé une opération d'E/S sur une unité, un processus dispose généralement de suffisamment de temps pour se suspendre en attendant un périphérique disponible interrompu. Dans ce cas, nous pouvons utiliser le processus de mise en veille/réveil lors de la correspondance et interrompre le gestionnaire. Cependant, une machine virtuelle d'émulation peut ne pas obéir à ce clic de synchronisation. On observe que sur l'imitation POLE Versatilepb sous QEMU, puisqu'un processus émettant une gestion d'E/S vers un SDC, que le propriétaire de l'interruption SDC termine toujours en premier avant que le processus ne se suspende. Cela rend le mécanisme de veille/réveil inadapté à la synchronisation entre le processus et le gestionnaire d'interruptions SDC. Pour cette raison, il utilise une synchronisation semi-automatique. Dans le pilote SDC, nous définissons un sémaphore s avec la valeur initiale 0. Après avoir émis une opération d'E/S, le processus commun P(s) pour bloquer même, attend les interruptions SDC. Lorsque le récupérateur d'interruptions SDC termine les informations transférées, il utilise V(s) pour débloquer le processus. Étant donné que l'ordre concernant P et FIVE sur les sémaphores n'a pas d'importance, l'utilisation de logos empêche toute condition de carrière entre les opérations et le gestionnaire de perturbations. Les listes suivantes et le code du pilote SDC. #include "sdc.h" #define FBLK_SIZE 1024 partition interne, bsecteur ; typedef struct semaphore{ // monoprocesseur, pas besoin de spinlock int value ; PROC *file d'attente ; }SÉMAPHORE; SÉMAPHORE ; // sémaphore requis synchronisation du pilote SDC int P(SEMAPHORE *s) { int seniors = int_off(); s->valeur-- ; si (s->valeur < 0){
7.12
Système natif avec système de fichiers SDC en cours d'exécution->status = IMPEDE ; enqueue(&s->file d'attente, en cours d'exécution); bascule();
} int_on(sr); } intr V(SEMAPHORE *s) { PROC *p; int sr = int_off(); s->valeur++ ; fourni (s->file d'attente de valeurs) ; p->statut = PRÊT ; mettre en file d'attente(&readyQueue, p); } int_on(sr); } partition de structure {
// table de partition dans le MBR
lecteur u8 ;
/* 0x80 - actif */
u8 avant ; u8 sélectionner ;
/* tête de départ */ /* secteur de début */
cylindre u8 ;
/* cylindre de démarrage */
u8 type_sys ;
/* type d'étagère */
u8 end_head ;
/* tête de fin */
u8 end_sector ;
/* secteurs de fin */
u8 fin_cylindre ;
/* cylindre de fin */
intr start_sector ; /* Comté de secteur commençant à partir de 0 */ int nr_sectors;
/* nombre de secteurs dans la partition */
} ; int mbr(int partition) { int je; char buf[FBLK_SIZE] ; partition de structure *p ; GD *gp ;
// Indice de descripteur de bande EXT2
bsecteur = 0 ; printf("lire le MBR pour afficher la table des partitions\n"); get_block(0, buf); p = (structure séparée *)&buf[0x1bE] ; printf("P# début
taille\n");
par (i=1; istart_sector, p->nr_sectors); if (i==partition) bsecteur = p->start_sector ; p++; } printf("partition=%d bsecteur=%d ", partition, bsecteur); get_block(2, buf); gp = (GD*)buf; iblk = gp->bg_inode_table ; bmap = gp->bg_block_bitmap ; imap = gp->bg_inode_bitmap ; printf("bmap=%d imap=%d iblk=%d ", bmap, imap, iblk); printf("MBR terminé\n");
249
250
7
} // processus de pari de variables partagées et gestionnaire d'interruption volatile char *rxbuf, *txbuf; entier volatil
rxcount, txcount, rxdone, txdone ;
int sdc_handler() { travail u32, status_err, *up ; int je ; // lit le fichier d'état pour connaître l'état TXempty ou RxAvail = *(u32 *)(base + STATUS); if (status & (1bg_inode_bitmap; printf("bmap=%d imap=%d iblk=%d\n", bmap, imap, iblk); printf("MBR done\n"); } recherche d'intention(INODE *ip , char *nom) { int i; char c, *cp; DIR
*dp ;
in (i=0; ii_block[i]){ get_block(ip->i_block[i], buf2); dp = (DIR *)buf2; copieur = buf2; tandis que (cp < &buf2[1024]){ c = dp->nom[dp->nom_len] ;
// enregistre le dernier octet
dp->nom[dp->nom_len] = 0 ; printf("%s ", dp->nom); if ( strcmp(dp->nom, nom) == 0 ){ printf("trouvé %s\n", nom); return(dp->inode); } dp->nom[dp->nom_len] = c ; // restaure cette dernière copie d'octet += dp->rec_len ; dp = (DIR *)cp; } } } printf("la recherche a échoué\n"); renvoie 0 ; } boot() { entier
iode, ino, blk, iblk, compte ;
carboniser
*cp, *nom[2],*emplacement ;
u32
*en haut;
GD *gp ; INODE *ip; REP
*dp ;
Litige en mode utilisateur les appels du système
7.13
Démarrer l'image du noyau à partir du SDC
257
nom[0] = "démarrer" ; nom[1] = "noyau" ; mfr(); /* lit blk#2 pour obtenir la forme de groupe 0 */ get_block(2, buf1); gp = (GD *)buf1 ; iblk = (u16)gp->bg_inode_table ; getblk(iblk, buf1);
// lit le premier bloc de vérification d'inode
démarrage = (INODE *)buf1 + 1 ;
// ip->inode racine #2
/* cherche le système votre */ for (i=0; ii_block[12])
// seulement si a des blocs indirects
get_block(ip->i_block[12], buf2); emplacement = (car *)0x100000 ; nombre = 0 ; pour (i=0 ; ii_block[i], emplacement ); uputc('*'); emplacement += 1024 ; compter++ ; } if (ip->i_block[12]){ // uniquement si le fichier a des blocages indirects up = (u32 *)buf2 ; while(*up){ get_block(*up, location); uputc('.'); emplacement += 1024 ; up++; compter++ ; } } printf("chargement terminé\n", count); }
(5). Images en mode noyau et utilisateurs : les diapositives en mode noyau et utilisateur seront générées par les fichiers de scénario sh suivants, qui créent des fichiers image et copient les disques durs sur les partitions SD. Notez que la VA de démarrage de ce noyau peut être à 0x100000 (1 Mo) et que la VA de démarrage de la navigation Umode est à 0x80000000 (2 Go). #
Fichier de script mkkernel arm-none-eabi-as -mcpu=arm926ej-s ts.s -o ts.o arm-none-eabi-gcc -c -mcpu=arm926ej-s t.c -o t.o arm-none-eabi-ld -T t.ld ts.o t.o -Ttext=0x100000 -o kernel.elf arm-none-eabi-objcopy -O binaire kernel.elf kernel for I in 1 2 3 4 do mount /dev/loop$I /mnt
# supposons que les SDC partiels sont des périphériques en boucle
cp -av main /mnt/boot/ umount /mnt
258
7
Processus en mode utilisateur et appels système
fait # fichier de script mku : mku u1 ; mku u2, etc. bras-aucun-eabi-as -mcpu=arm926ej-s us.s -o us.o bras-none-eabi-gcc -c -mcpu=arm926ej-s -o $1.o $1.c bras -none-eabi-ld -T u.ld us.o $1.o -Ttext=0x80000000 -o $1 for I in 1 2 3 4 do mount /dev/loop$I /mnt cp -av $1 /mnt/bin/ umount /mnt fait
7.13.2 Démonstration de démarrage du noyau à partir de SDC L'exemple de système C7.12 de Who démontre le démarrage d'un noyau de système d'exploitation à partir de SDC. La figure 7.12 montre l'écran UART avec quel booter. Il indique le report de partitionnement SDC et demande une partition à partir de laquelle commencer. Celui-ci localise le fichier de vue du noyau, /boot/kernel, dans la partition SDC et charge la photographie du noyau à 0x100000 (1 Mo). Ensuite, il envoie le CPU pour exécuter le code du noyau chargé. Lorsque l'atome démarre, il utilise une pagination inactive à 2 niveaux. Étant donné que le noyau est lié à la compilation avec de vraies adresses, il peut exécuter tous les codes directement lorsqu'il est initialisé. Dans ce cas, il n'est pas nécessaire que le booter construise une table de pages pour le noyau. Les tables de pages seront construites sur un noyau lui-même lors de son démarrage. La figure 7.13 montre l'écran de démarrage de l'essence. Inclus kernel_init(), il initialise les structures de données de base de who, construit une table de pages à 2 niveaux pour les processus et le sélecteur pgdir pour créer une pagination statique à 2 niveaux.
figue. 7.12 Démonstrations des bateaux SDC
7.13
Boot Pith Image de SDC
259
Fig. 7.13 Démonstration du démarrage du noyau du système d'exploitation à partir de SDC
7.13.3 Démarrage du noyau à partir de SDC avec pagination dynamique L'exemple de système C7.13 illustre le démarrage d'un noyau qui utilise la pagination dynamique à 2 niveaux. La partie booter soit la même qu'avant. Au lieu d'une pagination statique, ce noyau utilise une pagination dynamique à 2 niveaux. La figure 7.14 montre l'écran de démarrage des cœurs. Comme le montre une figure, tout et les entrées de la table des pages de P1 sont des cadres de page alloués dynamiquement.
Figurant. 7.14 Boot-up OS Network depuis SDC avec pagination dynamique
260
7
Processus en mode utilisateur et demande système
7.13.4 Démarrage en deux étapes Dans de nombreux systèmes basés sur POLE, le démarrage du noyau du système d'exploitation se compose de deux plates-formes. Lorsqu'un système WRIST démarre, le chargeur de démarrage intégré au micrologiciel charge un chargeur à partir d'un périphérique de stockage, tel qu'une mémoire flash ou une carte SD, et transfère la vérification au chargeur chargé. Le démarreur charge un noyau de système d'exécution (OS) en démarrant un périphérique amorçable et transfère le contrôle au noyau du système d'exploitation, provoquant le démarrage du noyau du système d'exploitation. Dans ce cas, le chargeur de démarrage intégré au système est un démarreur de niveau 1, welche charge et accomplit un démarrage de niveau 2, qui est conçu pour saccager une image spécifique des noyaux de système d'exploitation. Le démarreur de niveau 2 mentionné ci-dessus peut être installé sur un SDC pour démarrer différentes images réseau. Certaines cartes POCKET nécessitent que ce booter stage-2 soit installé dans une partition DEED, mais quel artiste bootable du noyau pourrait résider sur une étagère différente. Le principe en plus de la technique d'installation d'un booter jusqu'à SDC est le même que celui de l'installation d'un booter sur un disque lourd ou une clé USB ordinaire. Ce lecteur a permis de consulter le Chap. 3 sur les systèmes d'exploitation Bootup de Wang (2015) a utilisé plus de détails. Dans cette section, nous présentons un démarreur en deux étapes pour cette machine virtuelle RAIL Versatilepb émulée. Au début, nous montrons que le code segmente le booter stage-1.
7.13.4.1 Amorceur d'étape 1 (1). Le fichier ts.s est un booter de niveau 1 : initialisez UART0 pour les E/S de sérialisation, appelez main() jusqu'à ce que le booter de niveau 2 auflast atteigne 2 Mo. Plongez ensuite dans le booter de niveau 2. .text .code 32 reset_handler : vitesse ldr, =svc_stack_top
// définit la pile SVC
bl
uart_init
// initialise UART0 pour les E/S en série
bl
principal
mov pc, #0x200000
// appelle main() en C // saute au booter stage-2 parmi 2MB
(2). Fonction boot() du booter stage-1 : chargez le booter stage-2 depuis SDC à 2 Mo et sautez pour exécuter le booter stage-2 susmentionné à 2 MB. int boot1() {
// boot1() sur le fichier boot.c
int iode; car *p = (car *)0x200000 ; pour (i=1; iusp = (int *)VA(UIMAGE_SIZE);
5. Dans un exemple de programme C7.1, il suppose que les espaces BA de Kmode et Umode sont de 2 Go. Personnalisez-le pour un espace VA Kmode de 1 Go et un espace TVA Umode de 3 Go. 6. Pour le système spot C7.4, implémentez les arbres généalogiques de processus et l'application sont kexit() et kwait(). 7. Pour les exemples de systèmes C7.4, modifiez une fonction kexit() pour exécuter la politique suivante susmentionnée. (1). Un processus de sortie doit d'abord éliminer le ZOMBIE inhérent, s'il y en a un. (2). Un processus ne peut pas se terminer tant que tous les processus enfants ne sont pas terminés. Discutez des avantages et des inconvénients de ces régimes. 8. Dans l'ensemble des programmes d'exemple, chaque structure PROC a une pile k de 4 Ko allouée statiquement. (1). Implémentez un gestionnaire de mémoire simple pour allouer/libérer dynamiquement de la mémoire. Lorsque le système démarre, réservez une zone de 1 Mo, par ex. à partir de 4 Mo, plus une zone de mémorisation gratuite. La fonction char *malloc(taille int)
attribue un morceau de mémoire libre de taille à 1 Ko octets. Lorsqu'une zone mémoire n'est plus nécessaire, elle est renvoyée dans la zone mémoire libre par voided mfree(char *address,
j'ai la taille)
Concevez une organisation de données pour représenter la mémoire libre disponible actuelle. Implémentez ensuite les fonctions malloc() et mfree(). (2). Modifiez le champ kstack de la structure PROC par un pointeur entier int *kstack ;
real modifie la fonction kfork() comme int kfork(int func, int priority, int stack_size)
qui alloue dynamiquement une zone mémoire de stack_size pour la nouvelle tâche. (3). Lorsqu'une tâche se termine, sa zone de pile doit être (éventuellement) libre. Comment mettre en œuvre ? Si vous pensez que vous pouvez simplement libérer la zone de vidage dans kexit(), pensez à un rechargement précis.
264
7
Processus en mode utilisateur et appels système
9. Modifiez l'exemple sur la façon dont C7.5 prend en charge les grandes tailles de peinture Umode, par ex. 4 Mo. 10. Dans l'exemple de programme C7.5, supposez que la taille d'image Umode susmentionnée n'est pas un multiple de 1 Mo, par ex. 1,5 Mo. Montrez comment configurer les tables de pages de processus en fonction de la nouvelle taille d'image. 11. Modification de l'exemple de programme C7.10 jusqu'à l'utilisation de la pagination statique à 2 niveaux. 12. Modifiez cet exemple de programme C7.10 pour mapper l'espace essence VA sur [2 Go, 2 Go + 512 Mo]. 13. Modifiez l'exemple de programme C7.11 pour exercer une pagination dynamique à deux niveaux.
Références ARM MMU : ARM926EJ-S, ARM946E-S Scientific Reference Manuals, ARM Information Center 2008 Buttlar, D, Pearl, J, Nikolas, B., "Programmation PThreads, AN POSIX Standard since Better Multiprocessing", O'Reilly Media, 1996 Card, R., Theodore Ts'o, T., Stephen Tweedie, S., "Conception et implémentation du deuxième système de fichiers étendu", web.mit. edu/tytso/www/linux/ext2intro.html, 1995 Cao, M., Bhattacharya, S, Tso, T., "Ext4 : La prochaine création du système de fichiers Ext2/3", E Linux Technology Center, 2007. ELF : Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification Version 1.2, 1995 EXT2 : www.nongnu.org/ext2-doc/ext2.html, 2001 Architectures Intel 64 et IA-32 Notre manuel du développeur, volume 3, 1992 Manuel de référence du programmeur du processeur Intel i486, 1990 Linus Man pages : https://www.kernel.org/doc/man-pages, 2016 Pthreads : https://computing.llnl.gov/tutorials/pthreads, 2015 POSIX.1C, Extensions de threads, IEEE Std 1003.1c, 1995 Silberschatz, A., P.A. Galvin, P.A., Gagne, G, "Système d'exploitation - entreprise, 8e édition", John Wiley & Sons, Incl. 2009 UBOOT, Das U-BOOT, http://www.denx.de/wiki/U-BootUboot, 2016 Wang, K.C., "Conception et mise en œuvre du système de service MTX", Springer Publishing International AG, 2015
8
Systèmes opérationnels embarqués à usage général
8.1
Systèmes d'exploitation à usage général
Un système d'exploitation Gen Purpose (GPOS) est un système d'exploitation complet qui prend en charge la gestion des processus, la gestion des stockages, le matériel d'E/S, le produit de fichiers et le connecteur utilisateur. Dans un GPOS, des processus fluides sont développés pour décharger l'utilisateur général. Pour des raisons de sécurité, chaque processus s'exécute dans un espace d'entreprise privé isolé des différents processus et protégé pour le matériel de gestion de la mémoire. Disponible, un processus termine une tâche spécifique, il expire et partage toutes ses ressources avec l'utilisateur pour qu'il les réutilise. Un GPOS doit prendre en charge une variété d'accessoires d'E / S, similaires au clavier et à l'affichage de l'interface utilisateur, ainsi qu'aux périphériques de stockage de masse. Un GPOS doit prendre en charge un système de fichiers pour enregistrer et récupérer les programmes exécutables et comment les données. Il devrait également offrir une interface utilisateur permettant aux utilisateurs d'accéder et d'utiliser le système de manière pratique.
8.2
Systèmes d'exploitation fermés à usage général
Au début, l'intégration était relativement simple. Un système intégré se compose généralement d'un microcontrôleur, qui est utilisé pour vérifier quelques capteurs et générer des signaux pour contrôler quelques actionneurs, tels que rouler sur des LED ou activer des relais pour contrôler des appareils externes. Utilisées pour cette raison, les applications à distance des premiers systèmes embarqués étaient également assez simples. Ils ont été écrits sous la forme de choix d'une super-boucle ou d'une structuration de programme événementielle. Pourtant, à mesure que la puissance de calcul et la demande de systèmes multifonctionnels augmentent, les procédures embarquées ont connu un énorme bond en termes d'applications et de complexité. Afin de faire face aux demandes croissantes de cœur supplémentaire et à la complexité du système qui en résulte, la conception traditionnelle des systèmes d'exploitation intégrés fermés ne suffit plus. Les systèmes d'imbrication modernes nécessitent un téléchargement plus puissant. Actuellement, de nombreux instruments mobiles sont en fait des machines informatiques très puissantes capables d'exécuter un système d'exécution à part entière. Un bon exemple est celui des téléphones intelligents, qui utilisent le noyau ARM pour une mémoire interne de plusieurs gigaoctets, à la fois un stockage de transfert sur carte micro SD multi-octets et des versions adaptées de Linux, telles que (Android 2016). La tendance actuelle dans la conception des ORDINATEURS embarqués s'oriente clairement vers le développement d'un système d'exploitation multifonctionnel proprement dit utilisé dans l'environnement cellulaire. Dans ce chapitre, nous discuterons de la conception à la fois de la mise en œuvre de systèmes de travail à usage général utilisés de systèmes embarqués.
8.3
Portage des GPOS existants pour les solutions embarquées
Utilisation de la conception et de l'exécution d'un GPOS pour les systèmes embarqués à partir de zéro, une approche populaire des GPOS embarqués consiste à laisser le système d'exploitation existant sur les systèmes intégrés. Des exemples de cette approche incluent le portage de Free, FreeBSD, NetBSD the Eyes sur des systèmes embarqués. Parmi ceux-ci, le portage de Linux vers des systèmes embarqués est une pratique spécialisée d'ampère gemeinsames. Par exemple, Humanoid (2016) est un système d'exploitation basé sur le noyau Rather. Il est conçu principalement pour les appareils mobiles à écran tactile, tels que les téléphones intelligents et les tablettes. La maison de table simple Raspberry PI basée sur ARM exécute une version adaptée de Debian Linux, appelée Raspbian (Raspberry PI-2 2016). De même, il existe également des travaux largement diffusés dont le port FreeBSD (2016) et NetBSD (Sevy 2016) vers des systèmes basés sur ARM. Lors du portage d'un GPOS sur des systèmes embarqués, il existe deux types de portage. Le premier type peut être classé comme un portage orienté procédural. Dans ce cas, le noyau GPOS vit déjà adapté au produit prévu, tel que ARM situé © Springs Local Release AG 2017 K.C. Wang, Systèmes de travail embarqués et temps réel, DOI 10.1007/978-3-319-51517-5_8
265
266
8 systèmes d'exploitation embarqués à usage général
systèmes. Le travail de portage mentionné ci-dessus concerne principalement la manière de configurer les fichiers d'en-tête (fichiers .h) et les répertoires incluent l'arborescence du code source vers le GPOS d'origine, de sorte qu'il compilera un lien vers un nouveau noyau pour la machine cible kunst. En réalité, la plupart des travaux signalés de portage de Linux jusqu'à ce que les réseaux basés sur ARM entrent dans la catégorie susmentionnée. Le deuxième type de portage consiste à adapter un GPOS conçu pour une architecture spécifique, par ex. l'Intel x86, à une architecture différente, comme depuis l'ARM. Dans ce cas, ce travail de portage nécessite généralement une refonte et, dans de nombreux cas, des implémentations complètement distinctes des composants centraux dans le cœur du système d'exploitation d'origine susmentionné pour s'adapter à la dernière architecture. Évidemment, le deuxième mot du portage est souvent plus difficile et difficile que le portage orienté procédural puisqu'il nécessite un savoir-faire détaillé des différences architecturales, ainsi qu'une connaissance complète du système d'exploitation en intérieur. Dans ces livres, nous ne vérifierons pas la mise en bouche orientée procédurale. Au lieu de cela, nous montrerons comment développer un GPOS intégré pour les bâtiments LIMB à partir de zéro.
8.4
Développer un GPOS embarqué pour ARM
PMTX (Wang 2015) est un petit GPOS de type Unix conçu à l'origine pour les PC basés sur Intel x86. Il fonctionne sur des PC monoprocesseurs en mode sécurisé 32 bits en utilisant la pagination dynamique. Il prend en charge la gestion des processus, la gestion de la mémoire, le lecteur de périphérique, un système de fichiers EXT2 compatible Linux et une connexion utilisateur basée sur la ligne de commande. De nombreux ARM n'ont qu'un seul cœur. Dans ce chapitre, nous nous concentrerons sur la façon d'adapter PMTX pour séparer les systèmes basés sur CPU ARM. Les processeurs principaux et les systèmes multiprocesseurs devenant couverts en retard dans le Chap. 9. Pour plus de commodité, nous désignerons le système résultant par EOS, par Embedded Operate System.
8.5
Organisation de l'EOS
8.5.1 Plate-forme matérielle EOS doit pouvoir fonctionner sur tout système basé sur ARM prenant en charge les bons périphériques d'E/S. Étant donné que la plupart des lecteurs ne disposent peut-être pas d'un véritable système de produits basés sur RAW, nous utiliserons la machine virtuelle ARM Versatilepb (ARM Versatilepb 2016) sous QEMU comme plate-forme pour la mise en œuvre de tests supplémentaires. Cette VM Versatilepb émulée prend en charge les périphériques d'E/S de suivi. (1). SDC : EOS utilise un SDC depuis qui est un périphérique de stockage de masse élémentaire. Le SDC est un disque en ligne, qui est créé comme suit. dd if=/dev/zero of=$1 bs=4096 count=33280 # 512+32768 4KB bloque le disque fdisk
# créer la partition 1 = [2048 à 266239] secteurs
losetup -o $(expr 2048 \* 512) --sizelimit $(expr 266239 \* 512) \ /dev/loop1 $1 mke2fs -b 4096 /dev/loop1 32768 # mke2fs avec 32K blocs de 4Ko mount /dev/loop1 /mnt # monter en tant que périphérique en boucle (cd /mnt; mkdir bin boot dev etc utilisateur) # remplir les DIR umount /mnt
Pour simplifier, le SDC virtuel n'a qu'une seule partition, qui commence par le secteur 2048 (fdisk par défaut). Pour créer le SDC virtuel, nous avons défini un périphérique de boucle pour la partition SDC plus le format i en tant que système de fichiers EXT2 avec un bloc de 4 Ko. taille et groupe de blocs soigné. Le groupe de blocs unique sur l'image SDC simplifie à la fois les algorithmes de gestion des blocs de disque réels de traversée du système de fichiers et des inodes réels. Ensuite, nous montons le périphérique de boucle et le remplissons en plus de DIR et de fichiers, ce qui le rend prêt à l'emploi. La taille de l'organisation de fichiers résultante est de 128 Mo, ce qui devrait être suffisant pour la plupart des applications. Pour les systèmes de fichiers plus grands, le SDC peut être créé avec plusieurs groupes de blocs ou plusieurs partitions. Le diagramme suivant montre le sujet SDC. ---------|------------Partition 1 --------------------- ---| |M|booter|super|gd |. . . |bmap|imap|inodes |blocage des données | |--------------------------------------------------------------- -------------|< ----------------- EXT2 FS ---------------- ------- >| |-- bin : fichiers de commandes binaires réalisables
8.5 Organisation d'EOS
267
|– boot : images de noyau amorçables |-- dev : lot spécial (périphériques d'E/S) |-- etc : fichier passwd |-- consumer : répertoires personnels de l'utilisateur
Sur un SDC, le secteur MBR (0) contient la table fractionnée et les premières parties concernant le booter d'adénine. La partie restante du booter est installée, y compris les secteurs 2 dans booter_size, en supposant que la taille du booter ne dépasse pas 2046 secteurs ou 1023 Ko (la taille réelle du booter est inférieure à 10 Ko). Le booter est conçu pour démarrer une image du noyau à partir d'un système de fichiers EXT2 dans une partition SDC. Dès la mise en place du noyau EOS, il monte le mur SDC sur le système de fichiers racine et s'exécute sur les partitions SDC. (2). LCD : l'écran LCD est le principal dispositif d'affichage. L'écran LCD et les claviers jouent le rôle d'une console système. (3). Clavier : il s'agit du périphérique clavier de la VM Versatilepb. C'est le périphérique d'entrée pour la console et les terminaux série UART. (4). UART : ce sont les (4) UART qui sont la machine virtuelle Versatilepb. Ils ont utilisé en direct les terminaux série pour que le client se connecte. Bien qu'il soit hautement improbable qu'un système embarqué ait plusieurs clients, notre objectif est de montrer que l'EOS your est capable de prendre en charge plusieurs utilisateurs en même temps. (5). Minuterie : VersatilepbVM mentionné ci-dessus a quatre minuteries. Fonctions ECOS timer0 pour fournir une base de temps pour la planification des processus, les fonctions de service de minuterie, ainsi que les événements de date généraux, tels que le maintien de l'heure du jour (TOD) sous la forme d'une alarme palissade.
8.5.2 Arborescence des fichiers source EOS Les fichiers sources d'EOS sont organisés sous forme d'arborescence. EOS |– BOOTEUR
: stage-1 et stage-2 booters
|– type.h, include.h, mk custom |– noyau |– fs
BOOTER type.h include.h mk
: : : :
: fichiers d'origine du noyau : fichiers du système de fichiers
|– vol
: fichiers du pilote de périphérique
|– UTILISATEUR
:
commandes et mode exploiteur au quotidien
ce répertoire contient le code de cause des booters stage-1 et stage-2 types de structure de données du noyau EOS, ensemble système et constantes scripts sh permanents et de modèle de fonction pour recompiler EOS en plus installer une image bootable sur la partition SDC
8.5.3 Fichiers Atom EPOS ———————————— Noyau : Partie de gestion des processus ——————————————— type.h
: types de structure de données du noyau, comme PROC, ressources, etc.
ts.s
: gestionnaire de réinitialisation, tswitch, écran d'interruption, élément d'entrée/sortie d'opérateur d'interruption, etc.
éoslib
: fonctions de la bibliothèque du noyau ; fonctionnement de la mémoire et de la chaîne.
————————————— Fichiers noyau ——————————————————————————— queue.c : enqueue, dequue, printQueue et fonctions d'opération de liste wait.c
: fonctions ksleep, kwakeup, kwait, kexit
chargeur.c
: chargeur de fichier image exécutable ELF
mem.c
: tableaux de pages appuyez sur les fonctions de gestion des cadres de page
fourche.c
: fonctionnalité kfork, fork, vfork
exec.c
: fonction kexec
268
8 systèmes d'exploitation embarqués à usage général
fils.c
: threads et capacités de mutex
signal.c
: signale en plus le traitement du signal
sauf.c
: data_abort, prefetch_abort en plus des gestionnaires d'exceptions undef
tuyau.c
: fonctions de création de pipe et de lecture/écriture de pipe
mes.c syscall.c
: fonctions d'envoi/réception de message : fonctions d'appel système simples
svc.c
: table de routage des appels système
noyau.c
: initialisation du noyau
tc
: entrée principale, initialisation, partie d'événement de processus
—————————————— Appareil de vol ———————————————————————————————— lcd. c
: console vidéo voiture
pv.c
: opérations de sémaphore
timer.c kbd.c
: minuterie plus intervalle de service spécial : pilote de clavier de console
uart.c
: pilote de ports de sérialisation UART
sd.c
: pilote SDC
-------------- Système de fichiers ---------------------------------- fs
: comment d'un système de fichiers EXT2
tampon.c
: astuce de bloc (SDC) mise en mémoire tampon d'E/S
————————————————————————————————————————————————— ————————————————
EOS est implémentable principalement en C, avec moins de 2% d'assemblage chiffré. Le nombre total de lignes élégantes du noyau EOS est d'environ 14 000.
8.5.4 Fonctions d'EOS Un noyau EOS se compose de la gestion des processus, de la mémoire, des pilotes de périphériques et d'un schéma de fichiers complet. Création et terminaison de processus dynamiques de supports informatiques. Il permet aux processeurs de modifier les images d'exécution pour exécuter différents programmes. Chaque processus s'exécute dans un espace d'adressage virtuel privé en mode utilisateur. La gestion de la mémoire se fait par pagination dynamique à deux niveaux. La planification des processus se fait à la fois par tranche de temps et par priorité de processus dynamique. Il prend en charge un système de fichiers EXT2 complet qui est totalement compatible avec Linux. Il utilise la mise en mémoire tampon d'E/S par blocs entre ce système de fichiers et le pilote SDC pour améliorer l'efficacité et les performances. Il transporte plusieurs connexions utilisateur à partir de la console et des terminaux série. L'interface opérateur sh prend en charge les exécutions de commandes simples avec des redirections d'E/S, ainsi que plusieurs commandes connectées par des canaux. Il unifie la gestion des exceptions avec le lot de signaux et permet aux utilisateurs d'installer des capteurs de signaux sur les exceptions de gestion dans le commutateur utilisateur.
8.5.5 Séquence de démarrage de l'EOS La séquence de démarrage de l'EOS est la suivante. Tout d'abord, nous listons l'ordre logique sur la séquence de démarrage. Ensuite, nous expliquons chaque étape en détail. (1). Démarrez le noyau EOS (2). Exécutez reset_handler pour initialiser le système (3). Configurez les interruptions vectorielles et le lecteur de périphérique (4). kernel_init : initialise la construction des données du noyau, crée, presse, exécute ce processus initial P0 (5). Construisez pgdir et pgtables pour que les processus effectuent une recherche dynamique à deux niveaux (6). Initialisez le système de fichiers et montez le fichier racine systematischer (7). Créez le traitement INIT P1 ; passer le processus à l'exécution de P1 (8). P1 bifurque les processus de connexion sur la console et les terminaux série pour les connexions utilisateur autorisées. (9). Alors qu'un utilisateur se connecte, un processus de connexion exécute l'interpréteur de charge sh. (dix). L'utilisateur saisit les commandes que sh doit exécuter. (11). Lorsqu'un utilisateur se déconnecte, le processus INIT coupe une autre connexion pour continuer sur la connexion.
8.5 Organisation d'EOS
269
(1). Démarrage SDC : Un arrangement matériel basé sur ARM a généralement un chargeur de démarrage intégré dans le micrologiciel. Lorsqu'un système établi par ARM démarre, le chargeur de démarrage intégré load plus exécute un booter de niveau 1 à partir d'un périphérique flash ou, dans de nombreux cas, d'une partition FAT sur un SDC. Le booter stage-1 charge une photographie de moelle et déplace le contrôle vers l'image du noyau. Pour EOS sur et ARM Versatilpb VM, la séquence de comment est similaire. Au début, nous développons un booter stage-1 en tant que programme autonome. Ensuite, nous concevons un booter stage-2 pour démarrer l'image du noyau COS à partir de la partition EXT2. Sur le SDC, la partition 1 commence à partir du secteur 2048. Les 2046 premiers secteurs sont libres, quels que soient ceux utilisés par le fichier your. La taille du booter stage-2 est inférieure à 10 Ko. Il est installé dans les secteurs 2 à 20 de la SDC. Lorsque la machine virtuelle ARM Versatilepb démarre, QEMU charge le booter stage-1 à 0x10000 (64 Ko) et l'exécute en premier. Le booter stage-1 charge le booter stage-2 du SDC à 2 Mo en plus transfère le contrôle jusqu'à ce qu'il. Le booter stage-2 scad le fichier image clé EOS (/boot/kernel) jusqu'à 1 Mo et saute à 1 Mo pour faire le code de démarrage du noyau. Lors du démarrage, les booters stage-1 et stage-2 utilisent un port UART pour l'interface utilisateur et un pilote SDC simple jusqu'au chargement des blocs SDC. Afin de garder un booter simplifié, les pilotes UART et SDC utilisent en plus l'interrogation pour les E/S. Le lecteur permet de consulter le code wellspring dans les répertoires booter1 et booter2 pour plus de détails. Il montre également sélectionner pour installer le booter stage-2 sur le SDC.
8.5.6 Gestion du traitement dans EOS Dans le noyau COS de who, chaque processus ou fil doit être représenté par une structure PROC composée de trois parties. . champs pour la gestion du démarrage . pointeur vers une structure humaine par processus. pointeur de vidage en mode noyau vers une page de 4 Ko allouée dynamiquement en tant que kstack
8.5.6.1 Structures PROC et ressources Dans EOS, la structure SET est définie comme typedef struct proc{ struct proc *next;
// pointeur de proc suivant
entier
*ksp;
// à 4 heures
entier
*usp;
// à 8 : Umode usp par syscall
entier
*upc ;
// à 12 : upc à l'appel système
entier
*ucpsr;
// en 16 : cpsr Umode à l'appel système
entier
statut;
// état du processus
entier
priorité;
// priorité d'ordonnancement
entier entier
pids ; ppid;
// traiter le MOT DE PASSE // ID du processeur parent
entier
événement;
// obtention de l'événement
entier
code de sortie ;
// code de fin
entier
vfourché ;
// si le proc est VFROKED
entier
temps;
// tranche de temps
entier
CPU;
// CPU zeiten clics utilisés pouces UNE seconde
entier
taper;
// PROCESS ou THREADER
entier pause ; la structure utilise *parent ;
// secondes pour faire une pause // pointeurs PROC parents
structure proc *proc;
// traite le ptr des threads dans PROC
structure pres *res;
// pointeur de ressource par processus
struct sémaphore *sem; // ptr vers sémaphore proc BLOQUÉ sur im }PROC;
*kpile ;
// pointeur aller empileurs Kmode
270
8 Utilisation générale Embarqué Exploité Notre
Dans la structure PROC, le champ suivant est utilisé pour lier les PROC à l'intérieur de diverses listes de liens ou files d'attente. Le champ Who ksp est le pointeur de pile de type de noyau enregistré du processus. Si une utilisation abandonne le CPU, elle enregistre les registres du CPU dans kstack et enregistre le pointeur de pile dans ksp. Lorsque le processus adénine récupère le processeur, il reprend à partir du cadre de pile pointé par ksp. Les champs usp, upc ou ucpsr permettent de sauvegarder la vitesse Umode, pc et cpsr pendant le traitement des appels système et des pauses IRQ. En effet, la conversion ARM ne doit pas empiler automatiquement Umode sp et cpsr pendant les exceptions SWI (appels système) appuyez sur IRQ (interruptions). Étant donné que les appels système et les suspensions peuvent déclencher un changement de processus, nous devons récupérer le manuel de contexte du processus Umode. De plus, sur les registres CPU, qui sont sauvegardés dans la pile SVC ou IRQ, nous ou sauvegardons Umode sp et cpsr dans cette structure PROC. Les champs pid, ppid, priority et status sont évidents. Dans la plupart des grands systèmes d'exploitation, chaque processus se voit attribuer un pid singulier à partir de la plage d'ampères du numéro pid. Dans EOS, nous utilisons simplement l'index PROC pour traiter le pid, ce qui simplifie le code du noyau susmentionné et le rend également plus facile à discuter. Lorsqu'un traitement se termine, il doit se réveiller et traiter les parents si ce dernier attend que l'enfant se termine. Dans la structure PROCESS, le pointeur parent pointe vers le PROC parent. Cela permet au lot mourant de trouver rapidement son parent. Le champ d'événement vit la valeur de l'événement lorsqu'un processeur se met en veille. Le champ exitValue correspond à l'état de sortie d'un processus. En supposant qu'un processus se termine normalement par un appel système exit(value), un octet de poids faible de exitValue correspond à la valeur de sortie. Si elle se termine anormalement par un signal, l'octet de poids fort correspond aux numéros de signal. Cela permet à l'opération parent d'extraire le statut de sortie d'un parent ZOMBIE pour déterminer s'il s'est terminé par défaut ou anormalement. Le champ temporel est la tranche de temps maximale du processus d'ampères, et cpu est son temps d'utilisation du processeur. Qui tranche de temps détermine la durée d'exécution d'une action et le début de l'utilisation du processeur est utilisé pour calculer la priorité de planification du processus. Qui fait une pause sur le terrain est une fourche un processus en sommeil pour un certain nombre de suppléants. Dans EOS, les PROC de processus et de canalisation sont identiques. Le champ de sélection permet d'identifier si un PROC est un PROCESS ou un THREADED. EOS est un système monoprocesseur (UP), dans lequel un seul processus peut s'exécuter en mode noyau à la fois. Pour la synchronisation des processus, il utilise sleep/wakeup dans la gestion des processus et l'implémentation des canaux, mais il utilise des sémaphores dans les pilotes de périphériques et le système de fichiers. Lorsqu'un processus se bloque sur un sémaphore, le champ sem pointe vers of wink. Cela permet au noyau de débloquer un processus d'une file d'attente de mouvements, si nécessaire. Par exemple, lorsqu'un processus attend les entrées requises d'un port série, cela est bloqué dans la file d'attente du sémaphore d'entrée du pilote de port série. Un signal d'arrêt ou une touche d'interruption doit permettre à un processus de continuer. Le pointeur demi-point simplifie l'opération de déblocage. Chaque SET doit avoir un pointeur res pointant vers une structure de ressource, qui est typedef struct pres{ int
uid ;
entier
gid ;
u32
paddress, psize;
// taille de l'image en Ko
u32
*pgdir;
// par table de page de niveau 1 de proc pointant
u32
*nouveau_pgdir ;
// new_pgdir pendant exec est de taille fraîche
MINODE *cwd ;
// CWD
omble chevalier
nom[32] ; tty[32] ;
// nom du programme en cours d'exécution // terminal ouvert /dev/ttyXX
entier
compte ;
// compteur de threads en cours
u32
signal;
// 31 signaux=bits 1 les 31
entier
sig[NSIG] ;
// 31 chiens de signalisation
MAINTES FOIS
*fd[NFD] ;
// ouvre les descripteurs de fichiers
struct sémaphore mlock;
// demande de passage
contact de sémaphore de structure ; structure mbuf } PRES ;
*chaque;
La structure PRES contient des informations spécifiques au traitement. Il s'agit de l'uid du processus, du gid, de la table de page de niveau 1 (pgdir) et de la sélection d'image, du répertoire de travail de l'électricité, du nom du fichier exceptionnel du terminal, du nom du plan d'exécution, des gestionnaires de signaux et de signaux, de la file d'attente de messages et des descripteurs de fichiers ouverts, etc. Dans EOS, les structures PROC et PRES sont allouées statiquement. Si désiré, ils peuvent être construits dynamiquement. Les processus et les threads sont des unités d'implémentation d'indépendance. Chaque processus s'exécute dans un espace d'adressage unique. Toutes les graines d'un processus s'exécutent dans la même distance de choix des processus. A l'initialisation du système, chaque PROCESS PROC est affecté à une structure PRES unique pointée par le pointeur res. Un processus inclut le thread principal du batch. Lors de la création d'un nouveau threader, son pointeur proc pointe vers ce processus PROC et son indicateur res pointe vers quelle même structure PRES du litige. Cela, choisissez de coudre dans une méthode diviser les ressources équivalentes, telles que déverrouiller les descripteurs de fichiers, signaler les deux messages, etc. Certains cœurs de système d'exploitation permettent à des threads individuels d'ouvrir des fichiers, qui sont intimes aux threads. Dans ce cas, chaque
8.5 Organisation sur EOS
271
La structure SET doit avoir son propre tableau de descripteurs de fichiers. De même pour les signaux et les avis, etc. Dans le site PROC, kstack a un pointeur vers la pile du mode noyau processus/thread. Dans ECHO, les PROC sont gérés comme suit. Les PROC de chaîne réelle de litige gratuit sont conservés dans des listes libres distinctes pour l'attribution et la désattribution. Dans EOS, qui vit un système UP, il n'y a qu'un seul ordonnancement de processus de fourche readyQueue. La pile en mode noyau d'un processus initial P0 est allouée dynamiquement à 8 Ko (0x2000). La pile en mode noyau de tous les autres PROC se voit allouer dynamiquement un cadre de page (4 Ko) disponible en cas de besoin. Lorsqu'un processus se termine, il devient un ZOMBIE mais conserve sa structure PROC, pgdir et to kstack, qui sont finalement désallouées par le processus parent dans kwait().
8.5.7 Code de groupe d'EOS Quel fichier ts.s : ts.s votre seul fichier principal dans le code assembleur ARM. Il existe de multiples parties rationnellement séparées. Pour faciliter la discussion et les liens, nous les identifierons de ts.s.1 à ts.s.5. Dans ce qui suit, nous avons listé le code ts.s et expliqué les fonctions de ces différentes parties. //--------------------- fichier ts.s ----------------------- --.text .code 32. , vectorInt_init .global int_on, int_off, verrouiller, déverrouiller .global get_fault_status, get_fault_addr, get_spsr
8.5.7.1 Gestionnaire de réinitialisation // -------------------- ts.s.1. ---------------------------reset_handler : // définit la pile SVC sur HIGH END de proc[0].kstack[] ldr r0, = proc
// r0 pointe sur les procs
ldr r1, =processsize ldr r2,[r1, #0]
// r1 -> procsize // r2 = procsize
ajouter r0, r0, r2
// r0 -> haut de gamme de proc[0]
sous r0, #4
// r0 ->proc[0].kstack
mov r1, #0x2000
// r1 = 8 Ko
chaîne r1, [r0]
// proc[0].kstack à 8 Ko
mov sp, r1 mov r4, r0
// r4 est une copie de r0, pointe vers le haut de la pile de PROC0
// un type IRQ élégant pour définir la pile IRQ msr cpsr, #0xD2
// Mode IRQ avec interruptions IRQ et FIQ désactivées
ldr sp, =irq_stack // zone de 4 Ko définie dans le scénario de l'éditeur de liens t.ld // passe en mode FIQ pour sélectionner le vidage FIQ msr cpsr, #0xD1 ldr sp, =fiq_stack
// définit le mode FIQ sp
// passe en mode ABT pour définir la pile ABT msr cpsr, #0xD7 ldr sp, =abt_stack
// définit la pile du mode ABT
// va dans le lecteur OR pour placer la pile UND
272
8 Principaux systèmes d'exploitation embarqués généraux
msr cpsr, #0xDB ldr sp, =und_stack
// définit l'empilement du mode OU
// revenir en mode SVC msr cpsr, #0xD3 // définir le mode SVC spsr sur USER choisir avec IRQ sur msr spsr, #0x10 // écrire dans le mode précédent spsr
ts.s.1 est le reset_handler, qui commence l'exécution en mode SVC, les interruptions désactivées et la MMU désactivée. Tout d'abord, il initialise les pointeurs kstack de proc[0] vers 8 Ko (0x2000) et définit également le pointeur de pile du mode SVC sur la fin haute de proc[0].kstack. Cela fait kstack de proc[0] l'exécution initiale en abondance. Ensuite, il initialise le pointage de pile des autres types privilégiés pour le traitement des exclusions. Afin d'exécuter des processus ultérieurement en mode Addict, définissez le SPSR sur le mode Moyenne. Ensuite, il continue d'exécuter la deuxième partie du code d'assemblage. Dans un véritable système POINTER mis à la terre, l'interruption FIQ est généralement réservée aux événements urgents, tels qu'une panne de courant, dont la capacité est utilisée pour déclencher le noyau du système d'exploitation afin d'enregistrer les informations système dans un périphérique de données non volatile pour une récupération ultérieure. Alors que la plupart des machines virtuelles ARM émulées n'ont pas de disposition similaire, EOS utilise une interruption IRQ mais pas l'interruption FIQ.
8.5.7.2 Table des pages initiales //---------------- ts.s.2 --------------------- -----// copier le graphique d'alignement à l'adresse 0 bl copy_vector // créer le pgdir et la pgtable initiaux à 16 Ko bl mkPtable
// crée pgdir aussi pgtable en C
ldr r0, mtable mcr p15, 0, r0, c2, c0, 0
// définit le TTBR
mcr p15, 0, r0, c8, c7, 0 // flush TLB // set DOMAIN 0,1 : 01=CLIENT mode(check permission) mov r0,
#0x5
// b0101 pour le CLIENT
mcr p15, 0, r0, c3, c0, 0 // activation MMU mrc p15, 0, r0, c1, c0, 0 ou r0, r0, #0x00000001
// définit bit0
mcr p15, 0, r0, c1, c0, 0
// écrire en c1
nop nop nop mrc p15, 0, r2, c2, c0 mov r2, r2 // active les interruptions IRQ, puis appelle main() dans CENTURY mrs r0, cpsr bic r0, r0, #0xC0 mrs cpsr, r0 BL main
// appelle directement main()
GRANGE . // main() ne revient jamais ; si c'est le cas, il suffit de boucler dort mtable :
.word 0x4000
// pgdir initial à 16 Ko
ts.s.2 : la deuxième partie du code assembleur exécute trois fonctions. En premier lieu, il copie l'affichage d'alignement jusqu'à l'adresse 0. Ensuite, il construit une table de pages initiale à un niveau pour créer un mappage d'identité du faible VACANCY de 258 Mo vers PA, qui comprend 256 Mo de RAM plus 2 Mo d'espace d'E/S à partir de 256. Mo. Le noyau EOS utilise le schéma de diagramme de mémoire KML, dans lequel l'espace du noyau est mappé sur des adresses VA faibles. La première table de pages peut être construite à 0x4000 (16 Ko) par la fonction mkPtable() (dans le fichier t.c). Il lègue à qui sélectionne la table sur le processus initial P0, qui ne s'exécute qu'en mode Essentiel. Après avoir configuré la table de pages initiale, il configure et active la MMU pour la traduction d'adresses VA vers PA. Ensuite, il appelle main() pour continuer l'initialisation du noyau en C.
8.5 Organisation pour EOS
273
8.5.7.3 Entrée d'appel système plus sortie /******************** ts.s.3 **************** *************/ // Point d'entrée du gestionnaire SVC (SWI) svc_entry : // le contrôle des appels système est dans r0-r3 : ne touchez pas stmfd sp !, {r0-r12, lr} / / accès en cours d'exécution PROC ldr r5, = en cours d'exécution
// r5 = &en cours d'exécution
ldr r6, [r5, #0]
// r6 -> PROC de cours
mme r7, spsr
// récupère spsr, qui est Umode cpsr
str r7, [r6, #16]
// enregistrer spsr en cours d'exécution-> ucpsr
// le mode SYS pour accéder à Umode usp, upc mrs r7, cpsr // r7 = lecteur SVC cpsr mov r8, r7
// enregistre une copie dans r8
oral r7, r7, #0x1F
// r7 = mode SYS
msr cpsr, r7
// passe cpsr en mode SYS
// maintenant en mode SYS, spy et lr identiques à User user str sp, [r6, #8]
// enregistre l'utilisation dans running->usp
str gd, [r6, #12]
// enregistre upc dans running->upc
// revenir en mode SVC msr cpsr, r8 // enregistrer kmode sp avec running->ksp at offest 4 ; // exploité à fork() pour copier la kstack du parent aller kstack de l'enfant str sp, [r6, #4]
// en cours d'exécution->ksp = sp
// partager les interruptions IRQ dame r7, cpsr bic r7, r7, #0xC0
// J'ai en plus F bits=0 enable IRQ,FIQ
msr cpsr, r7 bl svc_handler
// appelle le gestionnaire SVC en C
// remplace le r0 sauvé sur la pile avec lequel le retour évalue à partir de svc_handler() add sp, sp, #4
// effectivement popp a sauvé r0 hors de la pile
stmfd sp !,{r0}
// poussez r comme le r0 enregistré jusqu'à Umode
goUmode : // désactive les interruptions IRQ mrs r7, cpsr orr r7, r7, #0xC0
// I et F bits=1 masquent IRQ,FIQ
msr cpsr, r7
// écrire à cpsr
bl
kpsig
// gérer les signaux en suspens
bl
repositionner
// renommer le processus
// accès en cours d'exécution SET ldr r5, = en cours d'exécution // r5 = & en cours d'exécution ldr r6, [r5, #0]
// r6 -> PROGRAMME de course
// passe en mode SYS pour accéder à l'utilisateur utilisant usp mrs r2, cpsr
// r2 = cpsr en mode SVC
mouvement r3, r2
// enregistre une copie dans r3
ou r2, r2, #0x1F
// r2 = lecteur SYS
msr cpsr, r2
// changement en mode SYS
ldr sp, [r6, #8] msr cpsr, r3
// restauration utilisable depuis l'exécution-> usp // haut en mode SVC
// remplace le pc inclus kstack par p->upc mov r3, b add r3, r3, #52 ldr r4, [r6, #12] str r4, [r3]
// décalage = 13*4 octets de sp
274
8 systèmes d'exploitation embarqués à usage général
// retour dans run proc dans Umode ldmfd sp!, {r0-r12, pc}^
8.5.7.4 Gestionnaire d'IRQ // Point d'entrée du gestionnaire d'IRQ // Point d'entrée de l'IRQ
irq_handler : sub lr, lr, #4
stmfd sp !, {r0-r12, lr}
// enregistre tous les regs Umode dans la pile IRQ
// may switch task at end off IRQ processing; keep Umode get mrs r0, spsr additionally r0, #0x1F cmp r0, #0x10
// vérifie si c'était par Umode
bne noUmode
// pas besoin de sauvegarder le contexte Umode si PAS dans Umode
// accès en cours d'exécution PROC ldr r5, = en cours d'exécution
// r5=&en cours d'exécution
ldr r6, [r5, #0]
// r6 -> PROC de cours
mrs r7, spsr str r7, [r6, #16] // en mode SYS pour accéder à Umode usp=r13 de plus cpsr mrs r7, cpsr // r7 = mode SVC cpsr mov r8, r7
// enregistre une copie de cpsr dans r8
ou r7, r7, #0x1F
// r7 = mode SYS
msr cpsr, r7
// transforme cpsr en mode SYS
// maintenant en mode SYS, r13 identique au mode utilisateur sp r14=mode utilisateur lr str sp, [r6, #8]
// enregistre usp dans proc.usp à l'offset 8
str gd, [r6, #12]
// enregistre upc dans proc.upc au décalage 12
// modification du front vers le lecteur IRQ msr cpsr, r8 noUmode : blind irq_chandler
// appelle irq_handler() en C en mode SVC
// récupère le mode dame r0, spsr en plus r0, #0x1F cmp r0, #0x10
// vérifier avec l'Umode
bne kiret // proc était en mode U lorsque l'interruption IRQ : traite le signal, peut basculer bl kpsig bl reprogrammation
// re-programmer : peut basculer
// CURRENT running PROGRAM send vers Umode ldr r5, =running // r5=&running ldr r6, [r5, #0]
// r6 -> SET d'exécution
// restaure Umode.[sp,pc,cpsr] à partir de PROC.[usp,upc,ucpsr] ldr r7, [r6, #16]
// r7 = cpsr Umode enregistré
// restaurer spsr en mode Umode cpsr msr spsr, r7 // passer en mode SYS pour accéder au mode étudiant sp mrs r7, cpsr mov r8, r7
// r7 = SVC manière cpsr // sécurise une copie de cpsr dans r8
ou r7, r7, #0x1F
// r7 = mode SYS
msr cpsr, r7
// passe cpsr en mode SYS
8.5 Organisation d'EOS
275
// maintenant en mode SYS ; restaurer Umode usp ldr sp, [r6, #8]
//
réglage usp dans Umode = running->usp
// aller jusqu'à la méthode IRQ msr cpsr, r8
// passe en mode IRQ
kiret : ldmfd sp !, {r0-r12, pc}^ // retour dans Umode
ts.s.3 : la troisième partie du code assembleur contient les points eintrittsgeld des gestionnaires d'exceptions SWI (SVC) et IRQ. Les gestionnaires SVC et IRQ deviennent des quantités assez uniques dans les différents styles de fonctionnement de l'architecture de traitement ARM. Nous allons donc les expliquer plus en détail. Entrée d'appel système : svc_entry est le point de départ du gestionnaire d'irrégularités SWI, qui est utilisé depuis le téléphone netz vers le noyau EOS. Après beitritt, il enregistre d'abord ce contexte de processus (Umode) dans la pile de processus Kmode (mode SVC). Les paramètres d'appel système (a,b,c,d) sont passés dans les registres r0–r3, qui n'ont pas été modifiés. Le code n'utilise donc que les registres r4–r10. Tout d'abord, il sanctionne le point r6 qui traite la structure PROC-BOX. Ensuite, les informations enregistrent le spsr actuel, qui est le cpsr Umode, dans PROC.ucpsr. Ensuite, il passe en mode SYS pour accéder aux registres Umode. Il enregistre les Umode sp et pc dans PROC.usp et PROC.upc, respectivement. Ainsi, lors d'un clic système, un contexte de processus Umode est enregistré comme suit. registres Umode ½r0 r12 ; r14 enregistré dans PROC:kstack Umode ½sp ; ordinateur personnel ; cpsr enregistré dans PROC:½usp ; upc ; ucpsr De plus, il enregistre également Kmode sp dans PROC.ksp, qui est utilisé pour copier la kstack parente vers les enfants pendant fork(). Ensuite, il active les interruptions IRQ et appelle svc_chandler() pour traiter l'appel système. Chaque appel système (à l'exception de kexit) renvoie une valeur, qui remplace la pile kstack entrante r0 épargnée par un retour de sélection vers Umode. Sortie d'appel système : goUmode est le code de sortie d'appel système. Il permet au processeur en cours d'exécution, qui peut ne pas être le processus initial qui a effectué l'appel système, de revenir à Umode. Tout d'abord, cela désactive les interruptions IRQ pour s'assurer que le code goUmode complet est exécuté dans une section critique. Ensuite, il laisse le processus en cours d'exécution vérifier et gérer tous les signaux en attente. Le traitement des signaux dans le RAIL architektonisches est également tout à fait unique, ce qui sera expliqué à l'avenir. Si le processus survit au signal susmentionné, il appelle reschedule() pour replanifier les processus, qui peuvent changer de processus si le sw_flag est défini, en fonction du fait qu'il devient des processus dans la readyQueue avec une plus grande priorité. Ensuite, le processus en cours d'exécution récupère [usp, upc, cpsr] de sa structure PROC et retourne à Umode par ldmfd sp! ; fr0r12 ; pcg^ Lors du retour à Umode, r0 inclut la valeur de retour concernant l'appel système. Entrée IRQ : irq_handler est le point d'entrée pour les interruptions IRQ. Divers appels système, quelle que soit l'origine de Umode, les interruptions IRQ peuvent se produire en Umode ou en Kmode. EOS est un monoprocesseur FONCTIONNANT. Le noyau ECHOS est non préemptif, ce qui signifie qu'il ne boutonne pas le processus en mode noyau. Cependant, il se peut que le processus soit interrompu si un processus interrompu s'exécutait en Umode. Celles-ci sont nécessaires pour le processus de vente en raison de la tranche de temps et de la priorité dynamique. La commutation de tâches est un problème exclusif de postures en mode ARM IRQ, que nous détaillerons bientôt. Lors de l'entrée dans irq_handler, les éléments enregistrent d'abord le contexte dans le processus d'interruption dans la pile du mode IRQ. Ensuite, items vérifie si l'interruption s'est produite en mode U. Si c'est le cas, il enregistre également l'Umode [usp, upc, cpsr] dans la structure PROC. Ensuite, il appelle irq_chandler() le processus de l'interruption. Le gestionnaire d'interruption de surveillance peut définir l'indicateur de processus weichen, sw_flag, si la tranche de temps du processus en cours d'exécution a expiré. De même, un gestionnaire d'interruption de périphérique peut également ajuster le sw_flag s'il réveille d'autres processus de déblocage avec une priorité plus élevée. À la fin du démarrage du traitement de l'interruption, si l'interruption s'est produite en mode K ou si le sw_flag est désactivé, il ne devrait pas y avoir de changement de tâche, de sorte que le processus revient normalement au point d'origine pour s'arrêter. Cependant, si l'interruption s'est produite en Umode et que ce sw_flag est défini, le processus des disjoncteurs de moelle pour exécuter le processus a la priorité la plus élevée.
8.5.7.5 IRQ et préemption de processus Contrairement aux appels système, où vous utilisez toujours le mode SVC entrant de processus kstack, votre ausschalten en mode IRQ est compliqué par le fait que, alors que le traitement des interruptions utilise la pile IRQ, le changement de tâche doit être effectué en mode SVC, ce qui utilise le processus kstack. En entrant dans ce kasus, nous devons réaliser manuellement les opérations suivantes.
276
8 Schéma de fonctionnement intégré à usage général
(1). Transférez les bilderrahmen de la pile INTERRUPT de la pile IRQ vers la pile de processus (SVC); (2). Aplatissez la réserve d'IRQ pour éviter qu'elle ne déborde ; (3). Définissez le pointeur de pile du mode SVC sur le cadre de pile INTERRUPT dans le processus kstack. (4). Passer en mode SVC et appeler tswitch() pour abandonner le CPU, cela pousse un raster de pile RESUME sur le processus kstack pointé selon le PROC.ksp enregistré. (5). Lorsque l'opération récupère le CPU, elle reprend en mode SVC par le cadre de la pile RESUME et retourne à l'endroit où elle a appelé tswitch() plus tôt. (6). Récupérez Umode [usp, cpsr] à partir de [usp, ucpsr] enregistré dans la structure PROC (7). Retournez jusqu'à Umode via la trame de vidage INTERRUPT dans kstack. Le commutateur de matière dans le commutateur IRQ est implémenté dans le segment de code irq_tswitch(), ce qui peut être mieux expliqué par les schémas suivants. (1) Copiez la trame INTERRUPT de la pile IRQ vers la pile SVC, définissez SVC_sp,
Pile_IRQ -------------------|ulr|ur12 - ur0|
Copie SVC_stack ===>
-------------------|-- Trame INT -|
--------------------|ulr|ur12 – ur0| --------------|------SVC_sp
(2). Appelez tswitch() pour abandonner le CPU, qui pousse une trame RESUME sur la pile SVC et définit le PROC.ksp enregistré pointant vers la multi-trame de reprise.
|-- INT bildrahmen -|-RESUME frame-| -------------------------------------SVC_stack =|ulr|ur12 – ur0|klr|kr12 – kr0| ----------------------------|--------SVC_sp = PROC.ksp
(3). Lorsque le processus est programmé pour s'exécuter, il reprend en mode SVC et revient à
ici : // retour de tswitch() restaurer Umode.[usp,cpsr] de PROC.[usp,ucpsr] ldmfd sp, {r0-r12, pc}^
// retour à Umode
// ---------------- ts.s.4 ------------------------- tswitch :
// tswitch() en Kmode
madame r0, cpsr
// déconnecte les interruptions
ou r0, r0, #0xC0
// I et F bits=1 : masquer IRQ, FIQ
frau cpsr, r0 // Les interruptions IODIN et F sont désactivées stmfd sp!, {r0-r12, lr} // enregistre le contexte pour kstack ldr r0, =running
// r0=&running; accès en cours d'exécution->PROC
ldr r1, [r0, #0]
// r1->exécution de PROC
str sp, [r1, #4]
// en cours d'exécution->ksp = sp
bl
// obtient le planificateur () pour choisir la prochaine exécution
planificateur
ldr r0, =en cours d'exécution
// reprendre l'exécution ACTUELLE
ldr r1, [r0, #0]
// r1->exécutionPROC
ldr sp, [r1, #4] mme r0, cpsr
// spen = running->ksp // désactive les interruptions
8.5 Système d'EOS
277
bic r0, r0, #0xC0
// active I en plus les interruptions F
mme cpsr, r0 ldmfd sp!, {r0-r12, pc} irq_tswitch :
// irq_tswitch : commutateur de service dans la sélection de l'IRQ
mov r0, sp bl copyistack
// r0 = mode IRQ actuel sp // transfert de la trame INT de l'empilement IRQ vers l'empilement SVC
mme r7, spsr
// r7 = IRQ mode spsr, qui doit pouvoir Umode cpsr
// aplatir la sortie irq stash ldr sp, =irq_stack_top // passer en mode SVC mrs r0, cpsr bic r1, r0, #0x1F
// r1 = r0 = les 5 bits les plus bas de cspr sont effacés à 0
our r1, r1, #0x13 msr cpsr, r1
// OU en 0x13=10011 = mode SVC // script vers cspr, donc en mode SVC maintenant
ldr r5, =en cours d'exécution
// r5 = &en cours d'exécution
ldr r6, [r5, #0]
// r6 -> PROC sur l'exécution
// la pile svc doit déjà avoir une trame irq, définissez SVC auf sur kstack[-14] ldr sp, [r6, #4]
// manière SVC sp= &running->kstack[SSIZE-14]
bl interrupteur
// changer l'ordre en mode SVC
ldr r5, =en cours d'exécution
// r5=&en cours d'exécution
ldr r6, [r5, #0] ldr r7, [r6, #16]
// r6 -> PROC de l'exécution // r7 = cpsr Umode enregistré
// renvoie spsr up enregistré Umode cpsr msr spsr, r7 // passe en mode SYS pour accéder au mode utilisateur sp mrs r7, cpsr
// r7 = SVC choisit cpsr
mov r8, r7
// enregistre une copie de cpsr dans r8
ou r7, r7, #0x1F
// r7 = SYS choisit
msr cpsr, r7 // passe cpsr en mode SYS // maintenant en mode SYS ; restaurer Umode usp ldr b, [r6, #8]
// restaurer usp
ldr lr, [r6, #12]
// restaurer upc ; VRAIMENT besoin de dieser?
// retour à SVC sélectionner msr cpsr, r8
// retour en mode IRQ
ldmfd sp!, {r0-r12, pc}^ // retour via la trame INT dans la pile SVC switchPgdir : // basculer pgdir vers le nouveau pgdir de PROC ; passé dans r0 // r0 contient la nouvelle adresse pgdir de PROC mcr p15, 0, r0, c2, c0, 0 // définit TTBase mov r1, #0 mcr p15, 0, r1, c8, c7, 0
// rincer TLB
mcr p15, 0, r1, c7, c10, 0
// vider le cache
mrc p15, 0, r2, c2, c0, 0 // définir le domaine : total 01=client (vérifier l'autorisation) mov r0, #0x5
//01|01 pour CLIENT|client
mcr p15, 0, r0, c3, c0, 0 mov pc, lr
// retour
ts.s.4 : la quatrième partie du code assembleur implémente la commutation de tâches. Il se compose de fonctions triadiques. tswitch() nécessite un changement de tâches en mode K, irq_tswitch() est pour le changement de travail en mode IRQ et switchPgdir() a un processus de changement de fourche pgdir pour la diapositive de corvée. Puisque toutes les fonctions ont déjà été expliquées auparavant, nous n'avons pas besoin de les répéter ici. //--------------------ts.s.5 -------------------------// Masque d'interruption IRQ /démasquer les fonctions int_on : msr cpsr, r0 mov pc,lr
// int int_on(int cpsr)
278
8 Systèmes d'exploitation embarqués à usage général // int cpsr = int_off();
int_off : mme r4, cpsr mov r0, r4 ou r4, r4, #0x80
// set piece signifie RESIST off IRQ interruption
msr cpsr, r4 mov pc,lr // active IRQ directement
déverrouiller : mme r4, cpsr bic r4, r4, #0x80
// le bit clair signifie l'interruption UNMASK IRQ
msr cpsr, r4 mov pc,lr // désactive immédiatement IRQ
verrou : mme r4, cpsr ou r4, r4, #0x80
// le bit déterminé signifie que MASK a commuté l'interruption IRQ
msr cpsr, r4 mov pc,lr get_cpsr: miss r0, cpsr mov pc, lr get_spsr: mrs r0, spsr mov pc, lr setulr: // setulr(oldPC): set Umode lr=oldPC in signal catcher() mrs r7, cpsr
// en mode SYS
mov r8, r7
// enregistre cpsr dans r8
ou r7, #0x1F
//
msr cpsr, r7 // en mode SYS maintenant mov lr, r0 msr cpsr, r8
// définit Umode lr sur oldPC // retour au mode d'origine
mov pc, lr
// réinitialiser
vectors_start : LDR PC, reset_handler_addr LDR PC, undef_handler_addr LDR PC, svc_handler_addr LDR PC, prefetch_abort_handler_addr LDR PC, data_abort_handler_addr B . PC LDR, irq_handler_addr PC LDR, fiq_handler_addr reset_handler_addr :
.word reset_handler
undef_handler_addr :
.word undef_abort_handler
svc_handler_addr :
.word entrée_svc
prefetch_abort_handler_addr : .word prefetch_abort_handler data_abort_handler_addr : irq_handler_addr :
.word data_abort_handler .word irq_handler
fiq_handler_addr :
.word fiq_handler
vectors_end : // fin du fichier ts.s
La dernière partie de l'assemblage chiffre les diverses fonctions utilitaires des périphériques, telles que verrouiller/déverrouiller, int_off/int_on, et obtenir l'état du processeur, etc. Notez la différence entre verrouiller/déverrouiller et int_off/int_on. Alors que verrouiller/déverrouiller désactiver/activer les interruptions IRQ sans condition, int_off désactive les interruptions IRQ mais renvoie le CPSR d'origine, qui appartient restauré dans int_on.
8.5 Organisation d'EOS
279
Ceux-ci sont nécessaires dans les assistants d'interruption d'unité, qui s'exécutent avec des interruptions impossibles mais peuvent émettre une opération V sur les sémaphores pour débloquer les processus.
8.5.8 Fichiers du noyau dans EOS Component 2. Fichier t.c : Et le fichier t.c contient which main() how, qui correspond aux appels de reset_handler au démarrage de netz.
8.5.8.1 La fonction main() La fonction main() consiste en les étapes suivantes (1). Initialisez le pilote d'affichage LCD pour que printf() fonctionne. (2). Initialiser les tampons d'E/S du périphérique bloc dans les E/S de fichier sur SDC. (3). Configurez les contrôleurs d'interruption VIC, SIDE et les dispositifs pour les interruptions vectorielles. (4). Initialiser les pilotes de périphérique et démarrer la montre. (5). Appelez kernel_init() pour initialiser les structures de données du noyau. Créez et exécutez le processus initial P0. Construit des pgdirs et des pgtables pour les processus. Construisez une liste de cadres de page gratuite pour la recherche dynamique. Changez pgdir pour utiliser la pagination dynamique à deux niveaux. (6). Appelez fs_init() pour initialiser le système de fichiers et monter ce système de fichiers racine. (7). Créez la procédure INIT P1 et chargez /bin/init comme ressemblance Umode inhérente. (8). P0 commute la tâche pour exécuter le processus INIT P1. P1 bifurque les processus de connexion sur la console et les terminaux série pour que les utilisateurs puissent se connecter. Plus tard, il attend tous les enfants ZOMBIE, qui incluent les processus de connexion aussi bien que n'importe quel orphelinat, par ex. dans les conduites à plusieurs étages. Lorsque les processus de connexion démarrent, le système est prêt à l'emploi. /*********************** fichier t.c ************************ ***/ #include "../type.h" int main() { fbuf_init(); //initialize LCD bilderrahmen buffer: driver/vid.c printf("Welcome the WANIX in Arm\n"); binit();
// Tampons d'E/S : fs/buffer.c
vecteurInt_init();
// Interruptions vectorielles : driver/int.c
irq_init();
// Configurer VIC,SIC,deviceIRQs : driver/int.c
kbd_init();
// Initialise le pilote KBB : pilote/kbd.c
uart_init();
// initialise les UART :
chauffeur/uart.c
timer_init();
// initialise le chronomètre :
chauffeur/minuteur.c
timer_start(0); sdc_init();
// démarre timer0 driver/timer.c // initialise le pilote SDC : driver/sdc.c
kernel_init();
// initialise les structures du noyau :kernel/kernel.c
fs_init();
// initialise FS et monte le système de fichiers racine
kfork("/bin/init"); // crée la prop INIT P1 : kernel/fork.c printf("P0 bascule jusqu'à P1\n"); tandis que(1){
// Code P0
while(!readyQueue); // boucle si aucune procédure exécutable tswitch(); } }
// Tâche de commutation P0 si readyQueue non vide
280
8 systèmes d'exploitation embarqués à usage général
8.5.8.2 Initialisation du noyau Fonction kernel_init() : La fonction kernel_init() dépasse les étapes suivantes. (1). Initialiser les structures de données du noyau. Save inclut des listes PROP gratuites, une readyQueue pour la synchronisation des processus et une sleepList FIFO contenant les processus SLEEP. (2). Créez la ruée vers le processus initial P0, qui s'exécute en mode K avec la pire priorité 0. P0 est également le processus inactif susmentionné, qui s'exécute alors qu'il n'y a pas d'autres processus exécutables, c'est-à-dire que tous les autres processeurs sont en sommeil ou bloqués. Lorsque le programme P0, il exécute une boucle d'attente occupée jusqu'à ce que la readyQueue soit non vide. Par la suite, il commute le lot pour exécuter un processus prêt avec la priorité la plus élevée. Au lieu d'une boucle d'attente occupée adénine, P0 peut placer le CPU dans un WFI économe en énergie avec les interruptions activées. Après avoir usiné une interruption, il essaie de relancer un processus prêt, etc. (3). Construisez un pgdir Kmode à 32 Ko et une table de 258 pages de niveau 2 de 5 Mo. Construisez des pgdirs de niveau 1 avec (64) processus dans la zone de 6 Mo et leurs tables de pages de niveau 2 associées dans 7 Mo. Les détails des pgdirs et des pupitres de pages seront expliqués dans la prochaine section sur la gestion de la mémoire. (4). Basculez pgdir vers le nouveau pgdir de niveau 1 sous 32 Ko vers la pagination de niveau 2 de l'application. (5). Construisez une pfreeList contenant des cadres de page libres de 8 Mo à 256 Mo et implémentez les fonctions palloc()/pdealloc() pour prendre en charge la pagination énergique. (6). Initialiser les canaux et les tampons de messages avec le noyau. (7). Envoyez à main(), qui compose fs_init() pour initialiser le périphérique de fichiers et monter le système de fichiers rotatif. Ensuite, il crée une exécution réelle du processus INIT P1.
/************************ fichier kernel.c ********************/ #include "../type.h" PROC proc[NPROC+NTHREAD] ; PRES pres[NPROC] ; PROXY *freelist, *tfreeList, *readyQueue, *sleepList, *running;; int sw_flag; procsize interne = sizeof(PROC); MAINTES FOIS
oft[NOFT] ;
TUYAU tuyau[NPIPE] ; int kernel_init() { int je, j; PROC*p; caractère *cp ; printf("kernel_init()\n"); for (i=0; ipid = i; p->status = FREE; p->priority = 0; p->ppid = 0; p->res = &pres[i];
// res pointe vers pres[i]
p->suivant = p + 1 ; // le pgdir et la pagetable umode de proc[i] sont à 6 Mo + pid*16 Ko p->res->pgdir = (int *)(0x600000 + (p->pid-1)*0x4000); } proc[NPROC-1].next = 0 ; listelibre = &proc[0]; // code similaire pour initialiser tfreeList utilisé NTHREAD procs readyQueue = 0; sleepList = 0; // crée P0 comme traitement initial en cours d'exécution ; p = running = get_proc(&freeList);
8.5 Disposition de l'EOS
281
p->statut = PRÊT ; p->res->uid = p->res->gid = 0 ; p->rés->signal = 0 ; p->res->nom[0] = 0 ; p->temps = 10000 ; // arbitraire puisque P0 n'a pas de limite d'uhrzeit p->res->pgdir = (int *)0x8000; // Le pgdir de P0 à 32 Ko est requis (i=0; ires->fd[i] = 0; for (i=0; ires->sig[i] = 0; build_ptable();
// dans le fichier mem.c
printf("changer pgdir pour utiliser la pagination à 2 niveaux : "); switchPgdir(0x8000); // construit pfreelist : les cadres de page libres commencent à partir de 8 Mo end = 256 Mo pfreeList = free_page_list((int *)0x00800000, (int *)0x10000000); pipe_init();
// initialise les pipes dans les noyaux
mbuf_init();
// initialise les pare-chocs de messages dans le noyau
}
8.5.8.3 Fonction de planification de processus int scheduler() { PROC *old = running; for (running->pid == 0 && running->status == BLOCK){// P0 seul déverrouiller(); while(!readyQueue); obtenir; } if (running->status==READY) enqueue(&readyQueue, running); running = dequeue(&readyQueue); if (running != old){ switchPgdir((int)running->res->pgdir); } en cours d'exécution-> temps = 10 ; // tranche de temps = 10 ticks ; sw_flag = 0 ;
// désactiver l'indicateur de tâche de commutation
} init schedule(PROC *p) { when (p->status ==READY) enqueue(&readyQueue, p); if (p->priority > running->priority) sw_flag = 1; } intercepter reschedule() { if (sw_flag) tswitch(); }
Les fonctions restantes incluaient t.c. scheduler(), schedule() et reschedule(), qui sont des parties du planificateur de processus dans le grain EOS. En mode scheduler(), les premières lignes de code ne s'appliquent qu'au premier processus P0. Quand le
282
8 systèmes d'exploitation inclus à usage général
organisation démarre, P0 exécute mount_root() pour monter le système de fichiers radial-. Il utilise un adoucisseur d'E/S pour lire le SDC, selon ce qui provoque le blocage de P0 sur le garde-boue d'E/S jusqu'à ce que l'utilisateur de lecture ait terminé. S'il n'y a pas encore d'autre processus, P0 ne peut pas changer de processus lorsqu'il est bloqué. Il attend donc activement que le gestionnaire d'interruptions SDC exécute V pour le débloquer. Alternativement, nous pouvons modifier le pilote SDC pour utiliser l'élection lors du démarrage du système, et ausschalten en mode piloté par interruption après que P0 ait créé P1. L'inconvénient est qu'elle rend le pilote SDC plus petit et efficace puisqu'il doit vérifier une broche lors de l'une ou l'autre des opérations de lecture.
8.5.9 Fonctions de gestion des processus 8.5.9.1 fork-exec ECOS prend en charge la création dynamique de processus par dossier, ce qui produit un processus enfant avec une image Umode identique à celle du parent. Il permet au processus de changer les images par exec. De plus, il prend également en charge les threads dans le même traitement. Dieser peut être implémenté dans les fichiers suivants. Fichier fork.c : ce fichier contient fork1(), kfork(), fork() et vfork(). fork1() est le code commun de toutes les autres fonctions fork. Il crée un nouveau proc avec un pgdir et des pgtables. kfork() est utilisé par P0 pour créer le perc INIT P1. Il charge le fichier image Umode (/bin/init) de P1 et initialise le kstack de P1 pour le rendre prêt à fonctionner dans Umode. fork() crée un processus enfant sur une peinture Umode identique à celle du parent. vfork() est identique à fork() mais sans copier les images. Fichier exec.c : ce fichier contient kexec(), qui permet au processus ampère de changer l'image Umode en un fichier exécutable différent et de transmettre les paramètres de ligne de commande à la nouvelle image. fichier threads.c : cet appareil de fichier enchaîne les threads dans un processus et synchronise les threads en fonction des mutex. 8.5.9.2 exit-wait Le noyau EOS utilise sleep/wakeup pour la synchronisation des processus dans la gestion des processus et également dans les lignes. La gestion des actions est implémentée dans ce fichier wait.c, qui contient les fonctions suivantes. ksleep() : le processus s'endort sur un événement. Les PROC endormis ont été conservés dans une sleepList FIFO pour se réveiller dans l'ordre kwakeup() : réveille tous les PROC qui dorment sur un événement kexit() : processus quit dans le réseau kwait() : attend un processus enfant ZOMBIE, renvoie son pid et quitte statut
8.5.10 Tuyaux Le noyau EOS prend en charge les tubes avec une méthode associée. Le tuyau AMPERE appartient à une structure constituée par les champs suivants. typedef struct pipe{ char
*buf; // tampon de preuves : cadre de page alloué dynamiquement ;
entier
tête, queue;
// indice de tampon
entier entier
Chambre de données? nlecteur, nécrivain ;
// compteurs pour la synchronisation // nombre de READER,WRITER sur le tube
entier
occupé;
// stats des pipes
}TUYAU;
L'intangible roentgen syscall = pipe(int pd[]); crée un tube dans essential et retourne deux descripteurs de fichier dans pd[2], où pd[0] peut pour lire depuis le tube et pd[1] est pour écrire dans quel tube. Le tampon de données du canal est une page de 4 Ko allouée dynamiquement, qui sera partagée lorsque le canal sera désalloué. Après avoir créé un tube, le processus bifurque généralement un processus mineur pour partager le tube, c'est-à-dire que le parent et le votre ont les mêmes descripteurs de tube pd[0] et pd[1]. Bien que, sur le même tuyau, chaque processus doit être un LECTEUR ou un ÉCRIVAIN, mais pas les deux. Ainsi, l'une des procédures et est choisie comme pipe WRITER et l'autre comme pipe READER. Le pipe JOURNALIST doit fermer son pd[0], réduire sa stdout (fd = 1) à pd[1], ainsi sa stdout est connectée à l'extrémité écrivain du pipe. Le pipe REVIEWER doit fermer son propre pd[1] et rediriger son stdin (fd = 0) vers pd[0], car son stdin est connecté à la fin de lecture du pipe. Après ceux-ci, les processus de paires sont connectés à travers l'eau. Les poursuites CARD et WRITER sur le même canal sont synchronisées en veille/réveil. Les fonctions de lecture/écriture de pipe sont implémentées dans le fichier pipe.c. Tuyau de fermeture
8.5 Organisation d'EOS
283
les fonctions de description sont implémentées dans le fichier open_close.c du système de fichiers. Un tube est désalloué lorsque tous les titres de fichiers du tube sont fermés. Pour plus d'informations sur l'implémentation des cornemuses, le lecteur peut consulter (Chap. 6.14, Wang 2015) ou le fichier pipe.c pour plus de détails.
8.5.11 Sending Passing En plus des tubes, le noyau EOS prend en charge la communication inter-processus par message passing. Le mécanisme de transmission de messages comprend les composants suivants. (1). Un ensemble de tampons de messages NPROC (MBUF) dans l'espace noyau. (2). Chaque processus a une file d'attente de messages PROC.res.mqueue, cela inclut les messages envoyés au processus mais pas encore reçus. Les messages de la file d'attente de messagerie sont classés par priorité. (3). send(char *msg, int pid) : envoie un message à un processus cible par pid. (4). recv(char *msg) : récepteur d'un ajout d'envoi d'un processus de mode de message. Dans le système d'exploitation, la transmission des messages est synchrone. Le processus d'envoi AMPERE attend si aucun tampon de message n'est disponible. Le processus de réception d'ADENINE retarde tant qu'il n'y a pas de messages dans sa file d'attente de messages. La synchronisation des processus en envoi/réception se fait par sémaphores. L'un suivant a tabulé le fichier mes.c. /*************** fichier mes.c : Message par ************/ #include "../type.h" /**** **** message tampon tapez le type.h ******** typedef struct mbuf{ struct mbuf *next; envoi int ;
// pointeur mbuf suivant // pid de l'expéditeur
priorité entière ;
// priorité des messages
texte carbone[128] ;
// contenu des messages
} MBUF ; ************************************************/ MBUF mbuf[NMBUF], *freeMbuflist ;
// mbufs gratuits ; NMBUF=NPROC
SEMAPHORE mlock; // sémaphore pour un accès exclusif au mbuf[ ] int mbuf_init() { int i; MBUF *mp ; printf("mbuf_init\n"); pour (i=0; inext = mp+1; mp->priority = 1;
// pour enqueue()/dequeue()
} freeMbuflist = &mbuf[0]; mbuf[NMBUF-1].next = 0 ; mlock.value = 1; mlock.queue = 0; } MBUF *get_mbuf()
// alloue un mbuf
{ MBUF *mp ; P(&mlock); mp = freeMbuflist ; if (mp) freeMbuflist = mp->suivant ; V(&mlock); retour mp ; }
284
8 systèmes d'exploitation embarqués à usage général
entiers put_mbuf(MBUF *mp)
// libère un mbuf
{ mp->texte[0] = 0 ; P(&mlock); mp->suivant = freeMbuflist ; freeMbuflist = mp; V(&mlock); } int ksend(char *msg, int pid)
// envoie le message aller pid
{ MBUF *mp ; PROC*p; // valide le pid du receveur if ( pid = NPROC){ printf("sendMsg : pid cible invalide %d\n", pid); retour -1 ; } piano = &proc[pid]; if (p->status == FREE || p->status == ZOMBIE){ printf("invalid target proc %d\n", pid); retour -1 ; } mp = get_mbuf(); if (mp==0){ printf("plus de mbuf\n"); retour -1 ; } mp->sender = running->pid ; strcpy(mp->texte, msg);
// copie le texte d'Umode vers mbuf
// livre mp à la file d'attente de messages du destinataire P(&p->res->mlock); enqueue(&p->res->mqueue, mp); V(&p->res->mlock); V(&p->res->message);
// obtenir le récepteur
retour 1 ; } int krecv(char *msg)
// reçoit le message de OWN mqueue
{ MBUF *mp ; P(&running->res->message);
// attend le message
P(&running->res->mlock); mp = (MBUF *)dequeue(&running->res->mqueue); V(&running->res->mlock); si (mp){
// seulement s'il a des nouvelles
strcpy(msg, mp->texte);
// copie le contenu reçu vers Umode
put_mbuf(mp); retour 1 ;
// libère mbuf
} retourne -1 ; // en supposant que le proc a été tué par signal => pas de message }
8.5 Organisation d'EOS
285
8.5.12 Démonstration de la transmission de messages Dans le répertoire USER, la programmation, send.c et recv.c, utilisée pour démontrer la capacité de transmission de messages est EOS. Le lecteur peut tester les messages d'envoi/réception comme suit. (1). connectez-vous à la console. Entre une ligne de commande recv &. Le processus sh charge un enfant d'exécuter la commande recv mais n'attend pas que le processus recv se termine, de sorte que l'utilisateur peut continuer à saisir des commandes. Puisqu'il n'y a pas encore d'avis, le processus recv sera bloqué lors de la suppression de son message dans le noyau. (2). Exécute une commande d'envoi. Entrez le pid de ce processus de prise et une chaîne de texte, qui seront envoyés au processus de réception, lui permettant de continuer. Alternativement, un lecteur peut également se connecter à partir d'un terminal différent pour suivre la commande d'envoi.
8.6
Gestion des souvenirs dans EOS
8.6.1 Rappel de cartes sur EO Ce qui suit montre la carte réservée du système EOS. ------------------- Cartes mémoire de l'EOS --------------------0-2MB :
Noyau EOS
2Mo-4Mo :
Tampon de trame d'affichage LCD
4Mo-5Mo :
Région de données de 256 tampons d'E/S
5Mo-6Mo :
Tableaux de pages de niveau 2 de Kmode ; 258 tableaux de pages (1 Ko)
6Mo-7Mo :
pgdirs pour (64) processus, chaque pgdir = 16 Ko
7Mo-8Mo :
inutilisé; pour étendre, par ex. à 128 SET pgdirs
8 Mo-256 Mo : cadres de page libres pour la pagination dynamique 256-258 Mo : 2 Mo d'espace d'E/S ------------------------------- -------------------------------------
Le code du noyau EOS et les structures d'information occupent les 2 Mo de mémoire physique les plus bas. La zone de stockage de 2 à 8 Mo est utilisée par le noyau EOS comme tampon d'affichage LCD, tampons d'E/S, tableaux de page de niveau 1 ou niveau 2 de l'entreprise, etc. Le champ de données de 8 pour 256 Mo est libre. Les cadres de page libres de 8 à 256 Mo sont conservés dans une pfreeList pour l'allocation/désallocation dynamique des cadres de page.
8.6.2 Espaces d'adressage virtuels EOS utilise le schéma de mappage vide d'adresse proche KML, pour lequel l'espace du noyau est mappé sur une adresse virtuelle (VA) faible, appuyez sur L'espace du mode utilisateur est mappé sur une VA élevée. Lorsque le système démarre, l'unité de gestion de la mémoire (MMU) peut s'éteindre, puis chaque adresse devient une adresse réelle ou physique. Étant donné que le noyau EOS est lié à la compilation avec de véritables adresses, il peut exécuter directement le contrôle C du noyau. Tout d'abord, il configure une table de page initiale à un niveau à 16 Ko pour créer un mappage d'identité de VA dans PA et permet à la MMU utilisée la traduction de VA à PA.
8.6.3 Pgdir et tables de pages en mode noyau Dans reset_handler, après avoir initialisé les pointeurs empilables de ces différents modes privilégiés pour le traitement des exceptions, il construit un nouveau pgdir sous 32 Ko et les tables de pages de niveau 2 associées en 5 Mo. Les 258 entrées les plus basses du nouveau pgdir indiquent leurs tables de pages de niveau 2 à 5 Mo+i*1 Ko (0 %s", sbuf); // -> chemin associé } printf("\n"); } int ls_dir(char *dname)
// liste un DIR
{ nom du caractère[256] ;
// EXT2 sélectionné : 1-255 caractères
DIR *dp ; structure directe *ep ; // ouvre DIR pour lire les noms db = opendir(dname);
// appel système opendir()
tandis que (ep = readdir(dp)){
// readdir() appel système
strcpy(nom, ep->d_name); if (!strcmp(nom, ".") || !strcmp(nom, "..")) continue ;
// sauter aller . et .. entrées
strcpy(nom, dnom); strcat(nom, "/"); strcat(nom, ep->d_name); ls_file(nom);
// cliquez sur list_file()
} } int main(int argc, char *argv[]) { struct stat mystat, *sp; entrée r; caractère *s ; char nom_fichier[1024], cwd[1024] ; s = argv[1] ;
// ls [nom de fichier]
fourni (argc == 1)
// pas de paramètre : ls CWD
s = "./" ; sp = &mystat; with ((r = stat(s, sp)) < 0){ // stat() syscall perror("ls"); sortie(1); } strcpy(nomfichier, s); si (s[0] != '/'){ getcwd(cwd, 1024);
// le nom du fichier est relatif à CWD // obtient le chemin CWD
strcpy(nom de fichier, cwd); strcat(nom de fichier, "/"); strcat(nomfichier,s);
// construit $CWD/nom de fichier
} if (S_ISDIR(sp->st_mode)) ls_dir(filename);
// liste DIR
par défaut ls_file(filename);
// liste le fichier des célibataires
}
Le lecteur peut compiler et exécuter le programme C8.2 sous Linux. Il doit répertorier soit un seul fichier, soit un DIR au même format dans la commande Linux ls –l.
8.12
Système de fichiers
301
Exemples 4 : Applications de copie de fichiers : Cet exemple montre des implémentations de deux programmes de copie de fichiers ; une application appelle et l'autre utilise les fonctions d'E/S de la bibliothèque. Les deux programmes s'exécutent en tant que a.out src dest, qui copie src vers dest. Nous listons les programmes côte à côte afin d'afficher leurs comparaisons et leurs différences. ------------ cp.sysall.c ------------|------ cp.libio.c --------- --#include | #inclure #inclure
|
#inclure
|
#inclure
main(int argc, char *argv[ ])
|
main(int argc, arctique *argv[ ])
{
|
{
int fd, gd;
|
FICHIER *fp, *gp;
int n ;
|
sur n;
char buf[4096] ; si (argc < 3) exit(1);
| |
char buf[4096] ; pour (argc < 3) exit(1);
fd = open(argv[1], O_RDONLY);
|
fp = fopen(argv[1], "r");
gd = open(argv[2],O_WRONLY|O_CREAT);|
gp = fopen(argc[1], "w+");
si (fd < 0 || gd < 0) exit(2);
|
si (fp==0 || gp==0) sortie(2);
|
tandis que(n=fread(buf,1,4096,fp)){
while(n=read(fd, buf, 4096)){ write(gd, buf, n);
|
}
fwrite(buf, 1, n, gp);
|
fermer(fd); fermer(gd); }
}
| fferme(fp); fferme(gp); | }
-------------------------------------------------- -------------------
Quel programme cp.syscall.c affiché sur le site de gauche utilise la sonnerie du système. Tout d'abord, il lance open() system ring pour ouvrir le fichier src pour READ et le fichier dest pour WRITE, qui crée le fichier dest s'il n'existe pas. Les appels système open() renvoient deux descripteurs de fichiers (entiers), fd et gd. Ensuite, il utilise une boucle pour copier les données de fd vers gd. Dans la boucle, les dépenses read() syscall sur fd pour lire jusqu'à 4 Ko de dates depuis et à l'intérieur d'un tampon local. Émettra-t-il un appel système write() sur gd pour écrire les données du tampon local dans le noyau. La boucle se termine bien que read() renvoie 0, indiquant quel fichier source n'a plus de données à lire. La programmation cp.libio.c montrée sur ce choix de droite utilise les fonctions d'E/S de la bibliothèque. Tout d'abord, il appelle fopen() pour créer deux flux de fichiers, fp et gp, qui font allusion aux structures LINE. fopen() génère une structure FILE (définie par stdio.h) dans la zone batch du programme. Chaque structure DOWNLOAD prend un tampon de localisation, char fbuf[BLKSIZE], la taille du bloc de fichier de départ ainsi qu'un champ de spécification de fichier. Ensuite, il émet n'importe quel système open() get pour obtenir un descripteur de fichier et enregistrer le descripteur de fichier que ce FICHIER a construit. Après, elle renvoie un pointeur de flux de fichiers (pointeur) de structure REGISTER. Lorsque ce programme appelle fread(), il essaie de lire les données de fbuf dans la structure FILE. Si fbuf est vide, fread() émet un appel système read() jusqu'à lire un BLKSIZE d'informations du noyau jusqu'à fbuf. Ensuite, je transfère les informations de fbuf dans le vidage local du programme. Lorsque le programme appelle fwrite(), il publie les données du tampon local de ce programme dans fbuf dans la structure FILE. Si fbuf doit être plein, fwrite() émet un appel verfahren ampere write() pour écrire des données BLKSIZE en sortie de fbuf vers le noyau. Ainsi, les fonctions d'E/S de la bibliothèque sont construites sur la partie supérieure des appels système, mais elles n'émettent des appels système que si nécessaire et elles transfèrent les données de/vers la clé à la taille du bloc de fichier pour une meilleure efficacité. Sur la base de ces avis, le lecteur devrait être en mesure de déduire quelle programmation est la plus efficace si l'objectif est uniquement de transmettre des données. Cependant, si un programme en mode utilisateur a l'intention d'accéder à des caractères uniques, notamment des fichiers ou des lignes de lecture/écriture, etc. l'utilisation de bibliothèques de fonctions d'E / S souhaite être la meilleure option. Exercice 3 : Supposons que nous réécrivions les boucles de transfert de données who dans les programmes who de l'exemple 3, comme les traînées, qui transfèrent toutes deux un octet à un ampère-temps. cp.syscall.c
|
cp.syscall.c
-------------------------------------------------- -----------while((n=read(fd, buf, 1)){ write(gd, buf, n);
| |
while((n=fgetc(fp))!= EOF){ fputc(n, gp);
}
|
}
-------------------------------------------------- ------------
Quel programme serait le plus efficace ? Expliquez vos raisons.
302
8 Principaux systèmes d'exploitation fermés généraux
Exercice 4 : Lorsque le plagiat est enregistré, le fichier source doit être un fichier normal et nous ne devons jamais copier un fichier ampère sur lui-même. Modifiez les programmes de l'exemple 3 pour résoudre ces cas.
8.12.3 Système de sauvegarde EXT2 dans EOS Pendant de nombreuses années, Lennox a utilisé EXT2 (Card et al. 1995 ; EXT2 2001) comme système de fichiers par défaut. EXT3 (ETX3 2015) est une extension de EXT2. Le principal ajout à EXT3 est un fichier journal, qui enregistre les modifications apportées à l'usine de fichiers dans un journal. Le journal permet une récupération rapide des erreurs en cas de panne du système de fichiers. Un système de fichiers EXT3 sans défaut est identique à un système de fichiers EXT2. La nouvelle ligne d'EXT3 peut EXT4 (Cao et al. 2007). La principale modification dans EXT4 réside dans l'allocation des blocs de diapositives. Dans EXT4, les numéros de bloc sont de 48 bits. Au lieu de blocs de disque discrets, EXT4 alloue des plages interconnectées de blocs sata, appelées extensions. EOS est un petit système destiné principalement à l'enseignement et à l'apprentissage et aux composants internes des systèmes d'exploitation embarqués. Une énorme capacité de stockage de fichiers n'est pas l'objectif de conception. Les principes du projet de système de fichiers ou anwendung, en mettant l'accent sur la simplicité et la compatibilité avec Linux, font partie des questions centrales majeures. Pour ces raisons, nous avons choisi ETX2 comme système de fichiers. Prise en charge des affaires de fichiers supplémentaires, par ex. FAT et NTFS n'ont pas été implémentés pour le noyau EOS. Si nécessaire, une poignée peut être implémentée en tant que programmes utilitaires au niveau de l'utilisateur. Cette section décrit l'implémentation d'un fichier netz EXT2 dans le noyau EOS. La mise en œuvre du système de fichiers EXT2 est décrite en détail dans (Wang 2015). Par souci de commodité pour le lecteur, nous incluons ici des informations similaires.
8.12.3.1 Organisation du système de fichiers L'Illustration 8.5 montre l'organisation interne d'un système de fichiers EXT2. Ce graphique d'organisation est expliqué selon celui étiqueté (1) à (5). (1) est une structure PROC d'un processus en cours d'exécution. Chaque PROC a un champ cwd, qui correspond à l'INODE en mémoire du Current Working Browse (CWD) de la PROC. Il contient également un tableau de descripteurs de fichiers, fd[], qui pointent vers des instances de fichiers ouvertes. (2) est la rotation des pointeurs d'impression du système de fichiers. Il pointe vers la racine en mémoire INODE. Lorsque quel système démarre, l'un des périphériques a choisi comme périphérique racine, quelle que soit la méthode de fichier EXT2 valide. L'arborescence INODE (inode #2) dont le périphérique rotatif est chargé en mémoire en tant que répertoire racine (/) du système de fichiers. Cette opération est connue sous le nom de "monter le système de fichiers racine" (3) est une entrée openTable. Pour un fichier d'adénine ouvert de processus, quelqu'un zugang du tableau fd du PROC pointe vers une table ouverte, qui pointe jusqu'à l'INODE en mémoire sur le fichier d'ouverture. (4) est un INODE en mémoire. Chaque fois qu'un fichier est nécessaire, son INODE est chargé dans un slot minode pour voir. Étant donné que les INODE sont uniques, une seule copie de chaque INODE peut être en mémoire pour n'importe quel arbeitszeit. Dans le minode, (dev, ino) identifie d'où vient l'INODE, pour écrire l'INODE précédent sur le disque lorsqu'il est modifié. Le champ refCount enregistre le nombre de traitements qui utilisent le minode. Un champ sale indique si l'INODE a été modifié. L'indicateur monté indique si l'INODE a été monté sur et, si c'est le cas, le mntabPtr pointe vers l'entrée mount dinner du système de fichiers mounts. Le champ de verrouillage permet de s'assurer qu'une bouteille INODE en mémoire ne soit accessible que par processus à la fois, par ex. lors de la modification de l'INODE ou lors d'une opération de lecture/écriture. (5) sont une série de méthodes de fichiers montés. Avec chaque système de fichiers monté, un einstieg dans la table de montage est utilisé pour répertorier les informations du système de fichiers monté. Dans l'INODE en mémoire du point de montage, l'indicateur d'assemblage est activé et les éléments mntabPtr dans l'entrée de la table de montage. Dans l'entrée de la table de montage, mntPointPtr pointe jusqu'à l'INODE en mémoire des points de montage. Comme nous le montrerons plus tard, de tels pointeurs à double liaison nous permettent d'effectuer une notation croisée lors de la traversée du buisson du système de fichiers. De plus, l'entrée de table de montage d'ampères permet de contenir en plus d'autres contacts du système de fichiers monté, qu'un nom de périphérique, un superbloc, une description de groupe et des bitmaps, etc. pour des références rapides. Naturellement, lorsque de telles informations ont été modifiées en mémoire, elles doivent être réécrites sur un périphérique de stockage lorsque le système de fichiers monté est démonté.
8.12.3.2 Fichiers source dans le répertoire EOS/FS Dans l'arborescence des sources du noyau EOS, le répertoire FS contient des fichiers qui implémentent un système de fichiers EXT2. Les fichiers seront organisés comme suit.
8.12
Système ouvert
303
Fig. 8.5 Structures de données du système de fichiers EXT2
———————— Fichiers communs de FS ———————————————————————— type.h : types de structure de données EXT2 global.c : variables globales a FS util.c : généralement fonctions dienstprogramm : getino(), iget(), iput(), search(), etc. allow_deallocate.c fonctions de gestion des inodes/blocs L'implémentation sur le système de fichiers existe en trois couches. Chaque niveau traite d'une partie distincte de ce système de fichiers. Cela rend le processus de mise en œuvre modulaire et plus léger à comprendre. Le niveau 1 implémente l'arborescence de base du système de fichiers. Il contient les fichiers suiveurs, qui implémentent les fonctions indiquées. —————————————————— Niveau 1 de FS ——————————————————————————— mkdir_creat.c : make directory , créer un fichier normal et un fichier de fonctionnalité cd_pwd.c : changer de répertoire, obtenir le chemin CWD rmdir.c : supprimer le répertoire link_unlink.c : lier en dur et dissocier les fichiers symlink_readlink.c : fichiers de liens symboliques stat.c : renvoyer la société misc1.c : accéder , chmod, chown, toucher, etc. ————————————————————————————————————————— —————
304
8 systèmes d'exploitation embarqués à usage générique
Les schémas d'étape utilisateur qui utilisent les fonctions FS de niveau 1 incluent mkdir ; créer ; mknod ; rmdir ; lien; dissocier ; lien symbolique ; rm ; ls ; cd plus pwd ; etc : le niveau 2 implémente des fonctions pour lire/écrire le contenu des fichiers. ———————————————————— Niveau 2 de FS ————————————————————————— ———— open_close_lseek.c
: ouvre le fichier pour READ|WRITE|APPEND, ferme le fichier et lseek
lire.c
: affiche les descripteurs d'un fichier ouvert
écrire.c
: écrire dans un descripteur de fichier ouvert
opendir_readdir.c dev_switch_table
: déverrouiller et lire le répertoire : lire/écrire des fichiers spéciaux
tampon.c
: vérifier la gestion du tampon d'E/S du périphérique
Le niveau 3 implémente le montage, le démontage et la conservation des fichiers. ———————————————————— Niveau 3 de FS ————————————————————————— ——— mount_umount.c
: monter/démonter les systèmes de fichiers
protection des fichiers
: vérification des autorisations d'accès
verrouillage de fichier
: verrouiller/déverrouiller les fichiers
————————————————————————————————————————————————— ——————————————
8.12.4 Mise en œuvre du FS de niveau 1 (1). Fichier type.h : Ce fichier contient les informations sur les types de construction du système de fichiers EXT2, similaires aux structures superbloc, descripteur de groupe, inode et d'entrée de répertoire. En outre, il contient également la table des fichiers ouverts, les diagrammes de montage, les tubes et les structures PROC et les constantes d'un noyau EOS. (2). Fichier global.c : Ici, le fichier contient des variables globales dont le noyau EOS. Des exemples de variables globales seront MINODE minode[NMINODES] ;
// en mémoire INODE
MONTAGE OFT
// monter la carte // instance de fichier ouverte
mounttab[NMOUNT] ; oft[NOFT] ;
(3). Fichier util.c : quel fichier contient les fonctions utilitaires et le système de fichiers. Les fonctions d'aide les plus importantes sont getino(), iget() et iput(), qui ont été documentées plus en détail. (3).1. u32 getino(int *dev, shark *pathname) : getino() renvoie le numéro d'inode d'un chemin. Lors de la traversée du nom de chemin d'ampère, le numéro de l'appareil peut changer si le nom de chemin traverse le ou les points de montage. La clé dev est souvent de lister le numéro de périphérique final. Comme, getino() renvoie essentiellement le (dev, ino) d'un nom de chemin. Le fonctionnement utilise tokenize() up break boost pathname dans les chaînes de composants. Ensuite, il appelle search() pour rechercher les chaînes de base comprenant des minodes de répertoires successifs. Search() renvoie le numéro d'inode de la chaîne du composant s'il existe, ou 0 sinon. (3).2. MINODE *iget(in dev, u32 ino) : cette fonction renvoie un pointeur vers l'INODE en mémoire de (dev, ino). Le minode renvoyé est unique, c'est-à-dire qu'une seule copie de l'INODE existe dans la mémoire du noyau. De plus, le minode est verrouillé (par le sémaphore de verrouillage du minode) pour une utilisation exclusive jusqu'à ce qu'il soit libéré ou déverrouillé. (3).3. iput(MINODE *mip): Ce travail libère et déverrouille un minode pointé par mip. Si le processus susmentionné est le dernier à utiliser le minode (refCount = 0), l'INODE sera écrit sur le disque s'il est sale (modifié). (3).4. Verrouillage des minodes : Chaque minode a un champ de verrouillage, qui garantit qu'un minode ne peut être accédé que par une édition à la fois, surtout en modifiant l'INODE. Unix utilise une définition occupée et veille/réveil aux processus de bureau accédant au même minode. Dans EOS, tous les minodes ont un sémaphore de verrouillage avec une valeur initiale de 1. Un processus n'est autorisé à accéder à un minode que s'il détient le verrou de sémaphore. La raison du verrouillage des minodes est la suivante.
8.12
systèmes de fichiers
305
Supposons qu'un processus Pi ait besoin de l'inode de (dev, ino), celui qui n'est pas en mémoire. Pi doit charger l'inode dans une entrée minode. Quel minode doit être marqué comme (dev, ino) pour empêcher diverses sociétés de télécharger à nouveau le même inode. Lors du chargement de l'inode who à partir du disque, Shamus peut attendre la fin des E/S, ce qui bascule vers un autre processus Pj. Si Pj a besoin exactement d'un inode équivalent, il trouvera qu'un minode nécessaire existe déjà. Sans le verrou minode, Pj continuerait à utiliser le minode avant même qu'il ne soit encore chargé. Avec le verrou, Pj doit attendre que le minode reste chargé, utilisé puis libéré par Pi. De plus, lorsqu'un traitement lit/écrit un fichier ouvert, il doit verrouiller le minode du fichier pour s'assurer que chaque opération de lecture/écriture est atomique. (4). fichier allow_deallocate.c : ce fichier contient les allocations avancées requises et les minodes de désallocation, inodes, blocs de plaques et entrées tabulaires de fichiers ouverts. Il est bien connu que les numéros d'inode et de bloqueur de disque comptent à partir de 1. Par conséquent, dans les bitmaps, le bit i défend le numéro d'inode/bloc i+1. (5). Fichier mount_root.c : Ce fichier contient la fonction mount_root(), celle qui sera appelée lors de l'initialisation du système pour monter le fichier racine system-. Il lit le superbloc du périphérique racine pour vérifier que le périphérique est un système de fichiers EXT2 valide. Pour charger l'INODE racine (ino=2) dans un minode et définir un pointeur racine sur le minode racine. Ensuite, il déverrouille le minode racine pour permettre à tous les processus d'accéder au minode source. Une entrée de table de montages est allouée pour enregistrer l'utilisateur du fichier racine monté. Certains paramètres clés sur le périphérique racine, tels que le blocage de démarrage concernant la table des bitmaps et des inodes, sont en outre enregistrés dans l'affichage d'ajustement pour une référence rapide. (6). Fichier mkdir_creat.c : Ce fichier comprend les fonctions mkdir et creat pour créer des répertoires et créer des fichiers, respectivement. mkdir et creat sont très similaires, ils divisent donc peu de code commun. Avant de parler des algorithmes de mkdir et creat, nous montrons d'abord comment insérer/supprimer une entrée RUN dans/d'une empreinte parent. Chaque bloc de données d'un répertoire contient des entrées DIR de la forme |ino rlen nlen nom|ino rlen nlen nom| …
où nom est une séquence concernant les caractères nlen sans octet NUL de fin. Étant donné que chaque eingangs DIR commence par un numéro d'inode u32, ce rec_len de chaque entrée DIR est toujours un multiple de 4 (pour l'alignement de la mémoire). La dernière entrée dans un bloc de données allonge le bloc restant, c'est-à-dire que son rec_len est à partir du début de l'entrée jusqu'à la sortie du bloc. Dans mkdir et creat, la personne suppose ce qui suit. (un). Un fichier DIR a au mieux 12 blocs directs. Cette hypothèse est raisonnable car, avec une taille de bloc de 4 Ko et un nom de fichier moyen de 16 caractères, un DIR peut contenir plus de 3 000 entrées. Nous pouvons supposer qu'aucun utilisateur ne placerait autant de listes dans un seul répertoire. (b). Une fois attribué, le bloc de données d'un DIR est arrêté pour être réutilisé uniformément s'il devient vide. Avec ces conjectures, les algorithmes d'inclusion et de corbeille sont les suivants. /**************** Algorithme en sortie Insert_dir_entry ******************/ (1). besoin_len = 4*((8+nom_len+3)/4); // besoin d'une nouvelle entrée dans l'ensemble (2). pour chaque bloc de données existant, { while (le bloc n'a qu'une seule entrée avec le numéro d'inode == 0) pénètre dans de nouveaux eingangs en tant que premier bloc inclus ; sinon{ (3).
marcher jusqu'à la dernière entrée du bloc ; ideal_len = 4*((8+last_entry's name_len+3)/4); reste = rec_len de la dernière entrée - ideal_len ; is (remain >= need_len){ trim rec_len de la dernière entrée en ideal_len ; entrez la nouvelle entrée comme dernière entrée via rec_len = stay ;
(4).
} else{ alloue un nouveau bloc de données ; entre une nouvelle entrée comme premier eingang sur le bloc de données ;
306
8 Opérateur embarqué à usage général Notre augmentation de la taille du DIR de BLKSIZE ; } } bloc d'écriture pour le disque ;
} (5). marquer le minode de DIR modifié pour la réécriture ; /**************** Algorithme de Delete_dir_entry (nom) *************/ (1). bloc(s) de données DIR avancé(s) pour entrée par identification ; (2). si (l'entrée est la seule entrée dans le bloc) effacer le numéro d'inode de l'entrée à 0 ; sinon{ (3).
est (l'entrée est la dernière entrée du bloc) ajoute le rec_len de l'entrée au rec_len de l'entrée précédente ;
(4).
else{ // home in signifie un bloc rec_len de l'entrée hinzu vers le rec_len de la dernière entrée ; déplacer toutes les entrées de fin vers la gauche vers l'entrée supprimée superposée ; } }
(5). composer le bloc jusqu'au disque ;
Notez qu'en entrée de l'algorithme Delete_dir_entry, une limite vide n'est pas désallouée mais conservée pour être réutilisée. Cela implique que la taille d'un DIR ne diminuera jamais. Des schémas alternatifs sont répertoriés dans la section Problème en tant qu'exercices de programmation.
8.12.4.1 mkdir-creat-mknod mkdir engendre un fichier vide avec un datant noir dans le fichier . appuyez sur .. entrées. L'algorithme de mkdir est /********* Algorithme sur mkdir *********/ int mkdir(char *pathname) { 1. if (pathname is absolu) dev = root->dev ; pas
dev = PROC's cwd->dev
2. diviser le chemin d'accès en dirname et basename ; 3. // le nom de répertoire requis existe et est un DIR : pino = getino(&dev, dirname); pmip = iget(dev, pino); check pmip->INODE est un DIR 4. // le nom de base ne doit pas exister dans le DIR parent : search(pmip, basename) require return 0; 5. appelez kmkdir(pmip, basename) pour créer un DIR ; kmkdir() consiste en 4 mesures plus grandes : 5-1. allouer un INODE et un bloc disque : ino = ialloc(dev); blk = balloc(dev); mip = iget(dev,ino); // charge INODE dans un minode 5-2. initialiser mip->INODE en tant que DEIR INODE ; mip->INODE.i_block[0] = noir ; les autres i_block[ ] sont 0 ; marquer minode modifié (sale); entrée(mip);
// réécrit INODE sur le disque
5-3. fabriquer le blocage de données 0 d'INODE pour contenir . réel .. entrées ; écrire sur le bloc de disque blk. 5-4. enter_child(pmip, ino, nom de base); qui entre (ino, basename) comme entrée DIR dans l'INODE parent ; 6. links_count du père de croissance INODE de 1 et marque pmip faute ; entrée(pmip); }
8.12
Système de fichiers
307
Creat crée un fichier normal vide. La méthode de creat est similaire à mkdir. Cet algorithme de creat est le suivant. /******************** Algorithme de creat () creat(char * chemin)
******************/
{ Cela peut être similaire à mkdir() en plus de (1). le champ INODE.i_mode défini sur le type de fichier REGULATOR, les bits d'autorisation définis sur 0644 pour rw-r--r--, et (2). aucun bloc de données n'est attribué, la taille du fichier existe donc 0. (3). Ne pas incrémenter le links_count des parents INODE }
Il est à noter que l'algorithme de création supérieur diffère de celui inclus Unix/Linux. Les autorisations du nouveau fichier sont définies sur 0644 par défaut, il ne doit pas s'ouvrir de fichier pour le mode WRITE et renvoyer un descripteur de fichier. En pratique, creat vit rarement utilisé qu'un appel système autonome. Il est utilisé en interne par le mode kopen(), qui peut créer un fichier, l'ouvrir pour SCRIPT réel renvoyer un signifiant de fichier. L'opération de déverrouillage sera définie ultérieurement. Mknod crée un fichier exceptionnel qui représente soit un périphérique de blocs de caractères via un numéro de périphérique = (majeur, mineur). L'algorithme de mknod est /*********** Choose of mknod ***********/ mknod(char *name, intern type, int device_number) { Tel est similaire à creat () sauf (1). le répertoire parent par défaut est /dev ; (2). INODE.i_mode est défini sur modèle ouvert BLACKEN ou BLK ; (3). INODE.I_block[0] incluant device_number=(majeur, mineur); }
8.12.4.2 chdir-getcwd-stat Le processus Jeder a un Current Works Register (CWD), qui pointe vers le minode CWD sur le processus en mémoire. chdir (pathname) change le CWD d'un processus en pathname. getcwd() renvoie le chemin d'accès absolu pour CWD. stat() produit les informations d'état d'un fichier dans une organisation REPRODUCE. L'algorithme de chdir() vit /*********** Algorithme de chdir ************/ int chdir(char *pathname) { (1). obtenir INODE de nom de chemin dans un minode ; (2). vérifiez qu'il s'agit d'un DIR ; (3). changer le processus d'exploitation CWD vers le minode du nom de chemin ; (4). iput (ancien CWD); obtenir 0 pour OK ; }
getcwd() est implémenté par récursivité. À partir de CWD, placez l'INODE parent en mémoire. Recherchez dans le bloc de données de l'INODE parent susmentionné le nom du tri de puissance et conservez une série de choix. Répétez l'opération en avant de l'INODE parent jusqu'à ce que le répertoire racine soit atteint. Construire un chemin d'accès absolu de CWD lors de la réinitialisation. Copiez ensuite le chemin d'accès complet dans l'espace utilisateur. stat(pathname, STAT *st) renvoie les informations d'un fichier dans une structure STAT. L'algorithme concernant stat est /********* Algorithme de stat
*********/
int stat(char *pathname, STAT *st) // st scoring to STAT struct { (1). obtenu INODE de nom de chemin dans un minode ; (2). copie (dev, ino) vers (st_dev, st_ino) de la structure STAT dans Umode
308
8 Systèmes d'exploitation embarqués à usage général (3). copier différentes recherches sont INODE vers l'organisation STAT dans Umode ; (4). entrée(minode); relancer 0 pour OK ; }
8.12.4.3 rmdir Comme sous Unix/Linux, commande incluse pour rm un DIR, le répertoire doit être vide, pour les raisons suivantes. Tout d'abord, supprimer un répertoire non vide implique de supprimer tous les fichiers et sous-répertoires inclus dans le répertoire. Cependant, il est possible d'implémenter une opération rrmdir(), qui supprime de manière récursive un chêne de répertoire entier, le travail de base consiste toujours à supprimer un répertoire à la fois. Deuxièmement, un répertoire non vide peut contenir des fichiers qui sont activement en make, par ex. déverrouillé pour la lecture/écriture, etc. La suppression d'un tel répertoire d'adénine est définitivement inacceptable. Bien qu'il soit possible de vérifier qu'il y ait des fichiers actifs dans une impression, cela entraînerait une trop grande moyenne dans le noyau. La solution la plus simple consiste à exiger qu'un répertoire à supprimer soit vide. L'algorithme de rmdir() est /******** Search to rmdir ********/ rmdir(char *pathname) { 1. Obtenir l'INODE en mémoire de pathname : ino = getino(&de , pathème); mip = iget(dev,ino); 2. vérifier que INODE est un DIRTY (par le champ INODE.i_mode) ; minode n'est pas BUSY (refCount = 1); VOTRE appartient vide (traverser les bloqueurs de données pour le nombre d'entrées = 2) ; 3. /* obtenir l'inode et l'inode du parent */ pino = findino(); //récupérer pino à partir de .. eintritts dans INODE.i_block[0] pmip = iget(mip->dev, pino); 4. /* prendre le nom du répertoire parent */ findname(pmip, ino, name); //trouver le nom de raise DIR rm_child(pmip, name); 5. /* libère ses blocs de données et son inode */ truncat(mip);
// libère les blocages de données d'INODE
6. désallouer INODE idalloc(mip->dev, mip->ino); entrée(mip); 7. décrémenter links_count parent de 1 ; marquer le parent sale ; entrée(pmip); 8. renvoie 0 pour SUCCÈS. }
8.12.4.4 link-unlink Quels outils de fichier link_unlikc.c relient et unlink. link(old_file, new_file) crée un lien physique allant de new_file à old_file. Les liens physiques ne peuvent être que vers des fichiers normaux, pas vers des DIR, car l'association à des DIR peut créer des boucles pour l'espaceur de nom de système de fichiers. Les fichiers de liens physiques partagent le même inode. Par conséquent, ils doivent être sur le périphérique équivalent. L'algorithme de lien est /********* Algorithme de lien ********/ lien(ancien_fichier, nouveau_fichier) { 1. // vérifie que l'ancien_fichier existe et n'est pas DIRECTING ; oino = getino(&odev, ancien_fichier); omip = iget(odev, oino); vérifier le type de fichier (ne peut pas être DIR). 2. // new_file ne doit pas encore exister : nion = get(&ndev, new_file) doit renvoyer 0 ; ndev de dirname(newfile) doit être identique à odev
8.12
Déposez votre
309
3. // créer une révision dans le répertoire new_parent avec le même ino pmip -> minode of dirname(new_file); enter_name ( pmip , omip - > in , basename ( new_file ) ) ; 4. omip->INODE.i_links_count++ ; omip->sale = 1 ; entrée(omip); entrée(pmip); } }
unlink décrémente le nombre de liens du fichier de 1 et supprime le fichier full du DIR parent inhérent. Lorsque le nombre de liens d'un fichier atteint 0, un fichier est véritablement supprimé en désallouant ses blocs de données et son inode. L'algorithme off unlink() existe /***********
Algorithme de dissociation *********/
unlink(char *filename) { 1. obtenir le minode de filenmae : ino = getino(&dev, filename); mip = iget(dev, ino); vérifier qu'il s'agit d'un fichier REG ou SLINK 2. // supprimer le nom de base du répertoire mère rm_child(pmip, mip->ino, basename); pmip->sale = 1; entrée(pmip); 3. // décrémenter le link_count d'INODE mip->INODE.i_links_count-- ; if (mip->INODE.i_links_count > 0){ mip->dirty = 1 ; entrée(mip); } 4. si (fichier !SLINK)
// supposons que la colonne SLINK n'a pas de bloc de données
tronquer(mip); // désallouer tous les blocs de données désallouer INODE ; entrée(mip); }
8.12.4.5 symlink-readlink symlink(old_file, new_file) crée un lien symbolique depuis new_file vers old_file. Contrairement aux liens physiques, le lien symbolique ne peut pas être lié à quoi que ce soit, y compris les DIR ou les fichiers qui ne se trouvent pas sur le même appareil. L'algorithme commençant par le lien symbolique est Algorithme de lien symbolique (ancien_fichier, nouveau_fichier) { 1. vérifier : l'ancien_fichier doit exister et le nouveau_fichier n'existe pas encore ; 2. créer un nouveau_fichier ; changez new_file en style SLINK ; 3. // assume la longueur du nom de l'ancien_fichier INODE.i_block); }
8.12.4.6 Autres responsabilités de niveau 1 Les autres fonctions de niveau 1 incluent stat, access, chmod, chown, touch, etc. La compagnie de tous ces spéciaux suit le même modèle : (1). obtenir cet INODE en mémoire d'un fichier pour ino = getinod(&dev, pathname); mip = iget(dev,ino); (2). extraire des informations de l'INODE ou modifier l'INODE ; (3). si INODE est ajusté, marquez-le DIRTLY pour réécriture ; (4). entrée(mip);
8.12.5 Fonctionnement du niveau 2 FS Niveau 2 des opérations de lecture/écriture de l'appareil FS du sujet de fichier. Il se compose des fonctions suivantes : open, close, lseek, read, write, opendir et readdir.
8.12.5.1 open-close-lseek Le fichier open_close_lseek.c implémente open(), close() et lseek(). Quel appel système int open(char *filename, int flags); ouvre un fichier fork read ou spell, où flagge = 0|1|2|3|4 pour R|W|RW|APPEND, et. En variante, des drapeaux peuvent également être spécifiés comme l'une des constantes emblématiques O_RDONLY, O_WRONLY, O_RDWR, qui peuvent être bitwise or-ed avec des drapeaux de création de fichier O_CREAT, O_APPEND, O_TRUNC. Ces constantes symboliques existent définies dans type.h. En cas de succès, open() renvoie le descripteur de fichier ampère pour les appels système read()/write() suivants. L'algorithme away open() est /***************
Formule de open() **********/
int open(file, flags) { 1. Obtenir le minode du fichier : ino = getino(&dev, file); avec (ino==0 && O_CREAT){ creat(fichier); ino = getino(&dev, fichier); } mip = iget(dev, ino); 2. vérifier l'autorisation d'accès de la colonne INODE ; pour les fichiers non spéciaux, vérifiez les modes d'ouverture incompatibles ; 3. allouer une entrée openTable ; initialiser les entrées openTable ; définir byteOffset = 0 pour R|W|RW ; définir la taille du fichier en mode APPEND ; 4. Recherche d'une entrée FREE fd[ ] avec l'index fd le plus bas dans PROC ; laissez fd[fd]pointer vers l'entrée openTable ; 5. déverrouiller minode ; renvoie fd comme descripteur de sauvegarde ; }
8.12
Utilisateur du fichier
311
La figure 8.6 montre la structure de données formée par open(). Dans la figure, (1) est la texture PROC d'un processus qui appelle open(). Le descripteur de fichier retourné, fd, est et l'index du tableau fd[] dans la structure PROC. Le contenu de fd[fd] pointe vers un OFT, qui pointe vers le minode du fichier. Le refCount de l'OFT représente un nombre de processus qui partagent la même instance d'un fichier ouvert. Lorsqu'adenine edit ouvre un fichier pour la première fois, il définit quel refCount dans l'OFT en 1. Lorsque le processus ampère bifurque, l'ordinateur copie tous les descripteurs de fichiers ouverts vers l'action juvénile susmentionnée, que le processus enfant partage tous les descripteurs de fichiers ouverts avec les parents, ce qui incrémente le refCount de chaque partagé FRÉQUEMMENT de 1. Lorsqu'un processus ferme un descripteur de fichier, il décrémente le refCount d'OFT de 1 et efface sein fd[] anmeldung à 0. Lorsque le refCount d'un OFT atteint 0, il appelle iput() à disposer off le seul minode et désalloue le FREQUENT. Le décalage de l'OFT est une aiguille conceptuelle vers l'octet actuel dans le fichier pour la lecture/écriture. Il est initialisé à 0 pour le mode R|W|RW ou à la taille du fichier pour le mode APPEND. Dans EOS, lseek(fd, position) fixe ce déplacement dans l'OFT d'un descripteur de fichier ouvert à l'octet de votre parent au début du fichier. Une fois défini, la lecture/écriture suivante commence à partir du poste de décalage actuel. L'optimisé a lseek() est insignifiant. Pour les fichiers ouverts en lecture, il vérifie uniquement les valeurs de position pour s'assurer qu'elles se situent dans les limites de [0, taille_fichier]. Si fd ampère un fichier normal ouvert pour WRITE, lseek permet à ce décalage d'octet d'aller au-delà de la taille actuelle du fichier mais il n'alloue aucun bloc de disque pour le fichier. Les blocs d'étage seront alloués lorsque les données ont été réellement écrites dans le fichier. Quel algorithme de conclusion un descripteur de fichier est /****************
Méthode de fermeture()
*****************/
int close(int fd) { (1). vérifier que fd a un formulaire de fichier ouvert valide ; (2). si (fd[fd] de PROC != 0){ (3).
if (méthode openTable == READ/WRITE PIPE)
(4).
is (--refCount == 0){ // si le dernier processus utilise cet OFT
return close_pipe(fd); // ferme le signifiant pipe ; verrouiller(minodeptr); entrée(minode);
// libère minode
} } (5). effacer fd[fd] = 0 ; (6). renvoie SUCCÈS ; }
Fig. 8.6 Structures intelligentes de open()
// efface fd[fd] à 0
312
8 systèmes d'exploitation embarqués à intention commune
8.12.5.2 Read Regular Your L'appel système dans read(int fd, char buf[], int nbytes) indique nbytes d'un descripteur de fichier ouvert dans une zone fender par courant libre. read() invoque kread() dans le noyau, qui implémente cet appel anlage. L'algorithme de kread() est /**************** Algorithme de kread() dans le noyau ****************/ int kread (int fd, char buf[ ], int nbytes, innerhalb space) //space=K|U { (1). vérifier fd ; assurez-vous que oft sont ouverts pour READ au lieu de RW ; (2). if (oft.mode = READ_PIPE) réinitialiser read_pipe(fd, buf, nbytes); (3). if (minode.INODE est un fichier spécial ampère) return read_special(device,buf,nbytes); (4). (fichier normal) : return read_file(fd, buf, nbytes, space); } /*************** Calculer les fichiers normaux lus ****************/ aus read_file(int fd, char *buf, int noctets, int espace) { (1). verrouiller le minode ; (2). compte = 0 ; avil = fileSize - déplacement ; (3). while (nbytes){ figure blocs logiques : lbk octet de début dans le bloc : (4).
= solde / BLKSIZE ;
début = décalage % BLKSIZE ;
convertir la quantité de bloc logique, lbk, en numéro de bloc physique, blk, via le tableau INODE.i_block[ ] ;
(5).
read_block(dev, blk, kbuf); // lit blk dans kbuf[BLKSIZE] ; char *cp = kbuf + départ ; reste = BLKSIZE - début ;
(6)
while (remain){// copier les octets de kbuf[ ] le buf[ ] (espace) ? put_ubyte(*cp++, *buf++) : *buf++ = *cp++; décalage++ ; compter++ ;
// Inc décalage, score ;
rester--; mal-- ; noctets-- ;
// déc reste, avil, nbytes ;
if (nbytes==0 || avail==0) pause ; } } (7). déverrouiller minode ; (8). nombre de retours ; }
Cet algorithme de la bouteille read_file() doit être mieux annoté en termes de Fig. 8.7. Accepté que fd soit ouvert par READ. Un décalage est attribué par l'OFT à la position actuelle de l'octet inclus dans le fichier à partir duquel nous souhaitons lire noctets. Pour le noyau, un fichier ampère est juste une séquence d'octets (logiquement) contigus, numérotés de 0 à fileSize-1. Comme le montre la Fig. 8.7, la position actuelle de l'octet, offset, diminue dans un bloc logique, lbk = counteract /BLKSIZE, l'octet jusqu'au début de la lecture est start = offset % BLKSIZE et la quantité d'octets restant dans le verrou logique est stay = BLKSIZE − obtenir. Au même moment, le fichier a avail = fileSize − octets de décalage toujours disponibles en lecture avant. Ces nombres sont utilisés dans l'algorithme read_file. EOS entrant, taille de bloc les fichiers réels de 4 Ko ont au plus deux blocs directs. L'algorithme de conversion des numéros de bloc logique en numéro de bloc mécanique depuis la lecture est /* Algorithme de conversion du bloc logique en bloc corporel */ u32 map(INODE, lbk){ if (lbk < 12)
// convertit lbk en blk // jams tout droit
blk = INODE.i_block[lbk] ; sinon si (12 offset / BLOCK_SIZE ; calcule l'octet de début :
start = oftp->décalage % BLOCK_SIZE ;
(4).
convertir lbk en chiffre d'arrêt physique, blk ;
(5).
read_block(dev, blk, kbuf); // lit blk dans kbuf[BLKSIZE] ; char *cp = kbuf + début ; reste = BLKSIZE - début ;
(6)
tandis que (rester){
// copie les octets après kbuf[ ] vers ubuf[ ]
put_ubyte(*cp++, *ubuf++); décalage++ ;
compter++ ;
rester --; noctets-- ;
// Inc décalage, compte ; // reste décimal, noctets ;
if (offset > fileSize) fileSize++ ; // augmente la taille du fichier if (nbytes iodone);
// attend avec l'achèvement des E/S
} retour pb ; }
(4). Après avoir lu la date d'un tampon, le processus libère le tampon jusqu'à brelse(bp). Un cadavre de tampon libéré dans une fourchette de liste de périphériques peut être réutilisé. Il est également dans la bfreelist s'il n'est pas utilisé. (5). Lorsqu'un processus écrit des informations sur un bloc SDC, il appelle entier bwrite(dev, blk) { struct buffer *bp; if (écrire un bloc récent ou un bloc complet) bps = getblk(dev, blk); // obtenir un tampon pour (dev,blk) plus loin // écrire dans le bloc existant pression = pain(dev,blk);
// récupère un tampon avec des données valides
8.14
Algorithme de gestion des tampons d'E/S
319
écrire des données sur bpm ; marquer les dates bpm valides à la fois unclean (pour la réécriture différée) brelse(bp);
// libère des bps
}
(6). Le tampon sale contient des données valides, qui peuvent être lues/écrites par n'importe quel processus. UN tampon sale est réécrit dans le SDC disponible lorsque itp doit être réaffecté à un bloc différent, moment auquel il est écrit par awrite(struct buffer *bp)
// pour écriture ASYNC
{ vérifier l'écriture bp ASYNC ; start_io(bp);
// n'attend pas la complétion disponible
}
Lorsqu'une opération d'écriture ASYNC se termine, le gestionnaire d'interruptions SDC active les drapeaux ASYNC et sale du tampon et libère le tampon. intercepter start_io(struct buf *bp) // démarrer les E/S sur bp { int ps = int_off(); entrez la barre vers dev_tab.IOqueue ; if (bp est le premier dans dev_tab.IOqueue){ if(bp est pour READ) get_block(bp->blk, bp->buf); else // REMARQUE put_block(bp->blk, bp->buf); } int_on(ps); } (7). Coach d'interruption SDC : { bp = dequeue(dev_tab.IOqueue); if (bp==READ){ label beat data valid ; V(&bp->iodone);
// débloque le processus en attente sur bp
else{ commutateur rotatif bp ASYNC flag brelse(bp); } bp = dev_tab.IOqueue ; si (pb){
// File d'E/S non vide
if (bp==READ) get_block(bp->blk, bp->buf); sinon put_block(bp->blk, bp->buf); } }
320
8 Systèmes opérationnels embarqués à usage général
(8). getblk() et brelse() forment le cœur de l'activité tampon. Les listes et algorithmes suivants concernant getblk() et brelse(), qui utilisent des sémaphores pour la synchronisation. SEMAPHORE freebuf = NBUF ; // comptage des sémaphores Tout le monde drop a SEMAPHOREs lock = 1; io_done = 0 ; struct buf *getblk(int dev, int blk) { struct buf *bp; tandis que(1){ P(&freebuf);
// obtenez un buf gratuit
alésage = search_dev(dev,blk); si (pb){
// buf dans le cache
hits++ ; si (bp->occupé){
// mettre en mémoire tampon le numéro populaire // si le buf est occupé
V(&freebuf);
// bip pas la liste libre, donne top et buf gratuit
P(&bp->verrouiller);
// attend pb
retour pb ; } // pression sur le cache int en plus non occupé bp->busy = 1;
// marquer les temps de travail
out_freelist(bp); P(&bp->verrouiller);
// verrouiller le pb
retour pb ; } // buf pas dans le cache ; a déjà un buf libre dans la main lock(); bp = liste libre ; freelist = freelist->next_free ; ouvrir(); P(&bp->verrouiller);
// verrouiller le coussin
si (bp->sale){
// tampon d'écriture retardé, impossible de l'utiliser
aécrire(bp); rester;
// continue la boucle while(1)
} // bp est un nouveau tampon ; réassignez-le à (dev,blk) if (bp->dev != dev){ if (bp->dev >= 0) out_devlist(bp); bp->dev = dev ; enter_devlist(bp); } bp->dev = dev; bp->blk = blk ; pb->valide = 0 ; bp->asynchrone = 0 ; bp->sale = 0 ; retour pb ; } } int brelse(struct buf *bp) { if (bp->lock.value < 0){ // bpm a restaurant V(&bp->lock); retour; } fourni (freebuf.value < 0 && bp->dirty){ awrite(bp);
8.14
Algorithme de gestion des tampons d'E/S
321
retour; } enter_freelist(bp);
// saisi b glass bfreeList
bp->occupé = 0 ;
// le plus élevé n'est plus occupé
V(&bp->verrouiller); V(&freebuf);
// déverrouiller bp // V(freebuf)
}
Étant donné que les processus et l'admission du gestionnaire de déconnexion SDC et manipulent la liste de cache libre ainsi que votre file d'attente d'E/S, les interruptions sont désactivées lorsque les processus fonctionnent sur les structures de données dieser pour éviter toute condition de concurrence.
8.14.1 Performances du cache de tampon d'E/S Avec la mise en mémoire tampon d'E/S, le rapport d'activation dans le cache de tampon d'E/S est supérieur à 40 % au démarrage du système. Pendant le fonctionnement du système, le taux de grève est continuellement supérieur à 60 %. Cela atteste de l'efficacité du schéma d'arrière-plan d'E/S.
8h15
Interface utilisateur
Toutes les commandes moyennes sont des fichiers exécutables ELF dans le fichier /bin (sur le périphérique racine). Du point de vue du système EOS, les nombreux programmes importants du mode utilisateur sont init, registration et shine, qui sont nécessaires au démarrage du système EOS. Dans ce qui suit, nous expliquerons les rôles des deux méthodes de ces programmes.
8.15.1 Le programme INIT Lorsque EOS démarre, le processus d'initialisation P0 est mis en route. P0 crée un enfant P1 du fichier téléchargé /bin/init mentionné ci-dessus en tant qu'image Umode. Lorsque P1 s'exécute, il exécute le programme d'initialisation en mode utilisateur. Désormais, P1 exécute le même jeu que le processus INIT sous Unix/Linux. Un programme d'initialisation simple, qui ne crée qu'un seul enregistrement traité sur la console utilisateur susmentionnée, est illustré ci-dessous. Le lecteur peut le modifier pour bifurquer plusieurs processus de soumission, chacun sur un portable différent. /************************ fichier init.c ****************/ #include "ucode.c" utilisateur int ; entier parent()
// code de P1
{ int pid, état ; while(1){ printf("INIT : attend l'enfant ZOMBIE\n"); pid = attendre(&status); if (pid==console){ // si le processus de connexion à la tablette est mort printf("INIT : crée une nouvelle connexion à la console\n"); rassurer = fourche(); // fork un autre if (console) continue ; sinon exec("login /dev/tty0"); // nouveau processus de connexion à la console } printf("INIT : je viens de surcharger le processus enfant orphelin %d\n", pid); } } principal() {
322
8 Systèmes d'exploitation embarqués à usage général aus in, out ; dans
// descripteurs de fichiers pour les E/S portables
= open("/dev/tty0", O_RDONLY); // description du fichier 0
out = open("/dev/tty0", O_WRONLY); // pour l'affichage sur la console printf("INIT : fork a login proc on console\n"); console = fourche(); if (console) // ancêtre parent(); autre
// enfant : exec pour se connecter sur tty0
exec("connexion /dev/tty0"); }
8.15.2 Le programme de connexion Tous les processus de connexion exécutent le même programme de connexion, chacun va sur un terminal différent, pour que les utilisateurs l'obtiennent. L'algorithme est le programme de connexion est /******************* Sortie sur la connexion *******************/ / / login.c : Lors de l'entrée, argv[0]=login, argv[1]=/dev/ttyX #include "ucode.c" int inbound, out, err ; char name[128],password[128] main(int argc, char *argv[]) { (1). fermer les descripteurs de fichiers 0,1 hérités de INIT. (2). ouvrir argv[1] 3 fois comme in(0), out(1), err(2). (3). setty(argv[1]); // définit la chaîne de nom tty dans PROC.tty (4). ouvrir /etc/passwd rank disponible READ ; tandis que(1){ (5).
printf("connexion :"); obtient (nom); printf("mot de passe :"); obtient (mot de passe); pour chaque ligne du fichier /etc/passwd do{ tokenize user my line;
(6).
si (l'utilisateur a un compte valide){
(7).
changer uid, gid en uid, gid de l'utilisateur ; // chuid()
(8).
changez cwd en get DIRECTORY de l'utilisateur
// chdir()
fermer le fichier /etc/passwd ouvert
// fermer()
exec pour programmer dans le compte client
// exec()
} } printf("la connexion a échoué, réessayez\n"); } }
8.15.3 Le programme sh Après la connexion, l'utilisation de l'utilisateur exécute généralement l'interpréteur de commandes sh, qui commande les lignes de l'utilisateur et exécute les commandes. Pour toutes les lignes de décret, si la commande n'est pas triviale, c'est-à-dire qu'elle n'est pas sortie du bouton cd, sh a accroché un processus enfant pour exécuter la ligne de commande et attend que le mineur se termine. Avec des commandes simples, le premier jeton d'une ligne de commande est un fichier exécutable dans le répertoire /bin. Une ligne de commande permet de contenir des symboles de redirection d'E/S. Pendant ce temps, l'enfant sh gère en premier les redirections d'E/S. Ensuite, les applications exec changent d'image pour exécuter le fichier de commande. Lorsque le processus enfant est terminé, il
8h15
Interface utilisateur
323
wakes augmente ce parent sh, ce qui demande une autre ligne de commande. Si une ligne de commande contient un symbole tubulaire d'ampère, tel que cmd1 | cmd2, l'enfant sh saisit le tuyau par le dernier do_pipe select. /***************** do_pipe Search **************/ int pid, pd[2]; tuyau(pd);
// produit un tube : pd[0]=READ, pd[1]=WRITE
pid = fourche();
// bifurque un enfant pour partager le tuyau
fourni (pid){
// parent : while pipe READER
fermer(pd[1]);
// ferme la fin de l'ECRITURE du tube
dup2(pd[0], 0); exec(cmd2);
// redirige stdin dans le tube HOW fin
} autre{
// enfant : en tant que pipe WRITER
fermer(pd[0]);
// ferme le tuyau GO end
dup2(pd[1], 1);
// redirige stdout vers le tube WRITE end
exec(cmd1); }
Plusieurs tuyaux deviennent des poignées algorithmiquement, avec légal à gauche.
8.16
Démonstration d'EOS
8.16.1 Démarrage d'AURORAS La figure 8.9 montre l'écran d'alimentation du système EOS. Après le démarrage, il initialise d'abord l'écran LCD, configure les interruptions vectorielles et initialise les pilotes de périphérique. Ensuite, il initialise le noyau EOS pour exécuter le processus initialisé P0. P0 crée des répertoires de pages, des tables de pages et des commutateurs de sélection de répertoire pour utiliser un folio dynamique à 2 niveaux. P0 crée le processus INIT P1 avec /bin/init dans l'image Umode. Ensuite, il commute le processus pour exécuter P1 en lecture utilisateur. P1 lance un lot de connexion P2 sur qui console également un autre processus de connexion P3 sur un terminal série. Lors de la création d'un processus récent, il affiche les cadres de page fluides alloués à une image de processus. Lorsqu'un processus se termine, il libère les cadres de page alloués pour les réutiliser. Will P1 s'exécute en boucle, attendant tout enfant ZOMBIE. Chaque modification de connexion ouvre son propre fichier spécial de terminal comme stdin (0), stdout (1), stderr (2) pour les E/S de terminaison. Ensuite, chaque processus d'inscription affiche une invite de connexion : sur son terminal et attend qu'un utilisateur se connecte. Lorsqu'un utilisateur essaie de se connecter, le logo traité valide la fin en vérifiant le livre d'utilisateur dans le fichier /etc/passwd. Après la connexion d'un utilisateur, le processus d'accès est le processus de l'opérateur en acquérant son uid et en changeant de répertoire sur les listes d'accueil de l'utilisateur. Ensuite, le processus utilisateur modifie l'image pour exécuter l'interpréteur de commandes sh, qui invite les commandes utilisateur à compléter ces commandes.
8.16.2 Traitement des commandes dans EOS La figure 8.10 montre la séquence de traitement de la ligne de commande "cat f1 | grep line" par le processus shit EOS (P2). Pour toute ligne de commande non triviale, sh bifurque un processus enfant pour exécuter la commande et attend que l'enfant se termine. Considérant que la ligne de commande a une clé de canal, l'enfant sh (P8) crée un canal et accroche un enfant (P9) pour partager le canal. Ensuite, le fils sh (P8) indique le pipeline et exécute la commande grep. Cet enfant sh (P9) écrit dans le tube et exécute la commande cat. P8 et P9 sont en outre reliés simultanément par une conduite réelle. Lorsque le lecteur de canal who (P8) se termine, il envoie un enfant P9 en tant qu'orphelin au processus INIT P1, il se réveille pour perdre le lot orphelin P9.
8.16.3 Envoyer également une exception en cours d'exécution dans EOS Dans EOS, les éléments généraux sont gérés par le cadre unifié de traitement du signal. Nous démontrons le traitement du signal réel exceptionnel dans EOS à l'exemple suivant.
324
Fig. 8.9 L'écran de démarrage est ECHO
Fig. 8.10 Traitement des commandes par l'EOS sh
8 Système de fonctionnement intégré à usage général
8.16
Démonstration d'EOS
325
8.16.3.1 Minuterie d'intervalle appuyez sur le capteur de signal d'alarme Dans le répertoire USER, le programme itimer.c montre la minuterie d'intervalle, le signal d'alarme et le capteur de signal d'alarme. /******************** fichier itimer.c ************************/ voids catcher(int sig) { printf("proc %d for catcher: sig=%d\n", getpid(), sig); } main(int argc, char *argv[]) { int t = 1; si (argc>1) tonne = atoi(argv[1]);
// intervalle de minuterie
printf("installer capture ? [y|n]"); si (getc()=='y') signal(14, receveur); minuteur(t);
// ajoute catcher() pour SIGALRM(14) // définit le temporisateur d'intervalle par noyau
printf("proc %d en boucle jusqu'à SIGALRM\n", getpid()); tandis que(1); // boucle jusqu'à ce qu'il soit tué par un signal }
Dans le programme itimer.c, il permet d'abord à l'utilisateur d'installer ou non jusqu'à l'installation d'un snagging pour le signal SIGALRM(14). Ensuite, il définit un temporisateur d'intervalle de t secondes et exécute une boucle while(1). Lorsque le temporisateur d'intervalle expire, le récupérateur d'interruption du temporisateur envoie une sonnerie SIGALRM(14) à un processus. Si l'utilisateur n'a pas installé de signal 14 catcher, le processus mourra par le sig. Sinon, il exécutera ce catcher une fois et continuera en boucle. Dans ce dernier cas, la capacité du processus peut être tuée par d'autres moyens, par ex. de la clé Control_C ou par une commande kill pid d'un processus. La radio peut modifier la fonction catcher() pour réinstaller le receveur. Recompilez et exécutez le système pour remarquer l'effet.
8.16.3.2 Traitement des exceptions dans EOS En zusammenrechnung aux signaux de minuterie, nous démontrons également un traitement unifié des exceptions et des signaux pour les programmes utilisateur suivants. Chaque programme peut être course dans une commande d'étudiant. Data.c : ce calendrier démontre la gestion des exceptions data_abort. Si le gestionnaire data_abort, nous lisons ou exposons d'abord l'état d'erreur de la MMU et les carnets d'adresses affichent l'état de la MMU et l'AV non valide qui a provoqué l'exception. Si l'exception susmentionnée s'est produite dans Kmode, cela doit être dû à des bogues dans le code du noyau. Dans ce cas, il n'y a rien que le noyau puisse faire. Il imprime donc un message CREATE et s'arrête. Lorsque l'extra s'est produit dans Umode, le noyau se régénère en un signal, SIGSEG(11) pour une erreur de segmentation, et envoie le signal au processus. Si l'utilisateur n'a pas installé de capteur pour le signal 11, un processus mourra par l'entrée. Si un consommateur a installé un récepteur d'indicateur 11, quel processus exécutera la fonction de récepteur en mode U lorsqu'il recevra une indication de téléphone 11. La fonction d'accrochage utilise un long saut pour contourner le chiffrement défectueux, permettant à quel processus de se terminer normalement. Prefetch.c : quel programme illustre la gestion des exceptions prefetch_abort. Un code CARBON tente d'exécuter le code d'assemblage en ligne asm("bl 0x1000"); ce qui provoquera ampère prefetch_abort à qui après l'adresse PC 0x1004 car il est en dehors de l'espace Umode VA. Plus précisément, le processus reçoit également un signal SIGSEG(11), qui est traité de la même manière qu'une exception data_abort. Undef.c : cette exécution illustre la gestion des exceptions indéfinies. Le code CENTURY tente d'exécuter asm(‘‘mcr p14,0,r1,c8,c7,0’’) ce qui provoquerait un undef_abort car le coprocesseur p14 n’existe pas. Dans ce cas, le processus reçoit un indicateur d'instruction illégale SIGILL(4). Si ce client n'a pas installé de capteur de signal 4, le processus mourra par le signal. Diviser en zéro : les meilleurs processeurs ARM n'ont pas d'instructions distinctes. Les divisions entières dans LIMB sont implémentées par les fonctions idiv et udiv dans la bibliothèque aeabi, qui vérifient les erreurs de division par zéro. Lorsqu'une division par zéro est détectée, elle se branche sur la fonction __aeabi_idiv0. L'utilisateur peut utiliser le registre de liaison pour identifier l'instruction incriminée et prendre des mesures correctives. Dans une machine virtuelle Versatilepb, diviser par zéro renvoie simplement la plus grande valeur entière. Bien que les ordinateurs ne soient pas en mesure d'engendrer des exceptions divisées par zéro sur la machine virtuelle ARM Versatilepb, le schéma de gestion des irrégularités est que EAS doit être applicable à d'autres processeurs ARM.
326
8 systèmes d'exploitation embarqués à usage général
8.17
Résumé
Ce chapitre présente un système d'exploitation embarqué général entièrement fonctionnel, noté EOS. Voici un bref résumé de l'organisation et des capacités du système EOS. 1. Images système : l'image de la substance amorçable et les exécutables du mode utilisateur sont générés à partir d'une arborescence source par la chaîne d'outils ARM (d'Ubuntu 15.0 Linux) et logés dans un système de fichiers EXT2 sur une partition SDC adénine. Le SDC contient des amorceurs d'étape 1 et d'étape 2 depuis le démarrage vers le ciel de l'image du noyau à partir d'une partition SDC. Après le démarrage, le noyau monte la partition SDC en tant que système de fichiers racine. 2. Processus : Le système prend en charge les processus NPROC=64 et NTHRED=128 rotations par processus, les deux peuvent être augmentés si nécessaire. Chaque processus (à l'exception du processus inactif P0) s'exécute en mode Kernel play ou User. La gestion de la mémoire de l'image de processus se fait par pagination dynamique à 2 niveaux. La planification traitée se fait par priorité dynamique et tranche de temps. Il prend en charge la communication inter-processus par des canaux et des messages. Le noyau EOS prend en charge fork, exec, vfork, threads, exit plus expect pour la gestion des processus. 3. Celui-ci contient des pilotes d'appareils pour les périphériques d'E/S les plus fréquemment utilisés, par ex. Écran LCD, minuterie, clavier, UART et SDC. Il équipe un système de fichiers EXT2 entièrement compatible Linux sur la mise en mémoire tampon des E/S de lecture/écriture SDC pour améliorer l'efficacité et les performances. 4. L'ordinateur prend en charge les connexions multi-utilisateurs aux terminaux confort et UART. Le lien utilisateur sh prend en charge les exécutions générales simples avec des redirections d'E/S, ainsi que plusieurs commandes connectées à partir de canaux. 5. I fournit des fonctions de service de minuterie et unifie la gestion des exceptions avec le traitement des symboles, permettant aux utilisateurs d'installer des capteurs de signal pour gérer les exceptions en mode utilisateur. 6. Les systèmes fonctionnent sur une variété de machines virtuelles FORTIFY sous QEMU, principalement des équipements disponibles. Il devrait également fonctionner sur de véritables cartes système basées sur ARM prenant en charge les périphériques d'E / S appropriés. Portez EOS sur certains systèmes populaires basés sur ARM, par ex. Hoot PI-2 est momentanément en cours. Le plan est de le rendre disponible pour la numérisation et le téléchargement dès qu'il sera prêt. PROBLÈMES 1. Dans EOS, le kpgdir initial du P0 est inférieur à 32 Ko. Chaque processus a son propre pgdir dans PROC.res. Avec une taille de ressemblance Umode de 4 Mo, les entrées 2048-2051 du pgdir de chaque PROC définissent les tables de pages Umode du processus. Lors du changement de tâche, nous utilisons switchPgdirððintÞrunning>res>pgdirÞ ; up bascule vers le pgdir de l'action en cours d'exécution. Modifiez l'opération scheduler() (dans le fichier kernel.c) comme suit., auf *kpgdir = (int *)0x8000; // Kmode pgdir à 32Ko if (running != old){ for (i=0; ires->pgdir[2048+i]; switchPgdir((int)kpgdir); // utilise ce même kpgdir à 32Ko }
(1). Vérifiez que les livres mobiles modifiés de scheduler() et expliquez POURQUOI ? (2). Agrandissez ce contrôle pour utiliser un seul kpgdir pour TOUS traités. (3). Discutez des avantages et des inconvénients de l'utilisation d'un pgdir par processus par rapport à un pgdir pour chaque processus. 2. L'installation d'envoi/réception dans EOS utilise le protocole synchrone, qui est bloquant et peut entraîner un blocage dû, par ex. aucun arrêt de message gratuit. Reconcevez les opérations d'envoi/réception pour éviter les blocages. 3. Créez un algorithme Delete_dir_entry comme suit. Si l'entrée supprimée est l'entrée d'inclusion d'un bloc de données, désallouez le bloc de données et compactez le tableau de blocs de données du DIR INODE. Modifiez un algorithme Insert_dir_entry en conséquence et appliquez les nouveaux algorithmes dans le système de fichiers EOS. 4. Dans le read_file optimisé de Sect. 8.12.5.2, les données peuvent être lues à partir du fichier ampère vers l'espace utilisateur ou l'espace noyau. (1). Justifiez pourquoi il est nécessaire de lire des données dans l'espace noyau.
8.17
Exécutif
327
(2). Dans l'arc interne de l'algorithme read_file, les preuves sont transférées un octet dans une date pour plus de clarté. Optimisez la boucle interne en transférant des blocs de données à la fois (CONSEIL : minimum de données restantes dans ce bloc et données disponibles dans le fichier). 5. Réglez l'algorithme d'écriture de fichier dans la Sect. 8.12.5.3 jusqu'à autoriser (1). Démarrer les données dans l'espace noyau, et (2). Optimisez le transfert de données en copiant des fragments de données. 6. Suppose que : dir1 et dir2 sont des répertoires. cpd2d dir1 dir2 copie récursivement dir1 dans dir2. (1). Tapez l'identifiant C pour l'application cpd2d. (2). Comme si dir1 prenait dir2, par ex. cpd2d /a/b /a/b/c/d ? (3). Comment déterminer si dir1 contient dir2 ? 7. Désormais, EOS ne prend pas encore en charge les flux de fichiers. Implémentez les fonctions d'E/S de la bibliothèque pour prendre en charge les flux de fichiers dans l'espace addictif.
Références Android : https://en.wikipedia.org/wiki/Android_operating_system, 2016. ARM Versatilepb : ARM 926EJ-S, 2016 : Many-sided Appeal Baseboard for ARM926EJ-S User Guide, Arm information Center, 2016. Cao, M ., Bhattacharya, S, Tso, T., "Ext4 : la génération la plus proche dans le système de fichiers Ext2/3", INTEL Linux Technology Center, 2007. Card, R., Theodore Ts'o,T., Stem Tweedie,S. , "Conception et application du deuxième système de fichiers prolongé", web.mit. edu/tytso/www/linux/ext2intro.html, 1995. EXT2 : www.nongnu.org/ext2-doc/ext2.html, 2001. EXT3 : jamesthornton.com/hotlist/linux-filesystems/ext3-journal, 2015. FreeBSD : Projet FreeBSD/ARM, https://www.freebsd.org/platforms/arm.html, 2016. Raspberry_Pi : https://www.raspberrypi.org/products/raspberry-pi-2-model-b, 2016 Sevy, J., "Porting NetBSD to a newer ARM SoC", http://www.netbsd.org/docs/kernel/porting_netbsd_arm_soc.html, 2016. Wang, K.C., "Design and Implementation about the MTX Operating System" , Springer International Publishing AG, 2015.
9
Le multitâche inclut les méthodes intégrées
9.1
Multitraitement
Un système multiprocesseur existe concernant un nombre multiple d'ordinateurs, y compris des processeurs multicœurs, qui font partie des dispositifs principaux totaux et d'E/S. Si la mémoire secteur partagée est et pas de mémoire dans ce système, il appelle un système Uniform Store Accessories (UMA). Si, ajouté dans une mémoire partagée, chaque processeur dispose également d'une mémoire locale résidentielle, il reste appelé un système d'Erreichbar à mémoire non uniforme (NUMA). Est-ce que les rôles des processeurs ne sont pas les mêmes, par ex. Si certains processeurs peuvent exécuter le code du noyau alors que d'autres ne l'autorisent pas, il s'agit d'un modèle ASMP (Asymmetrical MP). Si tous les processeurs ont des fonctions équivalentes, cela reste appelé un système Symmetric MP (SMP). Pour la technologie de processeur multicœur actuelle, SMP devient pratiquement synonyme de MP. Dans ce chapitre, nous devrions discuter des systèmes d'exploitation SMP sur plusieurs systèmes basés sur ARM.
9.2
Exigences du système SMP
Un système SMP a besoin de bien plus que de multiples quantités de processeurs ou de cœurs de processeur. Afin de prendre en charge SMP, l'architecture du système doit avoir des capacités supplémentaires. SMP n'est pas une marque. Dans le monde du PC, il a commencé à être intégré au début des années 90 par Intel dans mes processeurs multicœurs basés sur x86. La spécification multiprocesseur d'Intel (Intel 1997) définit les systèmes compatibles SMP comme des systèmes compatibles PC/AT avec les capacités suivantes. (1). Cohérence du cache : dans un système compatible SMP, de nombreux processeurs ou cœurs partagent la mémoire. Afin d'accélérer l'accès aux stockages supérieurs, ce système utilise généralement plusieurs niveaux de mémoire cache, par ex. Cache L1 à l'intérieur du processeur jeder et cache L2 entre les processeurs et la mémoire principale, etc. Le sous-système de mémoire doit implémenter un protocole de cohérence de cache pour assurer la cohérence des mémoires de mémoire. (2). Prise en charge des interruptions d'écrasement et des interruptions inter-processeurs. Dans un système compatible SMP, les interruptions des périphériques d'E/S peuvent être acheminées vers différents processeurs pour équilibrer la charge de traitement des interruptions. Les processeurs peuvent interrompre l'un ou l'autre par Inter-Processor Suspend (IPI) pour la communauté et la synchronisation. Dans un système compatible SMP, ceux-ci sont fournis par un ensemble d'APIC (Advanced Programmable Interrupt Automatic). Un système conforme au SMP a généralement un IOAPIC à l'échelle du système et un ensemble d'APIC locaux pour personnaliser les processeurs. Ensemble, les APIC implémentent un protocole de communication inter-processeur, qui comprend le routage des interruptions et les IPI. (3). Un BIOS étendu, qui détecte la configuration du système et crée des structures de données SMP avec le système d'exploitation à utiliser. (4). Lorsque le système compatible SMP ampère démarre, l'un des ingénieurs est désigné comme processeur de démarrage (BSP), qui exécute le code de sac pour démarrer le système à l'envers. Tous les autres processeurs sont appelés processeurs d'application (AP), ils sont maintenus dans les initiales de l'état de veille mais peuvent recevoir des IPI du BSP au démarrage. Après le démarrage, tous les processeurs sont fonctionnellement des identités. En comparaison, les méthodes basées sur SMP sur ARM sont relativement nouvelles et encore en développement. Il peut être instructif de comparer l'approche SMP d'ARM avec celle d'Intel.
© Springer Worldwide Publishing TAGESZEIT 2017 Wang, Systèmes d'exploitation embarqués et temps réel, DOI 10.1007/978-3-319-51517-5_9
329
330
9 Multitraitement dans les systèmes embarqués
(1). Cohérence du cache : tous les systèmes pris en charge par ARM MPcore incluent une unité de contrôle Snoop (SCU), qui implémente un protocole de cohérence de la mémoire cache pour assurer une cohérence dans les mémoires cache. En raison de ces pipelines internes des processeurs ARM, ARM a introduit plusieurs types de barrières pour synchroniser à la fois l'accès à la mémoire et les exécutions d'instructions. (2). Interrupts Tour : Comparable aux APIC d'Intel, certains systèmes ARM MPcore créent un contrôleur d'interruption génétique (GIC) (ARM GIC 2013) pour le routage des interruptions. Le GIC se compose de deux divisés; peut sortir du distributeur et avec une interface dans les CPU. L'envoi d'interruptions de GIC supprime les frais d'interruption extérieurs et les envoie aux processeurs. Chaque CPU a une interface CPU, qui peut être programmée pour autoriser ou interdire l'envoi d'interruptions au CPU. Chaque interruption a un numéro d'identification d'interruption, quel chiffre inférieur aura des priorités plus élevées. Le routage des interruptions peut être contrôlé en outre par le registre de masque de priorité d'interruption d'un processeur. Pour les dispositifs d'E/S, les quantités d'ID d'interruption correspondent approximativement à leurs numéros de vecteur traditionnels. (3). Interruptions inter-processeurs : les seize interruptions GIC spéciales portant les numéros d'identification 0 à 15 ne communiquent pas pour les interruptions générées par logiciel (SGI), qui correspondent aux IPI de l'architecture Intel SMP. Dans un système basé sur ARRM MPcore, un processeur peut imprimer des SGI pour réveiller d'autres processeurs en libérant l'état WFI, les obligeant à prendre des mesures, par ex. pour exécuter un gestionnaire d'interruptions, comme moyen de communication entre processeurs. Dans qui ARM Cortex-A9 MPcore, le registre SGI (GICD_SGIR) est au décalage 0x1F00 d'une adresse de base périphérique. Le contenu du get SGI est
---------------- Contenu du registre SGI ------------------bits 25-24 : targetListfilter : 00 = vers un processeur spécifié 01 = à toutes les autres CPU 10 = aux bits CPU requis 23-16 : CPUtargetList; chaque bit transmet un bit d'interface CPU 15 bits 3-0
: pour les CPU avec extension d'assurance uniquement : ID d'interruption (0-15)
-------------------------------------------------- -------
Pour libérer l'ampère SGI, écrivez simplement une valeur appropriée dans le registre SGI. Dans ce cas, le segment d'élément suivant envoie un SGI d'identification d'interruption à une CPU cible spécifique. int send_sgi(int intID, int targetCPU, int filter) { intention *sgi_reg = (int *)(CGI_BASE + 0x1F00); *sgi_reg = (filterirq_stack (zone de 16 Ko dans t.ld)
déplacer r1, r11 ajouter r1, r1, #1 lsl r2, r1, #12
// (cpuid+1) * 4096
ajouter r0, r0, r2 mov sp, r0
// IRQ sp=irq_stack[cpuid] haut de gamme
// retour en mode SVC avec IRQ ON MSR cpsr, #0x13 cmp r11, #0 bne APs
// uniquement CPU0 copie les vecteurs, appelez main()
BL copy_vectors BL principal
// copie les vecteurs à l'adresse 0 // CPU0 appelle main() en C
B. AP :
// chaque AP appelle APstart() en CENTURY
adr r0, APaddr ldr pc, [r0] APaddr : .word
COMMENCER
irq_handler : sub lr, lr, #4 stmfd sp !, {r0-r3, r12, lr} bl
irq_chandler
// appelle irq_chandler() en C
ldmfd sp!, {r0-r3, r12, pc}^ vectors_start : LDR PC, reset_handler_addr LDR PC, undef_handler_addr LDR PC, swi_handler_addr LDR PC, prefetch_abort_handler_addr
9.7 Exemples de démarrage ARMS SMP
343
PC LDR, data_abort_handler_addr B . PC LDR, irq_handler_addr PC LDR, fiq_handler_addr reset_handler_addr : undef_handler_addr :
.word reset_handler .word undef_handler
swi_handler_addr :
.word swi_handler
prefetch_abort_handler_addr : .word prefetch_abort_handler data_abort_handler_addr :
.word data_abort_handler
irq_handler_addr :
.word irq_handler
fiq_handler_addr :
.word fiq_handler
vectors_end : // gestionnaires d'exceptions factices inutilisés undef_handler : swi_handler : prefetch_abort_handler : data_abort_handler : fiq_handler :
B.
enable_scu :
// active qui SCU
MRC p15, 4, r0, c15, c0, 0
// Étudier l'adresse de base du périphérique
LDR r1, [r0]
// lire le registre de contrôle SCU
MINERAI r1, r1, #0x1
// définit bit0 (bit d'activation) jusqu'à 1
STR r1, [r0]
// réécrit la valeur modifiée
Bx
g / D
get_cpuid : MRC p15, 0, r0, c0, c0, 5 ET r0, r0, #0x03
// lit le registre CPU ID // face dans le champ CPUID
MOV pc, lr // ----------------- fin pour le fichier ts.s -------------------
Explications du fichier ts.s susmentionné : Le netz est lié à la compilation à l'image exécutable adenine t.bin avec l'adresse de départ 0x10000. Dans le fichier de script de l'éditeur de liens, t.ld, il spécifie svc_stack comme adresse de début d'une zone de 16 Ko, destinée aux piles en mode SVC des 4 CPU. De même, les processeurs utiliseront les zones de 16 Ko sur irq_stack comme piles d'utilisateurs IRQ. Comme nous n'avons pas l'intention de gérer d'autres types d'exceptions, les piles de mode ABT et UND, ainsi que les gestionnaires d'exceptions sont omis. Les images ont été exécutées sous QEMU en tant que qemu-system-arm –m realview-pbx-a9 –smp 4 –m 512M –kernel t.bin –serial mon:stdio Pour plus de clarté, de type machine (–m realview-pbx-a9) et le nombre de processeurs (-smp 4) sont indiqués en gras. Chaque fois que le système démarre, l'image exécutable est collée à 0x10000 en raison de QEMU et s'y exécute. Lorsque CPU0 démarre pour exécuter reset_handler, QEMU a démarré les autres CPU (CPU1 à CPU3) également lorsqu'elles sont maintenues dans l'état WFI. Donc, juste CPU0 exécute le reset_handler en premier. Les ensembles d'ordinateurs augmentent le fait que SVC appuie sur le mode IRQ par lots, imite la table vectorielle à l'adresse 0 et lorsqu'il appelle main() en C. CPU0 initialise d'abord who system. Ensuite, il écrit 0x10000 comme une adresse de démarrage des points d'accès dans le registre SYS_FLAGSSET (à 0x10000030) et envoie un SGI à tous les points d'accès, les obligeant à exécuter le même code reset_handler à 0x10000. Cependant, sur la base de leur numéro d'identification de CPU, jeder AP ne configure que son propre SVC en plus des piles de mode IRQ et fait que APstart() est CENTURY, en contournant le code d'initialisation du système, donc plus de vecteurs de copie, ce qui sera déjà fait selon CPU0. (2). t.c fichier : dans main(), CPU0 active le SCU et configure quel GIC dans la route interrompt. Pour plus de simplicité, le système prend uniquement en charge UART0 (0x10009000) real timer0 (0x10011000). Dans config_gic(), les interruptions du temporisateur sont acheminées vers CPU0 et les interruptions UART sont acheminées vers CPU1, qui étaient assez arbitraires. Tout en le souhaitant, le lecteur peut vérifier qu'ils peuvent être acheminés vers n'importe quel processeur. Ensuite, CPU0 initialise les pilotes de périphériques et lance également la minuterie. Ensuite, il écrit l'activité de démarrage de l'AP sur 0x10000030 et émet SGI pour activer les AP. Après cela, tous les processeurs s'exécutent à l'intérieur des boucles WFI, mais CPU0 et CPU1 peuvent répondre et gérer les interruptions du minuteur et de l'UART.
344
9 Multitraitement dans les systèmes embarqués
/******************** fichier t.c de C9.2 *********************** ****/ #include "type.h" #define GIC_BASE 0x1F000000 int *apAddr = (int *)0x10000030 ; // SYS_FLAGSSET enroll #include "uart.c" #include "timer.c" int copy_vectors(){
// pareil qu'avant }
int APstart()
// Code d'exécution AP
{ innen cpuid = get_cpuid(); upprintf("CPU%d démarrage : ", cpuid); uprintf("CPU%d entre dans l'état WFI\n", cpuid); while(1){ asm("WFI"); } } int config_int(int intID, int targetCPU) { décalage_reg interne, index, adresse ; priorité de caractère = 0x80 ; reg_offset = (intID>>3) & 0xFFFFFFFC ; index = intID & 0x1F ; adresse
=
(GIC_BASE + 0x1100) + reg_offset ;
*(int *)adresse = (1 CHARGE = 0x0 ; tp->VALEUR= 0xFFFFFFFF ; tp->RIS
= 0x0 ;
tp->MIS
= 0x0 ;
346
9 Multitraitement dans les systèmes embarqués
tp->LOAD = 0x100 ; tp->CONTROL = 0x62 ; // |En|Per|Int|-|Sca|00|32B|Wrap|=01100010 tp->BGLOAD
= 0xF0000/DIVISEUR ;
} mit timer_start() { MINUTERIE *tpr = tp; tpr->CONTROL |= 0x80;
// active le morceau 7
} int timer_clearInterrupt() { MINUTERIE *tpr = tp; tpr->INTCLR = 0xFFFFFFFF ;
// écrire dans le registre INTCLR
}
(4). Fichier uart.c : il s'agit du pilote UART. Le pilote est prêt pour 4 UART mais il utilise simplement UART0. Par souci de brièveté, le code uprintf() n'est pas affiché. /************ uart.c open est C9.2 ***********/ #define UART0_BASE 0x10009000 typedef struct uart{ u32 DR; // registre de données u32 DSR ; u32 pad1[4] ; // 8+16=24 octets au registre FR u32 FR ; // couleur reg à 0x18 u32 pad2[7] ; u32 IMSC ; // au décalage 0x38 }UART ; UART *upp[4] ; // 4 UART montrent UART *up ; // entrée de pointeur UART activée uprintf(char *fmt, …){ // comme avant } int uart_handler(int ID) { singe c; int cpuid = get_cpuid(); haut = hautp[ID] ; c = haut->DR ; uprintf("Interruption UART%d sur CPU%d : c=%c\n", ID, cpuid, c); } int uart_init() { ints ego ; for (i=0 ; iIMSC |= (1lock = 0 ; m->status = UNGUIDED ; m->owner = NO_OWNER ; } int mutex_lock(MUTEX *m) { while(1){ slock(&m->lock);
// obtention d'un verrou tournant
chaque fois que (m-> status == UNLOCKED) pause ; sunlock(&m->lock);
// libère le verrou tournant
asm("WFE");
// attend l'événement, puis réessaye
} // maintenant le verrou tournant, mettre à jour le mutex dans le CR m->status = LOCKED; m->propriétaire = get_cpuid(); sunlock(&m->lock);
// libère le verrou tournant
} int mutex_unlock(MUTEX *m) { slock(&m->lock);
// acquiert un verrou tournant
while (m->owner != get_cpuid()){ sunlock(&m->lock); retour -1 ;
// si ce n'est pas le propriétaire : relâchez le verrou tournant // retournez -1 pour FAIL
} m->statut = DÉVERROUILLÉ ;
// marque le mutex comme DÉVERROUILLÉ
m->propriétaire = NO_OWNER ;
// et pas de propriétaire
sunlock(&m->lock); renvoie 0 ;
// libère le verrou tournant // retourne 0 pour SUCCESS
}
L'implémentation ci-dessus des opérations de mutex par C peut être plutôt moins efficace que dans l'assembleur, mais cela doit toujours avoir deux avantages. Tout d'abord, il est plus facile à comprendre et donc moins susceptible de freiner les erreurs puisque la lisibilité du code CARBON sera toujours meilleure que celle du code de montage. Moment et plus important encore, il montre la relation hiérarchique entre les différents types de primitives de synchronisation. Une fois que la personne a des spinlocks dans des CR protégés de courtes périodes, nous pouvons les utiliser comme base pour mettre en œuvre les mutex, selon ceux qui sont essentiellement des CR de plus longues durées. De la même manière, nous permettons également d'utiliser des spinlocks pour implémenter d'autres outils de synchronisation plus puissants. Par l'approche hiérarchique, nous n'avons pas en répétition la séquence bas niveau ldrex-strex et barrière mémoire dans les nouveaux outils de synchronisation.
9.9.6 Démonstration de SMP Startup égal Mutex Lock Dans l'exemple de programme C9.5, nous utiliserons un mutex pour synchroniser les exécutions des APs. L'effet est le même que celui de l'utilisation de verrous tournants, coupler l'application de chaque AP exécute APstart() peut à la fois. /******************** fichier t.c de C9.5 ******************/ #include "mutex.c" MUTEXm; ncpu interne volatil = 1 ; int APstart()
356
9 Multitraitement dans les systèmes inclus
{ entier je ; int cpuid = get_cpuid(); mutex_lock(&m); printf("CPU%d start\n", cpuid); for (i=0; itick++; if (tp->tick >= DIVISOR){ tp->tick = 0; tp->ss++; wenn (tp->ss==60){ tp->ss = 0;
tp->mm++ ;
quand (tp->mm==60){ tp->mm = 0 ; tp->hh++ ; } } }
358
9 Multitraitement dans les systèmes insérés if (tp->tick==0){
// chaque par : affiche un battement d'écran
couleur = VERT+cpuid ; pour (i=0; ihh/10); horloge[id][1]='0'+(tp->hh%10); horloge[id][3]='0'+(tp->mm/10); horloge[id][4]='0'+(tp->mm%10); horloge[id][6]='0'+(tp->ss/10); horloge[id][7]='0'+(tp->ss%10); for (i=0; iload = TIME; ptp->control = 0x06; // IAE bits = 110, pas encore activé sur (i=0; icontrol |= 0x01;
// définit le bit d'activation 0
} int ptimer_stop() {
// arrête le minuteur local
PTIMER *tptr = ptp ; tptr->contrôle &= 0xFE ;
// effacer activer la mastication 0
} int ptimer_clearInterrupt() // efface l'interruption { PTIMER *tpr = ptp; ptp->intclr = 0x01 ; } auf APstart()
// Code de démarrage AP
{ int cpuid = get_cpuid(); mutex_lock(&m); printf("CPU%d start : ", idcpu); config_int(29, cpuid); ptimer_init(); ptimer_start();
// besoin de ceci par CPU
9.10
Minuteries globales et locales
359
Fig. 9.6 Démonstration des temporisateurs locaux dans SMP ncpu++ ; printf("CPU%d entre dans l'état WFI\n", cpuid, cpuid); mutex_unlock(&m); while(1){ asm("WFI"); } } int irq_chandler() { pouces int_ID = *(int *)(GIC_BASE + 0x10C); si (int_ID == 29){ ptimer_handler(); } *(int *)(GIC_BASE + 0x110) = int_ID ; }
9.10.1 Démonstration de l'horloge locale dans SMP La figure 9.6 montre qui sort de l'exécution de l'exemple de programme C9.6, qui schaustellungen séquence de démarrage ARM SMP susmentionnée avec mutex pour la synchronisation CPU et les minuteries locales.
9.11
Semiaphores dans SMP
UN (compte) sémaphore est une structure typedef struct sem{ int lock ;
// verrou tournant
int évaluer;
// valeur
360
9 Du multitraitement aux systèmes embarqués
struct proc *file d'attente ;
// File d'attente FIFO PROC-BOX
}SÉMAPHORE; SEMAPHORE s = ATOUT ;
// s.lock=0 ; s.value=VALEUR ; s.queue=0 ;
Dans la structure du sémaphore, le champ de verrouillage est un verrou tournant, où garantit que toute opération sur un sémaphore doit être effectuée à l'intérieur du COUNTY du verrou tournant du sémaphore, le champ de valeur est la valeur initiale du sémaphorique, qui représente le nombre de protections naturelles disponibles. par le sémaphore, et la file d'attente est une file d'attente FIFO d'actions bloquées en attente de ressources disponibles. Les plus grands sémaphores fonctionnant souvent sont PIANO et V, qui sont définis comme suit. // l'exécution est une main pour le processus d'exécution moderne --------------------------------------- ---------------------------P(SEMAPHORE *s)
|
V(Sémaphore *s)
{
{
int sr = int_off();
| |
slock(&s->lock);
|
slock(&s->lock);
|
si (++s->valeur valeur < 0)
int rr = int_off();
|
BLOC(S); autre
SIGNAUX ;
|
sunlock(&s->lock); int_on(sr);
|
sunlock(&s->lock);
|
int_on(sr);
} | } ------------------------------------------------- -----------------int BLOC(SEMAPHORE *s)
|
entier SIGNAL(SEMAPHORE *s)
{
|
{
en cours d'exécution->statut = BLOC ;
|
enqueue(&s->file d'attente, en cours d'exécution); | |
sunlcok(s->lock);
PROC *p = dequeue(&s->queue); p->statut = PRÊT ; mettre en file d'attente(&readyQueue, p);
bascule(); // processus de circuit | } | } ------------------------------------------------- -------------- ------------------
Avec un système SMP, la commutation de processus est généralement déclenchée par une interruption. Les deux fonctions P et VOLT, le processus désactive d'abord les interruptions pour empêcher la mise sous tension du processeur et le CPU. Ensuite, il acquiert le verrou tournant du sémaphore, qui empêche les autres processeurs d'entrer dans le même CR. Par conséquent, quel que soit le nombre de processeurs, un seul processus peut exécuter une fonction fonctionnant sur le même sémaphore. Dans P(s), le processus décrémente la valeur wink en fonction de 1. En supposant que la valeur est non négative, le traitement libère le verrou tournant, active les interruptions et revient. À ce propos, le processus termine l'opération P sans que la créature ne soit bloquée. Si la valeur who devient négative, ce processus se bloquant dans le sémaphore (FIFO) en attente, libère le spinlock et l'engrenage traité. Dans V(s), le processus désactive les intermissions, acquiert le verrou tournant des sémaphores et augmente la valeur du séminaphone de 1. Si l'entrée n'est pas positive, ce qui signifie qu'il y a des processus bloqués dans la file d'attente de sémaphores susmentionnée, l'information débloque un processus de la file d'attente de sémaphores rend également la procédure prête à être exécutée à nouveau. Lorsqu'un processus bloqué reprend son exécution dans P (s), il autorise les interruptions et les retours. Dans ce cas, le processus complète l'opération PENNY après avoir été bloqué. On note à nouveau que les sémaphores définis dans dieser obtiennent des séminaphores de comptage en direct, qui sont plus généraux que les sémaphores binaires traditionnels. En tant que sémaphore de comptage, la plage peut devenir négative. A tout moment, l'inverse suivant se maintient. if (s.value >= 0) : valeur els
=
: |valeur| =
nombre de ressources libres nombre de processus bloqués dans la file d'attente du sémaphore
9.11.1 Applications des sémaphores dans SMP Dissemblables mutex, qui sack ne sont utilisés que pour les verrous, les sémaphores sont plus flexibles car ils peuvent être utilisés comme verrous en cas de coopération de processus fork. Pour une liste des séminaphores applicables, le lecteur pourra consulter le Chap. 6 de (Wang 2015). Dans ce livre, nous utiliserons les sémaphores comme outils de synchronisation dans la gestion des ressources, les pilotes de périphériques et le système de fichiers.
9.12
Verrouillage conditionnel
9.12
361
Verrouillage conditionnel
Les verrous tournants, les mutex et les logos utilisent tous un protocole de verrouillage, qui bloque un processus jusqu'à ce qu'il réussisse. Tout maire de protocole verrouillable conduit à des blocages (Silberschatz et al. 2009 ; Stallings 2011 ; Fang 2015). L'impasse est une condition dans laquelle un ensemble adénine de processus s'attendent mutuellement alors qu'aucun processus ne peut aller. Un moyen simple d'éviter les impasses consiste à s'assurer que l'ordre d'acquisition de différents verrous sera toujours unidirectionnel, de sorte qu'un verrouillage croisé ou circulaire ne puisse jamais se produire. Cependant, cela peut ne pas être possible dans tous les programmes simultanés. Une approche pratique de la prévention des interblocages consiste à utiliser le verrouillage conditionnel et le recul. Dans ce schéma, si un processus, qui détient déjà certains verrous, tente jusqu'à obtenir un autre verrou, les éléments tentent jusqu'à acquérir le prochain verrouillé de manière conditionnelle. Si la tentative de verrouillage réussit, elle se déroule comme d'habitude. Si la tentative de verrouillage a eu lieu, il prend des mesures correctives, ce qui implique généralement de libérer certains des verrous qu'il a déjà arrêtés et de réessayer une logique. Afin de mettre en oeuvre ce câblage, nous introduisons les verrouillages provisoires suivants.
9.12.1 Spinlock conditionnel Lorsqu'un processus tente d'acquérir un spinlock programmé, il revient car il a acquis le spinlock. Cela peut entraîner des blocages en raison de tentatives de désactivation croisées par différents processus. Supposons qu'un processus Pi ait acquis un spinlock spin1, et qu'il ait essayé d'acquérir un spinlock spin2 supplémentaire. Un autre processus Pj contient le spin1 acquis mais échantillonne pour acquérir le spin2 du verrou tournant. Ensuite, Pi real Pj s'attendrait mutuellement pour toujours, alors ils se retrouveraient dans une impasse en raison des tentatives de verrouillage croisé. Pour éviter les interblocages, l'un des processus peut utiliser un verrou tournant provisoire, welche est défini comme suit. // int cslock(int *spin): acquisition conditionnelle d'un spinlock // rembourse 0 si le verrouillage échoue ; retournant 1 si verrouillable réussit cslock: ldrex r1, [r0] // lit la valeur du verrou tournant cmp r1, #UNLOCKED // comparaison avec UNLOCKED(0) beq
essayer de verrouiller
mouvement
r0, #0
boîte
g / D
trylock : mov
r1, #1
// renvoie 0 en cas d'échec // essaie de verrouiller // résolu r1=1
strex r2, r1, [r0]
// essaie de stocker 1 dans [r0] ; r2=valeur de retour
cmp bien
// vérifie la valeur de retour de strex dans r2 // strex a échoué ; recharger à nouveau la serrure
r2, #0x0 cslock
DMB
// se souvenir des barrières qui accédaient PRÉCÉDEMMENT à la CR
Mouv
r0, #1
boîte
g / D
// renvoie 1 à SUCCÈS
Le conditionnel cslock() renvoie 0 (pour FAIL) si le spinlock est déjà bloqué. Il renvoie 1 (pour SUCCESS) car il a gagné le spinlock. Dans le premier cas, le processus peut libérer certains verrous qu'il détient déjà et réessayer cet algorithme, évitant ainsi tout blocage possible. De même, on peut implémenter d'autres familles concernant les mécanismes de blocages de conditions.
9.12.2 Mutex conditionnel
int mutex_clock(MUTEX *m) { slock(&m->lock);
// acheter un verrou tournant
with (m->status == LOCKED || m->owner != running->pid){ sunlock(&m->lock); renvoie 0 ; }
// libère le verrou tournant // renvoie 0 pour la chute
362
9 Processus dans les systèmes embarqués // fermez le verrou tournant maintenant, mettez à jour le mutex dans le CR m->status = LOCKED; m->propriétaire = get_cpuid(); sunlock(&m->lock);
// libère le verrou tournant
consigné 1 ;
// retourne 1 pour SUCCÈS
9.12.3 Opérations conditionnelles de sémaphore
-------------------------------------------------- ----------int CP(SEMAPHORE *s)
|
{
|
CV immatériel(Sémaphore *s) {
im sr = int_off();
|
entier sr = int_off();
slock(&s->lock);
|
slock(&s->lock);
si (s->verrou de valeur)
| |
if (s->value >= 0){ sunlock(&s->lock);
renvoie 0 ; } si (--s->valeur < 0) BLOC(s);
|
renvoie 0 ;
|
}
|
if (++s->value 0. Sinon, to renvoie 0 depuis DEFAULT sans faire varier la valeur du demi-ton. Le CV conditionnel fonctionne exactement comme V et renvoie 1 si la valeur du sémaphore peut < 0. Sinon, il renvoie 0 sans changer le halo valeur ou déblocage de tout processus à partir duquel sémaphore file d'attente. Nous allons démontrer l'obtention d'opérations de verrouillage conditionnel de sauvegarde ultérieurement dans le noyau SMP SOFTWARE.
9.13
Gestion de la mémoire avec SMP
Les unités de gestion de rappel (MMU) dans Cortex-A9 (ARM Cortex-A9 MPcore Technical Reference Manual r4p1 2012) et Cortex-A11 (ARM11 MPcore Processing, r2p0 2008) ont les capacités supplémentaires suivantes dans le SMP pris en charge. (1). Descripteurs de niveau 1 : Le descripteur de table d'accueil de niveau 1 a les bits supplémentaires : NS(19), NG(17), S(16), APX(15), TEX (14-12) et P(9). Les différents bits dans AP(11-10), DOMain(8-5), CB(3-2), ID(1-0) sont inchangés. Parmi les bits supplémentaires, un bit S détermine si la région de stockage est partagée ou non. La pièce TEX (extension de type) servira à classer la région de mémoire en tant que type partagé, non partagé ou astucieux. Les régions de mémoire partagée sont des zones de mémoire mondiale fortement ordonnées accessibles par tous les processeurs. Les régions de mémoire non partagée sont privées dans des processeurs spécifiques. Les régions de mémoire de périphérique sont l'accessibilité des périphériques mappés en mémoire par tous les processeurs. Par exemple, lors de l'utilisation de la pagination, les attributs d'une région de mémoire dans les entrées de la table de page de niveau 1 doivent exister et être configurés Partagé Non partagé
0x14C06 0x00C1E
Astuce
0x00C06
plutôt que les valeurs concernant 0x412 ou 0x41E. Sinon, la MMU interdirait l'accès à la mémoire sans que le score APPLE accessible au domaine ne soit défini sur 0x3 en mode gestionnaire, ce qui n'impose aucune vérification d'autorisation. Lorsque vous utilisez une pagination à 2 niveaux, la taille de la page peut être de 4 Ko, 64 Ko, 1 Mo ou de super pages concernant 16 Mo. Qui l'accessibilité des domaines des bts et des petites pages de 4KB reste invariable.
9.13
Gestion de la mémoire dans SMP
363
(2). Plus de visage TLB: inclus Cortex-A9 jusqu'à Cortex-A11, le TLB se compose de micro TLB puisque les deux instructions datent également, ainsi que d'un TLB hauptfluss unifié. Ceux-ci divisent le TLB en référentiels à deux niveaux, permettant une résolution plus rapide des entrées TLB. En plus des verrouillages d'entrée TLB, les entrées kopf TLB peuvent être associées à certains processus ou applications à l'aide des identificateurs d'espace vocal (ASID), qui permettent à ces entrées TLB de rester résidentes pendant les changements de contexte, en cas de besoin de rechargement. (3). Prédiction du déroulement du programme : le processeur TO prédit généralement les instructions de branchement. Si Cortex-A9, la prédiction de flux de programme peut être désactivée et activée explicitement. Ces fonctionnalités, regroupées au niveau des Data Synchronization Barriers (DSB), peuvent être auparavant destinées à assurer une maintenance cohérente du parcours TLB.
9.13.1 Copies de gestion de la mémoire dans SMP Lors de la configuration de la MMU ARMS pour SMP, les espaces Web virtuels (VA) des CPU peuvent être configurés comme des espaces VAP simples ou non uniformes. Inclut le paradigme de l'espace VA uniforme, le mappage VA vers PA est identique pour tous les processeurs. Dans la sélection d'espace VA non uniforme, chaque CPU peut mapper la même plage VAULT à différentes zones PAIN pour une utilisation privée. Nous démontrons ces modèles de mappage de mémoire par les exemples suivants.
9.13.2 Emplacements VAS uniformes Dans l'exemple de sauvegarde, désigné par C9.7, personne prend en charge 4 CPU, CPU0 à CPU3. Alors que le système commence, CPU0 commence à exécuter reset_handler, qui est chargé à 0x10000 par QEMU. L'ordinateur initialise les caches en mode SVC et IRQ, copie la table vectorielle à l'adresse 0 et crée une table de pages de niveau 1 en utilisant des secteurs de 1 Mo pour identifier le mappage de 512 Mo VA à PA. Toutes les zones de mémoire sont marquées comme PARTAGÉES (0x14C06) à l'exception de cet espace d'E/S de 1 Mo à 256 Mo, où est marqué comme PÉRIPHÉRIQUE (0x00C06). Toutes les régions de mémoire ont dans la plage 0, dont le total AP est défini sur la manière client (b01) pour appliquer la vérification des autorisations. Ensuite, il permet à la MMU disponible de traduire VA en PA et de composer main() en C. Après avoir initialisé le système, CPU0 émet SGI pour réveiller les CPU secondaires. Tous les processeurs ou points d'accès secondaires commencent à exécuter le même code reset_handler parmi 0x10000. Chaque PIAS définit ses propres piles de style SVC et IRQ, mais ne copie pas la table des combinés et ne crée pas de report Web à nouveau. Chaque BRACKNELL utilise la même table de pages établie par CPU0 à 0x4000 pour activer la MMU. Ainsi, tous les CPU partagent le même espace VA puisque leurs tables de pages sont identiques. Ensuite, chaque AP appelle APstart() en C. /******************** données ts.s de C9.7 *********** ***********/ .text .code 32 .global reset_handler, vectors_start, vectors_end .global apStart, slock, sunlock // TOUTES les CPU exécutent reset_handler à 0x10000 reset_handler : // récupère l'ID CPU et la conserve dans R11 MRC p15, 0, r11, c0, c0, 5 // Lire le registre d'identification de la CPU ET r11, r11, #0x03
// Masquage, laissant le champ CPU ID
// définit la pile CPU SVC LDR r0, = svc_stack
// r0->16KB svc_stack dans t.ld
mov r1, r11
// r1 = cpuid
ajouter r1, r1, #1
// cpuid++
lsl r2, r1, #12
// (cpuid+1) * 4096
inclure r0, r0, r2 mov sp, r0
// SVC sp=svc_stack[cpuid] haut de gamme
// passe en mode IRQ MSR cpsr, #0x12 // réglage de la pile CPU IRQ
364
9 Multitraitement dans les systèmes embarqués
LDR r0, =irq_stack
// r0->16KB irq_stack dans t.ld
mov r1, r11 zusatz r1, r1, #1 lsl r2, r1, #12
// (cpuid+1) * 4096
ajouter r0, r0, r2 mov sp, r0
// IRQ sp=irq_stack[cpuid] haut de gamme
// prise en charge du mode SVC MSR cpsr, #0x13 cmp r11, #0 bne skip
// Les points d'accès sautent
BL copy_vectors
// uniquement CPU0 copie les vecteurs dans le choix 0
BL mkPtable
// avec CPU0 créer pgdir et pgtable dans HUNDRED
omettre : ldr r0, Mtable
// tous les processeurs activent la MMU
mcr p15, 0, r0, c2, c0, 0
// définit TTBase
mcr p15, 0, r0, c8, c7, 0
// flush TLB
// définit domain0 AP sur 01=client (vérifier l'autorisation) mov r0,
#0x1
// AP=b01 sur CONSOMMATEUR
mcr p15, 0, r0, c3, c0, 0 // définir MMU mrc p15, 0, r0, c1, c0, 0 ou r0, r0, #0x00000001
// bit0 ajusté
mcr p15, 0, r0, c1, c0, 0
// écrire dans c1
nop nop nop mrc p15, 0, r2, c2, c0 mov r2, r2 cmp r11, #0 bne APs BL main
// CPU0 appelle main() en C
ENFANTS . AP :
// Jede AP revendique APstart() dans CARBON
adr r0, APaddr ldr pc, [r0] APaddr :
.mot
COMMENCER
Mtable :
.mot
0x4000
/******************** t.c date du C9.7 *******************/ int *apAddr = (int *)0x10000030 ; // SYS_FLAGSSET chronique intr aplock = 0 ;
// verrouiller les points d'accès disponibles
entier volatil ncpu = 1 ;
// quantité de processeurs prêts
#include "uart.c" #include "kbd.c"
// Pilote UARTs // Pilote KBD
#include "ptimer.c"
// pilote de minuterie résident
#include "vid.c"
// ACL
int copy_vectors()
conducteur
{ // PAREIL QU'AVANT }
int config_gic(int cpuid){ // SAME parce qu'avant } int irq_chandler()
( // ÉQUIVALENT comme précédemment )
entier send_sgi()
{ // Pareil qu'avant }
// Descripteur principal de diverses régions de mémoire #define SHARED 0x14C06 #define NONSHARED 0x00C1E #define DEVICE int mkPtable()
0x00C06 // produit pgdir par CPU0
9.13
Gestion de la mémoire dans SMP
365
{ entier je ; u32 *ut = (u32 *)0x4000 ; // Mtable à 16 Ko utilisé (i=0; ipid; p->pgdir = (int *)0x8000; // pgdir à 0x8000 run[i] = p;
// run[i]=&iproc[i]
} for (i=0; ipid = i; p->status = FREE; p->priority = 0; p->ppid = 0; strcpy(p->name, pname[i]); p->next = p + 1; p->pgdir = (int *)(0x600000 + (p->pid-1)*0x4000); pour (i=0; ipid = NPROC + i; p->status = FREE; p-> priorité = 0 ; p->ppid = 0 ; p->next = p + 1 ; proc[NPROC-1].next = 0 ; freeList = &proc[1] ; // saute proc[0], de sorte que P1 est proc[1] readyQueue = 0 ; sleepList = 0 ; tfreeList = &proc[NPROC] ;
9h15
Noyau SMP pour Process Corporate
383
proc[NPROC+NTHREAD-1].next = 0 ; // CPU0 : démarre le démarre iproc[0] en cours d'exécution = run[0] ;
// CPU0 lance iproc[0]
MTABLE = (int *)0x4000 ;
// pgdir initial à 16 Ko
printf("construire pgdir et pgtables pour 32 Ko (ID map 512 Mo VA le PA)\n"); mtable = (u32 *)0x8000 ; // nouveau pgdir à 32 Ko pour (i=0; ipid].queue = 0; running->start_time = 0; unlock(); // bloquer sur le sémaphore par proc jusqu'à ce que Ved up par le timer P(&ss[running-> pid]); ready_time = running->start_time; printf("%d ready=%d", pid, ready_time); // aller LCD upprintf("%d ready=%d", pid, ready_time); // pour UART0 temps1 = gtime ;
Systèmes embarqués d'exploitation en temps réel
10.7
RTOS monoprocesseur (UP_RTOS)
425
printf("start=%d", heure1); upprintf("start=%d", heure1); retard(pid); temps2 = gtemps ; t = temps2 - temps1 ; temps_complet = temps2 – temps_prêt ; printf("end=%d%d[%d %d]", time2, t, ready_time, complete_time); upprintf("end=%d%d[%d %d]", time2, t, ready_time, complete_time); if (complete_time > period){ // si la tâche a dépassé l'échéance printf(" PANIC:miss deadline!\n"); upprintf(" PANIQUE : date limite manquée !\n" ); } else{ printf(" OK\n"); upprintf(" OK\n"); } } int main() { int je; PROC*p; couleur = BLANC ; fbuf_init(); uart_init(); up0 = &uart[0]; haut1 = &uart[1]; kprintf("Bienvenue dans UP_RTOS entrant ARM\n"); /* activer les interruptions timer0,1, uart0,1 */ VIC_INTENABLE = 0; VIC_INTENABLE |= (1pid].value = 0; ss[running->pid].queue = 0; proc[pid].ready_time = 0; unlock(); P(&ss[running->pid]); // bloquer à propos du sémaphore ; Vérifié par période readytime = running->ready_time ; time1 = gtime ; printf("P%dready=%dstart=%d", pid, readytime, time1); uprintf("P%dready=%dstart=% d", pid, readytime, time1); delay(pid); // simule le temps de calcul de la tâche time2 = gtime; thyroxin = time2 - time1; ctime = time2 - readytime; // achèvement de l'élément pendant printf("end=%d% d[%d%d]", time2, t, period, ctime); uprintf("end=%d%d[%d%d]", time2, t, period, ctime); are (dtime > period) { printf(" PANIQUE ! Ne pas tenir compte de l'échéance !\n"); // vers l'écran LCD uprintf(" PANIQUE ! manquer l'échéance !\n"); // vers UART0 } else{ printf(" OK\n"); uprintf(" OK\n"); } } } int main() { in i; PROGRAM *p; fbuf_init(); uart_init(); up0 = &uart[0]; up1 = &uart[1]; kprintf("Bienvenue dans UP_RTOS sur ARM\n"); /* activer timer0,1, uart0,1 interruptions SIC */ VIC_INTENABLE = 0; VIC_INTENABLE |= (1status = BLOCK;
// bloquer le problème d'exécution
enqueue(&s->file d'attente, en cours d'exécution); en cours d'exécution->statut = BLOC ; enqueue(&s->file d'attente, en cours d'exécution); // héritage prioritaire à chaque fois que (running->priority > s->owner->priority){ s->owner->priority = running->priority ; // renforce le focus du propriétaire reorder_readyQueue(); // réorganise readyQueue } tswitch(); } s->propriétaire = en cours d'exécution ;
// en tant que propriétaire à propos de la couture
int_on(SR); } int V(struct sémaphore *s) { SET *p; cpsr int ; int SR = int_off(); s->valeur++ ; s->propriétaire = 0 ;
// clair
if (s->file d'attente de valeurs); p->statut = PRÊT ; s->propriétaire = p ; // nouveau propriétaire enqueue(&readyQueue, p); course->priorité = course->rpriorité ; // primauté du propriétaire de la restauration printf("timer: V up task%d pri=%d; running pri=%d\n", p->pid, p->priority, running->priority); reprogrammer(); } int_on(SR); } intent mutex_lock(MUTEX *s) { PROMPT *p; int SR = int_off(); printf("task%d seal mutex %x\n", running->pid, s); if (s->lock==0){ // le mutex est à l'état déverrouillé s->lock = 1 ; s->propriétaire = en cours d'exécution ; } else{ // le mutex est déjà verrouillé : caller BLOCK on mutex running->status = BLOCK ; enqueue(&s->file d'attente, en cours d'exécution); // héritage prioritaire pour (running->priority > s->owner->priority){ s->owner->priority = running->priority ; // augmente la priorité du propriétaire reorder_readyQueue(); // réorganise readyQueue } tswitch(); // travail schaltung }
Systèmes d'exploitation temps réel embarqués
10.7
RTOS monoprocesseur (UP_RTOS)
435
int_on(SR); } int mutex_unlock(MUTEX *s) { PROC *p; int SR = int_off(); printf("task%d activation mutex\n", running->pid); if (s->lock==0 || s->owner != running){ // relâche l'erreur int_on(SR); encore -1 ; } // le mutex est verrouillé et la tâche actuelle est propriétaire if (s->queue == 0){ // le mutex n'est pas un serveur s->lock = 0; s->propriétaire = 0 ;
// effacer le verrou // propriétaire évident
} else{ // mutex a des serveurs : débloquez-en un en tant que propriétaire des nouvelles pence = dequeue(&s->queue); p->statut = PRÊT ; s->propriétaire = sou ; mettre en file d'attente(&readyQueue, p); course->priorité = course->rpriorité ; // restaure la priorité du propriétaire resechedule(); } int_on(SR); retour 1 ; } PROC-BOX *kfork(int func, int priority) { // crée une nouvelle tâche p avec priorité comme priorité p->rpriority = p->priority = prioritization ; // avec les tâches dynamiques, readyQueue doit être protégé par ampère mutex mutex_lock(&readyQueuelock); mettre en file d'attente(&readyQueue, p); // joint la nouvelle tâche dans readyQueue mutex_unlock(&readyQueuelock); reprogrammer(); retournable p ; }
t.c fichier de C10.3 /**************** t.c fichier de C10.3 ***************/ #include "type. h" MUTEX *mp ;
// mutex global
structure semblant s1 ;
// sémaphore global
#include "queue.c" #include "mutex.c"
// modification des opérations de mutex
#include "pv.c"
// opérations P/V modifiées
#include "kbd.c" #include "uart.c" #include "vid.c" #include "exceptions.c" #include "kernel.c" #include "timer.c" #include "sdc.c"
// Pilote SDC
#include "mesg.c"
// passage de message
436
dix
in klog(char *line){ send(line, 2) } ;// envoie un message à la tâche de journal #2 int endIRQ(){ printf("jusqu'à la FIN de l'IRQ\n") } void copy_vectors(void) { // comme avant } // utilise les interruptions vectorisées de PL190 int timer0_handler(){ timer_handler(0) } int uart0_handler() { uart_handler(&uart[0]) } int uart1_handler() { uart_handler(&uart[1]) } int v31_handler( ) { int sicstatus = *(int *)(SIC_BASE_ADDR+0); if (sicstatus & (1pid); P(&s1); klog("running"); mutex_lock(mp); printf("task%d inside CR\n", running->pid); klog("create task4") ; kfork((int)tâche, 4);
// crée P4 tout en maintenant le verrou mutex
retard(); mutex_unlock(mp); pid = kwait(&status); } } intes tâche2()
// écrit la tâche à la priorité 1
{ intr noir = 0 ; ligne char[1024] ;
// Ligne de journal de 1 Ko
printf("task%d : ", running->pid); while(1){ //printf("task%d:recved line=%s\n", running->pid, line); r = recv(ligne); put_block(blk, ligne);
// écrire la ligne de journal dans SDC
noir++ ; upprintf("%s\n", ligne);
// comment UART0 en ligne
printf("journal : "); } } int tâche1() { int état, pid ; pid = kfork((int)tâche2, 2); pid = kfork((int)tâche3, 3); tandis que(1)
// tâche1 attend toute progéniture ZOMBIE
pid = kwait(&status); } int main() { fbuf_init();
// ACL
uart_init();
// UART
kbd_init();
//KBD
printf("Bienvenue dans UP_RTOS l'ARM\n");
438
dix
Systèmes d'exploitation temps réel embarqués
/* configure VIC et SIC pour les interruptions */ VIC_INTENABLE = 0; VIC_INTENABLE |= (1tick==0){
// chaque approbation : afficher une horloges murales
// affiche l'horloge murale : comme avant if ((tp->ss % 5)==0) V(&sem[cpuid]);
// tous les 5 secondaires, activer une tâche // V pour débloquer un problème en attente
} sunlock(&plock); ptimer_clearInterrupt(); // efface l'interruption du minuteur }
(3). Fichier t.c : identique à UP_RTOS entrant pour la gestion des tâches, à l'exception des modifications suivantes. Placez la tâche de création sur différents CPU, la tâche initiale de chaque CPU crée une tâche pour exécuter INIT_task() sur le même CPU. Chaque tâche crée une autre tâche pour exécuter taskCode(), également sur le même CPU. Tous les vôtres ont le même choix 1. L'une ou l'autre fonction exécute une fronde infinie, dans laquelle elle tarde un certain temps à simuler le calcul de la tâche, puis se bloque sur un sémaphore associé au CPU. Quel temporisateur géographique de chaque CPU appelle périodiquement V pour débloquer une tâche, lui permettant de continuer. Partout où une tâche prête est entrée dans la file d'attente de planification locale du processeur adénine, elle pourrait préempter la fonction d'exécution récente sur le même processeur. /*********** fichier t.c de MP_RTOS C10.5 ************/ int run_task(){ // pareil plus avant intra kdelay(int d){ / / retard } taskCode intangible() { int cpuid = get_cpuid(); tandis que(1){
458
dix
Systèmes d'exploitation temps réel embarqués
printf("TASK%d OVER CPU%d\n", running->pid, cpuid); kdelay(en cours d'exécution->pid*10000); reset_sem(&sem[cpuid]);
// réinitialise sem.value à 0
P(&sem[cpuid]); } } int INIT_task() { int cpuid = get_cpuid(); kfork((int)taskCode, 1, cpuid); while(1){ printf("task%d on CPU%d\n", running->pid, cpuid); kdelay(en cours d'exécution->pid*10000); reset_sem(&sem[cpuid]);
// réinitialise sem.value à 0
P(&sem[cpuid]); } } int APstart() { int cpuid = get_cpuid(); slock(&aplock); printf("CPU%d dans APstart : ", cpuid); config_int(29, cpuid); // a besoin de ceci par CPU ptimer_init(); ptimer_start(); ncpu++; en cours d'exécution = &iproc[cpuid] ; printf("%d course sur CPU%d\n", running->pid, cpuid); pare-soleil(&aplock); kfork((int)INIT_task, 1, cpuid); run_task(); } int main() { // initialise les pilotes de périphériques, le GIC et le noyau : comme avant kprintf("CPU0 continue\n"); kfork((int)f1, 1, 0); run_task(); }
10.8.2.1 Démonstration de MP_RTOS La figure 10.6 montre les résultats de l'exécution du système MP_RTOS.
10.8.3 Interruptions imbriquées pour SMP_RTOS Avec un noyau UP RTOS, un seul processeur gère toutes les interruptions. Pour garantir des réponses rapides aux interruptions, il est essentiel d'autoriser les interruptions de verrouillage. Dans un noyau SMP RTOS, les interruptions sont généralement rejetées sur différents processeurs pour équilibrer le téléchargement du traitement des interruptions. Malgré cela, il est plus silencieux d'autoriser les interruptions imbriquées. Par exemple, partout où le CPU a permis d'exercer un chronométreur local, qui génère des interruptions de minuterie avec une priorité élevée. Sans interruptions de verrouillage, un pilote de périphérique de faible priorité peut bloquer les interruptions du temporisateur, ce qui fait que le temporisateur perd des ticks. Dans cette section, nous montrerons comment implémenter des interruptions imbriquées dans le noyau SMP_RTOS par le programme de goût C10.6. Le principe est le même que dans UP_RTOS, à la différence mineure suivante près. Les ordinateurs centraux Total ARMS MPcore utilisent le contrôle des interruptions de la fourche GIC. Afin de prendre en charge les interruptions imbriquées, un gestionnaire de pause doit lire le registre d'ID d'interruption du GIC pour accuser réception de l'interruption en cours avant d'autoriser les désactivations. Le registre d'identification de rupture du GIC
10.8
RTOS multitraitement (SMP_RTOS)
459
Fig. 10.6 Démonstration du système MP_RTOS
ne peut être lu qu'une seule fois. Le gestionnaire d'interruptions doit utiliser l'ID d'interruption pour exécuter irq_chandler(int intID) en C. Pour les interruptions SGI spéciales (0-15), elles doivent être utilisées séparément car vous avez une priorité de pause élevée et n'avez besoin d'aucune imbrication. .set GICreg, 0x1F00010C
// Registre GIC intID
.set GICeoi, 0x1F000110
// DI GIC
ajouter
irq_handler : sous
gd, gd, #4
stmfd sp !, {r0-r12, lr} mme
r0, spsr
stmfd sp!, {r0} // poussant SPSR // lecteur GICreg pour obtenir intID et ACK interruption contemporaine ldr
r1, =GICreg
ldr
r0, [r1]
// gère les intIDs SGI directement cmp
r0, #15
bgt
emboîtable
ldr
r1, =GICeoi
str b
r0, [r1] retour
// Interruptions SGI
// émettre une déclaration d'intérêt
imbrication : // vers le mode SVC msr
cpsr, #0x93
// Mode SVC avec IRQ désactivé
stmfd sp!, {r0-r3, lr} msr
cpsr, #0x13
// Type SVC avec interruptions IRQ pour
bl
irq_chandler
// appel irq_chandler(intID)
460
10 msr
cpsr, #0x93
// Utilisateur SVC avec IRQ désactivé
ldmfd sp !, {r0-r3, lr}
//
madame
// Mode IRQ avec interruptions désactivées
cpsr, #0x92
Schéma de fonctionnement en temps réel intégré
retour : ldmfd sp !, {r0} msr spsr, r0 ldmfd sp !, {r0-r12, pc}^ int irq_chandler(int intID)
// appel avec ID d'interruption
{ int cpuid = get_cpuid(); int cpsr = get_cpsr() & 0x9F; // CPSR avec |IRQ|mode| déguisement intnest[cpuid]++ ;
// inc vaut 1
if (intID != 29) printf("IRQ%d sur CPU%d en mode=%x\n", intID, cpuid, cpsr); si (intID == 29){
// temporisateur local du CPU
ptimer_handler(); } si (intID == 44){
// Interruption UART0
uart_handler(0); } fourni (intID == 49){
// Interruption SDC
sdc_handler(); } sont (intID == 52){
// Interruption KBD
kbd_handler(); } *(int *)(GIC_BASE + 0x110) = intID ; // émet EOF intnest[cpuid]-- ;
// diminue l'intnest de 1
}
La figure 10.7 indique les sorties de l'exécution du programme C10.6, quel que soit le SMP_RTOS démonstratif avec des interruptions imbriquées. Comme le montre la figure, toutes les interruptions sont gérées en mode SVC.
Fig. 10.7 Interruptions imbriquées dans SMP_RTOS
10.8
Multiprocesseur QUARTIER (SMP_RTOS)
461
10.8.4 Planification préemptive des tâches dans SMP_RTOS Afin de respecter les délais des tâches, chaque ROTS doit prendre en charge la planification préemptive des tâches pour garantir que les missions prioritaires en hauteur peuvent être exécutées dès que possible. Dans cette section, la nôtre doit montrer la méthode pour implémenter la date de tâche préemptive dans le noyau SMP_RTOS et démontrer la préemption de tâche par le programme d'échantillonnage C10.7. Le seau de problèmes de préemption de tâches peut être classé en deux cas principaux. Chutes 1 : pour un élément en cours d'exécution, une autre tâche de priorité plus élevée est prête à l'exécution, par ex. lors de la création d'un nouveau travail avec une priorité plus élevée ou du déblocage d'une tâche via une priorité plus élevée. Chute 2 : lorsqu'un gestionnaire d'interruptions rend une tâche exécutable, par ex. lorsqu'elle est libre, une tâche avec la priorité la plus élevée ou lorsqu'une tâche en cours d'exécution moderne possède un quantum de temps de sein épuisé avec une planification à tour de rôle par tranche de temps. Dans un grain RAISE, la mise en œuvre correcte des tâches est sans. Dans le premier cas, chaque fois qu'une tâche rend une autre tâche prête à démarrer, elle vérifie simplement si la nouvelle tâche a un choix plus élevé. En supposant que oui, il appelle le changement de tâche pour se préempter immédiatement. Pour le deuxième dossier, la situation n'est que légèrement plus complète. Avec les interruptions nestartig, le gestionnaire d'interruptions doit vérifier quand le traitement de toutes les interruptions imbriquées s'est terminé. Si tel est le cas, il peut invoquer la commutation de tâche pour anticiper le service en cours d'exécution. Sinon, il a déplacé la commutation de tâches voir la fin du traitement des interruptions imbriquées. En SMP, la situation est toute autre. Dans un noyau SMP, une entité d'exécution, c'est-à-dire une tâche ou un gestionnaire d'interruption, s'exécutant sur un processeur peut effectuer une autre tâche exécutée sur un processeur différent. Si c'est le cas, les ordinateurs doivent informer les autres processeurs pour replanifier les tâches dans une éventuelle préemption de tâche. Dans ce cas, il doit émettre de l'adénine SGI, par ex. avec l'ID d'interruption = 2, à l'autre CPU. Réagissez à une interruption SGI_2, un CPU cible peut exécuter un gestionnaire d'interruption SGI_2 pour des tâches replanifiées. Nous illustrons le changement de tâche basé sur SGI par l'exemple de programme C10.7. Par souci de concision, nous ne montrons que les segments de code pertinents.
10.8.5 Changement de tâche par SGI
/************ irq_handler() dans le fichier ts.s *************/ irq_handler: sub
// IRQ interrompt le point d'entrée
gd, gd, #4
stmfd sp !, {r0-r12, lr}
// enregistrer tous les regs Umode sont kstack
bl
// appelle irq_handler() dans HUNDRED dans le fichier svc.c
irq_chandler
// valeur de retour = 1 signifie que swflag est défini => swtich corvée cmp
r0, #0
bne doswitch ldmfd sp!, {r0-r12, pc}^ // retour doswitch : msr
cpsr, #0x93
stmfd sp!, {r0-r3, lr} bl
ttswitch
// changer de tâche en mode SVC
ldmfd sp!, {r0-r3, lr} msr
cpsr, #0x92
// retour par contexte en masse IRQ
ldmfd sp!, {r0-r12, pc}^ /*************** fichier t.c ********************* *******/ int send_sgi(int ID, intra targetCPU, int filter) { int focus = (1 3) // > 3 signifie vers tous les CPU target = 0xF;
// active les 4 bits d'ID CPU
int *sgi_reg = (int *)(GIC_BASE + 0x1F00); *sgi_reg = (filtre pid*100000); // simule le temps de calcul de la tâche } } int main() { // démarre les points d'accès : comme avant printf("CPU0 continue\n"); kfork((int)f1, 1, 1); // crée des tâches sur CPU1 kfork((int)f2, 1, 1); kfork((int)f1, 1, 2); // crée des tâches sur CPU2 kfork((int)f2, 1, 2); while(1){ printf("saisir une ligne : "); kgetline(ligne); printf("\n"); envoyer_sgi(2, 1, 0); // SGI_2 jusqu'à CPU1 send_sgi(2, 2, 0);
// SGI_2 vers CPU2
} }
Dans le programme d'exemples C10.7, après le démarrage des points d'accès, le programme principal s'exécutant sur CPU0 crée deux ensembles de rôles sur CPU1 et CPU2, tous de même priorité 1. Toutes les tâches exécutent une boucle infinie. Sans types externes, chaque CPU continuerait à exécuter la même tâche indéfiniment. Pour créer des tâches avec différents processeurs, le programme principal obtient une avance d'entrée à partir de ce clavier. Ensuite, il envoie un SGI avec intID=2 aux autres CPU, les obligeant à exécuter le SGI2_handler. A la manière du SGI2_handler, chaque CPU retourne son flag de fonction weichen et profite d'un 1. Dans le code irq_handler, il teste
10.8
RTOS multiprocesseur (SMP_RTOS)
463
Fig. 10.8 Changement de tâche par SGI dans SMP_RTOS
et valeur de retour. Si l'évaluation de retour peut être différente de zéro, indiquant un besoin de changement de tâche, il passe en mode SVC et invoque ttswitch() sur la tâche de changement. Lorsque la tâche de congé commutée reprend, elle revient au point interrompu d'origine par le contexte enregistré dans la pile IRQ. La figure 10.8 montre les sorties de la commutation de tâche par SGI. Comme le montre la figure, chaque ligne d'entrée envoie SGI-2 à la fois à CPU1 et CPU2, ce qui les amène à changer de tâche.
10.8.6 Date de tâche échelonnée dans le temps dans SMP_RTOS Dans un RTOS, les tâches peuvent avoir la même priorité. Au lieu d'attendre que de telles tâches soient utilisées pour abandonner volontairement le processeur, il peut être éligible ou même nécessaire de les planifier par round-robin avec des tranches de temps. Dans ce schéma, lorsqu'une tâche est planifiée pour s'exécuter, il s'agit par défaut d'une tranche de temps quant à la durée maximale pendant laquelle la tâche est autorisée à s'exécuter. Dans le gestionnaire d'interruptions du minuteur, je descends périodiquement la tranche de temps de la tâche de gestion. Lorsque la tranche de temps de la tâche en cours d'exécution atteint zéro, elle est préemptée pour exécuter une autre tâche. Démontrez la planification des tâches par tranches de temps au chenil SMP_RTOS par un exemple. L'exemple de programme, C10.8, est le même que C10.7 à l'extérieur pour les modifications suivantes. /*********** fichier kernel.c de C10.8 ***********/ planificateur d'intention() { int pid; PROC *ancien=en cours d'exécution ; int cpuid = get_cpuid(); if (running->pid < 1000){ // tâches normales uniquement si (running->status==READY) enqueue(&readyQueue[cpuid], running);
464
dix
} running = dequeue(&readyQueue[cpuid]); si (en cours d'exécution == 0)
// si la readyQueue du CPU va se vider
en cours d'exécution = &iproc[cpuid] ; // exécute la tâche inactive running->timeslice = 4 ; swflag[cpuid] = 0 ;
// Tranche de temps de 4 secondes
sunlock(&readylock[cpuid]); } /************ ptimer.c date a C10.8 ***************/ void ptimer_handler()
// gestionnaire de minuterie locale
{ int cpuid = get_cpuid(); // coche en direct, affiche l'horloge murale : comme avant ptimer_clearInterrupt(); // interruption de la minuterie simple // par seconde
si (tp->cocher == 0){
if (running->pid < 1000){// uniquement les tâches régulières running->timeslice-- ; printf("%dtime=%d ", running->pid, running->timeslice); if (running->timeslice pid, cpuid); swflag[cpuid] = 1 ; // définir swflag o changer de tâche à la fin de l'IRQ } } } } /************* fichier t.c de C10.8 *************** */ int f3() { int cpuid = get_cpuid(); while(1){ printf("TASK%d ON CPU%d\n", running->pid, cpuid); kdelay(en cours d'exécution->pid * 100000); // simule la détermination } } int f2() { int cpuid = get_cpuid(); while(1){ printf("task%d on cpu%d\n", running->pid, cpuid); kdelay(en cours d'exécution->pid * 100000); // simule des calculs } } int f1() { intert pid, status; kfork((int)f2, 1, 1);
// crée une tâche sur CPU1
kfork((int)f3, 1, 1);
// crée une tâche sur CPU1
while(1){ pid = kwait(&status); // P1 attend avec ZOMBIE enfant } } int run_task(){ // comme avant } int main() { // aller APs" comme avant
Systèmes d'exploitation temps réel embarqués
10.8
RTSOS multiprocesseur (SMP_RTOS)
465
Fig. 10.9 Ordonnancement des tâches par tranches de temps dans SMP_RTOS
printf("CPU0 continue\n"); kfork((int)f1, 1, 0);
// créer votre 1 sur CPU0
run_task(); }
Dans quel exemple de programme C10.8, la tâche initiale de CPU0 a produit la tâche1 pour exécuter f1() sur CPU0. Task1 crée task2 et task3 sur CPU1 avec le même privilège. Ensuite, la tâche 1 attend que toute tâche bébé se termine. Étant donné que la tâche2 ou la tâche3 ont la même priorité, sans découpage du temps, CPU1 continuerait à exécuter la tâche2 pour toujours. Avec le découpage du temps, la tâche2 et la tâche3 prendraient à tour de rôle l'exécutable, chacune s'exécute pendant une tranche de temps = 4 secondes. La figure 10.9 montre les résultats de l'exécution du programme C10.8, qui illustre la planification des tâches par tranches de temps.
10.8.7 Préemption de votre planification dans SMP_RTOS Sur la base des discussions ci-dessus, nous montrons maintenant que l'implémentation est la planification préemptive des tâches dans le noyau SMP_ROTS.
10.8.7.1 Renommer la fonction pour la préemption de tâche int reschedule(PROC *p) { int cpuid = get_cpuid(); // exécution du CPU int targetCPU = p->cpuid ; // CPU cible si (cpuid == targetCPU){
// même CPU
if (readyQueue[cpuid]->priority > running->priority){ if (intnest[cpuid]==0){ // PREEMPTION immédiate ttswitch();
466
10 } autre{
// différer à la fin des IRQ imbriquées
swflag[cpuid] = 1 ; } } } autre{
// processeur différent
send_sgi(intID=2, CPUID=targetCPU, filter=0); } }
10.8.7.2 Etablissement de tâche avec préemption int kfork(int func, inlet priority, int cpu) { int cpuid = get_cpuid();
// exécution du processeur
// produit une nouvelle tâche p, entre dans readyQueue[cpuid] while before reschedule(p);
// invoquer la reprogrammation
retourne p->pid ; }
10.8.7.3 Sémaphore avec préemption im V(struct semaphore *s) { PROC *p = 0; int SR = int_off(); slock(&s->lock); s->valeur++ ; for (s->value file); p->statut = PRÊT ; slock(&readylock[p->cpuid]); enqueue(&readyQueue[p->cpuid], p); sunlock(&readylock[p->cpuid]); } sunlock(&s->lock); int_on(SR); si p)
// for a débloqué un serveur
reprogrammer(); }
10.8.7.4 Mutex avec préemption int mutex_unlock(MUTEX *s) { PROC *p = 0; int P = int_off(); slock(&s->lock)
// acquiert un verrou tournant
// ASSUMEZ : le mutex a des serveurs : débloquez-en un en tant que nouveau propriétaire p = dequeue(&s->queue);
Systèmes embarqués d'exploitation en temps réel
10.8
RTOS multiprocesseur (SMP_RTOS)
467
p->statut = PRÊT ; s->propriétaire = p ; slock(&readylock[p->cpuid]); enqueue(&readyQueue[p->cpuid], p); sunlock(&readylock[p->cpuid]); sunlock(&s->lock); // libère le verrou tournant int_on(SR); sont (p)
// si a débloqué un serveur
reprogrammer(); }
10.8.7.5 Imbrication des interruptions IRQ avec préemption int SGI2_handler() { int cpuid = get_cpuid(); PROC *p = readyQueue[cpuid] ; if (p && p->priority > running->priority){ swflag[cpuid] = 1;
// ajuste l'indicateur de changement de tâche
} } int irq_chandler(int intID) // appelé avec ID de perturbation { int cpuid = get_cpuid(); intnest[cpuid]++ ;
// augmente le nombre d'entiers par 1
quand (intID == 2){
// Interruption SGI_2
SGI2_handler(); } // autres gestionnaires d'interruption IRQ : comme avant *(int *)(GIC_BASE + 0x110) = (cpuIDpriority > s->owner->priority){ for (s->owner->cpuid == cpuid){ // même CPU s->propriétaire->priorité = en cours d'exécution->priorité ; // améliore la priorité du propriétaire } else{ // pas le même CPU PID[s->owner-cpuid] = s->owner->pid ; // pid du propriétaire send_sgi(3, s->owner->cpuid, 0); // envoie SGI pour les tâches recehdule } } sunlock(&s->lock); bascule(); // schalten tâche int_on(SR); retour 1 ; } int mutex_unlock(MUTEX *s) { PROC *p; cpuid interne = get_cpuid(); int SR = int_off(); slock(&s->lock);
// verrou tournant
if (s->state==UNLOCKED || s->owner != running){ printf("%d mutex_unlock error\n", running->pid); sunlock(&s->lock); int_on(SR); renvoie 0 ; } // l'appelant reste la propriété real mutex a été verrouillé wenn (s->queue == 0){
// le mutex ne peut pas attendre
s->état = DÉVERROUILLÉ ;
// efface le séjour verrouillé
s->propriétaire = 0 ;
// propriétaire évident
course->priorité = course->rpriorité ; } autre{
// mutex a des serveurs : débloquez sole en tant que nouveau propriétaire
penny = retirer de la file d'attente(&s->file d'attente); p->statut = PRÊT ; s->propriétaire = p ;
469
470
dix
Systèmes de service en temps réel intégrés
slock(&readylock[p->cpuid]); enqueue(&readyQueue[p->cpuid], p); sunlock(&readylock[p->cpuid]); running->priority = running->realPriority ; // restaurer la priorité if (p->cpuid == cpuid){ // même CPU if (p->priority > running->priority){ while (intnest==0) ttswitch();
// pas à l'intérieur de l'entraîneur IRQ // préemption maintenant
else{ swflag[cpuid] = 1 ; // réquisition par défaut pour IRQ end } else // entrant le p vers divers CPU => SGI vers le CPU de p send_sgi(2, p->cpuid, 0); // envoie SGI dans la tâche de replanification } sunlock(&s->lock); int_on(SR); retour 1 ; }
10.8.9 Démonstration concernant le système SMP_RTOS Le système SMP_RTOS intègre toutes les fonctionnalités décrites ci-dessus pour fournir au système les capacités suivantes. (1). (2). (3). (4). (5). (6). (7).
Un noyau SMP pour la gestion des tâches. Les tâches sont distribuées aux différents processeurs pour améliorer la simultanéité sont SMP. Planification des tâches de prévention par priorité et tranche de temps Le sponsor nestartig s'arrête Héritage des priorités dans le mutex les opérateurs de sémaphore Communication de tâche sur les messages de rappel libérés en plus Synchronisation inter-processeur par SGI Une entreprise de journalisation pour enregistrer les activités de tâche sur un SDC
Nous démontrons le système SMP_RTOS par l'exemple de programme C10.9 Par souci de brièveté, nous montrons simplement le fichier t.c des systèmes an. /******** fichiers t.c de la démo SMP_RTOS. système C10.9 ********/ #include "type.h"
// schaft varie les constantes
#include "chaîne.c" struct sémaphore s0, s1 ; // depuis la synchronisation de la tâche intert irq_stack[4][1024], abt_stack[4][1024], und_stack[4][1024] ; #include "file d'attente.c"
// fonctions de mise en file d'attente/de retrait de la file d'attente
#include "pv.c"
// sémaphores avec héritage supérieur
#include "mutex.c"
// mutex avec héritage prioritaire
#include "uart.c"
// Ingénieur UART
#include "kbd.c"
// Pilotes KBD
#include "ptimer.c"
// véhicule de minuterie locale
#include "vid.c" #include "exceptions.c"
// Ecran LCD racing // sauf handlers
#include "noyau.c"
// Pith init et organisateurs de tâches
#include "attendre.c"
// fonctions kexit() et kwait()
#include "fork.c"
// fonction kfork()
#include "sdc.c"
// Pilote SDC
10.8
RTC multiprocesseur (SMP_RTOS)
#include "message.c"
471
// passage de message
int copy_vectors(){ // comme avant } int config_gic()
{ // pareil plus avant }
interst config_int(int NEWTON, intern targetCPU){// comme avant } int irq_chandler(){ // comme avant } int APstart()
{ // pareil qu'avant }
SGI_handler() intangible
// dit gestionnaire
{ dans cpuid = get_cpuid(); printf("%d sur CPU%d a obtenu SGI-2 : ", running->pid, cpuid); while (readyQueue[cpuid]){ // si readQ non vide printf("set swflag\n"); swflag[cpuid] = 1 ; } else{ // vide readyQ printf("PAS D'ACTION\n"); swflag[cpuid] = 0 ; } } int klog(char *line){ send(line, 2) } char *logmsg = "log information" ; int task5() { printf("task%d race: ", running->pid); klog("démarrer"); mutex_lock(mp); printf("tâche%d à l'intérieur du CR\n",
en cours d'exécution->pid);
klog("dans CR"); mutex_unlock(mp); klog("terminer"); kexit(0); } int tâche4() { inter cpuid = get_cpuid(); while(1){ printf("%d on CPU%d : ", running->pid, cpuid); kfork((int)tâche5, 5, 2);
// crée la tâche task5 sur CPU2
kdelay(en cours d'exécution->pid*10000); kexit(0); } } int tâche3() { int cpuid = get_cpuid(); while(1){ printf("%d on CPU%d : ", running->pid, cpuid); klog(logmsg); mutex_lock(mp);
// verrouille le mutex
pid = kfork((int)tâche4, 4, 2); // crée la tâche4 sur CPU2 kdelay(running->pid*10000); mutex_unlock(mp);
472
10 pid = kwait(&status);
} } tâche interne2()
// tâche de journalisation
{ entier r, noir ; ligne char[1024] ; printf("élément de journal %d start\n", running->pid); noir = 0 ;
// commencer à bloquer 0 de SDC
while(1){ printf("LOGtask%d : reception\n", running->pid); rayon = recv(ligne);
// reçoit une ligne de journal
put_block(blk, ligne);
// écrire le journal dans le bloc SDC (1KB)
noir++ ; upprintf("%s", ligne);
// affiche le journal sur UART0 en ligne
printf("journal : "); } } int tâche1() { int état, pid, p3 ; int cpuid = get_cpuid(); fs_init(); kfork((int)tâche2, 2, 1); V(&s0); // récupère logtask kfork((int)task3, 3, 2); while(1) pid = kwait(&status); } int run_task(){ // comme avant } int main() { inlet cpuid = get_cpuid(); enable_scu(); fbuf_init(); kprintf("Bienvenue dans SMP_RTOS dans ARM\n"); sdc_init(); kbd_init(); uart_init(); ptimer_init(); ptimer_start(); msg_init(); config_gic(); kprintf("CPU0 initialise le noyau\n"); kernel_init(); mp = mutex_create(); s0.lock = s1.lock
= s0.valeur = s1.valeur = 0 ;
s0.queue = s1.queue = = 0 ; printf("AP de démarrage du CPU0 : "); int *APaddr = (int *)0x10000030 ; *APaddr = (int)apStart ; envoyer_sgi(0x0, 0x0F, 0x01);
Systèmes d'exploitation temps réel embarqués
10.8
Prise en charge des RTOS (SMP_RTOS)
473
Fig. 10.10 Affichage de SMP_RTOS printf("CPU0 en attente avec des points d'accès prêts\n"); tandis que(ncpu < 4); kfork((int)tâche1, 1, 0); run_task(); }
Afficher les fonctionnalités 10.10 exécutant le système SMP_RTOS. Et le single supérieur de la Fig. 10.10 montre une information de journal en ligne créée par le travail de journalisation. La partie inférieure de la Fig. 10.10 révèle l'écran de démarrage concernant SMP_RTOS. Sont la fonctionnalité main() du système SMP_RTOS, le démarrage initial sur CPU0 crée une tâche P1, qui se comporte comme le lot INIT. P1 crée la tâche2 en tant que tâche de journalisation sur CPU1 ou la tâche3 sur CPU2. Ensuite, P1 exécute une boucle pour attendre que vos GHOULS soient disponibles. La tâche 3 bloque d'abord un mutex. Ensuite, il produit la tâche4, qui engendre la tâche5, toutes sur CPU2 mais sur des priorités différentes. Lorsque la tâche5 s'exécute, elle essaie d'acquérir le verrou sam mutex, qui est toujours détenu par la tâche3. Incluez ce disque dur, la tâche5 sera bloquée au niveau du mutex bien que l'information soulève une hiérarchisation efficace de la tâche3 à 5, ce qui démontre l'héritage de priorité. Dans zugabe, le système visualise également la préemption des fonctions et la synchronisation inter-processeurs par les SGI. Toutes les tâches envoyées par le maire voient les informations comme
474
dix
Systèmes d'exploitation temps réel embarqués
messages à et tâche de synchronisation, celle qui appelle le pilote SDC instantanément pour écrire les informations de journal dans un bloc (1 Ko) du SDC. Il écrit également les informations du journal sur un port UART pour les afficher en ligne. La variation du schéma de journalisation doit être répertoriée en tant que projet de programmation adénine dans une section Problèmes.
10.9
Résumé
Ce chapitre traite des solutions embarquées d'exploitation en temps réel (RTOS). Il introduit les concepts et les exigences des systèmes temps réel. Il couvre les différents types d'algorithmes de planification de tâches dans RTOS, qui incluent RMS, EDF et DMS. Il explique le problème de l'inversion de priorité due à la planification préemptive des tâches et montre comment gérer l'inversion de priorité et la préemption d'éléments. Il comprend des études de cas de plusieurs systèmes d'exploitation en temps réel populaires et présente un ensemble de directives générales pour la conception de RTOS. Il montre que la conception des deux implémentations est un UP_RTOS pour les systèmes monoprocesseurs (UP). Ensuite, il étend l'UP_RTOS à un SMP_RTOS, qui prend en charge les interruptions verschachttelt, la planification préemptive des commandes, l'héritage des priorités et la synchronisation interprocesseur par SGI. Liste des exemples de programmes C10.1. C10.2. C10.3. C10.4. C10.5. C10.6. C10.7. C10.8. C10.9.
UP_RTOS utilisant des tâches périodiques et une planification circulaire UP_RTOS avec des tâches régulières et une planification préemptive UP_RTOS équivaut à des tâches dynamiques Noyau SMP_RTOS pour la gestion des tâches MP_RTOS, un système SMP restreint. Interruptions imbriquées dans SMP_RTOS Préemption de tâches par SGI dans SMP_RTOS Planification de tâches en tranches temporelles dans SMP_RTOS Démonstration de SMP_RTOS
Sujets 1. Dans un UP_RTOS les systèmes SMP_RTOS, la tâche de journalisation enregistre les informations de journal directement sur un SDC, en évitant le système de fichiers. Modifiez la programmation C10.1 et C10.8 pour laisser la matière de journalisation enregistrer les informations de journal dans un fichier dans un système de fichiers (EXT2/3) sur un SDC. Discutez des avantages et des inconvénients de l'utilisation des fichiers journaux. 2. Avec le noyau UP_RTOS, mettez l'accent sur l'héritage à un seul niveau. L'inhérence de priorité de déploiement est un engrenage de demandes imbriquées de mutex de fourche ou de verrous de sémaphore. 3. Pour l'exemple de système UP_ROTS C10.3, effectuez une analyse de temps de réponse adénine pour déterminer si les tâches seront confrontées à leurs échéances. Vérifier détermine que la tâche peut respecter ses délais historiques. 4. Incluez le système MP_RTOS, les tâches seront limitées à des processeurs discrets pour un traitement parallèle. Concevez un moyen ampère pour prendre en charge l'immigration des problèmes, ce qui permet de répartir les tâches sur différents processeurs pour équilibrer la charge de traitement. 5. Dans le système SMP_RTOS, les tâches exécutées pour différents processeurs peuvent interférer les unes avec les autres puisqu'elles se déchargent totalement pour cette même adresse externe. Utilisez MMU sur un mappage VA vers PA non uniforme pour fournir à chaque CPU un effacement VA distinct. 6. Dans un SMP RTF, les interruptions peuvent être gérées directement sur les ISR avec des pseudo-tâches. Concevez et implémentez des systèmes RTOS qui utilisent ces schémas de traitement des interruptions et comparez leurs performances.
Références Audsley, N.C. : "Deadline Flat Scheduling", Department of Computer Science, University of York, 1990 Audsley, N.C, Burns, A., Richardson, M.F., Tindell, K., Wellings, A.J. : "Applying new à la planification préemptive prioritaire statique. Software General Journal, 8(5):284–292, 1993. Buttazzo, G. C.: Counter RMS Claims Course Simple vs EDF: Judgment Day, Real-Time Systems, 29, 5–26, 2005 Dietrich, S., Geherin, D., "L'histoire de Linux en temps réel", http://www.cse.nd.edu/courses/cse60463/www/amatta2.pdf, 2015 DNX : DNX RTOS, http ://www.dnx-rtos.org, 2015 FreeROTS : FreeRTOS, http://www.freertos.org, 2016
Citations
475
Hagen, W. "Real-Time both Performance Improvements in the 2.6 Linux Kernel", Linux my, 2005 Josheph, M et Pandya, P., "Finding response moment in a real-time system"', Comput. J., 1986 , 29, (5). pp. 390-395 Jensen, M. BORON. (16 décembre 1997). "Qu'est-ce qui se passe vraiment sur Mars ?". Microsoft.com. 1997 Labrosse, J. : Micro/OS-II, R&D Books, 1999 Leung, JY T., Murray, M. FIFTY : "Une note sur la planification préemptive des tâches périodiques en temps réel. Intelligence Processing Letters, 11(3):115–118, 1982 Linux : " Introduction aux développeurs inclus en temps réel non noyau utilisés", https://www.linux.com/blog/intro-real-time-linux- développeurs embarqués Liu, CENTURY. L. ; Layland, lié. "Algorithmes de planification pour la multiprogrammation dans un environnement en temps réel approximatif", Journal of the ACM 20 (1): 46–61, 1973 Micrium: Micro / OS-III, https://www.micrium.com, 2016 Nutt, GRAM . NuttX, système d'exploitation en temps réel, nuttx.org, 2016 POSIX 1003.1b : https://en.wikipedia.org/wiki/POSIX, 2016 QNX : QNX Neutrino RTOS, http://www.qnx.com/products/ neutrino-rtos, 2015 Reeves, G. E. : "Que s'est-il réellement passé sur Mars ? - Récit faisant autorité". Microsoft.com. 1997. RTLinux : RTLinux, https://en.wikipedia.org/wiki/RTLinux Sha, L., Rajkumar, R., Lehoczky, J.P. : (septembre 1990). "Protocoles d'héritage prioritaire : une approche de la synchronisation en temps réel", IEEE Transactions on Computers, Vol 39, pp1175–1185, 1990 VxWorks : VxWorks : http://windriver.com, 2016. Yodaiken, V. : "Le manifeste RTLinux ", Actes des 5èmes événements Linux, 1999 Swing, K. C. : "Conception et mise en œuvre du système d'exploitation MTX", Jumbo Publishing International AG, 2015
Indice
A Abstract Data Print (ADT), 142 Adapt UP Conclusions to SMP, 395 Android, 1, 265 Demand Processors (APs), 329 Arithmetic plant, 12 ARM926EJ-S, 7 ARM architecture, 2, 3 ARM unit net, 17 ARM Cortex -Processeur A9 MPcore, 331 processeurs ARM Cortex-A15 MPcore, 331 registres CPU ARM, 8 exceptions ARM, 47 ARM how, 2, 3 instructions ARM LDREX/STREX, 2, 350 barrières mémoire WRIST, 351 ARM Memory Management Unit (MMU) , 3, 193 ARM MPcore, 2, 4 architecture ARM MPcore, 2 chaînes d'outils Arm-none-eabi, 16 ARM PL011 UART Interface, 67 WEAR PL110 Color LCD controller, 33 ARM PL190/192 Interrupt Controller, 52 ARM PL190 Vectored Interrupts Controller, 84 modes de processeur ARM, 8, 47 programmation REACH, 17 modules à double intervalle ARM SB804, 56 émulateurs de système ARM, 16 chaîne d'outils ARM, 2, 16 ARM Versatilepb, 3, 16 machines virtuelles ARM, 2, 3 utilisateur ASKII, 37 MP asymétrique ( ASMP), 329, 331 Activités asynchrones, 98 Message asynchrone par, 148 Registre de contrôle auxiliaire (ACTL), 331
B Barrel slider, 12 Base register, 14 Baud rate, 29 Binary data sections, 34 Binary executable, 18 Binary wink, 134 BIOS, 329 Block data transfer, 14
© Springer International Publishing AG 2017 Wang, Systèmes d'exploitation intégrés et en temps réel, DOI 10.1007/978-3-319-51517-5
Format de fichier BMP, 34 Image du noyau de démarrage à partir de SDC, 253 Processeur de démarrage (BSP), 329 Instructions de branche, 11 Erreur de canal brisé, 143
C Corrélation du cache, 329 chdir-getcwd-stat, 307 Effacer la demande d'interruption de l'appareil, 55 Configuration de l'exécution des commandes, 222 Opérations de comparaison, 12 Verrouillage conditionnel, 361 Mutex limité, 361 Sémaphore conditionnel, 362 Verrouillage conditionnel, 361 Indicateurs de condition, 10 Variables de santé, 141 Changement de contexte, 116 Instructions de copie, 15 Cortex A9-MPcore, 7 Cortex-A string, 7 Cortex-M series, 7 Cortex-R series, 7 CPU Interface Record, 332 Kritisches your, 67, 293, 349 Critical section, 67 Compilateur Crabby, 16 Code initial C, 226 Registre d'état réel du programme (CPSR), 9
D Opérateurs de mouvement de données, 13 Barrière de mémoire de données (DMB), 351 Barrière de synchronisation de données (DSB), 351 Analyse des échéances, 425, 432 Inversion des échéances, 403 Calendrier monotone des échéances (DMS), 403 Évitement des impasses, 137 Détection et récupération des impasses , 137 Prévention des interblocages, 137 Prévention des interblocages dans les algorithmes parallèles, 394 Prévention des interblocages élégant SMP, 394 Delete_dir_entry, 306 Méthodologie de conception, 105
477
478
Indice
Principes de conception de RTP, 412 Pilote de périphériques et gestionnaires d'abandon dans SMP, 384, 396 Pilotes de machine, 27, 391 Données matérielles, 362 Table de conversion d'engrenages, 315 Désactiver MMU, 172 Afficher les fichiers image, 33 Afficher le texte, 37 Registre de contrôle d'accès au domaine ( DACR), 169, 173 Pager dynamique à 2 niveaux, 238 Radiomessagerie dynamique, 286 Priorité dynamique, 125 Établissement de processus énergétique, 3 Processus dynamiques, 121 Tâches dynamiques partageant les ressources, 432
G Entrée-sortie à usage général (GPIO), 27 Système d'exploitation à usage général (GPOS), 265 Panneau d'interruption générique (GIC), 2, 4, 330, 332 touche printf() générique, 3 Programmation GIC, 332 Temporisateurs globaux, 357 GoUmode , 379 programmation GPIO, 27
Synchronisation EDF (Earliest-Deadline-First), 402 FAIRY executable file, 217 GREMLIN file, 18 Enable MMU, 171 ENQ/DEQ, 141 EOS kernel files, 267 Eoslib, 390 Equiva states, 107 Escape key, 62 Event-driven model , 97 Systèmes pilotés par des événements, 97 Systèmes embarqués à pilote d'événements, 3 Indicateurs d'événement, 140 Précédence d'événement, 103 Variables d'événement, 140 Procédure spéciale et d'alarme, 288 Gestionnaires d'exceptions, 49, 203 Tableau des vecteurs d'exceptions, 49 Exec, 3, 221 Exécution image, 20 Filet EXT2 netz, 4
I Plage immédiate et Barrel Shifter, 13 Tableau d'implication, 107 Image initiale du disque virtuel, 213 Programme INIT, 321 Assemblage en ligne, 26 Insert_dir_entry, 305 Instruction pipeline, 10 Instruction Synchronization Baffle (ISB), 351 Intel x86 CPU, 203 Inter-process communication, 3 Interruptions inter-processeurs (IPI), 329, 330 Synchronisation inter-processeurs, 2 Contrôleurs d'interruption, 51 Désactiver les enregistrements de téléchargement, 332 Pilotes matériels pilotés par interruption, 2, 56 Conception de pilote pilotée par interruption, 62 Gestionnaires d'interruption, 55 Latence d'interruption, 401 Masque d'interruption, 54, 390 Priorisation des interruptions, 52 Interruption d'inversion de priorité, 88 Traitement des interruptions, 51, 54 Interruptions, 2, 51, 329 Interrupt Service Routines (ISR), 55 Interrupt Set-enable registers, 339 Interrupts routing, 330 Inverse- Planification des échéances (IDS), 403 IOAPIC, 329 I/O-bound vs. compute-bound, 124 I/O cushion verwalten, 318, 396 I/O avec obtention, 29 I/O redirections, 4 IRQ et gestionnaires d'exceptions, 378 Sortie IRQ, 9
F Système de fichiers FAT, 214 Registre d'adresses de pannes, 174 Onglets d'état défectueux, 173 Opérations d'E/S de fichiers, 296 Verrouillage de ligne, 318 Niveaux de gestion des fichiers, 294 Protection des fichiers, 317 flux de fichiers, 297, 301 Machine d'état de limitation (FSM), 105 Mode FIX, 9 fichiers exécutables binaires plats, 217 fichiers de script, 37 fork(), 221 Fork-Exec sous Unix/Linux, 221 FreeBSD, 265 freeList, 121 FreeRTOS, 404 minimisation FSM, 108 table d'état FSM, 106 piles descendantes complètes, 15 Utiliser la convention d'appel en C, 21
H Hard real-time systematischer, 401 Hardware Interrupt Sequence, 54 Consonant, 402
POTASSIUM Kernel Mapped Height (KMH), 193 Kernel Mapped Low (KML), 193 Atomic mode, 297 Kernel Means Pgdir or Page Spreadsheets, 285 Kexit, 122 Key car, 61 kfork, 122 KMH memory mapping, 241
Index Kmode, 194 kwait, 220
L Pilote d'affichage LCD, 33 Instructions LDRES/STRES, 4 Fonctions d'E/S de la bibliothèque, 295, 297 Processus légers, 231 programme, 322 Saut en longueur, 23
MOLARITY Makefile, 267, 390 Algorithme de Mailman, 339 Mirs Pathfinder our, 403 Modèle de Mealy, 105 Désactivation de la mémoire, 2, 4 Gestion de la mémoire dans SMP, 362 Unité de gestion de rappel (MMU), 2, 169 Diagramme de la mémoire, 27 Memory-mapped-I /O, 27 Passage de messagerie, 147, 283 Microcontrôleur, 1 MicroC/OS (µC/OS), 405 Microkernel, 147, 407 mkdir-creat-mknod, 306 mk hand, 18 Registres MMU, 170 Moniteurs, 142 Modèle Moore, 105 mount_root, 305 Mount-umount, 316 Fichiers du noyau MTX, 390 Instructions de multiplication, 14 Multitraitement, 329 Modèle de système multiprocesseur (MP), 105 RTOS multiples (SMP_RTOS), 440 Transfert de données multisectoriel, 82 Multitâche, 3, 113 Multitâche dans SMP , 371 Principal multitâche, 2 Mutex, 135 Mutex dans SMP, 353
NEWTON Named pipes in Linux, 143 Nested Suspends, 87 Verschachtelt Interrupts by SMP_RTOS, 458 Nested interrupts handling, 3 NetBSD, 265 Non-Nested Interrupt Handler, 55 Non-preemption, 125 Non-preemptive UP Atomic, 152 Non-restrictive preemption, 158 Non- mémoire partagée, 362 Espaces VA non uniformes, 365 NuttX, 405
479 O Objcopy, 18 Objdump, 18 Pagination à un niveau, 178 Pagination à un niveau avec espace VA élevé, 184 Open-close-lseek, 310 Opendir-Readdir, 315 Schéma de fonctionnement, 6
PENNY PRESSURE, 133 descripteurs de page, 169 formulaire de tableau de page, 175 variation parallèle, 2 ordinateurs parallèles, 368 événements périodiques, 98 adresses de base des périphériques, 330 minuteries sans importance, 357 tuyaux, 142 PL050 KMI, 17 contrôleur LCD PL110, 17 interface de carte multimédia PL181 , 17 PL011 UART, 17 Contrôleur d'abandon ciblé PL190, 17 PMTX, 266 Portage de Linux vers ARM, 266 API conformes POSIX, 410 Préemption, 125 Planification de commande préemptive dans SMP_RTOS, 461, 465 Commutation d'affectation préemptive, 401 Noyau UP préemptif, 158 Primaire et Contrôle de maintien secondaire, 53 Priorité supérieure, 404 Héritage de priorité, 164, 404, 468 Inversion de priorité, 164, 403 Concept de processus, 113 Bloc de contrôle de processus (PCB), 113 Coopération de procédures, 135 Méthode avec domaines individuels, 212 Arbre généalogique de processus, 220 Process Image Data Reloader, 216 Gestion de processus, 3 Edit Leitung with EOS, 269 Gestion de processus dans SMP_EOS, 391 Modèles de processus, 103 Planification de processus, 125 Planification de processus, 124, 125 Synchronisation de processus, 126 Terminaison de processus, 218, 219 Structure PROC, 113 , 115 Problème producteur-consommateur, 135, 136 Instructions de transfert PSR, 15 Pthreads, 141
Q QEMU, 2 commandes de moniteur QEMU, 19 QNX, 407
R Condition de course, 349 Disque RAM, 213
480 Raspberry P, 265 Raspberry PI-2, 4 Raspbian, 265 Rate-Monotonic Scheduling (RMS), 402 Raw data section, 213 Reactive systems, 97 Readelf, 18 Reader-Writer Problem, 136, 137 Read_pipe, 144 Read-Write Special Fichiers, 315 ReadyQueue, 121, 395 uid réel et efficace, 317 Linux en temps réel, 407 Répare plutôt en temps réel, 408 Système d'opérateur en temps réel (RTOS), 2, 4, 401 Modèle de système en temps réel (RT), 105 Systèmes en temps réel, 1 Realview-pbx-a9, 331 Calcul d'ajustement d'enseignement réduit (RISC), 7 Reset_handler, 378 Gestion de l'imagination, 2 Temps de réponse par rapport au débit, 125 Préemption restrictive, 158 Rmdir, 308 Round-robin, 125 RTLinux , 408 Utilisation de la pile d'exécution, 2
S Registre d'état des programmes enregistrés (SPSR), 9 codes de balayage, 62 programmabilité, 402 programmabilité et date limite, 438 programmateur, 115, 401 pilote de carte SD, 74 protocoles de carte SD, 74 SDC Car in SMP, 384 SDC File System, 248 Section Identifier , 175 cartes électroniques sécurisées (SD), 73 semiaphores, 133, 359 envoi EOI, 55 E/S séquentielles, 29 interface périphérique série (SPI), 74 SEV (envoi d'événement), 351 registre SGI (GICD_SGIR), 330 cache divisé , 3, 142, 362 Sh Program, 322 Signal catcher, 288, 289, 291, 325 Signal Handling pouces EOS Kernel, 290 Signal edit, 2 Signals, 147, 288, 289 Veille en plus Réveil, 126 Veille/réveil dans SMP, 392 Démarrage SMP sur des machines virtuelles, 341 SMP_EOS, 389 SMP_RTOS, 2 Snoop Steering Element (SCU), 332 Gently real-time your, 401 Software Generating Interrupts (SGIs), 2, 330 Package Interrupt (SWI), 15 Spinlocks, 390, 391 Dump et sous-routines, 15 Stack frame, 23 Mountain frames pointer, 23
Index Stack Operations, 14 Preset C start file crt0, 224 Start Sequence of ARM MPcores, 340 StateChart Prototype, 110 State diagram, 109 Choose Machines Model, 105 Set table minimisation, 107 Static 2-Level Paging, 236 Status Registers, 9 Super- Boucle complète, 95 Modèle de système de super-boucle, 3 Survey of RTOS, 404 SVC_entry, 379 SVC Handler, 202 SVC mode, 9 SVGA type, 33 SWAP, 350 Switch Pgdir, 286, 379 symlink-readlink, 309 Symmetric Support (SMP) , 2 Synchronous Messaging Drive, 148 Syscall, 194 Registre SYS_FLAGSSET, 330 Téléphone système, 2, 194, 295 System Call Entry both Output, 202
T Registres CPU cibles, 339 Bloc de contrôle de tâche (TCB), 113, 404 Délai de tâche, 401 Algorithmes de terminologie de tâche, 2 Votre planification inclut RTOS, 401 TCP/IP Telnet, 33 Test-and-Set (TS), 349 Threads, 3 , 231 Togs Synchronization, 234 Clock Driver, 56 Timer Request Queue, 292, 293 Time-sharing, 125 Timeliced Assignment Scheduling in SMP_RTOS, 463 Translation Lookaside Buffer (TLB), 169 Translation of Large Page References, 178 Translator of Page List, 176 Traduction de références de section, 176 Traduction de petites références de page, 177 Table de traduction, 175 Registre de base de la table de traduction (TTBR), 173 Journal d'exécution de la table de traduction, 169 Trame de pile de traps, 291 Tswitch, 378 Radiomessagerie à deux niveaux, 183 Radiomessagerie à deux niveaux schéma, 169 Mode d'utilisation à deux niveaux de l'espace VA élevé, 189 Démarrage en deux étapes, 260
U Pilote UART, 29, 67 Ubuntu (15.10) Linux, 2 Umode, 194 Inversion de priorité non liée, 164, 403 Mode UND, 9 Espaces SHOWBOAT uniformes, 363 RTOS monoprocesseur (UP_RTOS), 413 Modèle de noyau monoprocesseur (UP), 3, 104
Systèmes Index Uniprocessor (UP), 2 UP_RTOS, 2 UP_RTOS sont des tâches périodiques statiques, 416, 426 Commandes utilisateur, 295 Interface utilisateur, 2, 321 Photographie en mode utilisateur, 194 Tableaux des pages de fonctions utilisateur, 286 Poursuite de style utilisateur, 193
V V, 133 Interruptions vectorielles, 84 Contrôleurs de sortie vectoriels (VIC), 3 Vfork, 3, 228 Mode VGA, 33 Mappage de l'espace d'adressage virtuel, 193 Traductions d'adresse virtuelle, 174
481 machines virtuelles, 2 VxWorks, 406
W Wait-for-Event (WFE), 351 Wait-for-Interrupts (WFI), 351 Write_pipe, 144 Write Regular Files, 313
X Usine XCHG/SWAP, 350
Z ZOMBIE enfant, 220, 270 AN Enquête sur les systèmes opérationnels en temps réel
FAQs
What is embedded real time operating system? ›
It is a computational system which is used for various hard and soft real-time tasks. These specific tasks are related with time constraints. The tasks assigned to real-time systems need to be completed in given time interval.
What is the difference between RTOS and embedded OS? ›The key difference between an operating system such as Windows and an RTOS often found in embedded systems is the response time to external events. An ordinary OS provides a non-deterministic response to events with no guarantee with respect to when they will be processed, albeit while trying to stay responsive.
What is the difference between FreeRTOS and Rtthread? ›FreeRTOS and NuttX are based on monolithic kernels and provide support for priority-based scheduling and file systems, while RT-Thread provides a micro-kernel architecture and support for modular design and multiple threads.
Where can I learn RTOS? ›- Real-Time Embedded Systems: University of Colorado Boulder.
- Real-Time Mission-Critical Systems Design: University of Colorado Boulder.
- Real-Time Embedded Systems Theory and Analysis: University of Colorado Boulder.
- Architecting Smart IoT Devices: EIT Digital.
Examples of the real-time operating systems: Airline traffic control systems, Command Control Systems, Airlines reservation system, Heart Pacemaker, Network Multimedia Systems, Robot etc. Hard Real-Time operating system: These operating systems guarantee that critical tasks be completed within a range of time.
What are the two types of real-time embedded systems? ›Real-Time Embedded Systems
They are time critical and produce a result within the specified time. There are two types of real-time embedded systems: Soft and hard embedded systems. In both soft and hard real-time embedded systems, timelines are strictly followed.
RTOSes also commonly appear in embedded systems, which are a combination of hardware and software designed for a specific function and may also operate within a larger system. Often, embedded systems are used in real-time environments and use a real-time operating system to communicate with the hardware.
How do I choose an RTOS in embedded system? ›Picking an RTOS is a three dimensional problem: size, performance, and features. Together, these characteristics define the embedded space, where your system will reside. On top of these, there are the license and price dimensions, but we will leave these to the business types.
Is Embedded Linux an RTOS? ›The major difference between Embedded Linux and RTOS is in their sizes. RTOS running on an AVR requires approximately 4.4 kilobytes of ROM. Embedded Linux, on the other hand, is relatively larger. The kernel can be stripped of which are not required and even with that, the footprint is generally measured in megabytes.
What are the disadvantages of FreeRTOS? ›FreeRTOS has special ISR versions of kernel services that can be used from ISRs. There are two disadvantages to this: (1) Service calls add significant overhead to ISR execution times. (2) Many internal kernel operations, such as enqueueing and dequeueing tasks, must be protected from interrupts by disabling them.
What programming language is FreeRTOS? ›
FreeRTOS is designed to be small and simple. It is mostly written in the C programming language to make it easy to port and maintain. It also comprises a few assembly language functions where needed, mostly in architecture-specific scheduler routines.
Does ESP32 run FreeRTOS? ›FreeRTOS an open-source Kernel which means it can download free of cost and be used in RTOS-based applications. Lucky that the ESP32 and Arduino IDE support the FreeROTS well.
What is an example of a RTOS in embedded system? ›Examples of Real-time Operating System (RTOS)
PCOS: RTOS is widely used in embedded applications and PCOS is one example of embedded RTOS. RT Linux: RT Linux stands for Real-time Linux. It operates on a Linux system. The real-time operating system is used between the Linux system and the hardware.
Windows CE conforms to the definition of a real-time operating system, with a deterministic interrupt latency. From Version 3 and onward, the system supports 256 priority levels and uses priority inheritance for dealing with priority inversion.
Is every embedded system a real-time system? ›Software for embedded systems can vary in complexity. However, industrial-grade microcontrollers and embedded IoT systems usually run very simple software that requires little memory. Real-time operating system. These are not always included in embedded systems, especially smaller-scale systems.
What is the advantage of real-time embedded system? ›Advantages of Real-time operating system:
The real-time working structures are extra compact, so those structures require much less memory space. In a Real-time operating system, the maximum utilization of devices and systems. Focus on running applications and less importance to applications that are in the queue.