WEBVTT

00:10.220 --> 00:14.660
Herzlich willkommen zur dritten Übung, nein, zur vierten Übung.

00:16.880 --> 00:19.480
Wir haben noch einen Teil von der dritten Übung, den wir noch ein

00:19.480 --> 00:20.440
bisschen abarbeiten müssen.

00:20.760 --> 00:24.080
Deswegen hier die Fortsetzung der dritten Übung plus danach gleich die

00:24.080 --> 00:24.620
vierte Übung.

00:26.720 --> 00:29.280
Wir waren letztes Mal stehen geblieben beim Befehlsatz von MIPS.

00:30.640 --> 00:32.560
Ein paar Befehle haben uns aber noch gefehlt.

00:33.340 --> 00:37.140
Konkret Lade- und Speicherbefehle haben wir hier zum Beispiel LA für

00:37.140 --> 00:38.040
Load Address.

00:39.400 --> 00:42.080
Zielregister und der Immediate-Wert für eine Adresse.

00:43.740 --> 00:46.720
Dann gibt es verschiedene Befehle zum Laden und Speichern von Bytes,

00:46.940 --> 00:49.800
Halbwörtern und Wörtern und Doppelwörtern.

00:50.460 --> 00:56.500
Konkret hier Load Byte, Load Byte Unsigned, Store Byte, Load Halfword,

00:56.660 --> 01:00.640
Store Halfword, Load Halfword Unsigned.

01:01.180 --> 01:02.080
Load Word, Load Double.

01:03.920 --> 01:06.880
Dann gibt es noch Befehle zum Laden und Speichern von Coprocessor

01:06.880 --> 01:07.480
-Registern.

01:07.560 --> 01:14.460
Konkret hier LWC für Coprocessor, also Load Word Coprocessor und ein

01:14.460 --> 01:14.740
Z.

01:15.120 --> 01:21.140
Z ist hier repräsentativ für eine Zahl zwischen 0 und 3 und hier kann

01:21.140 --> 01:23.080
man die verschiedenen Coprocessoren ansprechen.

01:23.080 --> 01:26.640
Das heißt, wenn ich jetzt hier diesen Befehl ausschreiben würde für

01:26.640 --> 01:28.040
die FPU,

01:32.400 --> 01:38.400
schwarz, dann wäre das der LWC1.

01:45.590 --> 01:50.830
Wir haben ja gesagt, Wörter müssen ausgerichtet sein im Speicher.

01:50.830 --> 01:53.830
Sie müssen an einer Stelle stehen, die durch 2 hoch N, in dem Fall 2

01:53.830 --> 01:55.810
hoch 2, adressierbar ist.

01:56.130 --> 02:01.030
Es gibt aber auch Befehle, um Daten, die unausgerichtet im Speicher

02:01.030 --> 02:04.350
liegen, lesen oder schreiben zu können.

02:05.490 --> 02:10.290
Dafür gibt es zum Beispiel Load Word Left, Load Word Right.

02:10.410 --> 02:16.770
Das heißt, ich kann hier von dieser Adresse das 32-Bit-Wort nehmen und

02:16.770 --> 02:19.870
auf die linke Seite, oder das 16-Bit-Wort in dem Fall, nehmen und auf

02:19.870 --> 02:22.990
die linke Seite meines Registers schreiben oder auf die rechte Seite.

02:24.270 --> 02:29.970
Dann gibt es aber noch spezielle Befehle, wie zum Beispiel ULH für

02:29.970 --> 02:35.410
Unaligned Load Half Word, also ein halbes Wortladen, das an einer

02:35.410 --> 02:41.090
nicht -aligned Stelle im Speicher stehen kann, aber nicht muss, von

02:41.090 --> 02:43.090
der Adresse in dieses Register.

02:43.910 --> 02:47.530
Hier dasselbe nochmal mit Unsigned, also Unaligned Load Half Word.

02:47.830 --> 02:55.790
Unsigned, in dem Fall, dass man das Wort dort als positive Zahl

02:55.790 --> 02:56.630
interpretieren würde.

02:58.070 --> 03:00.290
Und Unaligned Load Word natürlich nochmal.

03:02.470 --> 03:03.970
Wie sehen diese Befehle aus?

03:04.030 --> 03:07.790
Wir sehen hier, das ist der R-Typ, das heißt, wir haben hier Register.

03:07.790 --> 03:11.270
Wir haben Load Word, Register, Adresse.

03:11.910 --> 03:18.090
Diese Adresse ist ein 16-Bit-Immediate, das die Adresse des 32-Bit

03:18.090 --> 03:20.510
-Worts angibt, das geladen werden soll.

03:20.910 --> 03:27.110
Und zwar geladen im Sinne von, lade das Wort aus dem Speicher in das

03:27.110 --> 03:27.530
Register.

03:29.270 --> 03:30.410
Ja, wichtig.

03:30.930 --> 03:35.550
Also, wir laden in das Register, wir speichern vom Register in den

03:35.550 --> 03:35.890
Speicher.

03:36.770 --> 03:37.490
Haben wir dann hier.

03:39.050 --> 03:43.290
Also, das Immediate ist die Adresse und diese Adresse gibt uns dann

03:43.290 --> 03:44.830
die Stelle des 32-Bit-Wortes.

03:45.250 --> 03:51.210
In dem Fall soll hier geschrieben werden, das heißt, mein Datum ist in

03:51.210 --> 03:53.210
meinem Register und wird in den Speicher geschrieben.

03:53.210 --> 03:57.010
Und hier lade ich aus dem Speicher in mein Register.

03:57.470 --> 04:00.070
Also, ich schreibe eigentlich in mein Register und hier lese ich aus

04:00.070 --> 04:00.690
meinem Register.

04:01.330 --> 04:06.010
Das wird später nochmal wichtig werden, wenn wir uns Abhängigkeiten

04:06.010 --> 04:09.610
anschauen, wo Register gelesen oder geschrieben werden.

04:11.050 --> 04:16.330
Hier findet dann meistens nämlich die Verwirrung, ist nämlich ein

04:16.330 --> 04:19.990
bisschen Verwirrung da, dass man immer denkt, LW liest ja von einem

04:19.990 --> 04:22.850
Register und SW speichert in einem Register.

04:23.090 --> 04:23.910
Das ist genau andersrum.

04:24.250 --> 04:27.990
Ich lade aus dem Speicher und schreibe in das Register hier.

04:28.690 --> 04:29.250
Das ist wichtig.

04:33.510 --> 04:37.730
Dann schauen wir uns sehr gut an, wo ich zum Beispiel ein Array habe

04:37.730 --> 04:40.770
und auf einen bestimmten Index im Array zugreifen möchte.

04:40.770 --> 04:45.170
Hier lade ich aus meinem Array an der achten Stelle, also das achte

04:45.170 --> 04:47.610
beziehungsweise, wenn man von null zählt, das neunte Element.

04:50.010 --> 04:52.650
Addiere eine Variable drauf und will das wieder an der gleichen Stelle

04:52.650 --> 04:53.190
speichern.

04:53.670 --> 04:55.230
Wie würde das ganze MIPS-Code aussehen?

04:55.730 --> 04:58.890
Ich hätte ein Load Word T0.

04:59.930 --> 05:09.810
Gehen wir mal davon aus, dass S3 die Anfangsadresse meines Arrays ist.

05:09.810 --> 05:13.890
Und T0 ist ein temporäres Register hier.

05:14.230 --> 05:17.550
Das heißt, ich lade aus meinem Array plus Offset 32.

05:18.490 --> 05:19.830
Und warum hier 32?

05:22.230 --> 05:27.950
Wir haben gesagt, wir adressieren Bytes, aber wir arbeiten ja mit

05:27.950 --> 05:28.310
Wörtern.

05:29.490 --> 05:34.070
Das heißt, ich interpretiere hier A an der Stelle 8 als Wort oder den

05:34.070 --> 05:38.430
Array A als Array mitstehend aus Wörtern.

05:39.790 --> 05:47.250
Will aber Bytes adressieren, das heißt, ich mache hier 8 mal 4 und

05:49.480 --> 05:50.520
komme dann auf die 32.

05:52.060 --> 05:58.400
Also, ich lade aus dem Speicher, also aus meinem Array an der Position

05:58.400 --> 06:01.460
32, das Wort in mein temporäres Register T0.

06:02.140 --> 06:10.700
Ich addiere auf T0 S2, was hier mein H repräsentiert, speichere das

06:10.700 --> 06:14.040
Ergebnis wieder im gleichen temporären Register und kann dann wieder

06:14.040 --> 06:16.820
das Ergebnis zurückschreiben an die gleiche Speicherstelle.

06:23.080 --> 06:28.300
So, dann kann ich mit LB und LBU Bytes adressieren.

06:28.300 --> 06:32.700
Nochmal, wir arbeiten mit Wörtern, haben aber Zugriff auf einzelne

06:32.700 --> 06:33.080
Bytes.

06:33.660 --> 06:36.800
Die Frage ist jetzt natürlich, warum diese Us immer?

06:36.980 --> 06:39.800
Wir haben ja schon verschiedene Befehle gesehen mit Add, Add

06:39.800 --> 06:42.500
Immediate, Add Immediate Unsigned und so weiter.

06:43.540 --> 06:47.340
Und hier wollen wir uns den Unterschied deutlich machen zwischen Load

06:47.340 --> 06:50.820
Byte regulär und einem Load Byte Unsigned.

06:52.040 --> 06:53.880
Angenommen, wir haben jetzt dieses Codefragment.

06:55.160 --> 06:59.040
Hier ist unser Datenblock, hier ist unser Datenblock, hier ist unser

06:59.040 --> 07:00.980
Programmblock, also unser Textblock.

07:01.980 --> 07:04.620
Wir haben hier eine Variable, die steht an der Adresse result.

07:05.800 --> 07:10.300
Und es handelt sich hier um ein Wort als 0x89abcdef.

07:13.020 --> 07:16.880
Hier meine Assembler Direktive global main und ich mache jetzt hier

07:16.880 --> 07:22.340
ein Load Byte Unsigned nach A0 von result.

07:23.240 --> 07:25.440
Und die Frage ist jetzt, was ist in A0 drin?

07:27.560 --> 07:30.060
Result ist die Adresse, die hier anfängt.

07:31.200 --> 07:34.000
Und wenn ich das erste Byte hier nehme, habe ich hier die 0x89.

07:38.080 --> 07:42.960
Also 4 Bytes, 1 Byte.

07:44.500 --> 07:48.700
Ich lade das erste Byte an dieser Adresse, das heißt ich habe Zugriff

07:48.700 --> 07:49.340
auf die 0x89.

07:50.800 --> 07:53.200
Und ich habe jetzt hier ein LBU verwendet.

07:53.420 --> 07:59.660
Das heißt, mein 32-Bit-Register A0 enthält jetzt hier dieses Byte

07:59.660 --> 08:00.300
0x89.

08:00.300 --> 08:04.080
Es wird aber hier an

08:09.480 --> 08:11.900
der niederwärtigsten Stelle eingefügt.

08:15.350 --> 08:19.070
Wenn ich dasselbe machen würde mit LB statt LBU.

08:20.650 --> 08:21.970
Jemand eine Ahnung, was passieren könnte?

08:27.880 --> 08:28.440
Eine Vorstellung?

08:30.740 --> 08:31.100
Nichts?

08:33.760 --> 08:35.320
Also passiert folgendes.

08:39.920 --> 08:40.720
0x89

08:43.820 --> 08:52.340
ist binär gesehen eine 1001001.

08:53.660 --> 08:59.620
Wenn ich das jetzt als Zweikomplement interpretiere, dann habe ich

08:59.620 --> 09:00.700
hier eine führende 1.

09:02.420 --> 09:04.320
Das heißt, meine Zahl ist negativ.

09:05.440 --> 09:09.660
Und wir haben ja gelernt bei der Zahlendarstellung, negative Zahlen im

09:09.660 --> 09:11.960
Zweikomplement werden vorne aufgefüllt mit Einsen.

09:13.360 --> 09:23.540
Das heißt, ich habe dann hier binär meine 8, hier meine 9 und ich

09:23.540 --> 09:24.800
fülle hier vorne auf mit Einsen.

09:26.740 --> 09:29.100
Und diese Einsen fassen sich natürlich zusammen

09:33.630 --> 09:39.590
in 4 Blöcken und die geben jeweils Fs.

09:44.810 --> 09:45.670
Soweit so klar?

09:46.970 --> 09:47.930
Wer hat das nicht verstanden?

09:50.450 --> 09:51.510
Ja, alle haben es verstanden.

09:51.770 --> 09:52.190
Sehr gut.

09:53.570 --> 09:53.870
Frage?

09:55.810 --> 09:56.610
Ein Räuspern?

09:56.750 --> 09:57.470
Habe ich mich verhört?

10:19.200 --> 10:19.720
Also,

10:23.280 --> 10:24.180
wir laden hier ein Byte.

10:24.260 --> 10:25.060
Byte sind 8 mit.

10:25.780 --> 10:28.620
Dieses Byte wird als Zweikomplement interpretiert.

10:29.420 --> 10:34.200
In der Zweikomplementinterpretation dieses Bytes, 8, 9, habe ich hier

10:34.200 --> 10:38.060
eine Binärdarstellung von 100001001.

10:39.560 --> 10:41.980
Beim Zweikomplement haben wir gesagt, negative Zahlen sind

10:41.980 --> 10:44.480
charakteristisch erkennbar dadurch, dass vorne eine 1 ist.

10:45.660 --> 10:49.880
Erweitere ich jetzt diese Darstellung von einem Byte auf 4 Byte, also

10:49.880 --> 10:53.980
von 8 Bit auf 32 Bit, dann will ich ja den Zahlenwert erhalten.

10:54.800 --> 10:59.740
Das heißt, ich möchte hier trotzdem dieses 0x89 in der Interpretation

10:59.740 --> 11:01.040
als Zweikomplement behalten.

11:01.520 --> 11:03.420
Das heißt, ich muss vorne hier mit Einsen auffüllen.

11:03.420 --> 11:06.220
Also ich kann nicht einfach die 8, 9 kopieren, sondern ich muss vorne

11:06.220 --> 11:10.360
mit Einsen auffüllen, um den Wert dieser Zahl gleich zu behalten.

11:10.640 --> 11:11.720
Auch in der 32-Bit-Darstellung.

11:15.580 --> 11:15.920
Genau.

11:16.200 --> 11:18.240
Stellen wir vor, wir hätten jetzt hier eine andere Zahl, das wäre die

11:18.240 --> 11:18.660
0,7.

11:19.020 --> 11:23.360
Und 0,7 ist binär das hier.

11:26.060 --> 11:26.220
Ja?

11:28.640 --> 11:31.280
Also charakteristisch eine 0 vorne, das heißt, es ist eine positive

11:31.280 --> 11:31.720
Zahl.

11:31.720 --> 11:37.340
Und wenn ich jetzt hier ein LoadByte unsigned machen würde, würde hier

11:37.340 --> 11:39.340
tatsächlich eine 0,79 stehen.

11:39.660 --> 11:42.360
Und auch bei einem LoadByte würde hier das Ergebnis dasselbe sein.

11:42.860 --> 11:47.760
Also die Unterschiede sind jetzt hier nur, wenn ich eine negative Zahl

11:47.760 --> 11:49.280
als Byte reinladen würde.

11:52.680 --> 11:53.200
Okay?

11:53.580 --> 11:54.400
Wer hat es nicht verstanden?

11:56.700 --> 11:57.440
Keine Hände.

11:58.020 --> 11:58.320
Gut.

11:58.580 --> 11:58.840
Leider.

12:00.680 --> 12:07.060
So, dann haben wir gesagt, wir haben immer 16-Bit-Immediates und wir

12:07.060 --> 12:11.300
wollen ja manchmal auch 32-Bit-Zahlen oder 32-Bit-Operanten in den

12:11.300 --> 12:11.960
Register laden.

12:12.600 --> 12:14.900
Jetzt stehen wir natürlich vor einem Problem, weil wir das direkt im

12:14.900 --> 12:16.360
Befehlscode nicht ausführen können.

12:16.460 --> 12:19.500
Also wir können diese 32-Bit nicht im Befehlscode angeben.

12:19.700 --> 12:23.000
Es gibt ja kein Befehlsformat, was diesen 32-Bit-Operator beinhaltet.

12:23.000 --> 12:28.020
Und unsere Register aber 32-Bit-breit sind.

12:29.680 --> 12:30.720
Wie mache ich das?

12:31.340 --> 12:38.060
Ich teile mir das auf in zwei Teilaufgaben sozusagen.

12:38.360 --> 12:42.540
Ich lade einmal den oberen Teil, also ich verwende den LUI-Befehl, den

12:42.540 --> 12:43.600
Load Upper Immediate.

12:44.260 --> 12:52.180
Lade den oberen Teil meines Operanten auch in den oberen Teil meines

12:52.180 --> 12:52.880
Registers.

12:54.240 --> 12:55.100
Wie sieht das aus?

12:55.700 --> 12:59.500
Ich habe hier jeweils 2 x 16-Bit-Blöcke, das heißt, dieser Teil soll

12:59.500 --> 13:00.040
da oben rein.

13:02.460 --> 13:04.500
Kurz gucken, ob wir das Befehlsformat drin hatten.

13:06.680 --> 13:10.360
Und 16-Bit-Immediates kann ich ja in meinem Befehlsformat codieren,

13:10.560 --> 13:11.780
wenn ihr euch an den R-Typ erinnert.

13:12.500 --> 13:18.760
Wenn ihr euch an dieses Befehlsformat erinnert, hat man hier dieses 16

13:18.760 --> 13:19.480
-Bit -Immediate drin.

13:19.880 --> 13:22.140
Man hat hier den Opcode und man hat hier nochmal zwei Register zur

13:22.140 --> 13:22.480
Verfügung.

13:22.640 --> 13:27.040
Das heißt, ich kann diesen Befehl so codieren, dass ich mein 16-Bit

13:27.040 --> 13:28.320
-Immediate direkt drin habe.

13:29.880 --> 13:31.620
Der Load Upper Immediate macht noch Folgendes.

13:31.960 --> 13:34.760
Er setzt den unteren Bereich des Registers komplett auf 0.

13:34.760 --> 13:38.780
Das ist auch wichtig, dass nicht Fragmente von vorherigen Inhalten

13:38.780 --> 13:43.400
noch in dem Befehl vorhanden sind und hier mein Ergebnis verfälschen.

13:44.380 --> 13:48.800
Das nutze ich jetzt aus und sage, ich führe jetzt einen Order Operator

13:48.800 --> 13:52.320
aus mit dem unteren Immediate, also mit dem unteren Teil meines

13:52.320 --> 13:55.760
Operanten und dem, was in dem Register schon drin ist, weil das kann

13:55.760 --> 13:56.120
ich machen.

13:57.500 --> 13:58.580
Und was passiert dann?

13:59.200 --> 14:02.480
Das hier ist mein T0, der aktuelle Stand meines T0.

14:03.080 --> 14:04.400
Das hier ist das Immediate.

14:04.640 --> 14:09.900
Ich kann hier den unteren Teil verändern durch direkte Angabe des

14:09.900 --> 14:10.340
Immediates.

14:10.800 --> 14:12.760
Und jetzt werden beide Teile verordert.

14:14.280 --> 14:15.500
Was passiert bei Order?

14:16.100 --> 14:21.620
Wir haben ja gesagt, 0 oder 0 ist 0, 1 oder 0 ist 1, 0 oder 1 ist 1, 1

14:21.620 --> 14:22.340
oder 1 ist 1.

14:22.460 --> 14:28.500
Das heißt, wenn ich eine Zahl x mit 0 verordere, kommt immer die

14:28.500 --> 14:29.600
ursprüngliche Zahl raus.

14:30.360 --> 14:35.760
Da ich weiß, dass hier im unteren Teil meines Registers T0 mit 0

14:35.760 --> 14:39.160
aufgefüllt sind, kann ich davon ausgehen, dass dann hier komplett der

14:39.160 --> 14:41.440
Inhalt dieses Immediates erhalten bleibt.

14:41.980 --> 14:43.640
Im oberen Teil ist es genau andersrum.

14:43.720 --> 14:49.440
Da ist mein Immediate sozusagen mit 0 ergänzt worden und der Inhalt

14:49.440 --> 14:53.700
des oberen Teils des Operanten, das ich vorher in das Register

14:53.700 --> 14:56.660
hereingeladen habe, wird dann hier übernommen mit der Veroderung.

15:01.860 --> 15:03.360
Also, was habe ich gemacht?

15:03.660 --> 15:06.840
Ich wollte einen 32-Bit-Operanten in meinen Register T0 laden.

15:07.800 --> 15:10.060
Ich kann es nicht direkt codieren, ich habe es in zwei Teile

15:10.060 --> 15:10.620
aufgeteilt.

15:11.060 --> 15:13.760
Ich habe einmal den oberen Teil meines Operanten in einen Register

15:13.760 --> 15:14.720
geladen bzw.

15:14.760 --> 15:18.080
in den oberen Teil des Registers geladen und mit einer Veroderung des

15:18.080 --> 15:20.840
Registers mit sich selbst oder in dem unteren Teil meines Operanten

15:20.840 --> 15:25.200
komme ich dann auf das Ergebnis, wo dann letztendlich in T0 der

15:25.200 --> 15:26.720
Operant, den ich haben wollte, drinsteht.

15:31.580 --> 15:39.020
So, dann gibt es einen globalen Zeiger, genannt GP für Global Pointer.

15:41.400 --> 15:45.000
Dieser globale Pointer soll uns helfen, Daten im Datensegment zu

15:45.000 --> 15:49.820
adressieren, dadurch, dass er eine Konstante beinhaltet und man immer

15:49.820 --> 15:53.740
relativ zu dieser Konstanten per Offset adressieren kann.

15:55.040 --> 15:58.180
Bisher wollten wir, wenn wir Wörter laden wollten von einer bestimmten

15:58.180 --> 16:04.380
Adresse, angenommen hier Adresse lege im Datensegment, musste man

16:04.380 --> 16:05.320
folgenden Weg gehen.

16:05.320 --> 16:14.920
Man musste zuerst dieses Assembler Temporary Register

16:21.760 --> 16:22.240
verwenden.

16:23.680 --> 16:26.700
Also das ist ein Register, das nur der Assembler benutzt und dieses

16:26.700 --> 16:30.800
Register wird häufig oder fast ausschließlich eigentlich nur dafür

16:30.800 --> 16:34.960
verwendet, Pseudo-Instruktionen umzuwandeln, also den als

16:34.960 --> 16:38.980
Hilfsregister, um bestimmte Befehle, die Pseudo-Befehle sind durch

16:38.980 --> 16:43.980
eine Ansammlung anderer Befehle zu ersetzen und hier wird es dann als

16:43.980 --> 16:45.000
Hilfsregister verwendet.

16:47.460 --> 16:51.060
Hier wird normalerweise in dieses AT, wird die Adresse 0x10 in den

16:51.060 --> 16:56.360
oberen Bereich geladen und dann per Offset kann dann auf diese Adresse

16:56.360 --> 16:57.920
im Datensegment zugegriffen werden.

16:59.980 --> 17:02.480
Offset hier mussten natürlich die niederwertigsten Stellen von der

17:02.480 --> 17:05.180
Adresse sein, also Adresse, diese Adresse hier.

17:14.790 --> 17:17.910
Und mit 0x100 habe ich dann gewährleistet, dass ich im Datensegment

17:17.910 --> 17:18.210
bin.

17:18.930 --> 17:22.030
Eine bessere Lösung ist aber, dass statt zwei Befehle, ein Befehl zu

17:22.030 --> 17:22.450
verwenden.

17:23.530 --> 17:25.930
Und das kann man dann so machen, dass man hier diesen Global Pointer

17:25.930 --> 17:30.250
verwendet und sagt Offset plus Global Pointer und das mit den RT

17:30.250 --> 17:30.630
laden.

17:30.950 --> 17:33.030
Hier ist natürlich wichtig zu wissen, was in GP drin ist.

17:33.110 --> 17:36.090
GP enthält immer diesen Wert und das ist genau die Mitte meines

17:36.090 --> 17:36.810
Datensegments.

17:36.810 --> 17:41.610
Das heißt mit einem Zweier-Komplement, 16-Bit-Immediate, habe ich dann

17:41.610 --> 17:49.290
Zugriff auf alle Adressen zwischen 100.000 bis 101.000 oder besser

17:49.290 --> 17:50.610
gesagt 100.000.

17:58.340 --> 18:01.160
Dann gibt es verschiedene Transportbefehle.

18:01.820 --> 18:03.940
Man kann Registerinhalte von einem Register in ein anderes

18:03.940 --> 18:06.160
verschieben, dafür benutzt man den Move-Befehl.

18:06.960 --> 18:10.220
Man kann die speziellen Register High und Low ansprechen, indem man

18:10.220 --> 18:16.900
sagt Move from High oder Move from Low nach einem Zielregister oder

18:16.900 --> 18:22.160
Move to High oder Move to Low von einem Quelleregister in dieses

18:22.160 --> 18:22.980
speziellen Register.

18:24.580 --> 18:30.220
Register des Co-Prozessors, des jeweiligen Co-Prozessors Z, auf die

18:30.220 --> 18:34.080
kann ich zugreifen mit Move from Co-Prozessor 0123.

18:34.080 --> 18:38.640
Also Target, Destination oder Destination, Target, wenn ich sage Move

18:38.640 --> 18:39.980
to Co-Prozessor.

18:40.620 --> 18:44.380
Dabei ist immer, wie machen wir das jetzt farbig,

18:48.440 --> 18:57.540
sei das der Co-Prozessor, dann ist bei Move to Co-Prozessor, erstmal

18:57.540 --> 19:07.920
Move from Co-Prozessor ist mein Destination Co-Prozessor und hier

19:07.920 --> 19:08.800
müsste auch Destination.

19:09.020 --> 19:11.300
Also ihr seht, hier sind auch die Reihenfolgen getauscht.

19:14.500 --> 19:22.160
Hier lade ich vom Co-Prozessorregister RD zum CPU-Register RT und das

19:22.160 --> 19:26.140
Z gibt mir nur Auskunft darüber, welchen Co-Prozessor ich gerade

19:26.140 --> 19:26.780
ansprechen will.

19:28.160 --> 19:31.420
Wichtig hier noch, es gibt noch die Möglichkeit ein Punkt D dahinter

19:31.420 --> 19:34.300
zu hängen, um Double-Werte zu haben.

19:35.000 --> 19:39.460
Double-Werte sind 64-Bit-Werte, die kann ich natürlich nicht in einem

19:39.460 --> 19:41.800
Register speichern, sondern nur in zwei Registern.

19:42.260 --> 19:46.400
Dafür ist natürlich wichtig, dass ich tatsächlich hier Source 1 und

19:46.400 --> 19:50.220
Source 1 plus 1 als CPU-Register und auch Rdestination und

19:50.220 --> 19:54.360
Rdestination plus 1 als CPU-Register habe.

19:54.560 --> 19:57.440
Also ich habe hier mit einem Befehl, mache ich hier einen Move von

19:57.440 --> 19:58.560
zwei Registern.

20:02.260 --> 20:05.380
Fließkomma-Arithmetik, eigentlich die selben Befehle wie vorher auch,

20:05.480 --> 20:08.240
nur dass man hier noch einen Punkt D dahinter hängt, wird dann vom Co

20:08.240 --> 20:09.080
-Prozessor ausgeführt.

20:09.220 --> 20:13.660
Das heißt, hier braucht man wieder die Floating-Point-Register von der

20:13.660 --> 20:14.120
FPU.

20:15.540 --> 20:20.240
Vergleiche hier ist ein Compare-Equal, Compare-Less-Equal, Compare

20:20.240 --> 20:24.360
-Less -Than oder beziehungsweise Less-or-Equal, Compare-Less-Than und

20:24.360 --> 20:25.120
jeweils als Double.

20:26.460 --> 20:29.020
Natürlich kann man das auch mit Single nochmal machen, also nicht mit

20:29.020 --> 20:32.120
64 -Bit-Floating-Points, sondern mit 32-Bit-Floating-Points.

20:32.440 --> 20:36.600
Laden und Speichern jeweils mit Load-Double und Store-Double oder Load

20:36.600 --> 20:37.820
-Single und Store-Single.

20:38.300 --> 20:41.440
Und jeweils hier darauf aufpassen, dass man hier die Co-Prozessor

20:41.440 --> 20:46.100
-Register und jeweils hier dann Emidits für die Adressen verwendet.

20:48.880 --> 20:53.140
Dann gibt es noch Befehle für Unterbrechungs- und Ausnahmebehandlung.

20:53.140 --> 20:58.620
Hier konkret zu erwähnen wäre auf alle Fälle RFE, also Return-From

20:58.620 --> 20:59.140
-Exception.

20:59.800 --> 21:02.200
Hier werden Status-Register nach einer Unterbrechung

21:02.200 --> 21:02.920
wiederhergestellt.

21:03.560 --> 21:04.840
Den Syscall, den kennt ihr ja schon.

21:05.220 --> 21:06.740
Hier werden Systemfunktionen ausgeführt.

21:07.200 --> 21:11.660
Dann gibt es noch einen Break mit einem numerischen Wert nochmal, der

21:11.660 --> 21:14.140
dann Auskunft darüber gibt, wo das Programm unterbrochen wurde.

21:14.620 --> 21:16.680
Wird dann hier drin kodiert mit Opcode 0.

21:16.680 --> 21:18.620
Hier kommt dann der Code für dieses Break.

21:18.820 --> 21:20.880
Also dieses Code ist quasi eine Zahl.

21:24.420 --> 21:26.960
Und hier noch die Funktionsnummer 0x0d natürlich.

21:28.640 --> 21:31.940
Dummy-Operationen werden wir nachher auch nochmal sehen, wenn wir ein

21:31.940 --> 21:35.840
bisschen vertieft in die Pipelining gehen, warum man das braucht.

21:36.380 --> 21:39.520
Und hier idealerweise natürlich einfach aufgefüllt mit Nullen hat man

21:39.520 --> 21:40.780
hier die Null-Operation.

21:43.420 --> 21:46.740
Okay, dann so die wichtigsten Befehle für MIPS.

21:47.400 --> 21:51.280
Addition, Multiplikation, Subtraktion, Set-Less-Than.

21:51.880 --> 21:56.040
Tatsächlich sehr wichtig, weil man hier Vergleiche sehr effizient

21:56.040 --> 21:56.720
durchführen kann.

21:57.420 --> 22:02.580
Branch-Equal, Branch-Not-Equal, Jump, Jump-and-Link, Jump-Register,

22:02.920 --> 22:05.440
also zur Adresse im Register zurückspringen.

22:05.440 --> 22:07.700
Load-Upper-Immediate, Load-World-Store-World.

22:07.880 --> 22:10.060
Ja, das sind so die gängigsten Sachen, die sollte man auf alle Fälle

22:10.060 --> 22:11.120
auswendig können.

22:11.760 --> 22:15.340
Schade nicht, wenn man die anderen auch auswendig kann, aber das ist

22:15.340 --> 22:19.400
tatsächlich hier unsere Grundlage, mit der kommt man schon sehr, sehr

22:19.400 --> 22:19.640
weit.

22:22.680 --> 22:25.180
Dann haben wir die ganze Zeit von Pseudo-Instruktionen geredet.

22:25.460 --> 22:26.620
Was sind Pseudo-Instruktionen?

22:26.640 --> 22:29.680
Pseudo-Instruktionen sind Befehle, die es direkt im Befehlssatz so

22:29.680 --> 22:33.320
nicht gibt und die vom Assembler dann durch entsprechend andere

22:33.320 --> 22:36.320
Befehle ersetzt werden, die aber dann genau diese Funktion ausführen.

22:37.020 --> 22:39.580
Beispielsweise gibt es den Befehl Move nicht.

22:40.200 --> 22:41.840
Ich kann aber den Befehl Move verwenden.

22:42.080 --> 22:46.380
Was Assembler macht, ist er ersetzt dieses Move-Register-Destination

22:46.380 --> 22:52.320
-Register -Source durch ein Add-Unsigned-RD mit $0 und Rs.

22:52.600 --> 22:56.780
Das heißt, zum 0-Register wird das Source-Register draufaddiert und

22:56.780 --> 22:58.440
das Ergebnis wird in RD gespeichert.

22:59.290 --> 23:01.940
Wenn ich die Negation habe, MAC gibt es nicht.

23:02.900 --> 23:10.900
Hier wird einfach von 0 das Source-Register abgezogen und im CE

23:10.900 --> 23:11.760
-Register gespeichert.

23:12.260 --> 23:15.320
Der Branch-Befehl B, den gibt es auch nicht, ist auch ein Pseudo

23:15.320 --> 23:15.720
-Befehl.

23:16.200 --> 23:20.800
Hier wird einfach ersetzt durch ein Branch-Greater-Equal-Zero mit $0,

23:20.940 --> 23:22.320
was immer eintritt.

23:22.320 --> 23:25.200
Also 0 ist immer größer gleich 0.

23:25.820 --> 23:30.900
Das heißt, dieser bedingte Sprung wird immer ausgeführt zum Symbol.

23:31.840 --> 23:34.180
Hier haben wir noch die Auflösung von verschiedenen anderen Befehlen.

23:37.020 --> 23:41.160
Hier ist ganz witzig, der Not-Befehl wird hier ersetzt durch ein Oder,

23:42.480 --> 23:46.140
das die 0 mit 0 verodert und im 0-Register, was ja eigentlich ein

23:46.140 --> 23:50.080
Register ist, in dem man nichts schreiben darf, versucht das Ergebnis

23:50.080 --> 23:50.660
zu speichern.

23:52.820 --> 23:58.180
Okay, das war das Ende der dritten Übung.

23:58.360 --> 23:59.620
Dann geht es jetzt hier weiter.

24:00.000 --> 24:02.020
Eigentlich ist es alles die gleiche Übung, aber wir haben so

24:02.020 --> 24:05.100
aufgeteilt gehabt, dass man hier nochmal Programmiertechniken in

24:05.100 --> 24:08.020
Assembler, Stack-Programmierung, Unterprogramme nochmal zusätzlich

24:08.020 --> 24:08.320
haben.

24:08.760 --> 24:10.640
Und mit Ausnahme und Unterbrechungsbehandlung schauen wir mal, wie

24:10.640 --> 24:11.100
weit wir kommen.

24:11.820 --> 24:14.820
Falls wir nicht ganz fertig werden, setze ich die Folien trotzdem auf

24:14.820 --> 24:15.460
die Homepage.

24:15.580 --> 24:17.780
Das heißt, ihr müsst dann selber nochmal drüber schauen.

24:19.140 --> 24:23.620
Wir sind dann aber heute dann durch mit MIPS und werden dann nächstes

24:23.620 --> 24:26.920
Mal, also in zwei Wochen Mittwoch, müsste die Woche für den

24:26.920 --> 24:32.100
Weihnachtsferien sein, machen wir dann Pipelining.

24:37.800 --> 24:38.280
Programmiertechniken.

24:38.280 --> 24:39.840
Was macht man so häufig in C?

24:40.740 --> 24:45.580
Man macht bedingte Schleifen, man sagt IF, irgendeinen Zustand oder

24:45.580 --> 24:48.740
irgendeine Variable, dann macht das hier, falls das nicht der Fall

24:48.740 --> 24:52.140
ist, dann macht eine zweite Prüfung über einen anderen Zustand, dann

24:52.140 --> 24:55.280
führt das aus, falls das nicht so zutrifft, dann und so weiter.

24:55.380 --> 24:58.620
Das heißt, man hat hier verschachtelte IF-Schleifen, ganz viele

24:58.620 --> 24:59.100
hintereinander.

25:01.000 --> 25:04.060
Eine schönere Lösung ist natürlich, oder eine übersichtlichere Lösung

25:04.060 --> 25:06.620
auch hier, ist der Befehl Switch.

25:08.000 --> 25:11.780
Das heißt, man hat hier, wenn wir jetzt im Kontext von Noten ausgehen,

25:12.920 --> 25:20.240
Switch Note, das heißt, falls die Note Case 1 beispielsweise gleich 1

25:20.240 --> 25:23.420
ist, macht man den 1-Doppelpunkt, dann gibt es hier die Anweisungen,

25:23.560 --> 25:25.820
falls die Note 1 ist und wenn man fertig ist, macht man hier ein

25:25.820 --> 25:26.180
Break.

25:27.180 --> 25:30.360
Das heißt, Break springt dann aus dieser Schleife heraus und geht dann

25:30.360 --> 25:30.820
hier weiter.

25:32.360 --> 25:36.140
Case 2 wäre bei der Note 2 Anweisungen, wenn man fertig ist ein Break,

25:36.480 --> 25:38.120
Case 3, Case 4 und so weiter.

25:38.780 --> 25:41.380
Dann hat man auch den Case 6, falls die Note eine 6 ist, macht

25:41.380 --> 25:44.040
irgendetwas, wenn du fertig bist, spring heraus.

25:44.600 --> 25:48.000
Und dann gibt es noch den Default, falls nämlich keine dieser

25:48.000 --> 25:50.960
Bedingungen erfüllt ist, wird das Default ausgeführt.

25:51.680 --> 25:54.980
Das heißt, das würde jetzt hier dem letzten, wenn man jetzt hier

25:54.980 --> 25:57.400
weiter verschachten würde, würde es dem letzten Else-Fall entsprechen.

25:59.700 --> 26:01.220
Ich glaube, das ist klar, das kennt jeder.

26:01.400 --> 26:02.040
Wer kennt es nicht?

26:06.660 --> 26:07.740
Jeder kennt es, super.

26:10.300 --> 26:12.820
Okay, das ist ja nichts Neues, wir wollen es aber in MIPS machen

26:12.820 --> 26:13.100
jetzt.

26:13.940 --> 26:15.000
Wie sieht es denn in MIPS aus?

26:17.940 --> 26:19.720
Zunächst einmal muss ich natürlich die Note laden.

26:20.200 --> 26:22.060
Die lade ich in mein temporäres Register T0.

26:22.800 --> 26:26.780
Ich verwende dann noch die temporären Register T1 bis T5 für die

26:26.780 --> 26:31.500
verschiedenen Noten, also lade Immediates, lade Werte direkt in die

26:31.500 --> 26:31.840
Register.

26:33.700 --> 26:36.300
Man könnte hier noch die 6, aber gehen wir mal davon aus, dass keiner

26:36.300 --> 26:39.420
eine 6 bekommt, sondern 5 zum Durchfallen ja ausreichend ist.

26:42.060 --> 26:43.840
Dann kommen hier meine Sprunganweisungen.

26:44.260 --> 26:46.080
Ich vergleiche T0 mit T1.

26:47.360 --> 26:50.820
Falls diese zwei Register den gleichen Inhalt haben, also falls die

26:50.820 --> 26:54.420
Note eine 1 ist, springe ich zur Marke 1, vergleiche mit T2,

26:54.640 --> 26:56.720
vergleiche mit T3, T4, T5 und so weiter.

26:57.880 --> 27:03.960
Und falls ich nicht herausgesprungen bin bis hierhin, wird hier die

27:03.960 --> 27:05.800
Default -Anweisung ausgeführt.

27:06.140 --> 27:08.700
Das heißt, hier kann ich meinen Code für diese Default-Händler

27:08.700 --> 27:09.080
hinzufügen.

27:10.240 --> 27:13.920
Ganz wichtig hier, ich brauche noch einen Branch auf Weiter.

27:14.080 --> 27:18.420
Das heißt, ich muss hier aus dem Default dann rausspringen, wo es

27:18.420 --> 27:18.820
weitergeht.

27:18.880 --> 27:19.500
Und wo geht es weiter?

27:19.780 --> 27:20.800
Weiter geht es ganz am Schluss.

27:22.380 --> 27:25.120
Und natürlich bei jeder Abhandlung hier, wenn ich die verschiedenen

27:25.120 --> 27:28.980
Cases hier abhandle, wenn ich fertig bin, muss ich natürlich wieder

27:28.980 --> 27:30.300
auf Weiter zurückspringen.

27:30.720 --> 27:35.720
Also nach Marke 1, Branch zu Weiter, Branch zu Weiter, Branch zu

27:35.720 --> 27:35.920
Weiter.

27:39.440 --> 27:42.940
Okay, und weiter würde jetzt hier behandeln, Matrikelnummer, hat die

27:42.940 --> 27:43.740
Note, so und so.

27:44.040 --> 27:45.320
Könnte man hier eine Ausgabe machen.

27:47.280 --> 27:49.860
Geht natürlich auch einfacher, aber es geht ja hier jetzt nur darum,

27:50.500 --> 27:53.560
euch diese Programmiertechnik in MIPS zu zeigen.

27:57.060 --> 28:00.340
Dann gibt es hier nochmal den Fall, dass man temporäre Variablen hat,

28:00.500 --> 28:03.400
int x, int y, macht hier eine Addition und speichert es dann wieder in

28:03.400 --> 28:04.180
der temporären Variable.

28:04.180 --> 28:06.940
Die kann man quasi 1 zu 1 umsetzen.

28:07.380 --> 28:11.540
Man würde hier ein temporäres Register T1 verwenden, 12 plus 0, das

28:11.540 --> 28:17.160
ist mein Load Immediate, 34 plus 0, Load Immediate und würde hier die

28:17.160 --> 28:19.040
Summe nochmal in x speichern, also T1.

28:24.380 --> 28:30.020
Was passiert, wenn ich die Register T1 und T2 in den Speicher ablegen

28:30.020 --> 28:30.300
will?

28:30.960 --> 28:34.780
Und zwar ab einer Adresse 0x1000004.

28:37.020 --> 28:40.980
Dafür schaue ich mir erstmal an, wie sieht mein Add Immediate Befehl

28:40.980 --> 28:41.560
aus hier.

28:42.040 --> 28:45.540
Ich habe hier den R-Typ, das heißt, ich habe zwei Register und ein

28:45.540 --> 28:45.960
Immediate.

28:47.980 --> 28:49.420
So funktioniert das auch.

28:50.880 --> 28:54.440
Wir haben aber gesagt, ich habe hier ein 32-Bit-Value und ich möchte

28:54.440 --> 28:57.700
diesen 32-Bit-Value in meinen Register laden.

28:57.940 --> 29:00.880
Das geht so direkt nicht, weil das Immediate ja nur 16-Bit zur

29:00.880 --> 29:01.540
Verfügung hat.

29:01.540 --> 29:04.780
Das heißt, ich greife hier wieder zurück auf meinen Load Upper

29:04.780 --> 29:06.180
Immediate Konstrukt.

29:07.160 --> 29:12.640
Ich versuche dann, dieses Immediate in ein anderes Register zu laden,

29:13.080 --> 29:16.540
an den oberen Teil.

29:16.960 --> 29:18.180
Das wird aufgefüllt mit Nullen.

29:18.180 --> 29:20.800
Ich mache hier wieder eine Veroderung, also das wäre jetzt der Code.

29:20.900 --> 29:25.140
Ich möchte die 0x1000 in S1 laden.

29:25.340 --> 29:26.900
Das heißt, die 0x1000 landet dann

29:32.680 --> 29:35.420
hier.

29:38.140 --> 29:43.540
Ich verodere dann die 0x004 mit S1 selber.

29:45.940 --> 29:47.860
Da fehlt glaube ich noch ein S1.

30:00.940 --> 30:05.800
Also S1 mit sich selber verodert und mit 0x004 und das Ergebnis wird

30:05.800 --> 30:06.740
in S1 gespeichert.

30:07.960 --> 30:15.280
Anschließend kann ich meine temporäre Variable in Offset 0 S1 und

30:15.280 --> 30:17.400
Offset 4 S1 jeweils speichern.

30:17.600 --> 30:22.360
Das heißt, wie gesagt nochmal, Byte-Adressierung, aber ich greife auf

30:22.360 --> 30:23.860
Wörter zu mit jeweils 4 Bytes.

30:24.340 --> 30:27.180
Das heißt, für die erste Variable habe ich ein Offset von 0 und für

30:27.180 --> 30:28.300
die zweite ein Offset von 4.

30:36.180 --> 30:38.000
So, das erkennen wir schon.

30:39.140 --> 30:43.560
Wenn a kleiner als b ist, dann setze ich gleich b oder wenn a nicht

30:43.560 --> 30:45.380
kleiner als b ist, dann setze ich gleich a.

30:46.440 --> 30:49.380
Das haben wir generell mit SetLessThan erledigt.

30:49.380 --> 30:52.040
Wie sieht es hier in MIPS-Assembler aus, dieses Konstrukt?

30:53.620 --> 30:58.740
Initialisieren von t0, initialisieren von t1 mit a und b jeweils.

30:59.160 --> 31:01.260
Das heißt, t0 entspricht a, t1 entspricht b.

31:02.160 --> 31:05.080
Ich mache mein SetLessThan, t2, t0, t1.

31:05.400 --> 31:08.960
Also ist t0

31:17.810 --> 31:20.330
ist a, t1 ist b.

31:21.850 --> 31:25.070
Wenn diese Bedingung erfüllt ist, setze ich t1 gleich 1,

31:30.000 --> 31:31.240
äh t2 meine ich.

31:42.430 --> 31:47.630
Und dann kann ich hier einfach t2 vergleichen mit equal 0, also branch

31:47.630 --> 31:47.930
equal.

31:48.030 --> 31:50.190
Wenn t2 0 entspricht, dann springe ich in den else-Fall.

31:52.290 --> 31:54.110
Also ich vergleiche a kleiner b.

31:55.590 --> 31:57.990
Falls das nicht der Fall ist, springe ich in den else-Fall.

31:58.510 --> 32:01.230
Falls das der Fall ist, habe ich hier meine Ausführung.

32:01.710 --> 32:02.710
Das ist x gleich b.

32:04.710 --> 32:07.310
Und der else-Fall sagt mir x gleich a.

32:08.670 --> 32:10.510
Und ganz wichtig hier, dieses Jump.

32:11.950 --> 32:15.710
Um den Programmfluss nicht zu stören, um hier eine korrekte Ausführung

32:15.710 --> 32:16.130
zu bekommen.

32:16.870 --> 32:18.110
Hier springe ich auf continue.

32:18.270 --> 32:19.330
Das heißt, es geht hier weiter.

32:20.170 --> 32:24.010
Das Jump hier bräuchte ich nicht, weil es ja direkt vom Programmfluss

32:24.010 --> 32:25.010
her direkt dort weitergeht.

32:29.330 --> 32:30.910
Dann Zugriff auf Arrays.

32:31.750 --> 32:36.210
Angenommen, ich habe ein Array x und sage, das Array an der Stelle 0

32:36.210 --> 32:38.670
soll mit dem Inhalt an der Stelle 1 und 2 addiert werden.

32:40.850 --> 32:43.970
Wir nehmen mal an, die Startadresse steht bereits in t1.

32:44.450 --> 32:48.710
Dann habe ich hier meinen Zugriff auf die erste Stelle mit Offset 4,

32:48.970 --> 32:50.490
auf die zweite Stelle mit Offset 8.

32:51.010 --> 32:53.930
Ich addiere das Ergebnis und das Ergebnis wird dann wieder in Offset 0

32:53.930 --> 32:54.510
gespeichert.

32:54.510 --> 32:56.450
Also 0, 1, 2.

32:56.950 --> 33:01.650
Ich habe jeweils n plus 4, plus 8, plus 12 und so weiter.

33:06.790 --> 33:09.150
Jetzt wird es ein bisschen interessanter oder ein bisschen

33:09.150 --> 33:09.650
schwieriger.

33:12.390 --> 33:13.110
Stackprogrammierung.

33:13.110 --> 33:15.550
Der Speicher meines Systems sieht so aus.

33:15.630 --> 33:18.490
Ich habe ein Kernel Datensegment, Kernel Code Segment.

33:19.070 --> 33:21.490
Das fängt dann bei 0x8000 an.

33:24.490 --> 33:30.590
Ab 0x8000, also bei 0x7FFF geht das Stack Segment los.

33:31.190 --> 33:33.110
Das Stack Segment selber wächst nach unten.

33:33.870 --> 33:37.730
Von unten habe ich wachsend mein Datensegment mit 0x1000.

33:38.030 --> 33:38.870
Das wächst nach oben.

33:40.950 --> 33:45.370
Und hier oben unten habe ich meine reservierten Speicherstellen mit

33:45.370 --> 33:46.530
jeweils meinem Programmcode drin.

33:48.730 --> 33:52.090
Um mit dem Stack Segment umgehen zu können, brauche ich einen Stack

33:52.090 --> 33:52.410
Pointer.

33:52.630 --> 33:56.330
Mein Stack Pointer zeigt immer auf das erste freie Wort in meinem

33:56.330 --> 33:56.690
Stack.

33:58.130 --> 34:04.670
Das heißt, ich habe hier mein erstes freies Wort, also die Adresse der

34:04.670 --> 34:05.990
ersten freien 4 Bytes.

34:07.810 --> 34:12.470
Und wenn ich jetzt beispielsweise 8 Bytes oder 2 Wörter reservieren

34:12.470 --> 34:16.490
will, dann subtrahiere ich 8 von meinem Stack Pointer.

34:17.590 --> 34:19.450
Damit reserviere ich mir 8 Bytes.

34:20.010 --> 34:23.690
Mein Stack Pointer zeigt auf die nächste freie Stelle in meinem Stack.

34:25.090 --> 34:28.910
Ich kann dann mit jeweils dem neuen Stack Pointer, der ja auf die

34:28.910 --> 34:34.530
freie Stelle zeigt, plus 4 kann ich auf das erste Wort zugreifen und

34:34.530 --> 34:36.810
mit plus 8 greife ich auf das zweite Wort zu.

34:39.010 --> 34:39.790
Soweit so klar?

34:42.790 --> 34:43.530
Fragen dazu?

34:46.090 --> 34:46.550
Okay.

34:47.430 --> 34:48.470
Das ist am Anfang ein bisschen verwirrend.

34:48.630 --> 34:53.430
Tatsächlich habt ihr hier nicht 0 und 4, wie so intuitiv vielleicht

34:53.430 --> 34:56.910
wäre, als erstes und zweites Wort, sondern man hat hier plus 4 und

34:56.910 --> 34:57.350
plus 8.

34:57.810 --> 35:00.690
Und Grund ist tatsächlich der, dass der Stack Pointer auf das erste

35:00.690 --> 35:01.470
freie Wort zeigt.

35:03.330 --> 35:06.530
Gehen wir davon aus, wir hätten jetzt etwas auf dem Stack gespeichert,

35:06.750 --> 35:11.350
hätten uns 12 Bytes reserviert, also Platz für 3 Wörter reserviert,

35:12.510 --> 35:15.030
hätten einmal T0 gesichert.

35:15.730 --> 35:19.930
Ich mache hier ein StoreWord T0, also Inhalt von T0 wird an diese

35:19.930 --> 35:21.350
Adresse gespeichert.

35:22.410 --> 35:25.470
Das macht man dann, wenn man das Register T0 sichern will zum

35:25.470 --> 35:25.830
Beispiel.

35:26.790 --> 35:28.110
Dann lege ich das hier auf den Stack.

35:28.710 --> 35:31.470
Das gleiche habe ich mit T1 und T2 gemacht, jeweils an der Stelle plus

35:31.470 --> 35:32.450
8 und plus 12.

35:33.570 --> 35:36.790
Und mein Stack sieht jetzt so aus, dass mein ursprünglicher Stack

35:36.790 --> 35:38.850
Pointer auf diese erste freie Stelle gezeigt hatte.

35:39.390 --> 35:43.010
Nachdem ich mir 12 Bytes reserviert habe, zeigt mein Stack Pointer auf

35:43.010 --> 35:46.310
die nächste freie Stelle und ich habe hier T0, T1 und T2 in meinem

35:46.310 --> 35:46.570
Stack.

35:48.730 --> 35:51.650
Das macht man meistens, wenn man jetzt Unterprogrammaufrufe hat und

35:51.650 --> 35:54.250
man möchte bestimmte Register überschreiben, dann muss man die vorher

35:54.250 --> 35:54.610
sichern.

35:54.690 --> 35:56.330
Diese Sicherung macht man dann auf dem Stack.

35:56.570 --> 35:59.630
Das heißt, man holt sich hier Speicher vom Stack, legt die Variablen,

35:59.710 --> 36:02.750
die man sichern will, legt die auf den Stack und wenn man fertig ist,

36:02.790 --> 36:05.270
muss man natürlich diese Wörter wieder vom Stack holen.

36:05.270 --> 36:09.350
Das ist mein aktueller Stack Pointer.

36:10.210 --> 36:15.910
Ich lade dann hier 4 plus Stack Pointer nach T0, 8 plus Stack Pointer

36:15.910 --> 36:19.110
nach T1 und 12 plus Stack Pointer nach T2.

36:20.250 --> 36:21.150
Das ist der erste Schritt.

36:21.290 --> 36:23.810
Das heißt, hier ist die Rücksicherung meiner Register, die ich

36:23.810 --> 36:24.430
gesichert hatte.

36:25.430 --> 36:29.770
Um den Speicher wieder freizugeben, addiere ich jetzt die 12 wieder

36:29.770 --> 36:31.190
auf meinen Stack Pointer.

36:32.230 --> 36:32.990
Was passiert dann?

36:33.650 --> 36:35.570
Mein Stack Pointer zeigt auf die nächste freie Stelle.

36:35.770 --> 36:40.490
Das heißt, diese Daten werden als freier Speicher interpretiert.

36:40.850 --> 36:42.650
Die werden nicht explizit gelöscht.

36:43.190 --> 36:53.430
Die Daten stehen noch drin, aber sie sind nicht mehr regulär als T0,

36:53.510 --> 36:54.410
T1 oder T2 zu sehen.

36:55.850 --> 36:56.370
Also,

37:00.230 --> 37:03.750
wir haben gesagt, bei Unterprogrammen muss man Register sichern,

37:03.930 --> 37:04.750
vielleicht vorher.

37:05.570 --> 37:07.470
Wie sieht ein Unterprogrammaufruf aus?

37:07.730 --> 37:11.570
Ich habe hier zum Beispiel ein Beispiel für einen Unterprogrammaufruf.

37:11.570 --> 37:14.890
Ich möchte zu meinem Unterprogramm MIN springen.

37:15.230 --> 37:16.750
MIN ist ein Label in meinem Code.

37:17.770 --> 37:21.870
Hier wird eine MIN-Funktion ausgeführt, die aus zwei gegebenen

37:21.870 --> 37:26.150
Parametern die kleinere Zahl registriert und irgendwie ablegt,

37:26.630 --> 37:28.990
irgendwo speichert und dann wieder zurückspringt.

37:30.810 --> 37:34.110
Und das Problem hier ist natürlich, wohin springe ich zurück?

37:35.490 --> 37:38.490
Wenn ich ein Unterprogrammaufruf mache, dann ist es ja immer

37:38.490 --> 37:38.990
dynamisch.

37:39.150 --> 37:42.990
Das heißt, ich kann von verschiedenen Stellen in meinem Code dieses

37:42.990 --> 37:44.130
Unterprogramm aufrufen.

37:44.650 --> 37:47.810
Das heißt, ich kann hier keine feste Rücksprungadresse definieren.

37:48.570 --> 37:49.750
Ich kann nicht hier sagen,

37:53.920 --> 37:59.700
weiter 1 und hier dann einen Jump weiter 1 machen, das geht nicht.

38:00.700 --> 38:04.180
Weil ich könnte ja später nochmal einen Jump MIN aufrufen und das wäre

38:04.180 --> 38:05.540
dann hier weiter 2 beispielsweise.

38:11.280 --> 38:13.220
So, wie löse ich dieses Problem?

38:15.780 --> 38:19.300
Idealerweise habe ich ein Register, wo meine Rücksprungadresse

38:19.300 --> 38:22.660
drinsteht und das ist meine Return Address oder RA genannt.

38:24.680 --> 38:28.700
Und diese Rücksprungadresse, dieses Register sichert mir die Adresse

38:28.700 --> 38:29.940
des nächsten Befehls.

38:30.740 --> 38:34.920
Also merkt sich dann, wo weiter 1 liegt oder wo weiter 2 liegt, je

38:34.920 --> 38:39.720
nachdem, von wo ich dieses Unterprogramm aufgerufen habe.

38:39.980 --> 38:46.240
Und diese Adresse muss man sichern und später wieder herstellen, wenn

38:46.240 --> 38:47.260
man hier wieder rausspringt.

38:48.320 --> 38:49.620
Warum muss man es wieder herstellen?

38:49.800 --> 38:52.840
Es könnte ja sein, dass ich mehrmals Aufrufe habe von meinem

38:52.840 --> 38:56.740
Unterprogramm oder dass ich hier Rekursionen drin habe und ich möchte

38:56.740 --> 38:59.660
ja nicht verlieren, von wo ich aufgerufen wurde.

39:00.060 --> 39:03.020
Das heißt, jedes Mal, wenn dieses Unterprogramm aufgerufen wird, muss

39:03.020 --> 39:05.700
diese Rücksprungadresse gesichert und wiederhergestellt werden.

39:05.980 --> 39:08.860
Genauso die Parameterübergabe, da muss man sich auch Gedanken darüber

39:08.860 --> 39:11.100
machen, welche Parameter müsste ich sichern oder wo sind meine

39:11.100 --> 39:14.760
Parameter drin und wie sieht meine Rückgabe aus.

39:16.400 --> 39:20.160
Dafür gibt es den tollen Befehl JAL, Jump and Link.

39:20.640 --> 39:25.520
Ist tatsächlich ein eigener Befehl, Opcode 3 und hier hat man dann die

39:25.520 --> 39:29.260
Target -Adresse, hier eine 26-Bit-Adresse, dass man ein bisschen

39:29.260 --> 39:32.240
größere Sprünge auch machen kann im Speicher.

39:32.920 --> 39:37.780
Und was dieser Befehl macht ist, er springt zu dieser Zieladresse und

39:37.780 --> 39:43.120
speichert die Adresse des nächsten Befehls in meinem Dollar-RA

39:43.120 --> 39:43.780
-Register.

39:46.900 --> 39:47.880
Habt ihr Fragen?

39:50.260 --> 39:50.500
Hallo?

39:52.180 --> 39:52.680
Fragen?

39:53.520 --> 39:53.980
Uns klar?

39:55.280 --> 39:55.740
Irgendwas unklar?

39:55.800 --> 39:57.080
Ich kann es nur mal erklären, ist kein Problem.

39:58.820 --> 39:59.340
Okay?

39:59.760 --> 40:00.120
Gut.

40:01.080 --> 40:05.980
Also, Jump and Link, springe und speichere die Adresse des nächsten

40:05.980 --> 40:10.840
Befehls und wie sieht es dann aus, wenn ich meine Main-Methode habe,

40:10.960 --> 40:15.080
ich führe hier irgendeinen Code aus, hab hier dann einen Einsprung in

40:15.080 --> 40:23.360
ein Unterprogramm, das sich hier befindet, also hier Sub-Procedure

40:23.360 --> 40:24.020
oder sonst irgendwas.

40:25.260 --> 40:30.060
Wenn der fertig ist, dann springt er zurück mit Jump-Register-RA, das

40:30.060 --> 40:35.280
ist meine Return-Adress von meinem ersten Aufruf, dann wird hier

40:35.280 --> 40:38.200
weiterhin Code ausgeführt und wenn das zweite Mal hier ausgeführt

40:38.200 --> 40:41.540
wird, dann ändert sich natürlich auch die Adresse des nächsten

40:41.540 --> 40:42.060
Befehls.

40:42.060 --> 40:46.200
Das heißt, einmal ist das meine Adresse und einmal ist das meine

40:46.200 --> 40:47.380
Rücksprung -Adresse, also RA.

40:48.820 --> 40:52.600
Und so kann ich gewährleisten, dass ich immer an die richtige Stelle

40:52.600 --> 40:55.680
zurückspringe und mein Programmfluss nicht gestört ist, ich aber auch

40:55.680 --> 40:59.680
diese Sub-Programme von verschiedenen Stellen öfter aufrufen kann.

41:04.230 --> 41:08.390
Dafür ist jetzt natürlich wichtig, dass wir Regeln und Konventionen

41:08.390 --> 41:11.650
haben, wie man Register und wie man den Stack verwendet bei einem

41:11.650 --> 41:12.650
Unterprogrammaufruf.

41:13.570 --> 41:15.930
Letztendlich bringt es nichts, wenn ich viele Sachen machen kann, aber

41:15.930 --> 41:18.610
sich keiner dran hält oder man sich selber nicht dran hält, daher

41:18.610 --> 41:21.490
möchten wir jetzt hier definieren, wie das Ganze funktionieren soll.

41:23.470 --> 41:29.130
Wir haben einmal einen Frame-Pointer zusätzlich zum Stack-Pointer, da

41:29.130 --> 41:33.130
sich bei einem Unterprogrammaufruf der Stack-Pointer jeweils verändern

41:33.130 --> 41:33.470
kann.

41:33.670 --> 41:36.650
Wenn ich temporäre Dateien, wenn ich temporäre Variablen ablegen will,

41:36.730 --> 41:37.970
kann ich das auch auf dem Stack machen.

41:38.610 --> 41:41.190
Das mache ich dann aber in meinem Unterprogramm und dadurch ändert

41:41.190 --> 41:42.170
sich mein Stack-Pointer.

41:42.770 --> 41:43.930
Manchmal will ich das aber nicht.

41:44.010 --> 41:47.490
Manchmal möchte ich wissen, wo mein Stack-Pointer vor dem

41:47.490 --> 41:48.670
Unterprogrammaufruf war.

41:48.790 --> 41:51.530
Dafür gibt es den Frame-Pointer und der zeigt dann genau auf diesen

41:51.530 --> 41:55.930
Rahmen, wo sich meine gesicherten Eingabe-Parameter zum Beispiel

41:55.930 --> 41:56.610
befinden können.

41:57.770 --> 41:59.170
Was sichert man dann in so einem Frame?

41:59.910 --> 42:02.910
Einmal die Argumente des Unterprogramms, lokale Variablen des

42:02.910 --> 42:06.750
Unterprogramms und Registerinhalte, die man eigentlich nicht verändern

42:06.750 --> 42:08.830
dürfte, aber die man vielleicht verändert hat und die man irgendwie

42:08.830 --> 42:10.570
ablegen muss vorher, die man sichern will.

42:13.630 --> 42:16.870
Wie sieht die Parameterübergabe aus bei einem Unterprogrammaufruf?

42:17.090 --> 42:20.010
Wir haben einmal die A-Register für Arguments.

42:21.050 --> 42:25.330
A0 bis A3 sind reserviert hier als Register für die Argumente des

42:25.330 --> 42:26.330
Unterprogrammaufrufs.

42:26.650 --> 42:30.230
Das heißt, hier können konkret Parameter für dieses Unterprogramm

42:30.230 --> 42:31.010
gespeichert werden.

42:32.450 --> 42:36.690
V0 und V1 sind definiert als Ausgabe-Parameter des Unterprogramms.

42:37.370 --> 42:40.830
Also das ist jetzt nur eine Konvention, das ist nicht auf Stein

42:40.830 --> 42:44.890
gemeißelt, aber wenn ihr euch daran haltet, dann seid ihr zumindest in

42:44.890 --> 42:48.250
der Programmierung konsistent und in der Interpretation ist es dann

42:48.250 --> 42:50.610
auch einfacher für euch selber dann auch nachher zu verstehen, was da

42:50.610 --> 42:51.130
passiert ist.

42:51.130 --> 42:53.750
T0 bis T9 sind temporäre Register.

42:53.990 --> 42:57.870
Die dürfen in dem Unterprogramm überschrieben werden, hat keinerlei

42:57.870 --> 42:58.570
Konsequenzen.

42:59.110 --> 43:01.770
Heißt aber auch, wenn man ein Unterprogramm aufruft, dann sollte man,

43:01.890 --> 43:05.350
falls man temporäre Register benötigt, die Inhalte vorher sichern.

43:07.450 --> 43:12.150
S0 bis S7 dürfen nicht von dem Unterprogramm überschrieben werden.

43:12.730 --> 43:15.690
Sollten sie jedoch überschrieben werden, weil man nicht genug Register

43:15.690 --> 43:19.170
hat oder sonstiges, dann müssen die vorher gesichert und bei der

43:19.170 --> 43:21.550
Fertigstellung, beim Rücksprung, vor dem Rücksprung nochmal wieder

43:21.550 --> 43:22.250
hergestellt werden.

43:22.250 --> 43:27.570
Das heißt, wenn ich ein Unterprogramm aufrufe, kann ich davon

43:27.570 --> 43:30.930
ausgehen, dass wenn das Unterprogramm zurückkommt, dass meine Register

43:30.930 --> 43:34.010
S0 bis S7 die ursprünglichen Werte beinhalten.

43:34.530 --> 43:36.530
Bei T0 bis T9 kann ich nicht davon ausgehen.

43:43.170 --> 43:46.050
So, ein einfacher Unterprogramm aufrufe würde so aussehen.

43:46.410 --> 43:48.530
Ich habe hier einen Jump and Link zu meiner Subroutine.

43:49.130 --> 43:53.150
Meine Subroutine hätte keine weiteren Unterprogramm Aufrufe und würde

43:53.150 --> 43:55.370
für lokale Variablen nur T0 bis T9 verwenden.

43:55.870 --> 43:59.630
Und sobald man hier fertig ist, würde hier ein Jump Register zurück zu

43:59.630 --> 44:01.150
meiner Return Address stattfinden.

44:04.350 --> 44:05.790
Wie würde sowas aussehen?

44:06.090 --> 44:10.210
Zum Beispiel hätte ich den Min-Befehl, den wir vorhin definiert haben,

44:10.990 --> 44:14.830
dass ich hier zwei Parameter übergebe, a und b, und ich möchte, dass

44:14.830 --> 44:19.270
mein Unterprogramm mir die kleinere der beiden Zahlen zurückliefert.

44:20.530 --> 44:24.750
In meinem Hauptprogramm habe ich einmal ein x und einmal ein y und ich

44:24.750 --> 44:30.190
sage int z, also meine z-Variable, ist das Minimum beider Zahlen und

44:30.190 --> 44:32.370
möchte dann dieses Minimum bestimmt haben.

44:35.380 --> 44:38.440
In MIPS führen wir jetzt folgendes Verfahren.

44:38.680 --> 44:43.260
Wir müssen natürlich einmal x laden, dafür verwenden wir das Register

44:43.260 --> 44:43.720
S0.

44:44.320 --> 44:48.160
Wir müssen einmal y laden, dafür verwenden wir das Register S1.

44:53.910 --> 44:57.210
Dann möchten wir unser Unterprogramm aufrufen und wir haben gesagt,

44:57.430 --> 45:00.550
für Unterprogrammaufrufe verwenden wir die A-Register als Arguments.

45:00.950 --> 45:07.550
Das heißt, hier kopieren wir den Inhalt von x oder S0 nach A0 für

45:07.550 --> 45:12.190
Argument 0 und von y oder S1 nach A1 als Argument 1.

45:14.430 --> 45:16.750
Als nächstes mache ich mein Unterprogrammaufruf.

45:16.930 --> 45:18.970
Ich sage jump and link nach Min.

45:20.130 --> 45:24.370
Min führt irgendwas aus, kommt zurück und mein Ergebnis steht in V0.

45:25.510 --> 45:27.830
Und das V0 muss ich mir jetzt kopieren in mein Z.

45:27.830 --> 45:35.050
Ich habe ja gesagt, unser Programm sagt uns, dass Z hier das Ergebnis

45:35.050 --> 45:39.310
speichern soll und wenn wir jetzt S0 als x und S1 als y haben, ist es

45:39.310 --> 45:40.250
wahrscheinlich S2 als z.

45:40.990 --> 45:45.890
Also speichern wir das Ergebnis dieses Unterprogrammaufrufs V0 in S2

45:45.890 --> 45:52.270
und können dann natürlich S2 zurück an die Speicherstelle Z im

45:52.270 --> 45:53.310
Speicher schreiben.

45:53.310 --> 45:55.690
Dafür muss man natürlich irgendwo nochmal definieren.

45:55.810 --> 45:56.970
Mal kurz gucken, ob das hier drin ist.

45:57.930 --> 46:00.630
Muss man im Datensegment natürlich nochmal drei Label definieren.

46:00.730 --> 46:02.270
Einmal x, y und einmal z.

46:02.750 --> 46:05.290
Jeweils als Wert oder was man halt haben will.

46:08.030 --> 46:10.430
Dann, wie würde das Unterprogramm selber aussehen?

46:14.150 --> 46:16.950
Das Unterprogramm darf T0 und T1 verändern.

46:17.410 --> 46:18.910
Das heißt, hier haben wir keine Probleme.

46:19.110 --> 46:22.850
Wir laden einmal das Argument 0 nach T0, wir laden einmal Argument 1

46:22.850 --> 46:26.910
nach T1 und können dann mit T0 und T1 arbeiten, ohne irgendetwas

46:26.910 --> 46:28.690
kaputt zu machen.

46:30.310 --> 46:34.150
Es wird ein SLT, Set Less Than, ausgeführt, also ein Vergleich

46:34.150 --> 46:35.350
zwischen T1 und T0.

46:36.470 --> 46:47.030
Falls dieser Vergleich nicht T1 kleiner als 0 ist, habe ich einen 0

46:47.030 --> 46:50.630
hier drin stehen und wenn ich hier mit 0 vergleiche, springe ich auf

46:50.630 --> 46:51.250
den else-Fall.

46:52.070 --> 46:55.830
Im else-Fall würde ich dann hier T0, also x, als Ergebnis speichern

46:55.830 --> 46:57.950
und hier würde ich y als Ergebnis speichern.

46:58.690 --> 47:03.270
Nochmal wichtig hier, das provisorische Jump, dass ich, wenn ich in

47:03.270 --> 47:15.250
dem Teil meiner if-Schleife bin, falls es fehlschlägt, kommt hier der

47:15.250 --> 47:15.830
else -Teil.

47:28.600 --> 47:34.200
Falls es fehlschlägt, kommt hier der else-Teil und falls es, ne

47:34.200 --> 47:36.140
Entschuldigung, jetzt kommt der if-Teil.

47:39.880 --> 47:45.840
Also der else-Teil meiner ursprünglichen if, aber in dem Fall ist es

47:45.840 --> 47:54.540
natürlich, ich mache den Vergleich T1 T0, also x kleiner y.

48:00.730 --> 48:02.170
Nein, y kleiner x.

48:02.750 --> 48:07.600
So, deswegen machen wir das vorne.

48:18.160 --> 48:19.580
So ein bisschen Pseudocode.

48:20.980 --> 48:28.860
Also falls y kleiner ist als x, dann ist das der when-Teil und das ist

48:28.860 --> 48:29.480
der else-Teil.

48:30.420 --> 48:35.680
Speichere y in meiner Ausgabe und hier speichere x in meiner Ausgabe.

48:40.040 --> 48:41.040
So weit, so klar?

48:43.580 --> 48:44.760
Keine weiteren Fragen?

48:45.200 --> 48:45.640
Machen wir weiter.

48:54.480 --> 48:57.680
Also, was können Argumente sein?

48:57.860 --> 49:00.660
Das können Integer oder Fließkommazahlen sein oder auch Zeiger auf

49:00.660 --> 49:02.720
verschiedene andere Datenstrukturen, die man natürlich vorher

49:02.720 --> 49:05.320
definieren muss oder man muss zumindest sich im Klaren darüber sein,

49:05.420 --> 49:06.880
wie diese Datenstrukturen aussehen sollen.

49:08.100 --> 49:12.540
Die Aufgaben des Aufrufes sind, temporäre Register, falls diese

49:12.540 --> 49:16.460
benötigt werden oder falls diese Inhalte beinhalten, die noch

49:16.460 --> 49:18.160
gebraucht werden, sich vorher zu sichern.

49:18.860 --> 49:21.820
Ansonsten könnten die natürlich im Unterprogramm, müssen nicht, aber

49:21.820 --> 49:22.800
können überschrieben werden.

49:24.740 --> 49:29.980
Man muss entsprechend Argumente A0 bis A3 oder auch verschiedene

49:29.980 --> 49:33.660
Zeigerstrukturen, die dann auf Datenstrukturen zeigen, die die

49:33.660 --> 49:36.440
Argumente beinhalten, in A0 bis A3 ablegen.

49:38.480 --> 49:42.020
Dafür sollten natürlich A0 bis A3, falls benötigt, nochmal vorher

49:42.020 --> 49:46.180
gesichert werden und nach dem Rücksprung wiederhergestellt werden und

49:46.180 --> 49:49.740
weitere Argumente kann man auf dem Stack übergeben, vor dem Aufruf.

49:57.730 --> 50:01.470
So, dann der nächste Schritt ist natürlich der Unterprogramm Aufruf.

50:01.550 --> 50:05.770
Der Unterprogramm Aufruf muss mit einem Jump and Link geschehen, mit

50:05.770 --> 50:06.610
der jeweiligen Adresse.

50:08.070 --> 50:10.550
Was dann automatisch gemacht wird, ist, dass die Rücksprungadresse

50:10.550 --> 50:14.590
gespeichert wird in RA, wie Rücksprungadresse oder Return Address.

50:18.130 --> 50:25.150
Und man kann auch verschiedene Sachen natürlich auf dem Frame ablegen,

50:25.290 --> 50:26.470
also mit dem Frame Pointer.

50:26.890 --> 50:28.690
Dafür schauen wir uns mal an, wie das funktioniert.

50:29.290 --> 50:32.230
Man hat eine Rahmengröße, die beinhaltet die Anzahl der Argumente plus

50:32.230 --> 50:35.710
Anzahl der zu sichernden Register plus Anzahl der lokalen Variablen

50:35.710 --> 50:36.890
und natürlich mal 4.

50:37.310 --> 50:40.410
Wie gesagt, wir arbeiten mit Wörtern, können aber beides adressieren,

50:40.770 --> 50:43.490
deswegen ist diese plus mal 4 immer notwendig.

50:46.350 --> 50:49.570
Fließkomma-Zahlen doppelter Genauigkeit benötigen natürlich 8 Bytes

50:49.570 --> 50:52.590
statt 4 Bytes, das heißt, das hier jeweils zu berücksichtigen.

50:53.350 --> 50:57.810
Was sind die Aufgaben des aufgerufenen Unterprogramms?

50:58.190 --> 51:02.970
Das Unterprogramm muss Speicherplatz reservieren für einen größeren

51:02.970 --> 51:07.010
Rahmen, in dem auch hier vom Stack Pointer subtrahiert wird.

51:07.510 --> 51:10.390
Die Register S0 bis S7 müssen gesichert werden, falls sie verwendet

51:10.390 --> 51:10.690
werden.

51:11.330 --> 51:15.410
Die Rücksprungadresse, und das ist sehr wichtig hier, muss zusätzlich

51:15.410 --> 51:16.190
gesichert werden.

51:17.710 --> 51:22.330
Wenn ich das nicht mache, kann es passieren, dass wenn ich rekursive

51:22.330 --> 51:24.510
Aufrufe habe, zum Beispiel, dass ich meine Rücksprungadresse

51:24.510 --> 51:27.530
irgendwann verliere und dann funktioniert mein Programm nicht mehr.

51:28.110 --> 51:30.990
Das heißt, auch die Rücksprungadresse muss gesichert werden und dieser

51:30.990 --> 51:33.290
Rahmenzeiger FP auch.

51:35.190 --> 51:42.110
Den neuen Rahmenzeiger erhalte ich durch Addition der Rahmengröße zum

51:42.110 --> 51:42.870
Stapelzeiger.

51:43.150 --> 51:45.190
Das sehen wir dann gleich, was damit gemeint ist.

51:46.610 --> 51:49.650
Wenn ich ein Unterprogramm aufrufe, dann sind mein Stack Pointer und

51:49.650 --> 51:51.010
mein Frame Pointer identisch.

51:51.150 --> 51:53.650
Die zeigen an der ersten freien Stelle.

51:54.930 --> 51:57.550
Ich reserviere jetzt Speicher auf dem Stack Pointer.

51:57.730 --> 52:00.330
Das heißt, ich subtrahiere vom Stack Pointer eine bestimmte Anzahl von

52:00.330 --> 52:00.690
Bytes.

52:01.250 --> 52:03.330
Mein Stack Pointer zeigt dann auf die nächste freie Stelle, die ist

52:03.330 --> 52:03.990
dann weiter unten.

52:05.610 --> 52:10.030
Und ich kann jetzt ausgehend davon lokale Variablen, gesicherte

52:10.030 --> 52:13.730
Register oder verschiedene Argumente auf meinem Stack ablegen.

52:19.090 --> 52:21.910
Mein Frame Pointer hier ist noch identisch, also der zeigt immer noch

52:21.910 --> 52:27.330
an die ursprüngliche erste freie Stelle vor dem Aufruf.

52:28.330 --> 52:30.170
Und hier sieht man, dass sich der Stack Pointer geändert hat.

52:34.390 --> 52:37.450
Diesen Speicherbereich, wo meine Argumente, meine gesicherten Register

52:37.450 --> 52:41.090
oder die lokalen Variablen liegen, das nennt man den Stack Rahmen oder

52:41.090 --> 52:41.790
den Stack Frame.

52:45.070 --> 52:50.490
Beispiel für ein Unterprogrammaufruf, das x-te Beispiel.

52:53.130 --> 52:56.490
Hier haben wir die Sicherung, also hier reserviere ich mir 8 Byte von

52:56.490 --> 53:04.150
meinem Stack, sichere RA und FP auf meinem Stack, addiere dann auf den

53:04.150 --> 53:07.170
Stack Pointer 8 und speichere das in meinem Frame Pointer.

53:09.270 --> 53:10.610
Gehen wir nochmal kurz zurück.

53:11.850 --> 53:14.470
Ich habe hier einen Frame Pointer, der zeigt auf meine erste freie

53:14.470 --> 53:16.670
Stelle, also ist identisch mit meinem Stack Pointer.

53:18.710 --> 53:22.810
Ich schreibe jetzt aber Stack Pointer plus x in den Frame Pointer.

53:27.460 --> 53:29.940
Also das ist die Addition, die ich vorhin gemeint habe.

53:30.960 --> 53:32.280
Warum muss ich das jetzt hier machen?

53:34.320 --> 53:38.260
Das Problem ist ja, dass ich den Frame Pointer zusätzlich ablegen

53:38.260 --> 53:42.000
muss, die ursprüngliche, auf meinem Stack.

53:49.170 --> 53:51.050
Eigentlich ändere ich ihn nicht, eigentlich könnte ich das genauso

53:51.050 --> 53:51.390
lassen.

53:52.810 --> 53:53.770
Das sehe ich gerade.

53:58.940 --> 54:01.640
Das heißt, ich kann nicht davon ausgehen, dass mein Frame Pointer

54:01.640 --> 54:06.900
tatsächlich hier an dieser Stelle zeigt, sondern erst durch diese

54:06.900 --> 54:10.420
Zeile zeigt mein Frame Pointer dann tatsächlich an die Anfangsadresse

54:10.420 --> 54:11.040
meines Stacks.

54:11.840 --> 54:15.980
Das ist die einzige Interpretation, die das Ganze erklärt.

54:19.880 --> 54:24.160
Wenn ich hier eine Subroutine aufrufe, dann kann der Frame Pointer

54:24.160 --> 54:26.400
natürlich irgendwo hin zeigen, aber ich möchte dieses irgendwo hin

54:26.400 --> 54:26.840
speichern.

54:27.580 --> 54:31.740
Daher dieses Store Word Frame Pointer auf dem Stack und ich sorge dann

54:31.740 --> 54:34.080
hier dafür, dass mein Frame Pointer tatsächlich auch an dieser Stelle

54:34.080 --> 54:34.400
zeigt.

54:35.980 --> 54:37.420
Anschließend mache ich meinen Jump and Link.

54:37.580 --> 54:38.900
Also ich rufe meine Subroutine auf.

54:39.220 --> 54:44.260
Wenn ich hier wieder rauskomme, dann hole ich mir wieder das RAA

54:44.260 --> 54:50.760
-Register und das FP-Register vom Stack, gebe den Speicher auf dem

54:50.760 --> 54:58.400
Stack wieder frei und springe wieder zurück auf meine

54:58.400 --> 54:59.120
Rücksprungadresse.

54:59.120 --> 55:02.140
Das funktioniert dadurch nicht, wenn ich in dem Hauptprogramm hier

55:02.140 --> 55:05.820
bin, sondern das müsste hier dann ein Unterprogramm sein.

55:11.230 --> 55:13.850
Und hier

55:17.230 --> 55:25.150
habe ich dann einen Subprogrammaufruf aus einem Subprogramm und nach

55:25.150 --> 55:27.110
Konvention funktioniert das dann auch tatsächlich.

55:27.810 --> 55:29.710
Es müsste demnächst ein Beispiel dafür kommen.

55:36.320 --> 55:41.160
Hier haben wir nochmal den Files-Register FP2, FP4.

55:41.320 --> 55:45.940
Das sind meine FPU-Register S0 und S1, die CPU-Register, verändert

55:45.940 --> 55:46.340
werden.

55:46.880 --> 55:49.410
Dann müsste man die vorher sichern in meinem Unterprogramm.

55:49.410 --> 55:56.710
Das heißt, ich sichere mir hier 32 Bytes Speicher auf meinem Stack.

55:57.730 --> 56:02.130
Floating Points, haben wir gesagt, sind jeweils 8 Bytes statt 4.

56:02.250 --> 56:07.410
Das heißt, hier habe ich 8, 8 sind wir bei 16, plus nochmal 8 hier

56:07.410 --> 56:13.270
sind wir bei 24, plus die zwei Register RAA plus FP, dann sind wir bei

56:13.270 --> 56:15.450
32 Bytes insgesamt.

56:16.050 --> 56:17.910
Ich sichere mir 32 Bytes.

56:18.070 --> 56:22.230
Auf meinem ersten Stelle lege ich meinen F2 ab.

56:23.270 --> 56:25.270
Das sind dann jeweils 8 Bytes.

56:25.670 --> 56:28.030
Das heißt, die nächste freie Stelle ist dann bei 12.

56:28.470 --> 56:32.530
Hier lege ich meinen F4 ab oder FP4 in dem Fall.

56:33.650 --> 56:35.050
Das sind dann nochmal 8 Bytes.

56:35.170 --> 56:36.690
Das heißt, die nächste freie Stelle sind 20.

56:37.310 --> 56:41.190
Danach kommt S0, S1, RAA und FP und dann bin ich bei 32 Bytes.

56:41.190 --> 56:43.550
Wenn ich jetzt hier irgendetwas verändere, ich fülle jetzt

56:43.550 --> 56:47.650
irgendwelche Berechnungen in meinem Unterprogramm aus, bin fertig, in

56:47.650 --> 56:51.410
der gleichen Reihenfolge hole ich mir dann meine Variablen wieder von

56:51.410 --> 56:51.910
meinem Stack.

56:52.190 --> 56:55.590
Das heißt, ich hole mir F2 von der Stelle 4, wie auch dort abgelegt,

56:56.010 --> 57:01.130
F4 von der Stelle 12, S0, S1, RAA, FP, gebe den Speicher auf dem Stack

57:01.130 --> 57:03.370
wieder frei und springe dann wieder zurück auf meine

57:03.370 --> 57:06.450
Rücksprungadresse, die gesichert wurde durch den JAL-Befehl.

57:10.640 --> 57:14.340
Also, hier nochmal visuell dargestellt.

57:14.800 --> 57:25.500
Ich habe hier FP2 oder F2, F4, jeweils S0, S1, RAA und FP, was ich auf

57:25.500 --> 57:26.240
dem Stack ablege.

57:26.340 --> 57:28.000
Das passiert hier im ersten Block.

57:28.000 --> 57:37.300
Dann verändere ich irgendetwas und hole mir die Variablen wieder von

57:37.300 --> 57:40.000
meinem Stack zurück, gebe den Speicher vom Stack dann wieder frei.

57:44.310 --> 57:48.390
So, ein Problem, was wir schon angesprochen haben, sind rekursive

57:48.390 --> 57:49.970
Aufrufe.

57:51.710 --> 57:55.330
Bei rekursiven Aufrufen wird natürlich auch die Rücksprungadresse in

57:55.330 --> 57:58.970
meinem RAA-Register gespeichert, da ich aber sukzessive

57:58.970 --> 58:02.010
Unterprogrammaufrufe mache, müsste ja mein RAA-Register überschrieben

58:02.010 --> 58:02.290
werden.

58:02.290 --> 58:09.730
Dafür ist natürlich wichtig, dass man dieses RAA-Register sichert und

58:09.730 --> 58:11.650
diese Sicherung kann nur auf dem Stack erfolgen.

58:13.890 --> 58:15.650
Wir wollen ein kleines Beispiel machen.

58:16.150 --> 58:20.070
Fakultät, klassische rekursive Aufrufprogramm.

58:21.150 --> 58:22.390
Was passiert hier?

58:23.210 --> 58:26.130
Ich habe einmal die Main-Methode, die mir sagt, die Fakultät wohnt 10

58:26.130 --> 58:28.130
ist und ruft dann die Fakultät 10 auf.

58:28.130 --> 58:32.170
Wenn ich Fakultät 10 betrachte, also die Subroutine Fakultät 10

58:32.170 --> 58:36.490
betrachte, dann prüft die zunächst, ob n kleiner ist als 1.

58:37.190 --> 58:38.830
Wenn ja, wird eine 1 zurückgeliefert.

58:38.950 --> 58:42.870
Ansonsten wird Fakultät mit sich selber aufgerufen.

58:43.990 --> 58:46.970
Im ersten Fall ist es natürlich n gleich 10.

58:47.330 --> 58:54.310
Das heißt, ich habe hier eine 10 mal Fakultät von n-1, also 9.

58:58.610 --> 59:00.930
Beim zweiten Aufruf ist n gleich 9.

59:01.390 --> 59:03.670
Wird geprüft, ist es kleiner als 1, ist es nicht.

59:03.930 --> 59:12.290
Also 9 mal Fakultät 8 und so weiter.

59:12.290 --> 59:20.710
Bis man dann bei Fakultät 1 ankommt und Fakultät 1 ruft dann Fakultät

59:20.710 --> 59:21.330
0 auf.

59:21.670 --> 59:33.560
Also hat man hier mal 1 und Fakultät 0 wird dann in dem Fall als 1

59:33.560 --> 59:34.180
zurückgegeben.

59:34.620 --> 59:41.260
Das heißt, ich bekomme hier als Ergebnis 10 mal 9 mal 8 mal 1

59:41.260 --> 59:41.620
irgendwann.

59:44.620 --> 59:46.540
Also ich glaube, das ist klar, da brauchen wir nicht weiter drauf

59:46.540 --> 59:46.920
eingehen.

59:47.640 --> 59:49.800
Interessant für uns ist natürlich, wie ist die Ausführung im MIPS

59:49.800 --> 59:52.360
-Assembler oder wie sieht das Programm im MIPS-Assembler aus.

59:52.960 --> 59:55.620
Ich würde hier ein Unterprogramm haben, das heißt Fakultät.

59:57.180 --> 59:58.740
Hier die klassischen Blöcke.

59:59.500 --> 01:00:00.680
Einmal das Sichern.

01:00:03.480 --> 01:00:06.100
Den Speicher auf dem Stack reservieren von 8 Bytes.

01:00:06.300 --> 01:00:08.200
Sichern von RA, sichern von A0.

01:00:09.340 --> 01:00:14.200
A0 ist in dem Fall mein Eingabe-Parameter, also mein Register, also

01:00:14.200 --> 01:00:15.920
mein Argument, das mitgegeben wurde.

01:00:17.140 --> 01:00:19.500
Und wenn ich fertig bin, dann hole ich mir natürlich wieder RA.

01:00:29.970 --> 01:00:35.830
Und wenn ich fertig bin, brauche ich meinen Eingabe-Parameter nicht

01:00:35.830 --> 01:00:38.770
mehr, das habe ich schon verarbeitet, aber ich muss meinen RA wieder

01:00:38.770 --> 01:00:39.330
rücksichern.

01:00:42.490 --> 01:00:46.670
Und natürlich der Rücksprung aus der Untermethode.

01:00:48.190 --> 01:00:51.510
Das hier ist der Vergleich, ob mein Eingabe-Parameter kleiner ist als

01:00:51.510 --> 01:00:51.950
1.

01:00:52.150 --> 01:00:52.990
Falls ja,

01:00:56.710 --> 01:00:58.270
gehe ich zu L1.

01:00:59.890 --> 01:01:08.190
Falls A0 kleiner ist als 1, setze T0 gleich 1.

01:01:09.170 --> 01:01:13.870
Also in dem Fall ist A0 nicht kleiner als 1, also ist größer gleich 1,

01:01:13.970 --> 01:01:15.130
dann springe ich zu L1.

01:01:16.110 --> 01:01:18.910
Falls es kleiner ist als 1, wird das hier ausgeführt.

01:01:19.150 --> 01:01:21.450
Hier wird einfach die 1 in V1 gesetzt.

01:01:21.990 --> 01:01:26.390
Also das Ergebnis einer Fakultät 0 oder Fakultät 1 ist in dem Fall 1.

01:01:28.230 --> 01:01:32.630
Falls nicht ein direktes Ergebnis erzielt werden kann, wird zur Marke

01:01:32.630 --> 01:01:33.470
L1 gesprungen.

01:01:33.470 --> 01:01:34.970
Was macht L1?

01:01:35.470 --> 01:01:38.630
L1 subtrahiert zunächst von dem Argument A0 die 1.

01:01:38.770 --> 01:01:40.790
Das können wir machen, weil wir A0 schon gesichert haben.

01:01:40.970 --> 01:01:43.910
Das heißt, für den vorherigen Aufruf wird nichts verändert.

01:01:45.710 --> 01:01:49.410
Dann ruft man sich selber hier nochmal auf mit Jump & Link Fakultät.

01:01:50.970 --> 01:01:54.190
Und wenn man rauskommt, holt man sich die Argumente vom Stack.

01:01:54.330 --> 01:01:58.870
Das ist hier A0 und RA.

01:02:06.300 --> 01:02:11.560
Addiert die 8 auf den Stack-Pointer, also gibt den Stack-Speicher

01:02:11.560 --> 01:02:12.120
wieder frei.

01:02:15.060 --> 01:02:15.740
Und

01:02:20.080 --> 01:02:32.960
die eigentliche Operation natürlich, Argument 0 mit V0 zu

01:02:32.960 --> 01:02:36.420
multiplizieren und das Ergebnis in V0 zu speichern und dann wieder

01:02:36.420 --> 01:02:37.040
rückzuspringen.

01:02:37.040 --> 01:02:43.560
Also das ist genau das gleiche Programm wie wir vorhin in C gesehen

01:02:43.560 --> 01:02:43.800
haben.

01:02:52.980 --> 01:02:55.400
Wenn wir uns jetzt überlegen, was passiert genau.

01:02:55.600 --> 01:03:00.180
Ich habe meinen Stack, ich rufe mein Main auf und meine Main-Methode

01:03:00.180 --> 01:03:03.320
ruft das erste Unterprogramm auf mit RA.

01:03:04.960 --> 01:03:09.080
Beim Aufruf dieser Main-Methode wird natürlich RA und mein Frame

01:03:09.080 --> 01:03:11.440
-Pointer gespeichert auf dem Stack.

01:03:12.520 --> 01:03:16.040
Beim Aufruf von Fakultät 10 wird zuerst die Rücksprung-Adresse und das

01:03:16.040 --> 01:03:19.480
Argument 0 gespeichert und danach sich selbst aufgerufen.

01:03:19.900 --> 01:03:23.280
Der nächste Aufruf speichert wieder RA und A0 und der nächste wieder

01:03:23.280 --> 01:03:24.640
RA und A0 und so weiter.

01:03:24.640 --> 01:03:30.660
Wenn man jetzt jeweils aus den einzelnen Unterprogramm-Aufrufen hier

01:03:30.660 --> 01:03:33.560
rausspringt, wird jeweils der Stack-Speicher wieder freigegeben und

01:03:33.560 --> 01:03:34.940
die Argumente werden wieder nachgeladen.

01:03:35.860 --> 01:03:44.840
Das heißt, der Stack wächst sukzessive bei jedem Aufruf und wenn man

01:03:44.840 --> 01:03:49.580
hier wieder auflöst, das heißt, wenn man aus diesen verlinkten

01:03:49.580 --> 01:03:52.480
Aufrufen wieder herausspringt, wird der Stack wieder freigegeben.

01:03:56.810 --> 01:03:58.750
Also, zusammenfassend.

01:04:00.150 --> 01:04:04.390
Das aufrufende Programm muss die Argumente übergeben, muss die

01:04:04.390 --> 01:04:08.010
temporären Register sichern und muss den Sprungbefehl ausführen.

01:04:09.710 --> 01:04:13.610
Das Unterprogramm selber muss sich den Rahmen allokieren, muss

01:04:13.610 --> 01:04:16.870
langlebige Register sichern und neuen Rahmenzeiger setzen.

01:04:19.210 --> 01:04:20.690
Dann wird irgendwas gemacht.

01:04:21.310 --> 01:04:23.770
Die Resultate werden gesichert in den Rückgaberegistern.

01:04:24.210 --> 01:04:26.570
Alles, was gesichert wurde, muss wieder hergestellt werden.

01:04:27.030 --> 01:04:30.210
Der Rahmen wird dialogiert und man springt wieder zurück ins

01:04:30.210 --> 01:04:35.890
Hauptprogramm mit dem RA-Register und das Hauptprogramm selber wird

01:04:35.890 --> 01:04:38.730
dann die temporären Register, die es vorher hier gesichert hat, wieder

01:04:38.730 --> 01:04:39.150
herstellen.

01:04:40.950 --> 01:04:42.670
Wichtig ist hier, dass man konsistent bleibt.

01:04:42.830 --> 01:04:45.750
Wenn man mehr auf dem Stack freigibt, als man reserviert hat, dann

01:04:45.750 --> 01:04:47.490
führt man natürlich irgendwann zu einem Speicherfehler.

01:04:49.070 --> 01:04:52.430
Wenn man mehr reserviert, als man freigibt, dann auch, weil irgendwann

01:04:52.430 --> 01:04:55.170
der Stack dann überläuft und dann hat man so einen Stackoverflow.

01:04:58.670 --> 01:05:00.670
Okay, dazu Fragen.

01:05:05.020 --> 01:05:06.020
Keine Fragen.

01:05:06.900 --> 01:05:09.500
Machen wir weiter mit Ausnahmen-Unterbrechungsbehandlung.

01:05:10.620 --> 01:05:14.040
Wir reden einmal von Ausnahmefehlern oder Exceptions und einmal von

01:05:14.040 --> 01:05:15.720
Unterbrechungen, Interrupts.

01:05:18.220 --> 01:05:19.560
Was ist eine Ausnahme?

01:05:20.620 --> 01:05:23.160
Jede unerwartete Änderung im Kontrollfluss ist eine Ausnahme.

01:05:24.300 --> 01:05:26.800
Es ist meistens synchron zum Programmablauf.

01:05:26.980 --> 01:05:29.620
Das heißt, man hat bestimmte Befehle, die dann diese Exceptions

01:05:29.620 --> 01:05:30.220
aufrufen.

01:05:31.000 --> 01:05:32.320
Die sind dann im Programm selber.

01:05:33.360 --> 01:05:36.520
Was können diese Fehler verursachen oder diese Ausnahmen verursachen?

01:05:37.080 --> 01:05:40.700
Ungültige Befehle, zum Beispiel arithmetische Fehler oder man kann sie

01:05:40.700 --> 01:05:41.420
auch selber werfen.

01:05:43.000 --> 01:05:46.020
Was ist ein Interrupt, also eine Unterbrechung?

01:05:47.600 --> 01:05:51.220
Interrupts werden meist durch äußere Einflüsse verursacht, sind

01:05:51.220 --> 01:05:54.460
asynchron zum Programmablauf, also haben mit dem Programmablauf selber

01:05:54.460 --> 01:06:02.080
in der Regel nichts zu tun, müssen durch das Betriebssystem behandelt

01:06:02.080 --> 01:06:02.380
werden.

01:06:02.380 --> 01:06:05.940
Und hier ein Beispiel wäre zum Beispiel Tastatur- und Mauseingaben,

01:06:06.100 --> 01:06:07.860
die verursachen jeweils Interrupts.

01:06:07.960 --> 01:06:10.340
Das heißt, während ein Programm läuft, drücke ich eine Taste.

01:06:10.840 --> 01:06:14.560
Das aktuelle laufende Programm muss unterbrochen werden und es wird

01:06:14.560 --> 01:06:19.020
dann an eine bestimmte Stelle gesprungen im Code, der dann dafür

01:06:19.020 --> 01:06:21.860
sorgt, dass diese Eingabe verarbeitet wird.

01:06:23.380 --> 01:06:28.040
Das sind die Unterschiede zwischen Ausnahmen und Unterbrechungen oder

01:06:28.040 --> 01:06:29.660
zwischen Exception und Interrupt.

01:06:30.140 --> 01:06:32.480
Bei Exception redet man auch manchmal von Traps.

01:06:34.540 --> 01:06:36.320
Wie sehen MIPS-Ausnahmen aus?

01:06:36.700 --> 01:06:39.100
Es gibt verschiedene Codes für verschiedene Ausnahmen.

01:06:39.660 --> 01:06:42.400
Einmal gibt es den Code 0 für externe Unterbrechungen.

01:06:42.600 --> 01:06:45.580
Das heißt, wenn Interrupts stattfinden, dann wird hier für die

01:06:45.580 --> 01:06:47.360
Exception ein Code 0 eingetragen.

01:06:48.740 --> 01:06:53.180
Sind fehlerhafte Adressen beim Laden oder Befehl holen, tritt sowas

01:06:53.180 --> 01:06:56.640
auf, hat man hier die Exception mit dem Code 4, fehlerhafte Adresse

01:06:56.640 --> 01:06:57.800
beim Speichern Code 5.

01:06:58.800 --> 01:07:05.890
Die 6 ist ganz wichtig, weil hier Busfehler beim Befehlholen auftreten

01:07:05.890 --> 01:07:06.190
können.

01:07:07.690 --> 01:07:11.090
Das heißt, wenn ich einen Befehl holen will und der Befehl kommt nicht

01:07:11.090 --> 01:07:15.050
richtig an, dann heißt das, dass der nächste Befehl wahrscheinlich ein

01:07:15.050 --> 01:07:18.250
ungültiger Befehl ist oder dass der nächste Befehl nicht mehr das ist,

01:07:18.290 --> 01:07:19.250
was man eigentlich ausführen will.

01:07:19.250 --> 01:07:21.990
Und hier ist eine Fortsetzung des Programms, wird dann ein bisschen

01:07:21.990 --> 01:07:22.390
schwierig.

01:07:22.550 --> 01:07:24.890
Bei allen anderen Fehlern kann man tatsächlich das Programm einfach

01:07:24.890 --> 01:07:25.830
weiter fortführen.

01:07:26.270 --> 01:07:29.190
Die korrekte Ausführung ist natürlich dann so oder so nicht

01:07:29.190 --> 01:07:30.370
gewährleistet.

01:07:32.510 --> 01:07:34.430
Exception 7, Busfehler beim Laden und Speichern.

01:07:34.650 --> 01:07:35.990
Und jetzt haben wir hier die 8 und die 9.

01:07:36.390 --> 01:07:43.650
Das sind Software Exceptions, also Systemaufrufe oder Breakpoints bei

01:07:43.650 --> 01:07:44.270
der Fehlersuche.

01:07:44.750 --> 01:07:48.290
Das heißt, wenn ihr dieses PrintInt ausführen wollt und den Syscall

01:07:48.290 --> 01:07:51.990
entsprechend ausführt, wird hier ein Exception mit dem Code 8

01:07:51.990 --> 01:07:52.550
ausgeführt.

01:07:52.550 --> 01:07:56.710
Wenn ich verschiedene Breaks einfüge in meinen Code, dann wird die

01:07:56.710 --> 01:07:59.110
Exception mit dem Code 9 ausgeführt.

01:08:00.770 --> 01:08:03.770
Reservierter Befehl heißt, wir können nochmal durchgehen, ich glaube,

01:08:03.830 --> 01:08:05.470
es gibt für jeden Code nochmal eine Aufschlüsselung.

01:08:05.790 --> 01:08:08.570
Externe Unterbrechungen, haben wir gesagt, sind Interrupts, also

01:08:08.570 --> 01:08:10.930
Drücken einer Taste, die Maus wurde bewegt, ein bestimmter Timer ist

01:08:10.930 --> 01:08:14.810
abgelaufen, Drucker ist fertig, jetzt im allgemeinen Fall natürlich.

01:08:15.850 --> 01:08:18.230
Oder man hat irgendwelche seriellen Datenempfang von der seriellen

01:08:18.230 --> 01:08:18.710
Schnittstelle.

01:08:21.190 --> 01:08:29.120
Fehlerhafte Adresse könnte zum Beispiel sein, dass man hier einen

01:08:29.120 --> 01:08:34.240
String hat im Speicher, der belegt 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

01:08:34.240 --> 01:08:41.900
Zeichen und es wird natürlich hier mit Z abgeschlossen.

01:08:42.040 --> 01:08:52.050
Das heißt, es gibt noch einen Nullcode, das ein Byte lang ist.

01:08:54.370 --> 01:08:57.370
Und jetzt habe ich ein Resultat oder einen Datenbereich, der ist

01:08:57.370 --> 01:09:01.530
adressiert mit Result und auf dieses Result greife ich dann zu und

01:09:01.530 --> 01:09:05.430
möchte dann hier ein Wort laden und wir haben gesagt, Wörter müssen

01:09:05.430 --> 01:09:09.410
aligned sein im Speicher, da ich hier aber eine Speicheradresse habe,

01:09:09.490 --> 01:09:13.550
die wahrscheinlich mit 11 anfängt, also ist hier ein Alignmentfehler

01:09:13.550 --> 01:09:17.490
und dann hat man hier Code 4 oder Code 5, je nachdem, ob man lesen

01:09:17.490 --> 01:09:18.110
oder schreiben will.

01:09:20.190 --> 01:09:23.650
Busfehler, wenn ein Timeout auftritt, wenn Übertragungsfehler wie

01:09:23.650 --> 01:09:28.110
Paritätsfehler noch zu, erinnert ihr euch bestimmt noch daran aus DT,

01:09:28.510 --> 01:09:31.270
wenn Paritätsfehler auftreten, das heißt die Fehlerkennung anspringt

01:09:31.270 --> 01:09:34.610
oder ungültige Port- oder Speicheradressen auftreten.

01:09:35.350 --> 01:09:36.930
Systemcall haben wir gehabt, ist die 8.

01:09:37.430 --> 01:09:38.910
Breakpoints ist Code 9.

01:09:39.770 --> 01:09:42.930
Reservierter Befehl heißt, man versucht ein Befehl auszuführen oder

01:09:42.930 --> 01:09:46.250
ein Befehl wurde jetzt in das Befehlsregister geladen, das einen

01:09:46.250 --> 01:09:47.310
ungültigen Opcode hat.

01:09:48.110 --> 01:09:51.170
Und dieser Opcode ist nicht definiert oder wird nicht erkannt, dann

01:09:51.170 --> 01:09:52.390
wird diese Exception geworfen.

01:09:53.910 --> 01:09:57.550
Arithmetischer Überlauf bei Ganzzahlberechnungen Code 12, wenn ich

01:09:57.550 --> 01:10:01.870
Zweierkomplementzahlen addiere zum Beispiel und das Ergebnis sich

01:10:01.870 --> 01:10:05.210
nicht mehr in dieser Anzahl von Bits im Zweierkomplement darstellen

01:10:05.210 --> 01:10:08.690
lässt, dann wird hier ein Überlauf geworfen und so weiter.

01:10:10.570 --> 01:10:14.510
So, für die Behandlung von Ausnahmen und Interrupts schauen wir uns

01:10:14.510 --> 01:10:16.910
nochmal an, wie unser MIPS Prozessor aussieht.

01:10:17.010 --> 01:10:19.790
Wir haben ja bisher sehr intensiv die Zentraleinheit, also die CPU

01:10:19.790 --> 01:10:24.430
behandelt mit ihren Registern, der ALU mit den speziellen Low- und

01:10:24.430 --> 01:10:25.990
High -Registern und dem Programme-Counter.

01:10:27.390 --> 01:10:29.990
Dann gibt es einen Co-Prozessor 1, die Floating-Point-Unit, die

01:10:29.990 --> 01:10:34.450
einfach Floating-Points in Single- oder Double-Precision nochmal

01:10:34.450 --> 01:10:37.690
verarbeiten kann, jeweils mit Addition, Subtraktion, Multiplikation

01:10:37.690 --> 01:10:38.170
und so weiter.

01:10:38.750 --> 01:10:41.550
Und hier das Wichtige für uns natürlich für die Ausnahmebehandlung ist

01:10:41.550 --> 01:10:45.630
der Co-Prozessor 0, der ist nämlich für diese Exceptions zuständig und

01:10:45.630 --> 01:10:52.830
der beinhaltet für uns wichtig eigentlich nur vier Register, einmal

01:10:52.830 --> 01:10:55.770
die Bad Virtual Address, Status, Cores und EPC.

01:10:57.930 --> 01:11:02.830
Bad Virtual Address ist ein Register, das bei Speicherzugriffsfehlern

01:11:02.830 --> 01:11:08.550
die Adresse dieses fehlerhaften Speicherzugriffs oder diesen

01:11:08.550 --> 01:11:12.310
Speicherzugriffs, der diesen Fehler hervorgerufen hat, beinhaltet.

01:11:12.610 --> 01:11:15.130
Also hier ist dann die Adresse drin von dieser schlechten Adresse.

01:11:16.010 --> 01:11:19.030
Das Cores Register sieht ein bisschen anders aus.

01:11:19.210 --> 01:11:24.630
Hier gibt es noch bestimmte Bits, die gesetzt werden oder die für uns

01:11:24.630 --> 01:11:26.490
wichtig sind als Information.

01:11:27.850 --> 01:11:30.350
Einmal hat man hier die anstehenden Unterbrechungen, also die

01:11:30.350 --> 01:11:32.810
kommenden Interrupts, insgesamt acht Bits.

01:11:33.610 --> 01:11:36.950
Und hier kann man verschiedene Level von Interrupts definieren, also

01:11:36.950 --> 01:11:38.890
verschiedene Prioritätenleveln auch definieren.

01:11:40.570 --> 01:11:44.010
Und man hat einmal die Unterbrechungsursache, also den Code, den wir

01:11:44.010 --> 01:11:48.970
vorhin gesehen haben, für Exceptions nochmal codiert von Bit 2 bis Bit

01:11:48.970 --> 01:11:53.310
6, jeweils mit fünf Bit dann, also insgesamt mit fünf Bit.

01:11:56.190 --> 01:11:59.770
So, das EPC-Register ist die Exception Program Counter, also

01:11:59.770 --> 01:12:02.910
beinhaltet die Adresse, in der sich der Befehl, der zu dieser

01:12:02.910 --> 01:12:04.290
Unterbrechung geführt hat, sich befindet.

01:12:05.110 --> 01:12:09.610
Und man hat noch einen Statusregister, gemeint in Register Nummer 12.

01:12:09.990 --> 01:12:11.570
Hier EPC ist Register 14.

01:12:12.490 --> 01:12:14.230
Cores Register ist Register Nummer 13.

01:12:14.490 --> 01:12:16.850
Das ist nachher wichtig, sehen wir dann gleich.

01:12:18.830 --> 01:12:20.730
Statusregister hat hier eine Unterbrechungsmaske.

01:12:20.970 --> 01:12:27.510
Es gibt hier nochmal Bits mit Kernel oder Usermode jeweils, was

01:12:27.510 --> 01:12:34.290
aktuell der Status ist, was vor dem Aufruf war und was noch davor,

01:12:34.370 --> 01:12:37.410
also noch älter ist, wie da der Status war.

01:12:37.550 --> 01:12:38.670
Das sind die Bits 0 bis 5.

01:12:40.090 --> 01:12:44.730
Es sind immer zwei Pärchen, einmal Kernel oder Usermode und einmal, ob

01:12:44.730 --> 01:12:47.470
Unterbrechungen, Interrupts erlaubt sind oder nicht erlaubt sind.

01:12:49.210 --> 01:12:54.710
Das ist wichtig, weil, stellt euch vor, ich habe einen Interrupt, der

01:12:54.710 --> 01:12:57.630
mir eine Tasteneingabe abfangen soll und diese Tasteneingabe

01:12:57.630 --> 01:12:58.410
verarbeiten soll.

01:12:59.370 --> 01:13:03.230
Während aber diese Tasteneingabe verarbeitet wird, drücke ich noch

01:13:03.230 --> 01:13:03.710
eine Taste.

01:13:04.610 --> 01:13:05.430
Was passiert jetzt?

01:13:05.910 --> 01:13:08.590
Der Interrupt wird dann nochmal ausgelöst, sprich nochmal in die

01:13:08.590 --> 01:13:10.610
gleiche Subroutine, was passiert mit dem vorherigen?

01:13:11.210 --> 01:13:16.270
Und hier ist es ein bisschen schwieriger, weil man ja asynchron ist,

01:13:16.310 --> 01:13:19.250
gibt es nicht die Möglichkeit tatsächlich zu sagen, ja, ich lege jetzt

01:13:19.250 --> 01:13:20.950
alles auf den Stack und rufe es dann nochmal auf.

01:13:21.110 --> 01:13:21.830
Da wird es ein bisschen schwierig.

01:13:22.770 --> 01:13:25.370
Hier ist noch vorgesehen, dass diese Methoden tatsächlich nur einmal

01:13:25.370 --> 01:13:26.050
ausgeführt werden.

01:13:26.510 --> 01:13:30.490
Das heißt, es ist sehr wichtig, wenn man in einem Interrupt, wenn da

01:13:30.490 --> 01:13:35.810
eine Interrupt-Behandlung ist, dass man diese Unterbrechungen

01:13:35.810 --> 01:13:36.730
verbietet einfach.

01:13:37.550 --> 01:13:40.670
Also man will nicht, dass Interrupts zweimal gleichzeitig ausgeführt

01:13:40.670 --> 01:13:40.930
werden.

01:13:40.930 --> 01:13:43.710
Das heißt, ich drücke eine Taste, der Interrupt wird ausgelöst.

01:13:43.830 --> 01:13:47.190
Das Erste, was gemacht wird, ist, dass man diesen Interrupt-Flag auf 0

01:13:47.190 --> 01:13:47.510
setzt.

01:13:47.850 --> 01:13:50.090
Dadurch wird verhindert, dass Interrupts nochmal ausgeführt werden,

01:13:50.690 --> 01:13:53.230
bis meine Behandlung des Interrupts fertig ist.

01:13:53.810 --> 01:13:57.430
Danach kann ich das Bit wieder auf 1 setzen und wieder Interrupts

01:13:57.430 --> 01:13:57.790
erlauben.

01:13:59.070 --> 01:14:03.070
Da diese Ausführungen natürlich oder die Behandlungen von

01:14:03.070 --> 01:14:07.910
verschiedenen Eingaben im Kernel-Mode passieren müssen, ist natürlich

01:14:07.910 --> 01:14:13.510
auch wichtig, dass man den Stand des Programms vorher abfängt und sich

01:14:13.510 --> 01:14:17.250
dann merkt, war mein Programm im Kernel-Mode oder war es im User-Mode?

01:14:17.670 --> 01:14:19.210
Und das sichert man dann auch hier.

01:14:21.830 --> 01:14:24.810
Jetzt gibt es natürlich die Möglichkeit, mit diesem Jetzt, Vorher und

01:14:24.810 --> 01:14:28.290
Alt die Sachen zu speichern, den Modus zu speichern.

01:14:31.150 --> 01:14:34.110
Angenommen, ich habe hier, mein Interrupts waren enabled und ich war

01:14:34.110 --> 01:14:39.350
im User-Mode, dann wird beim Auftreten einer Unterbrechung wird hier

01:14:39.350 --> 01:14:40.250
ein Shift-Left gemacht.

01:14:40.250 --> 01:14:43.170
Das heißt, mein aktueller Stand wird hier gespeichert.

01:14:44.210 --> 01:14:51.750
Hier werden Nullen eingefügt, die dann verbieten, dass man den

01:14:51.750 --> 01:14:53.210
Interrupt nochmal ausführt zum Beispiel.

01:14:55.230 --> 01:15:01.230
Und sobald ich fertig bin, wird alles wieder mit Shift-Right an die

01:15:01.230 --> 01:15:02.470
vorherige Stelle zurückversetzt.

01:15:02.470 --> 01:15:06.950
Das heißt, ich hatte hier meine 01 drin, kann dann hier meine 01

01:15:06.950 --> 01:15:13.110
wieder zurückbekommen und bin dann wieder in dem aktuellen Stand, was

01:15:13.110 --> 01:15:13.670
ich vorher hatte.

01:15:14.510 --> 01:15:20.070
Und diese Rück-, diese Wiederherstellung des vorherigen Zustands macht

01:15:20.070 --> 01:15:21.190
der RFE-Befehl.

01:15:21.330 --> 01:15:25.350
Hier wird ein Return-From-Exception ausgeführt und das Return-From

01:15:25.350 --> 01:15:28.390
-Exception sorgt dafür, dass meine Status-Bits alle erhalten bleiben

01:15:28.390 --> 01:15:32.710
und dass auch meine Kernel-Mode und Interrupt-Enable oder Disable,

01:15:33.170 --> 01:15:35.490
dass das alles wiederhergestellt wird in den ursprünglichen Zustand.

01:15:37.630 --> 01:15:42.390
So, man hat den jetzigen Zustand, man hat den vorherigen Zustand, man

01:15:42.390 --> 01:15:45.770
hat zusätzlich noch so einen Alt-Zustand, der den Zustand vorher

01:15:45.770 --> 01:15:46.290
speichert.

01:15:46.730 --> 01:15:48.210
Alles andere wird allerdings gelöscht.

01:15:48.390 --> 01:15:51.950
Also wenn man diese Aufrufe häufiger hätte hintereinander, dann würde

01:15:51.950 --> 01:15:55.670
es hier überlaufen und man könnte sich nicht alles merken.

01:16:00.590 --> 01:16:03.510
So, Ausnahmebehandlung geschieht meist durch das Betriebssystem.

01:16:03.670 --> 01:16:05.710
Also das Betriebssystem ist zuständig dafür, dass diese

01:16:05.710 --> 01:16:07.430
Unterbrechungen abgehandelt werden.

01:16:10.270 --> 01:16:14.090
Beim MIPS selber ist definiert, dass hier ein Sprung an eine bestimmte

01:16:14.090 --> 01:16:15.030
Adresse stattfindet.

01:16:15.230 --> 01:16:21.690
Diese Abarbeitung dieser Adresse befindet sich meist an 08000 0180

01:16:21.690 --> 01:16:22.550
Hexadezimal.

01:16:24.310 --> 01:16:26.730
Das ist bei MARS, glaube ich, auch standardkonfiguriert.

01:16:26.870 --> 01:16:28.630
Also das ist auch die Standardadresse, in die MARS springt.

01:16:30.370 --> 01:16:33.310
Das Kernel-Segment selber, haben wir ja gesagt, fängt bei 08000 an.

01:16:33.710 --> 01:16:37.990
Das heißt, hier haben wir ein bisschen Offset und hier liegen die

01:16:37.990 --> 01:16:42.750
Unterbrechungsbehandlungsroutinen und hier muss halt alles

01:16:42.750 --> 01:16:43.490
abgearbeitet werden.

01:16:45.430 --> 01:16:47.970
So, das haben wir schon gesagt.

01:16:48.350 --> 01:16:51.090
Für die Ausführung dieser Unterbrechungsbehandlung benötigt man Kernel

01:16:51.090 --> 01:16:51.330
-Rechte.

01:16:51.490 --> 01:16:56.970
Das heißt, da man im Kernel-Modus arbeitet, müssen sich diese

01:16:56.970 --> 01:17:01.630
Abarbeitungsroutinen im Kernel-Segment befinden.

01:17:02.770 --> 01:17:07.070
Wie definiert man jetzt, dass diese Programme im Kernel-Segment sind?

01:17:07.210 --> 01:17:09.570
Man kann hier den k-text verwenden.

01:17:10.770 --> 01:17:14.090
Mit der Assembler-Direktive .k-text sage ich, dass sich jetzt hier ein

01:17:14.090 --> 01:17:17.910
Programm -Segment folgt, das sich im Kernel-Speicher befindet oder

01:17:17.910 --> 01:17:23.330
sich im Kernel befindet, aber hier ein Programm-Segment folgt.

01:17:29.090 --> 01:17:33.630
Im Kernel-Modus kann ich, wie gesagt, den Stack nicht benutzen, weil

01:17:33.630 --> 01:17:37.350
ich da hier, also ich weiß ja nicht, wo ich jetzt her springe.

01:17:37.770 --> 01:17:40.630
Es kann sein, dass ich mitten im Reservieren vom Stack-Speicher,

01:17:40.870 --> 01:17:46.950
mitten beim Speichern oder Sichern von verschiedenen Registern in

01:17:46.950 --> 01:17:48.750
diese Unterbrechungsroutine gesprungen bin.

01:17:48.750 --> 01:17:50.210
Das heißt, hier ist es sehr kritisch.

01:17:50.270 --> 01:17:52.010
Ich darf nicht einfach irgendwas auf dem Stack machen.

01:17:52.630 --> 01:17:56.170
Daher müssen Daten, die jetzt hier beim Einsprung gesichert werden,

01:17:57.090 --> 01:17:59.690
die man sichern will, müssen natürlich auch im Kernel-Datensegment

01:17:59.690 --> 01:18:00.050
liegen.

01:18:00.990 --> 01:18:04.350
Und man macht hier zum Beispiel, wenn man jetzt A0, A1 und die

01:18:04.350 --> 01:18:08.430
Rücksprungadresse sichern will, benötigt man hier verschiedene

01:18:08.430 --> 01:18:10.330
Adressen.

01:18:10.390 --> 01:18:15.290
Die muss man auch nachher definieren mit .k-data und sagen A0 safe

01:18:15.290 --> 01:18:16.970
würde sich dann in .k-data befinden.

01:18:21.430 --> 01:18:24.690
Eine wichtige Sache ist dieses AT-Register.

01:18:25.230 --> 01:18:28.050
Das hat man ganz am Anfang der dritten Übung, glaube ich, gesagt.

01:18:29.150 --> 01:18:33.730
Es gibt die Assembler-Direktive mit .z, wo bestimmte Assembler

01:18:33.730 --> 01:18:35.130
-Variablen gesetzt werden können.

01:18:35.570 --> 01:18:41.110
Eine davon ist dieses NoAT, also die Warnung für das Überschreiben des

01:18:41.110 --> 01:18:45.350
AT -Registers auszuschalten und hier die Warnung wieder einzuschalten.

01:18:46.490 --> 01:18:49.930
Normalerweise, wenn ich jetzt hier kontrolliert natürlich AT

01:18:49.930 --> 01:18:55.790
überschreiben will oder lesen will, dann muss ich die Warnung zunächst

01:18:55.790 --> 01:18:56.450
ausschalten.

01:18:57.010 --> 01:18:59.990
Im Mars-Simulator selber ist diese Direktive aber nicht unterstützt.

01:19:00.130 --> 01:19:02.310
Das heißt, ihr könnt es zwar machen, aber es wird ignoriert.

01:19:02.910 --> 01:19:05.210
Also ihr könnt aber auch AT überschreiben, glaube ich, ohne dass er

01:19:05.210 --> 01:19:06.190
irgendwelche Warnungen ausgibt.

01:19:09.880 --> 01:19:16.540
So, wenn ich jetzt diese verschiedenen Register laden will, wir haben

01:19:16.540 --> 01:19:18.200
ja gesagt, es ist der Co-Prozessor 0.

01:19:18.460 --> 01:19:22.300
Mein Exception Handling und mein Trapping funktioniert hier.

01:19:22.900 --> 01:19:28.660
Das heißt, ich sage move from Co-Prozessor 0 und zwar das Co-Prozessor

01:19:28.660 --> 01:19:34.120
-Register ist das $13 und ich lade es Ihnen hier in $k0.

01:19:34.120 --> 01:19:39.420
Was mein Kernel-Register 0 ist in dem Fall.

01:19:40.600 --> 01:19:42.380
Das gleiche mache ich mit $14, also $13.

01:19:42.760 --> 01:19:43.640
Gehen wir nochmal kurz zurück.

01:19:45.840 --> 01:19:50.480
$13 ist mein Course-Register, also hier ist der Code für meine

01:19:50.480 --> 01:19:55.680
Unterbrechungsursache drin und $14 ist mein Program-Counter, der die

01:19:55.680 --> 01:20:00.020
Exception ausgelöst hat, also da ist die Adresse des nächsten Befehls

01:20:00.020 --> 01:20:00.220
drin.

01:20:01.150 --> 01:20:03.940
Also ich sichere die in meine Kernel-Register $k0, $k1.

01:20:04.760 --> 01:20:12.220
Wenn ich jetzt Interrupts ignorieren will, kann ich folgendes machen.

01:20:13.180 --> 01:20:19.720
Ich kann sagen branch greater than $k0, also $k0 war mein Status oder

01:20:19.720 --> 01:20:21.740
$k0 wurde kopiert aus Status.

01:20:21.740 --> 01:20:25.480
Wenn das Größe ist als 0x44, dann spring nach Fertig.

01:20:26.400 --> 01:20:27.520
Warum die 0x44?

01:20:28.680 --> 01:20:41.020
Wenn ich zurückgehe... 0x44 ist binär, das ist die zweite 4,

01:20:47.090 --> 01:20:49.470
das ist die erste 4.

01:20:52.620 --> 01:20:59.460
Und wenn ich das als Wertigkeit anschaue, bin ich hier bei 1, 2, 4, 8,

01:20:59.820 --> 01:21:02.100
16, also 16 plus 1 bin ich bei 17.

01:21:05.760 --> 01:21:10.240
Im Grunde ist bis 17 alles, was ich als Exception abhandeln will, geht

01:21:10.240 --> 01:21:10.840
bis 17.

01:21:13.880 --> 01:21:21.700
Das heißt, wenn Interrupts aufgetreten sind, haben wir gesagt, der

01:21:21.700 --> 01:21:24.500
Code hier ist eine 0 und der entsprechende Interrupt ist hier

01:21:24.500 --> 01:21:25.080
eingetragen.

01:21:26.500 --> 01:21:31.120
Wenn ich also Interrupts ignorieren will, dann kann ich hier sagen,

01:21:31.220 --> 01:21:35.800
falls das, was in meinem Status-Register drin ist, größer gleich 0x44

01:21:35.800 --> 01:21:37.140
ist, dann springe zu Fertig.

01:21:37.140 --> 01:21:39.500
Und Fertig muss ich natürlich irgendwo definieren.

01:21:40.000 --> 01:21:43.180
Ich kann natürlich auch zur Abhandlung von Exceptions eine

01:21:43.180 --> 01:21:48.460
Untermethode programmieren, die mir dann die verschiedenen Exceptions

01:21:48.460 --> 01:21:52.060
aufschlüsselt und als Text nochmal ausgibt, was da genau passiert ist.

01:21:53.120 --> 01:21:57.600
Hier ist natürlich wichtig, dass ich A0 und A1 vorher sichern müsste.

01:22:00.420 --> 01:22:02.720
Dann hat man hier das Label Fertig.

01:22:02.860 --> 01:22:07.480
Bei Fertig würde ich hier meine gesicherten Variablen wieder auslesen.

01:22:08.040 --> 01:22:11.420
Nicht vom Stack, sondern von den gesicherten Datensegmenten.

01:22:11.420 --> 01:22:13.760
Hier nochmal das AT-Register zurücksetzen.

01:22:14.160 --> 01:22:16.460
Auch hier mit Set NoAT die Warnung ausschalten.

01:22:18.640 --> 01:22:22.160
AT laden aus dem Speicher und die Warnung wieder einschalten.

01:22:24.340 --> 01:22:27.060
Letzter Schritt RFE, Return from Exception.

01:22:27.240 --> 01:22:31.440
Ich sorge dafür, dass meine Register und meine Status, meine Flags

01:22:31.440 --> 01:22:32.380
wiederhergestellt werden.

01:22:33.480 --> 01:22:41.740
Und der letzte, der allerletzte Befehl hier ist, dass ich K1, 4 auf

01:22:41.740 --> 01:22:42.620
die K1 addiere.

01:22:42.620 --> 01:22:48.040
Das heißt, wir erinnern uns, K1 war mein EPC, das war mein Exception

01:22:48.040 --> 01:22:48.920
Program Counter.

01:22:49.060 --> 01:22:52.920
Das heißt, der Befehl, der diese Exception ausgelöst hat, ist in K1.

01:22:53.300 --> 01:22:58.160
Durch eine Addition von 4 springe ich zum nächsten Befehl und kann

01:22:58.160 --> 01:23:03.080
damit die Weiterausführung meines Programms gewährleisten.

01:23:03.080 --> 01:23:06.980
Hier ist natürlich wichtig, klar, es ist nicht immer das, was man

01:23:06.980 --> 01:23:07.140
will.

01:23:07.220 --> 01:23:11.420
Normalerweise will man, dass das Programm anhält, aber hier könnte ich

01:23:11.420 --> 01:23:13.140
gewährleisten, dass es tatsächlich nochmal weitergeht.

01:23:15.300 --> 01:23:16.760
Jump Register K1.

01:23:17.120 --> 01:23:18.560
Ich springe dann wieder zurück an die Stelle.

01:23:19.520 --> 01:23:22.900
Natürlich ist auch wichtig, dass man diese A0 Save, A1 Save, dass man

01:23:22.900 --> 01:23:26.400
diese Daten im Kernel Datensegment definiert.

01:23:26.520 --> 01:23:28.980
Dafür haben wir gesagt, wir brauchen einen Punkt K-Data und dieses

01:23:28.980 --> 01:23:31.020
Punkt K-Data muss natürlich auch irgendwo definiert sein.

01:23:34.180 --> 01:23:35.860
Okay, SetNoAT, AT hatten wir.

01:23:38.260 --> 01:23:40.060
Die Warnung ausschalten und einschalten.

01:23:40.340 --> 01:23:42.720
Wie gesagt, in MARS ist es nicht implementiert, das heißt, ihr bekommt

01:23:42.720 --> 01:23:43.320
keine Warnung.

01:23:45.140 --> 01:23:48.180
Und dann gibt es noch ein SBRK, ein Syscall mit der Nummer 9.

01:23:48.720 --> 01:23:52.780
Der dient dazu, dass man das Datensegment vergrößern kann zwischen

01:23:52.780 --> 01:23:54.260
Stack und Datensegment.

01:23:54.320 --> 01:23:57.320
Wir haben gesagt, das Datensegment wächst von unten nach oben und das

01:23:57.320 --> 01:23:58.680
Stacksegment von oben nach unten.

01:23:59.460 --> 01:24:01.860
Das heißt, es gibt hier irgendwo eine Grenze und diese Grenze kann man

01:24:01.860 --> 01:24:06.460
dann mit SBRK oder SetBreakPoint, kann man die verändern.

01:24:06.840 --> 01:24:09.620
Dafür könnt ihr aber auch nochmal nachschauen mit, unter Linux, mit

01:24:09.620 --> 01:24:14.460
menSBRK, S-B-R-K, könnt ihr nochmal nachschauen, was genau dieser

01:24:14.460 --> 01:24:15.120
Befehl auch macht.

01:24:15.840 --> 01:24:19.420
Okay, ich bedanke mich und wir sehen uns in zwei Wochen Mittwoch

01:24:19.420 --> 01:24:20.160
wieder zur nächsten Übung.

