next up previous contents index
Weiter: Entwurf von Echtzeit- Hinauf: 18.8 Ein Platten-Treiber Zurück: Der Blockier-Task

Die Implementation des Paketes Platten-Treiber

 

Wir beginnen nun mit der sukzessiven Implementation des Platten-Treibers. Die Implementation des generischen Paketes für die doppelt verkettete Liste wollen wir dem Leser zum Teil zur Übung überlassen. Es sei allerdings darauf hingewiesen, daß die Realisierung des Paketes nicht notwendigerweise mit Pointerstrukturen erfolgen muß. Genauso denkbar ist, ein Array zu verwenden und dafür zu sorgen, daß, wenn die Liste voll ist, Tasks, die Leseanforderungen abgeben, vor dem Einfügen in die Liste blockiert werden. Wichtig ist jedoch, daß die Liste intern als geschütztes Objekt realisiert wird, z.B.:


package body Doppelt_verkettete_Liste is

type Listenelement;
type Zeiger_auf_Listenelement is access Listenelement;
type Listenelement is
record
Elmt: Element;
das_naechste: Zeiger_auf_Listenelement;
das_vorige: Zeiger_auf_Listenelement;
end record;

protected Geschuetzte_doppelt_verkettete_Liste is

function Liste_leer
return boolean;

procedure Geh_an_den_Anfang;

procedure Geh_an_das_Ende;

procedure Fuege_ein(
das_Element: Element);

function Geh_zu_naechst_groesserem_Element(
aktuelles_Element: Element)
return Element;

function Geh_zu_naechst_kleinerem_Element(
aktuelles_Element: Element)
return Element;

function Lies_aktuelles_Element
return Element;

procedure Loesche_aktuelles_Element;
private
Listen_Anfang: Zeiger_auf_Listenelement;
Listen_Ende: Zeiger_auf_Listenelement;
end Geschuetzte_doppelt_verkettete_Liste;

protected body Geschuetzte_doppelt_verkettete_Liste is separate;

function Liste_leer
return boolean is
begin
return Geschuetzte_doppelt_verkettete_Liste.Liste_leer;
end;

procedure Geh_an_den_Anfang is
begin
Geschuetzte_doppelt_verkettete_Liste.Geh_an_den_Anfang;
end;

procedure Geh_an_das_Ende is
begin
Geschuetzte_doppelt_verkettete_Liste.Geh_an_das_Ende;
end;

procedure Fuege_ein(
das_Element: Element) is
begin
Geschuetzte_doppelt_verkettete_Liste.Fuege_ein(das_Element);
end;

function Geh_zu_naechst_groesserem_Element(
aktuelles_Element: Element)
return Element is
begin
return
Geschuetzte_doppelt_verkettete_Liste.Geh_zu_naechst_groesserem_Element(
aktuelles_Element => aktuelles_Element);
end;

function Geh_zu_naechst_kleinerem_Element(
aktuelles_Element: Element)
return Element is
begin
return
Geschuetzte_doppelt_verkettete_Liste.Geh_zu_naechst_kleinerem_Element(
aktuelles_Element => aktuelles_Element);
end;

function Lies_aktuelles_Element
return Element is
begin
return Geschuetzte_doppelt_verkettete_Liste.Lies_aktuelles_Element;
end;

procedure Loesche_aktuelles_Element is
begin
Geschuetzte_doppelt_verkettete_Liste.Loesche_aktuelles_Element;
end;

end Doppelt_verkettete_Liste;

Die Implementation der geschützten Liste bleibt, wie oben angekündigt, dem Leser überlassen.

Wir beginnen also mit dem Paket Platten_Treiber, wobei wir von der Möglichkeit der getrennten Compilierung häufig Gebrauch machen werden:


with Doppelt_verkettete_Liste, Platten_Controller;

package body Platten_Treiber is

task type Blockiere is -derBlockier-Task
entry
Nimm_gelesene_Daten(
data: Platten_Defs.data);
entry
Gib_gelesene_Daten(
data: out Platten_Defs.data);
end Blockiere;

type Blockiere_Pointer is access Blockiere;

type Listen_element is -daswirdinderdoppeltverkettetenListeabgespeichert
record
Spur: natural;
Sektor: natural;
Blockierender_Task: Blockiere_Pointer;
end record;

task Platten_Treiber_Task is -derScheibenwischer-undSchutz-Task
entry Lies;
end PLatten_Treiber_Task;

function Kleiner( -die''<''-OperationfuerdieListenelemente
x: Listen_element;
y: Listen_element)
return boolean is separate;

package Die_doppelt_verkettete_Liste is
new Doppelt_verkettete_Liste(
Element => Listen_element,
"<" => Kleiner);

task body Blockiere is separate;

task body Platten_Treiber_Task is separate;

function Read(
Spur: natural;
Sektor: natural)
return Platten_Defs.data
is separate;

end Platten_Treiber;

Die eigentliche Implementation der Operation Read haben wir aufgeschoben. Am Beginn des Paket-Body's findet man die Spezifikation des Blockier-Tasks und des Listenelementes, danach folgen die Deklaration des ,,Scheibenwischer-Tasks``, im Paket Platten_Treiber_Task genannt, der geforderten Ordnungsrelation für die doppelt verkettete Liste sowie die Instantiierung dieser Liste. Die Implementation der beiden Task-Bodies sind ebenfalls wie die Implementation der Funktion Kleiner aufgeschoben worden und können getrennt übersetzt werden.

Als nächstes wollen wir die Implementation der Funktion Read  ins Auge fassen.


separate(Platten_Treiber)

function Read(
Spur: natural;
Sektor: natural)
return Platten_Defs.data
is

out_data: Platten_Defs.data;

mein_blockierender_Task: Blockiere_Pointer := new Blockiere;
-hierwirdderBlockier-Taskkreiert

Element: Listen_element := (
Spur => Spur,
Sektor => Sektor,
Blockierender_Task => mein_blockierender_Task);
-daseinzufuegendeListenelement

begin

Die_doppelt_verkettete_Liste.Fuege_ein(
das_Element => Element);
-EinfuegendesListenelementes

Platten_Treiber_Task.Lies;
-Leseanforderunganmelden

mein_blockierender_Task.Gib_gelesene_Daten(
data => out_data);
-hierblockiertderrufendeTaskbisdiegelesenen
-Datenangeliefertwerden.

return out_data;

end Read;

Im Deklarationsteil der Funktion wird der Blockier-Task kreiert und das einzufügende Listenelement angelegt und richtig initialisiert. Im Anweisungsteil wird zuerst das Listenelement in die Liste eingefügt, anschließend blockiert der rufende Task beim Aufruf des Entry's des Blockier-Tasks, bis die geforderten Daten gelesen und zurückgeliefert worden sind.

Bevor wir uns der Implementation der Tasks Blockiere und Platten_Treiber_Task zuwenden, wollen wir noch die Implementation der Funktion Kleiner   behandeln.


separate(Platten_Treiber)

function Kleiner(
x: Listen_element;
y: Listen_element)
return boolean
is
-
-DerimListenelementvorhandenePointeraufdenBlockier-Task
-istfuerdenVergleichzweierListenelementeirrelevant.
-NurSpur-undSektornummersindinteressant.
-EinListenelementistalsokleineralseinanderes,wenn
-entwederdieSpurnummerkleineristoderbeigleicherSpur
-dieSektornummerkleinerist.
-
begin
return (x.Spur < y.Spur) or else
((x.Spur = y.Spur) and (x.Sektor < y.Sektor));
end Kleiner;

Nun aber zu den Tasks! Zuerst implementieren wir den Blockier-Task  :


separate(Platten_Treiber)

task body Blockiere is
gelesene_Daten: Platten_Defs.data; -zumZwischenspeicherndergelesenenDaten
begin
accept -hierwartetderTaskaufdasEintreffendergelesenenDaten
Nimm_gelesene_Daten(
data: Platten_Defs.data) do
gelesene_Daten := data; -hierwerdensiezwischengespeichert
end Nimm_gelesene_Daten;
accept -undhierwartetderTaskaufdasAbholenderDaten
Gib_gelesene_Daten(
data: out Platten_Defs.data) do
data := gelesene_Daten;
end Gib_gelesene_Daten;
end Blockiere;

Es bleibt also noch, den Platten_Treiber_Task   zu implementieren:


with Platten_Controller;

separate(Platten_Treiber)

task body Platten_Treiber_Task is

type Richtung is (aufwaerts, abwaerts);
-Richtungen,indenensichderLese-Schreibkopfbewegenkann
akt_Richtung: Richtung := aufwaerts;
-Richtung,indersichderLese-Schreibkopfgeradebewegt
aktuelles_Element: Listen_element := (
Spur => 0,
Sektor => 0,
Blockierender_Task => null);
-Listenelement,daszuletztgelesenwurde
gelesene_Daten: Platten_Defs.data;
-zuletztgeleseneDaten

function Suche_aktuelles_Element(
momentan_aktuelles_Element: Listen_element)
return Listen_Element
is separate;
-suchtdasListenelement,dessenSpur/Sektor
-alsnaechstegelesenwerdensollen
begin
loop
accept -fuegtersteAnforderungein
Lies do
null;
end Lies;
loop
select
accept
-fuegtdiefolgendenAnforderungenein,
-bisalleAuftraegeerledigtwurden.
Lies do
null;
end Lies;
else
-fallsgeradekeinElementeingefuegtwird,
-erledigeeinenLeseauftrag
if Die_doppelt_verkettete_Liste.Liste_leer then
-fallskeineAnforderungmehrvorliegt,
-verlassedieinnereSchleifeundwartewieder
-aufdenerstenLeseauftrag
exit;
else
-dieListeistnichtleer,also:
aktuelles_Element := -suchenaechstesListenelement
Suche_aktuelles_Element(
momentan_aktuelles_Element => aktuelles_Element);
-liesDatenmittelsAufrufdesControllers
gelesene_Daten :=
Platten_Controller.Positioniere_Lese_Schreibkopf_und_lies(
Spur => aktuelles_Element.Spur,
Sektor => aktuelles_Element.Sektor);
-loescheListenelement
Die_doppelt_verkettete_Liste.Loesche_aktuelles_Element;
-uebergibDatenanBlockiere-Task
aktuelles_Element.Blockierender_Task.Nimm_gelesene_Daten(
data => gelesene_Daten);
end if;
end select;
end loop;
end loop;
end Platten_Treiber_Task;

Eine Beschreibung der Implementation des Tasks findet sich in den Kommentaren des Codes. Wir wollen aber explizit darauf hinweisen, warum hier eine geschachtelte Loop-Anweisung notwendig ist: Angenommen, wir hätten die äußere Schleife weggelassen und die Exit-Anweisung im Then-Zweig der If-Anweisung durch null ersetzt. Dann läge im Prinzip dieselbe Funktionalität vor, der wesentliche Unterschied ist jedoch, daß sich der Task, wenn keine Leseanforderung ansteht und sich kein Leseauftrag in der Liste befindet, in einer Endlosschleife befindet, in der er ständig die Select-Anweisung ausführt. Dieses Verhalten ist auch unter dem Begriff Polling  bekannt, und sollte tunlichst vermieden werden. Mit der geschickten Anordnung unserer beiden geschachtelten Schleifen ist uns das gelungen.

Als letztes bleibt also noch die Funktion Suche_aktuelles_Element   zu implementieren:


separate(Platten_Treiber)

function Suche_aktuelles_Element(
momentan_aktuelles_Element: Listen_element)
return Listen_Element
is
package Liste renames Die_doppelt_verkettete_Liste;
function "<"(x,y: Listen_element) return boolean renames Kleiner;
akt_El: Listen_element;
begin
if akt_Richtung = aufwaerts then
begin
return
Liste.Geh_zu_naechst_groesserem_Element(
aktuelles_Element => momentan_aktuelles_Element);
exception
when Liste.Ende_der_Liste =>
-esgibtkeingroesseres,daheraendernwirdieRichtung
akt_Richtung := abwaerts;
return
Liste.Geh_zu_naechst_kleinerem_Element(
aktuelles_Element => momentan_aktuelles_Element);
end;
else -akt_Richtung=abwaerts
begin
return
Liste.Geh_zu_naechst_kleinerem_Element(
aktuelles_Element => momentan_aktuelles_Element);
exception
when Liste.Anfang_der_Liste =>
-esgibtkeinkleineres,daheraendernwirdieRichtung
akt_Richtung := aufwaerts;
return
Liste.Geh_zu_naechst_groesserem_Element(
aktuelles_Element => momentan_aktuelles_Element);
end;
end if;
end Suche_aktuelles_Element;

Damit ist die Implementation des Platten-Treibers abgeschlossen. Wir haben in diesem Kapitel viel über die diffizilen Probleme bei der Task-Kommunikation und Task-Synchronisation gelernt. Das Beispiel als solches ist natürlich eine grobe Vereinfachung eines wirklichen Platten-Treibers, aber die wesentlichen Probleme wurden aufgezeigt. Welche Probleme bei interrupt-gesteuerten Geräte-Treibern auftauchen, werden wir im Kapitel 19 sehen.



next up previous contents index
Weiter: Entwurf von Echtzeit- Hinauf: 18.8 Ein Platten-Treiber Zurück: Der Blockier-Task

Johann Blieberger
Wed Feb 11 09:58:52 MET 1998