2.2.1 The Naming Model
Es ist hilfreich, ein Modell zu haben, wie Namen mit bestimmten Objekten verbunden sind. Ein Systemdesigner erstellt ein Benennungsschema, das aus drei Elementen besteht. Das erste Element ist ein Namensraum, der ein Alphabet von Symbolen zusammen mit Syntaxregeln umfasst, die angeben, welche Namen zulässig sind. Das zweite Element ist ein Namenszuordnungsalgorithmus, der einige (nicht notwendigerweise alle) Namen des Namensraums mit einigen (wiederum nicht notwendigerweise allen) Werten in einem Werteuniversum assoziiert, das das dritte und letzte Element des Namensschemas ist. Ein Wert kann ein Objekt sein oder ein anderer Name aus dem ursprünglichen Namensraum oder aus einem anderen Namensraum. Eine Zuordnung von Name zu Wert ist ein Beispiel für eine Bindung, und wenn eine solche Zuordnung existiert, wird der Name als an den Wert gebunden bezeichnet. Abbildung 2.10 veranschaulicht dies.
In den meisten Systemen sind typischerweise mehrere verschiedene Namensschemata gleichzeitig in Betrieb. So kann ein System beispielsweise ein Namensschema für E-Mail-Postfachnamen, ein zweites Namensschema für Internet-Hosts, ein drittes für Dateien und ein viertes für virtuelle Speicheradressen verwenden. Wenn ein Programminterpreter auf einen Namen stößt, muss er wissen, welches Benennungsschema er aufrufen soll. Die Umgebung, in der der Name verwendet wird, liefert normalerweise genügend Informationen, um das Namensschema zu identifizieren. In einem Anwendungsprogramm weiß der Programmautor beispielsweise, dass das Programm erwarten sollte, dass Dateinamen nur vom Dateisystem und Internet-Hostnamen nur von einem Netzwerkdienst interpretiert werden.
Der Interpreter, der auf den Namen stößt, führt den Namenszuordnungsalgorithmus des entsprechenden Namensschemas aus. Der Name-Mapping-Algorithmus löst den Namen auf, d.h. er ermittelt den zugehörigen Wert und gibt ihn zurück (aus diesem Grund wird der Name-Mapping-Algorithmus auch Resolver genannt). Der Name-Mapping-Algorithmus wird in der Regel durch einen zusätzlichen Parameter, den so genannten Kontext, gesteuert. Für ein bestimmtes Benennungsschema kann es viele verschiedene Kontexte geben, und ein einziger Name des Namensraums kann auf verschiedene Werte abgebildet werden, wenn der Resolver verschiedene Kontexte verwendet. Wenn beispielsweise eine Person im normalen Sprachgebrauch auf die Namen “du”, “hier” oder “Alice” verweist, hängt die Bedeutung jedes dieser Namen von dem Kontext ab, in dem die Person ihn ausspricht. Andererseits gibt es Bezeichnungsschemata mit nur einem Kontext. Solche Bezeichnungsschemata bieten so genannte universelle Namensräume und haben die angenehme Eigenschaft, dass ein Name innerhalb dieses Bezeichnungsschemas immer dieselbe Bedeutung hat, unabhängig davon, wer ihn verwendet. In den Vereinigten Staaten bilden beispielsweise die Sozialversicherungsnummern, die staatliche Renten- und Steuerkonten identifizieren, einen universellen Namensraum. Wenn es mehr als einen Kontext gibt, kann der Interpreter dem Resolver mitteilen, welchen er verwenden soll, oder der Resolver kann einen Standardkontext verwenden.
Wir können das Namensmodell zusammenfassen, indem wir die folgende konzeptionelle Operation auf Namen definieren:
Wert ← auflösen (Name, Kontext)
Wenn ein Interpreter auf einen Namen in einem Objekt stößt, findet er zunächst heraus, um welches Namensschema es sich handelt und welche Version von resolve er daher aufrufen sollte. Er identifiziert dann einen geeigneten Kontext, löst den Namen in diesem Kontext auf und ersetzt den Namen durch den aufgelösten Wert, während er die Interpretation fortsetzt. Die Variable context teilt resolve mit, welchen Kontext es verwenden soll. Diese Variable enthält einen Namen, der als Kontextreferenz bekannt ist.
In einem Prozessor sind die Registernummern Namen. In einem einfachen Prozessor sind sowohl die Menge der Registernamen als auch die Register, an die diese Namen gebunden sind, zur Entwurfszeit festgelegt. In den meisten anderen Systemen, die Namen verwenden (einschließlich des Registerbenennungsschemas einiger Hochleistungsprozessoren), ist es möglich, neue Bindungen zu erstellen und alte zu löschen, den Namensraum aufzuzählen, um eine Liste der vorhandenen Bindungen zu erhalten, und zwei Namen zu vergleichen. Für diese Zwecke definieren wir vier weitere konzeptionelle Operationen:
status ← bind (name, value, context)
status ← unbind (name, context)
list ← enumerate (context)
result ← compare (name1, name2)
Die erste Operation ändert den Kontext, indem sie eine neue Bindung hinzufügt; Das Statusergebnis meldet, ob die Änderung erfolgreich war oder nicht (sie kann fehlschlagen, wenn der vorgeschlagene Name die Syntaxregeln des Namensraums verletzt). Nach einem erfolgreichen Aufruf von bind gibt resolve den neuen Wert für name zurück.*Die zweite Operation, unbind, entfernt eine bestehende Bindung aus dem Kontext, wobei der Status wiederum Erfolg oder Misserfolg meldet (vielleicht weil es keine solche bestehende Bindung gab). Nach einem erfolgreichen Aufruf von unbind gibt resolve nicht mehr den Wert für name zurück. Die Operationen bind und unbind erlauben die Verwendung von Namen, um Verbindungen zwischen Objekten herzustellen und diese Verbindungen später zu ändern. Ein Designer eines Objekts kann, indem er einen Namen verwendet, um auf ein Komponentenobjekt zu verweisen, das Objekt auswählen, an das dieser Name entweder zu diesem Zeitpunkt oder zu einem späteren Zeitpunkt gebunden ist, indem er bind aufruft, und eine Bindung, die nicht mehr angemessen ist, durch den Aufruf von unbind aufheben, ohne das Objekt, das den Namen verwendet, zu verändern. Diese Fähigkeit, Bindungen zu verzögern und zu ändern, ist ein mächtiges Werkzeug, das bei der Entwicklung fast aller Systeme eingesetzt wird. Einige Namensimplementierungen bieten eine enumerate-Operation, die eine Liste aller Namen zurückgibt, die im Kontext aufgelöst werden können. Einige Implementierungen von enumerate können auch eine Liste aller derzeit im Kontext gebundenen Werte zurückgeben. Die Operation compare schließlich meldet (true oder false), ob name1 mit name2 identisch ist oder nicht. Die Bedeutung von “same” ist eine interessante Frage, die in Abschnitt 2.2.5 behandelt wird und möglicherweise die Angabe zusätzlicher Kontextargumente erfordert.
Die verschiedenen Benennungsschemata haben unterschiedliche Regeln für die Eindeutigkeit von Name-zu-Wert-Zuordnungen. Einige Benennungsschemata haben die Regel, dass ein Name genau einem Wert in einem bestimmten Kontext zugeordnet werden muss und ein Wert nur einen Namen haben darf, während in anderen Benennungsschemata ein Name mehreren Werten zugeordnet werden kann oder ein Wert mehrere Namen haben kann, sogar im gleichen Kontext. Eine andere Art von Eindeutigkeitsregel ist die eines eindeutigen Bezeichner-Namensraums, der eine Reihe von Namen bereitstellt, die während der Lebensdauer des Namensraums nie wieder verwendet werden und, einmal gebunden, immer an denselben Wert gebunden bleiben. Ein solcher Name wird als stabil gebunden bezeichnet. Wenn ein eindeutiger Bezeichner-Namensraum auch die Regel hat, dass ein Wert nur einen Namen haben kann, werden die eindeutigen Namen nützlich, um Objekte über einen langen Zeitraum hinweg zu verfolgen, um Referenzen zu vergleichen, um zu sehen, ob sie sich auf dasselbe Objekt beziehen, und um mehrere Kopien in Systemen zu koordinieren, in denen Objekte aus Leistungs- oder Zuverlässigkeitsgründen repliziert werden. Beispielsweise stellt die Kontonummer eines Kunden in den meisten Abrechnungssystemen einen eindeutigen Bezeichner-Namensraum dar. Die Kontonummer bezieht sich immer auf dasselbe Kundenkonto, solange dieses existiert, auch wenn sich die Adresse, die Telefonnummer oder sogar der Name des Kunden ändert. Wenn das Konto eines Kunden gelöscht wird, wird die Kontonummer dieses Kunden nicht eines Tages für ein anderes Kundenkonto wiederverwendet. Benannte Felder innerhalb des Kontos, wie z. B. der fällige Saldo, können sich von Zeit zu Zeit ändern, aber die Bindung zwischen der Kundenkontonummer und dem Konto selbst ist stabil.
Der Algorithmus für die Namenszuordnung und ein einzelner Kontext bilden nicht unbedingt alle Namen des Namensraums auf Werte ab. So kann ein mögliches Ergebnis der Ausführung von resolve ein nicht gefundenes Ergebnis sein, das resolve dem Aufrufer entweder als reservierten Wert oder als Ausnahme mitteilen kann. Erlaubt das Namensschema hingegen, dass ein Name auf mehrere Werte abgebildet werden kann, kann ein mögliches Ergebnis eine Liste von Werten sein. In diesem Fall kann die Operation unbind ein zusätzliches Argument erfordern, das angibt, welcher Wert unbind sein soll. Schließlich bieten einige Namensschemata die Möglichkeit der umgekehrten Suche, d.h. ein Aufrufer kann einen Wert als Argument an den Namenszuordnungsalgorithmus übergeben und herausfinden, welche(r) Name(n) an diesen Wert gebunden ist/sind.
Abbildung 2.10 veranschaulicht das Namensmodell und zeigt einen Namensraum, das entsprechende Werteuniversum, einen Namenszuordnungsalgorithmus und einen Kontext, der den Namenszuordnungsalgorithmus kontrolliert.
In der Praxis trifft man auf drei häufig verwendete Namenszuordnungsalgorithmen:
Table lookup
■
Recursive lookup
■
Multiple lookup
Die häufigste Implementierung eines Kontexts ist eine Tabelle mit {Name, Wert}-Paaren. Wenn die Implementierung eines Kontexts eine Tabelle ist, ist der Algorithmus für die Namenszuordnung nur eine Suche nach dem Namen in dieser Tabelle. Die Tabelle selbst kann komplex sein und Hashing oder B-Trees beinhalten, aber die Grundidee ist immer noch dieselbe. Das Binden eines neuen Namens an einen Wert besteht darin, das Paar {Name, Wert} zur Tabelle hinzuzufügen. Abbildung 2.11 veranschaulicht diese übliche Implementierung des Namensgebungsmodells. Für jeden Kontext gibt es eine solche Tabelle, und verschiedene Kontexte können verschiedene Bindungen für denselben Namen enthalten.
In der realen Welt gibt es zahlreiche Beispiele sowohl für das allgemeine Namensmodell als auch für die Implementierung von Table-Lookup:
Ein Telefonbuch ist ein Table-Lookup-Kontext, der Namen von Personen und Organisationen mit Telefonnummern verbindet. Wie im Beispiel des Datenkommunikationsnetzes sind die Telefonnummern selbst Namen, die von der Telefongesellschaft mit Hilfe eines Namenszuordnungsalgorithmus, der Vorwahlen, Vermittlungsstellen und physische Schaltanlagen einbezieht, in physische Anschlüsse aufgelöst werden. Die Telefonbücher für Boston und für San Francisco sind zwei Kontexte desselben Namensschemas; ein bestimmter Name kann in beiden Telefonbüchern auftauchen, aber wenn dies der Fall ist, ist er wahrscheinlich an verschiedene Telefonnummern gebunden.
Kleine Ganzzahlen benennen die Register eines Prozessors. Der Wert ist das Register selbst, und die Zuordnung von Name zu Wert wird durch Verdrahtung erreicht.
Speicherzellen werden in ähnlicher Weise mit Zahlen benannt, die Adressen genannt werden, und die Zuordnung von Name zu Wert wird wiederum durch Verdrahtung erreicht. In Kapitel 5 wird ein Mechanismus zur Umbenennung von Adressen beschrieben, der als virtueller Speicher bekannt ist und Blöcke virtueller Adressen an Blöcke zusammenhängender Speicherzellen bindet. Wenn ein System mehrere virtuelle Speicher implementiert, ist jeder virtuelle Speicher ein eigener Kontext; eine bestimmte Adresse kann sich auf eine andere Speicherzelle in jedem virtuellen Speicher beziehen. In diesem Fall kann dieselbe Speicherzelle dieselben (oder unterschiedliche) Adressen in verschiedenen virtuellen Speichern haben, wie durch die Bindungen bestimmt.
Ein typisches Computerdateisystem verwendet mehrere Schichten von Namen und Kontexten: Plattensektoren, Plattenpartitionen, Dateien und Verzeichnisse sind allesamt benannte Objekte. Verzeichnisse sind Beispiele für Table-Lookup-Kontexte. Ein bestimmter Dateiname kann in mehreren verschiedenen Verzeichnissen vorkommen, die entweder an dieselben oder an unterschiedliche Dateien gebunden sind. In Abschnitt 2.5 wird eine Fallstudie über die Namensgebung im Unix-Dateisystem vorgestellt.
Computer werden an Orten, die als Netzanschlußpunkte bekannt sind, an Datenkommunikationsnetze angeschlossen. Netzwerkanschlußpunkte werden normalerweise nach zwei verschiedenen Namensschemata benannt. Das erste, das innerhalb des Netzes verwendet wird, beinhaltet einen Namensraum, der aus Zahlen in einem Feld mit fester Länge besteht. Diese Namen sind, manchmal dauerhaft und manchmal nur kurz, an physische Ein- und Ausgangspunkte des Netzes gebunden. Ein zweites Namensschema, das von den Kunden des Netzes verwendet wird, bildet einen benutzerfreundlicheren universellen Namensraum aus Zeichenketten auf Namen des ersten Namensraums ab. Abschnitt 4.4 ist eine Fallstudie über das Domain Name System, das eine benutzerfreundliche Benennung von Anknüpfungspunkten für das Internet bietet.
Ein Programmierer identifiziert Prozedurvariablen durch Namen, und jede Aktivierung der Prozedur bietet einen bestimmten Kontext, in dem die meisten dieser Namen aufgelöst werden. Einige Namen, die als “statische” oder “globale Namen” bezeichnet werden, können stattdessen in einem Kontext aufgelöst werden, der zwischen den Aktivierungen oder zwischen verschiedenen Prozeduren gemeinsam genutzt wird. Wenn eine Prozedur kompiliert wird, können einige der ursprünglichen benutzerfreundlichen Namen von Variablen durch ganzzahlige Bezeichner ersetzt werden, die für eine Maschine bequemer zu handhaben sind, aber das Benennungsmodell bleibt bestehen.
Ein Uniform Resource Locator (URL) des World Wide Web wird durch einen relativ komplizierten Algorithmus auf eine bestimmte Webseite abgebildet, der die URL in mehrere Bestandteile zerlegt und die Teile unter Verwendung verschiedener Benennungsschemata auflöst; das Ergebnis identifiziert schließlich eine bestimmte Webseite. Abschnitt 3.2 ist eine Fallstudie zu diesem Benennungsschema.
Ein Kundenabrechnungssystem verwaltet normalerweise mindestens zwei Arten von Namen für jedes Kundenkonto. Die Kontonummer benennt das Konto in einem eindeutigen Bezeichner-Namensraum, aber es gibt auch einen eigenen Namensraum mit persönlichen Namen, die ebenfalls zur Identifizierung des Kontos verwendet werden können. Beide Namen werden in der Regel von einem Datenbanksystem auf Kontodatensätze abgebildet, so dass Konten entweder über die Kontonummer oder über den persönlichen Namen abgerufen werden können.
Diese Beispiele verdeutlichen auch einen Unterschied zwischen “Benennung” und Bindung. Einige, aber nicht alle Kontexte “benennen” Dinge in dem Sinne, dass sie einen Namen einem Objekt zuordnen, von dem man gemeinhin annimmt, dass es diesen Namen trägt. So “benennt” das Telefonbuch weder Personen noch Telefonleitungen. An anderer Stelle gibt es Kontexte, die Namen an Personen und Telefonnummern an bestimmte physische Telefone binden. Das Telefonbuch bindet die Namen von Personen an die Namen von Telefonen.