26 Apr

Samsaung PORTING UNIX TO THE 386-housse samsung a5-xinpvm

Compléter le noyau 386BSD

William Frederick Jolitz et Lynne Greer Jolitz

Bill était le principal développeur de 2.8 et 2.9BSD et était l’architecte en chef du projet GENIX de National Semiconductor, le premier système UNIX à mémoire virtuelle, basé sur un microprocesseur.Avant de fonder TeleMuse, une société d’études de marché, Lynne était vice-présidente du marketing chez Symmetric Computer Systems: ils organisent des séminaires sur BSD, RNIS et TCP / IP (c) 1992 TeleMuse.

Quand nous avons commencé le projet 386BSD en 1989, 386BSD était simplement destiné à être un portage de BSD au 386. Notre but en faisant 386BSD était que les étudiants, le corps professoral, le personnel et les chercheurs puissent utiliser BSD sur une plate-forme simple et peu coûteuse. Bien que nous ne voulions pas ajouter aux revenus exclusifs de licence de quiconque en repliant le nouveau code grevé (particulièrement en ce qui concerne le 386), supprimer ou remanier le nouveau code pour remplacer le vieux code encombré sortait du cadre de ce projet. Après avoir contribué 386BSD à l’Université de Californie à Berkeley (UCB) en décembre 1990, le personnel d’UCB commença sérieusement à se lancer dans la publication d’un code non encombré. Comme vous pouvez vous y attendre, c’était une corvée de mettre à jour continuellement le 386BSD et de le réviser, en accord avec le travail effectué par le personnel d’UCB, pour aboutir à Berkeley Networking Software, version 2 (‘NET / 2’).

Dans une rupture avec les versements passés, nous avons pris le noyau NET / 2 non encombré mais incomplet et avons fini les morceaux manquants nécessaires pour faire un noyau de démarrage amorçable qui fournit un environnement de développement autosupporté Pour faire le noyau complet de 386BSD, nous avions pour compléter un code qui n’était pas disponible lorsque UCB a composé la bande NET / 2. Certains de ces domaines, tels que execve (), n’étaient tout simplement pas disponibles à ce moment, tandis que d’autres (clists, ressources, cache buffer) étaient basés sur les parties obsolètes du système devant être remplacées par des installations plus modernes (comme les flux et le cache de pages) En fait, nous avons constaté que bon nombre de ces «nouvelles» installations sont encore loin.

Nous avions besoin de créer ces «pièces manquantes», nous avons donc utilisé le système NET / 2 lui-même pour faire évoluer les éventuels remplacements pour ces mêmes pièces. Pour remplacer les clists manquants, nous avons choisi de concevoir des tampons annulaires pour fonctionner à leur place. À la place des cartes de ressources manquantes, nous avons inventé un mécanisme plus flexible appelé liste de ressources qui exploite le mécanisme d’allocation de mémoire dynamique déjà présent dans le noyau NET / 2. Pour le mécanisme d’E / S basé sur le tampon et l’appel du système d’exécution du programme, nous nous sommes appuyés sur des matériaux de référence pour concevoir un substitut approprié pour nous servir alors que nous travaillons lentement sur notre nouvelle version d’un cache de pages.

Alors que nous avons toujours l’intention de mettre l’accent sur le travail novateur, nous sommes limités dans les limites du présent. Ainsi, nous avons choisi le chemin le plus court vers la fin de l’objectif de 386BSD, en mettant en place des installations qui accèderont au reste de la source du noyau NET / 2 avec un minimum de changement. En fait, le code source présenté au cours des deux prochaines tranches plus un petit nombre de corrections de bogues et une copie récente de la bande NET / 2 (disponible via M Online) vous permettront de construire un noyau opérationnel. Avant de commencer à utiliser le téléphone, sachez qu’en plus d’un noyau, vous avez besoin de bootstraps (DDJ, février 1991), de binaires des utilitaires (DDJ, mars, avril, mai 1991), d’un système de fichiers racine (DDJ, juillet, août , Septembre et octobre 1991), un mécanisme d’installation (DDJ, février et mai 1991 ainsi que mars et avril 1992) et de documentation (DDJ, janvier à novembre 1991 et février 1992) avant de faire du 386BSD un véritable système amorçable.

Les lecteurs qui ont suivi cette série ont maintenant suffisamment de matériel et d’informations pour mettre dans la graisse de coude et la terminer par leurs propres moyens. Pour ceux qui coque photo iphone ont moins de patience, nous espérons pouvoir suivre cela rapidement avec un vrai binaire de 386BSD qui peut être mis sur un PC sans travail majeur.

386BSD Méthodologie d’achèvement du noyau

La méthodologie que nous avons suivie pour compléter le noyau 386BSD NET / 2 a été essentielle à notre succès. Nous avons commencé par examiner la documentation qui décrit comment chacune de ces installations manquantes a fonctionné, puis nous avons examiné la structure de l’interface dans le noyau NET / 2. À partir de cette information, nous avons créé un modèle de sémantique pour chacune des installations manquantes. Parmi les références que nous avons trouvées utiles, citons la conception du système d’exploitation UNIX de Maurice Bach, la série MINIX de Tanenbaum, le livre BSD, le manuel de référence des programmeurs UNIX System V, l’art de la programmation informatique de Knuth, ainsi que Les comparaisons de ces références sont intéressantes car elles contiennent toutes des perspectives différentes qui «colorient» un peu différemment le casse-tête en utilisant ces matériaux de référence pour écrire les premières versions de ces modules à partir de zéro. Nous avons découvert des faiblesses dans les hypothèses initiales dont certaines nécessitaient une révision importante.Cette approche nous a également permis de construire un programme de test réaliste autour du code du noyau prévu qui a facilité le développement et le débogage du code dans un processus utilisateur. cadre.’

Bien que les débogueurs de noyau modernes et d’autres outils de développement soient maintenant omniprésents, le débogage isolé est toujours utile, car il vous permet de circonscrire le problème plus efficacement. Par exemple, un certain nombre de bogues dans le noyau NET / 2 qui avaient été masqués par l’ancien code manquant ont été découverts par ce processus. La validation indépendante que fournit le développement en mode utilisateur isolé est un outil précieux pour identifier les zones à problème dans le code NET / 2 ainsi que dans le nouveau logiciel.

Une fois qu’une version brute a été conçue pour fonctionner dans un environnement utilisateur, les cas complexes ont été testés pour localiser les problèmes de mise en œuvre. Il est dit que 90% de vos bogues proviennent de moins de 1% de votre code source, mais vous pouvez généralement deviner où le problème se posera, et c’est exactement là que vous ciblez vos vecteurs de test. Malheureusement, de nombreux programmeurs s’abstiennent de cette procédure, préférant ‘inspecter’ visuellement le code au lieu de faire le ‘test d’acidité’. Par exemple, tous les bogues dans le code de tampon d’anneau étaient situés dans les cas de passage de frontière sur des fonctions normales et inverses. Il y avait des cas analogues avec les opérations GET / PUT contiguës, donc celles-ci aussi devaient être examinées aux limites. Dans un cas, nous avons ridiculisé les tampons en anneau pour exacerber les problèmes sur les limites et, ce faisant, nous avons également exposé d’autres insuffisances.

Ensuite, nous avons dû faire face aux exigences environnementales et d’interface du noyau. Aucun framework de programme utilisateur ne peut espérer simuler l’environnement piloté par interruptions, enclenché dans le contexte d’un noyau amical de base (avec le résultat que le système se coince beaucoup). Contrairement à Mach, qui espère exporter cet environnement vers les processus utilisateur sous l’apparence de noyauter le système (le système reste coincé, mais les problèmes surviennent dans l’environnement utilisateur, rendant plus difficile la localisation du problème), nous préférons que l’environnement noyau ne pas faire partie du cadre de test isolé. Quand nous entrons dans le noyau, c’est une proposition tout ou rien, car les mécanismes interagissent énormément: cette coque personnalisée iphone 5c silicone complexité d’interaction est toujours présente, qu’elle reste dans le noyau ou qu’elle soit exportée vers un processus utilisateur.

Les méthodes par lesquelles nous réglons l’introduction du nouveau code dans le noyau sont la clé pour assurer le bon fonctionnement du code.Nous tirons parti de notre compréhension des points d’entrée afin d’examiner et de suivre les demandes transmises à notre nouveau code et comparer les contre ce que fait notre modèle de test en mode utilisateur.

Dans un sens, nous finissons par inverser notre processus de débogage. Au lieu de produire des cas pour perturber le fonctionnement interne du nouveau code, nous déboguons les interfaces et les procédures externes qui l’appellent. Nous faisons cela méthodiquement, en testant les cas limites de chacun au fur et à mesure que nous cherchons des hypothèses inattendues sur la sémantique du nouveau code.

Dans le cas du cache tampon, cette méthode a capturé des ‘gotchas’ significatifs. Une grande partie de la sémantique du cache tampon NET / 2 est impliquée par le code environnant. (Par exemple, le rééchelonnement de la taille de la mémoire tampon, l’invalidation de la mémoire tampon et le retour au stockage sont tous liés au système de fichiers virtuel et aux couches du système de fichiers subsidiaire.) Cela pourrait expliquer en partie pourquoi il est tampon de mémoire tampon. Sa sémantique est répartie sur toute la carte!

Avec notre méthodologie en place, nous avons commencé à terminer les pièces manquantes, en arrivant, finalement, à 386BSD Unbound.

Listes de ressources 386BSD

Une partie manquante du noyau NET / 2 386BSD est la facilité de stockage dense, ou allocation de région, connue dans la langue vernaculaire de Berkeley UNIX comme les cartes de ressources. Les cartes de ressources ont été créées dans 4BSD sous la forme d’une généralisation de l’allocateur de mémoire physique ‘core click’ trouvé dans la version 6 Bell Laboratories UNIX originale pour le PDP 11. Elles étaient largement utilisées dans l’ancien système de mémoire virtuelle 4BSD. Cependant, dans le système de mémoire virtuelle du noyau NET / 2, ils ne sont utilisés que pour allouer des espaces contigus d’espace de swap pour contenir les processus échangés (par ailleurs, le terme ‘map’ est trompeur, car cela n’a rien à voir avec l’utilisation par le système de mémoire virtuelle du terme ‘carte’ pour décrire l’utilisation du matériel de traduction d’adresses du processeur.

Les mappes de ressources fonctionnent en décrivant les segments allouables sous la forme d’un double (index, taille). Ces deux tuples sont stockés dans un tableau contigu de taille fixe. Lorsque les ma coque personnalisé allocations sont faites à partir de la carte, les segments se fragmentent et occupent de plus en plus d’espace dans le tableau. Lorsque des fragments sont renvoyés logiquement à la carte, la procédure free () rassemble les fragments et tente de réduire l’espace dans le tableau. Si le tableau est assez grand, la pire fragmentation possible ne peut pas dépasser la taille de la carte de ressources.

Les cartes de ressources, bien qu’élégantes, compactes et rapides pour l’allocation de mémoire PDP 11, présentent des inconvénients gênants. Pour faire une implémentation rapide, l’index ‘0th’ n’est pas utilisable, car il ne se distingue pas de ‘nothing’ sur la liste (en d’autres termes, il est utilisé comme sentinelle), et l’entrée qui lui correspond est utilisée pour Maintenez la limite des limites supérieures et le nom de l’instance de mappe de ressources donnée, de sorte que l’appelant doit déplacer la plage au-dessus de 0 avant de la transmettre aux routines de mappe de ressources ou ignorer la première unité d’allocation. De plus, la taille et l’étendue de la carte de ressources sont fixes au moment de l’initialisation et sont inaltérables, donc le stockage de la carte doit être réservé pour la ‘taille du cas le plus défavorable’.

Dans de nombreux systèmes UNIX précoces, certains noyaux ne liaient jamais ces tableaux et griffonnaient joyeusement tous les emplacements de mémoire adjacents après la carte! Le système fonctionnerait ensuite pour une période de temps après, et, lorsque l’accident inévitable s’est produit, il semblait provenir d’une partie non liée du système. Bien sûr, à ce moment-là, la carte était devenue moins fragmentée et son contenu ne présentait aucune irrégularité; en d’autres termes, un bug presque ‘auto-guérisseur’. Ceux qui avaient découvert cette astuce intelligente protégeaient la carte avec une ‘vérification des limites’ qui provoquait une panique du système si la carte se fragmentait en dehors du tableau. Après un certain temps, il devenait fastidieux de recompiler le noyau avec des tailles de cartes de plus en plus grandes, donc au lieu de paniquer, l’allocateur de la carte des ressources ne ferait que ‘laisser tomber’ le fragment. Cette approche avait des effets secondaires humoristiques sur les systèmes de partage à grande échelle, lorsque le fragment tombé s’avérait être quelque chose d’important, comme la moitié de tout l’espace d’échange disponible, parce que les gros fragments tendent à se retrouver en fin de liste.

Ce mécanisme statique a été toléré depuis longtemps parce qu’il a été utilisé comme allocateur de stockage de niveau inférieur, et l’autoriser à utiliser l’allocation dynamique a été considéré comme imprudent (par exemple, que se passe-t-il s’il est fragmenté)

De nombreuses décisions de conception dans le noyau changent lorsque l’allocation de mémoire dynamique devient disponible. Nous avons donc remplacé les mappes de ressources d’allocation fixes par des listes de ressources créées à partir d’un schéma d’allocation de mémoire dynamique.

Listes de ressources définies

Les listes de ressources (voir Listing 1) sont des listes de longueurs arbitraires, dont chaque élément décrit un segment comme un tuple inclusif [début, fin]. Les éléments de liste sont conservés dans un ordre trié, avec des entrées fragmentées provoquant l’allocation de nouvelles entrées spontanées (via malloc). Au fur et à mesure que les segments sont libérés, permettant le remplissage des trous et le réassemblage des fragments, les entrées adjacentes sont réduites à des simples, et les entrées de liste désormais superflues sont libérées et renvoyées à l’allocateur de mémoire dynamique. Ainsi, aucune perte de stockage n’a lieu. Le prix que nous payons est le coût supplémentaire de l’allocation dynamique. qui est généralement faible par rapport au nombre de fois que nos listes de ressources sont utilisées.

D’autres avantages des listes de ressources découlent de leur nature dynamique. Il n’y a pas de fonction initialize, seulement des appels d’allocation et d’entrée libre, car l’initialisation consiste simplement à passer de l’espace libre à une liste vide, dans n’importe quel ordre et à n’importe quel moment. Cela nous permet, par exemple, d’ajouter de l’espace de swap supplémentaire sans devoir réinitialiser la carte des ressources ni réserver d’espace à l’avance. De plus, étant donné que la plage dynamique complète est présente, les segments commençant et se terminant à n’importe quel point d’une plage de 32 bits peuvent être utilisés.

rlist_alloc (). La fonction d’allocation de liste de ressources (voir liste deux) bascule vers une liste chaînée à la recherche d’une région suffisamment grande à allouer. Pour ce faire, il utilise un seul pointeur indirectement double pour vérifier une entrée non allouée (un pointeur null rlist) ainsi qu’un pointeur sur le lien aller (au cas où nous aurions besoin de restructurer la liste). Lorsque nous trouvons une entrée de la taille appropriée, nous lui renvoyons éventuellement son emplacement et réduisons la taille de l’entrée de la liste de ressources (l’appelant peut ne pas le vouloir, mais cela garantit qu’il ne sera pas attribué par d’autres utilisateurs). Si nous réduisons la taille au point que l’élément est vide, nous libérons l’espace d’entrée de la liste et recâblons le pointeur de la liste précédente vers l’entrée suivante (s’il en existe une).

rlist_free (). La fonction libre de la liste des ressources (voir le Listing 2) est plus robuste, car elle doit coller les fragments ensemble et tenter de les simplifier (c’est-à-dire, représenter le moins d’entrées de liste). Cette routine tente en fait d’inverser les dégâts des nombreuses allocations non ordonnées et libère avant d’être appelée. Comme rlist_alloc (), il parcourt la liste avec un pointeur doublement indirect, mais il recherche un élément de liste dans lequel il peut fusionner ou un point dans la liste triée où il peut être inséré. Si une fusion se produit, cette fonction analyse la liste entière, en essayant de réduire les entrées adjacentes qui peuvent être fusionnées à la suite du remplissage d’un trou..

Leave a Reply

Your email address will not be published. Required fields are marked *