http://mycpu.eu/
Der hat sowas selber gemacht auch mit TTL-Bausteinen. Ist aber glaube ich ein CISC.
http://mycpu.eu/
Der hat sowas selber gemacht auch mit TTL-Bausteinen. Ist aber glaube ich ein CISC.
Jo. AFAIK war das auch der Auslöser für den erwähnten Thread "Verrückter als wir…?"
Meldet euch zu Wort!okay, dann melde ich mich auch mal (endlich) zu Wort:
- in TTL realisierbar (das ist das Endziel)Das dürfte teuer werden. Ordentliche FPGA-Starter-Kits, speziel für Soft-CPU-Entwicklung, gibt es bereits ab gut 250 Euronen und da ist dann ordentlich DRAM und Flash zusammen mit ein paar Schnittstellen (manchmal sogar Ethernet) drauf.
- Befehlssatz ähnlich MIPS, aber mit MAC-BefehlAlso richtige 3 Operanden Befehle möchte ich nicht mit TTL realisieren müssen. Da solltest Du Dich eher an CISC halten und versuchen komplexe Befehle seriell einzulesen und zu verarbeiten. Aber wenn Du wirklich RISC möchtest dann nur zu, ist eben eine Herausforderung.
- keine EPROMs zur ALU-RealisierungDas ist zwar löblich, macht es Dir aber nur noch schwieriger.
Ich möchte gerne mein eigenes System zum Anfassen haben. .... Außerdem lernt man so viel mehr über Schaltungsentwicklung und Rechnerarchitekturen, als man vorher wollte.Zwei sehr gute Gründe. Sind bei mir die selben. :-D
Wie? Naja, derzeit entwickle ich halt ein bisschen den Befehlssatz, immer mal wieder, und habe so quasi-diffuse Vorstellungen, wie man gewisse Teile implementieren kann.Das wirst Du dann aber mal konkretisieren müssen. Versuche einfach mal ein paar kleine Algorithmen händisch in Assembler-Code für Deine CPU umzusetzen um ein Gefühl dafür zu bekommen ob es noch Stellen gibt wo es möglicherweise hackt. Ich empfehle Dir die OpCodes in einer Tabelle zu verwalten, so behält man einen guten Überblick welche Bit-Kombinationen schon belegt sind und welche noch frei sind.
(möglichst in einem FPGA), da müsste ich mich aber auch erst einarbeiten.Das ist aber kein leichtes Thema, betrachte es als Herausforderung.
Zur RAM-Adressierung schwebt mir zZt eine Art Banking-System vor, quasi Segmentierung in Hardware.Wie stellst Du Dir das vor? So ähnlich wie auf einigen Mikro-Controllern? Oder eher in Richtung x86 Real-Mode? Compilerunterstützung ist damit übrigens ein ernstes Problem, (fast) alle heutigen Compiler gehen von Flat-Memory aus. Bezüglich meiner Segmentierung hab ich mich auch sehr intensiv umgeschaut und bin heute der Meinung das der LLVM da noch der interessanteste Kandidat ist (zwischenzeitlich hatte ich mal den gcc favorisiert), ich denke ich kann dem LLVM noch am ehesten meine Segmente beibringen, er arbeitet (fast) nie direkt mit Pointern so das ich diese spezielle Komplexität wohl gut verbergen kann.
ich hab auch meine eigene cpu, sogar 4 ums genau zu nerhmen ;)Kannst Du dazu mal was genaueres schreiben? Ist bestimmt interessant.
ein einhalb in logisim und diese dann vollständig in c# umgesetzt als emulatoren, ich weiß sind keine echten hardware cpu's, aber ich find das schon lustig
für die letztere (xcpu) screibe ich btw grade an einem assembler und an einem compiler (xasm & xcomp)
das war eine ziemlich ausführliche Beschreibung des Systems.Das war ein sehr grober Umriss meines Konzepts. :-D Da fehlen noch etliche Dinge.
Wahrscheinlich werde ich aber trotzdem eine FPGA-Lösung entwickeln, vermutlich wird ein CPLD aber nicht ausreichen. :-(Also für einen CPLD müsste Deine CPU schon sehr primitiv sein. Hast Du Dich schon für eine FPGA-Serie entschieden? Ich persönlich möchte auf die Spartan 6 von Xilinx setzen, aber die LXT-Variante. Zur Verkabelung der einzelnen Platinen untereinander will ich dann gekreuzte SATA-Kabel benutzen, die sind relativ billig und trotzdem gut.
Ich möchte mein System recht einfach stricken, also mich nicht an x86 orientieren,Der Original i8086 war schon recht simpel und der i8088 hatte sogar nur einen 8 Bit-Datenbus.
sondern eher an kleinen 8-Bit-Systemen und die "etwas" aufbohren.Eine interessante Idee, da gibt es viele Konzepte von dehnen Du was abschauen kannst.
Performance ist bei mir zweitrangig und wird hinter die Implementierbarkeit gestellt.Also für mich ist hohe Performance schon ganz klar ein Design-Ziel. Nur die Funktionalität, welche ich verwirklichen möchte, ist mir wichtiger.
Der Befehlssatz soll nur die wirklich notwendigen Befehle bereitstellen (plus einen MAC-Befehl, den halte ich für nützlich) und auch keine 3-Operanden-Form haben; das ganze System wird sehr Akkumulator-zentrisch gebaut werden (wobei ich noch nicht weiß, ob ich ein oder zwei Akkumulatorregister haben werde).Du hattest von MIPS geredet, daher bin ich von festen 32Bit-Befehlen mit 3 (manche Befehle haben auch 4) Operanden ausgegangen, so wie auf den meisten RISC-CPUs (Alpha, MicroBlaze, PowerPC, Sparc usw.). Erzähl doch mal was genaueres über die Befehls-Architektur. Sollen die Befehle z.B. alle die gleiche Größe haben?
Alle Instruktionen sollen in einem Taktzyklus abgearbeitet werden, mir schweben etwa 3-5 MHz (bei einer Gattergeschwindigkeit von ~20-50 MHz, im FPGA natürlich wesentlich mehr) vor. Das sollte realistisch machbar sein.Single-Cycle sollte Dir eine Zweistufige-Pipeline ermöglichen (so wie bei den 8 Bit AVRs). Zur Frequenz kann ich nichts sagen, ich denke das ist einer der Grüde warum der Macher von mycpu.eu sich einen Gate-Level-Simulator geschrieben hat der auch die Laufzeiten der Signale und der TTL-Bauteile berücksichtigt.
Um die Implementation zu vereinfachen, gibt es nur zwei Befehle für den Speicherzugriff (LOAD/STORE) und nur einen Befehl für Konstanten (LOAD IMMEDIATE).Das macht es auch dem Compiler recht einfach. Ich hab das ähnlich vorgesehen nur das ich noch viele Rechen-Befehle hab die auch mit Immediates umgehen können und auch komplexere Adressierungsarten für die Speicherzugriffe. Zusätzlich hab ich noch Speicherzugriffs-Befehle für mehrere Register vorgesehen (so wie LDM und STM bei ARM).
... schwebt mir ein Banking-System wie aus Frühzeiten vor; ich werde also Speicherblöcke in den Adressraum einblenden. Die Blockgröße und die Anzahl der Blöcke stehen aber noch nicht fest.Oh ja, damit bekommst Du sicher Probleme (nicht nur mit dem Compiler). Von richtigem Banking würde ich persönlich eher abraten.
Allein diese Speicheraufteilung erinnert stark an Overlay-Technik, wo mir jeder Compiler was husten wird.
Paging, Speicherschutz und SMP sind in der Idee nicht vorgesehen.Hatte ich bei einem TTL-Projekt auch nicht wirklich erwartet. :wink:
Es wird mir wahrscheinlich nicht gelingen, einen TCP/IP-Stack ohne Würgereize in 1K oder 4K-Overlays zu zerlegen.Ein Grund mehr vom Banking Abstand zu nehmen. 1 oder 4 kBytes sich wirklich zu wenig 16 kBytes sollten es schon sein.
.... Allerdings sind das nur Gedanken, die ich bisher noch weg schiebe.Ja, sowas kommt erst wenn Du Dich auf eine Befehls- und Plattform-Architektur festgelegt hast. Natürlich musst Du die gewünschten OS-Konzepte immer mit im Blick behalten.
Hast du den schon ne Website für dein Projekt?Nein. Ich bin mir noch nicht mal beim Namen sicher. Was mir auch noch fehlt ist eine geeignete Lizenz. Bis jetzt sieht für mich nur die Qt-Lizenz interessant aus.
Überhaupt nicht. Die Begrenzung liegt da im Geld (und in den anderen Projekten, die nebenher noch so laufen), daher hab ich mich damit noch nicht so wirklich beschäftigt.Wahrscheinlich werde ich aber trotzdem eine FPGA-Lösung entwickeln, vermutlich wird ein CPLD aber nicht ausreichen. :-(Also für einen CPLD müsste Deine CPU schon sehr primitiv sein. Hast Du Dich schon für eine FPGA-Serie entschieden? ...
Naja, ich werde CPU und System bei mir nicht trennen. Außerdem ist der 8088 intern relativ komplex... ich möchte den ja nirgends einbauen, sondern die CPU selbst entwickeln.Ich möchte mein System recht einfach stricken, also mich nicht an x86 orientieren,Der Original i8086 war schon recht simpel und der i8088 hatte sogar nur einen 8 Bit-Datenbus.
Kennst Du die 8 Bit AVRs von Atmel? Die habe nur 8 Bit Register (und gleich 32 Stück) können aber zur Speicheradressierung 2 davon zusammenfassen also 64 kBytes adressieren. Du könntest z.B. 16 Stück von 16 Bit Registern haben und immer 8 Paare für die flache Adressierung nutzen.Die AVR kenne ich, hab ich schon programmiert. Register sind aber in TTL sehr, sehr umständlich und teuer zu implementieren, weswegen ich möglichst wenig davon haben möchte. Ich denke mit ~<10 Registern je 8 bzw. 12 Bit auskommen zu können, muss mir da aber noch mehr Gedanken machen.
Ich dachte eher an den Befehlssatz MIPS I, aber nicht dessen encodierung. Alle Befehle sollen die gleiche Länge haben (eine Datenbreite), um in einem Takt auch geladen werden zu können. Einzige Ausnahme werden LOAD, STORE und LOAD IMMEDIATE sein müssen; wahrscheinlich werde ich dafür eine Art Zustandsautomat realisieren.Der Befehlssatz...Du hattest von MIPS geredet, daher bin ich von festen 32Bit-Befehlen mit 3 (manche Befehle haben auch 4) Operanden ausgegangen, so wie auf den meisten RISC-CPUs (Alpha, MicroBlaze, PowerPC, Sparc usw.). Erzähl doch mal was genaueres über die Befehls-Architektur. Sollen die Befehle z.B. alle die gleiche Größe haben?
Ich werde wohl keine Pipeline implementieren, das treibt den Aufwand in die Höhe. Höchstens eine Pipeline zwischen "Lesen" und "Ausführen", wobei ich (a) bei Sprüngen dem Compiler das NOP überlasse und (b) nichtmal einen Ahnungsansatz habe, wie ich länger andauernde Befehle in diesen Zyklus einbinden könnte. Es sei denn, ich halte die CPU und damit die Pipeline an...Alle Instruktionen sollen in einem Taktzyklus abgearbeitet werden, mir schweben etwa 3-5 MHz (bei einer Gattergeschwindigkeit von ~20-50 MHz, im FPGA natürlich wesentlich mehr) vor. Das sollte realistisch machbar sein.Single-Cycle sollte Dir eine Zweistufige-Pipeline ermöglichen (so wie bei den 8 Bit AVRs). Zur Frequenz kann ich nichts sagen, ich denke das ist einer der Grüde warum der Macher von mycpu.eu sich einen Gate-Level-Simulator geschrieben hat der auch die Laufzeiten der Signale und der TTL-Bauteile berücksichtigt.
Es vereinfacht die Implementation aber ungemein, da ich die gesamte "MMU" in zwei-drei Gatter verlagern kann und trotzdem die Adressleitungen einspare. Das ist der diskreten Realisierung geschuldet; ich kann keine 32 Leitungen eines Bussystems durch mehrere Platinen schaufeln und trotzdem noch Platz für Logik haben.... schwebt mir ein Banking-System wie aus Frühzeiten vor; ich werde also Speicherblöcke in den Adressraum einblenden. Die Blockgröße und die Anzahl der Blöcke stehen aber noch nicht fest.Oh ja, damit bekommst Du sicher Probleme (nicht nur mit dem Compiler). Von richtigem Banking würde ich persönlich eher abraten.
Allein diese Speicheraufteilung erinnert stark an Overlay-Technik, wo mir jeder Compiler was husten wird.
Ich überlege, wie ich einen 1 MB Adressraum in vier 256K-Bänke zerlegen kann und das trotzdem in eine Busbreite von 12/24-Bit kriege. Ohne sinnlos Bits zu verschwenden. Zumindest die Größenordnung schwebt mir vor. Außerdem sind 64 Bänke (davon vllt. 48 nutzbar) genug, um die Organisation für das OS einfach zu halten. Jedes Programm kriegt dann eine RAM-Bank für sich, eventuell für Code/Daten/Stack je eine.Es wird mir wahrscheinlich nicht gelingen, einen TCP/IP-Stack ohne Würgereize in 1K oder 4K-Overlays zu zerlegen.Ein Grund mehr vom Banking Abstand zu nehmen. 1 oder 4 kBytes sich wirklich zu wenig 16 kBytes sollten es schon sein.
Das erinnert mich an die IPC-Architektur von AmigaOS (Mikrokernel) - einfach einen JUMP in den Kernel. Wenn die Hardware eh keinen Speicherschutz bietet, ist das eine schnelle Variante mit den Vorteilen eines Mikrokernels. Prinzipiell orientiere ich das OS an der Hardware statt umgekehrt..... Allerdings sind das nur Gedanken, die ich bisher noch weg schiebe.Ja, sowas kommt erst wenn Du Dich auf eine Befehls- und Plattform-Architektur festgelegt hast. Natürlich musst Du die gewünschten OS-Konzepte immer mit im Blick behalten.
Oh ja, wenn das Geld-Problem nicht wäre dann würde ich natürlich den Virtex 6 bevorzugen.Hast Du Dich schon für eine FPGA-Serie entschieden? ...Überhaupt nicht. Die Begrenzung liegt da im Geld
ich möchte den ja nirgends einbauen, sondern die CPU selbst entwickeln.Richtig, ich vergaß. :-)
Register sind aber in TTL sehr, sehr umständlich und teuer zu implementieren,Ich hab mit der 74-Irgendwas-Reihe schon lange nicht mehr gearbeitet aber ganz normale egde-getriggerte D-FlipFlops mit Enable-Eingang sind doch nun wirklich was ganz normales. Wimre gibt es da z.B. 8 Bit in einem 20 Pin-Gehäuse. Aufwändiger stelle ich mir das Problem der Selektierung eines bestimmten Registers vor um dessen Daten z.B. an die ALU zu übergeben.
Der Aufbau ist möglicherweise:Das sieht doch schon gut aus. Wobei 8 Bit eventuell wirklich etwas wenig sind. Sieh Dir mal das http://www.opencores.org/project,zpu an, diese CPU kommt fast ganz ohne Register aus und der Befehlssatz scheint auch sehr simpel zu sein. 442 LUTs von einem Spartan 3 ist so wenig das sollte sich mit vertretbarem Aufwand in TTL realisieren lassen. Bestimmt kann man dieses Konzept noch etwas nach unten skalieren wenn man keine richtige 32 Bit CPU will.
- 4 Bit Instruktion (=Instruktionsplatine)
- 1 Bit Akkumulatorauswahl (=Ziel)
- 3 Bit Registerauswahl (=Quelle)
Wahrscheinlich werde ich aber 5 Bit Instruktion vorsehen müssen, und bei einer 12-Bit-Architektur habe ich auch wesentlich mehr Spielraum für die Codierung.Du weißt das Du mit einer 12 Bit Architektur zu nichts auf der Welt kompatibel bist? Nichtmal zu den üblichen C-Compilern. Oder Du verschwendest bei den Daten immer 4 Bit. Vielleicht wäre für Dich auch eine Harvard-Architektur (so wie die 8 Bit AVRs) interessant.
.... dann sollte ich aber lauffähigen (und krass unperformanten) Code hinkriegen. Flatmem-Simulation eben.Das erinnert mich an den Output von C-Compilern für den x86 Real-Mode bei großen Programmen, da musste auch für fast jeden zweiten Speicherzugriff ein Segmentregister geladen werden (es sind ja nur DS und ES frei verfügbar). Performance ist wirklich was anderes. Ich will ja auch Segmentierung aber da ich 16 Segmentregister vorgesehen hab und die Segmente volle Größe erreichen können denke ich mal werden solche Dinge auf meiner Architektur keine Performance kosten.
Vielleicht muss ich später auch einen C-Ableger basteln, wo ich dem Compiler die RAM-Verwaltung abnehme und den Teil per Hand machen muss.Das würde ich mir an Deiner Stelle gut überlegen. Ich verstehe zwar nicht ganz was Dir da vorschwebt aber ich vermute das für sowas mal problemlos ein Mann-Jahr drauf geht (in Sonntag-Nachmittage umgerechnet bist Du dann ziemlich lang beschäftigt).
Prinzipiell orientiere ich das OS an der Hardware statt umgekehrt.Also ich versuche CPU- und Plattform-Architektur mit dem OS als eine Einheit zu betrachten. Die HW soll die OS-Konzepte möglichst gut (und performant) unterstützen und das OS soll die Fähigkeiten der HW optimal ausreizen. Die HW wird bei mir nichts unterstützen was das OS nicht sinnvoll nutzen kann und das OS wird nichts voraussetzen was sich in HW nicht vernünftig umsetzen lässt.
Da das aber ein integriertes System ist, kann und will ich CPU, Speicher und I/O nicht trennen.Da haben wir etwas unterschiedliche Design-Ziele. CPU-Architektur und Plattform-Architektur sollen bei mir nur aus SW-Sicht aus einem Guss sein, aus HW-Sicht sind es mehr oder weniger unabhängige Teil-Systeme mit einer klar definierten Schnittstelle.
x x x x x x x x x x x x x x x x
| | | | | | | | | | | | | | | |
- - - - - - - - - -
| | | | | |
Addr Addr Alu Reg Data
OP OP CB CA
das bedeutet ein mov r1, 4
wurde zu 0000000000100100x x x x x x x x x x x x x x x x
| | | | | | | | | | | | | | | |
Enable - - - - - - - - - -
| | | | | |
J A R OP Data
m L e
p U g
| |
Write Read
aus einem mov r1, 4 wurde jetzt 0010000100000100jmp
je
jne
ja
jb
und konnte auf den ram und den rom dierekt zugreifen, zwar nur mit festwerten, aber immerhin. der programmcode lag immernoch im rom. alletdings hatte diese cpu schon einen screen port und einen keyboard port, die allerdings nie in logisim sorichtig funktioniert haben. aber dafür waren die befehle read (register) und disp (festwert | ram:[(adresse)] | rom:[(adresse)] | register) vorgesehen. xxxx | xxxx | OP
-----|------|---------
0000 | 0000 | HLT
0001 | 00 # | r# = <IP+1>
0001 | 0100 | [<IP+1>] = <IP+2>
0001 | 10 # | r# = [<IP+1>]
0001 | 11 # | [<IP+1>] = r#
-----|------|---------
0111 | #1#2 | cmp r#1, r#2
0110 | 00 # | cmp r#, <IP+1>
0110 | 01 # | cmp r#, [<IP+1>]
-----|------|---------
0011 | 0000 | jmp <IP+1>
0011 | 0001 | je <IP+1>
0011 | 0010 | jne <IP+1>
0011 | 0011 | ja <IP+1>
0011 | 0100 | jb <IP+1>
0011 | 0101 | jna <IP+1>
0011 | 0110 | jnb <IP+1>
-----|------|---------
1000 | #1#2 | r#1 = r#1 + r#2
1001 | #1#2 | r#1 = r#1 - r#2
1010 | #1#2 | r#1 = r#1 * r#2
1011 | #1#2 | r#1 = r#1 / r#2
1100 | #1#2 | r#1 = r#1 & r#2
1101 | #1#2 | r#1 = r#1 | r#2
1110 | 00 # | r# = ~r#
1111 | #1#2 | r#1 = r#1 XOR r#2
wie man erkennen kann recht simpel.meine cpus sind mehr oder minder sehr simpelMag sein, aber immerhin tun sie was, Respekt!
.... wodurch dann selbstmodifizierender code entsteht.Pfui, schäm Dich! :wink:
ich hab fast für jede meiner cpus eien eigenen assembler, in c#, geschriebenWie viel Arbeit hat das den in etwa gemacht bzw. wie viel Quell-Code ist das ungefähr?
also mov (ziel), (quelle). .... wie add r1, r2, r3 möglich sein (r3 = r1 + r2).Das passt irgendwie nicht zusammen, sollte es nicht "r1 = r2 + r3" heißen?
der assembler für die xcpu ist noch nicht ganz fertig, aber es gibt schon labels, jmp, data anweisung, org anweisung, times anweisung und die $ anweisung.Wie verwaltest Du die Befehle intern? Wie löst Du die Labels auf?
Das passt irgendwie nicht zusammen, sollte es nicht "r1 = r2 + r3" heißen?ist eigentlich egal. zudem noch ist es nicht im assembler implementiert, da können die operanden immernoch vertauscht werden ;)
data 0x10000000, :hallo ;mov r0, :hallo
data 0x10020000, $+3 ;mov r2, $+3 )
jmp :start ; ) call :start
jmp $-1
start:
data 0x20310000 ;mov r1, [r0]
data 0x01110000, 0 ;out 0, r1
data 0x30100000, 1 ;add r0, 1
cmp r1, 0
jne :start
jmp r2 ;ret
hallo:
data "Hallo, Welt!",10,13, 0
wird zuerst folgender objekt code in stage 1 generiert10000000 :hallo
10020000 $+3
40100000 :start
40100000 $-1
start:
20310000
01110000 00000000
30100000 00000001
00210000 00000000
42100000 :start
40020000
hallo:
00000048 00000061 0000006C 0000006C 0000006F 0000002C 00000020 00000057 00000065 0000006C 00000074 00000021 0000000A 0000000D 00000000
dann werden die labels eingelesen und in einer KeyValue liste abgespeichert zusammen mit ihren adressen, bei bedarf noch der org wert drauf addiert, und danach werden die labels mit ihrer adresse ersetzt.10000000 00000212
10020000 00000206
40100000 00000208
40100000 00000206
20310000
01110000 00000000
30100000 00000001
00210000 00000000
42100000 00000208
40020000
00000048 00000061 0000006C 0000006C 0000006F 0000002C 00000020 00000057 00000065 0000006C 00000074 00000021 0000000A 0000000D 00000000
der wirt dann einfach in die entsprechenden hexwerte umgewandelt und in die ausgabe datei geschreiben.zum selbstmodifizierenden code:schon klar
es war kein design ziel, sondern ist eine notwendigkeit aufgrund der einfachheit ;)
zu den assemblern:Ah ja, bei mir sind es momentan 150 kByte (Zeilen weiß ich jetzt nicht, muss ich mir mal ein passendes Tool besorgen) in 17 Dateien, wird aber noch deutlich mehr werden. Für die Immediate-Parameter von Befehlen hab ich einen Literal-Parser der schon allein über 1200 Zeilen hat, der kann momentan mit Zahlen in Binär, Dezimal und Hexadezimal umgehen (in C-Syntax und VHDL-Syntax). Des weiteren parst er ganze Gleichungen mit Klammern und Operatoren (+ - * / % | & ^ ~) mit Vorrangregeln, Label sind da aber noch nicht richtig implementiert. Gleitkommazahlen fehlen auch noch. Aus den Gleichungen wird ein Baum generiert welcher dann zusammengerechnet wird, bis jetzt kommt immer eine eindeutige Zahl raus weil ja noch die Labels fehlen. Dieser Baum wird dann ins Befehlsobjekt abgelegt und beim assemblieren mit den Label-Adressen ausgerechnet.
kpuasm: nur 281 zeilen (könnten aber reduziert werden)
kpuIIasm: 564 zeilen in 3 dateien (könnten auch reduziert werden)
xasm: 842 zeilen (werden noch mehr werden)
zu den befehlen:Naja, jeder fängt mal klein an. In meinem Assembler sind auch einige Stellen die ich noch mal ordentlich machen möchte.
ich mach das über eine böse variante, über string split und dann mit regex vergleichen, da ich da noch nichts vom ordentlichen compilerbau/assemblerbau gehört hab.
je komplexer die cpu bzw deren befehlssatz, desto komplexer der assembler.Da schreibst Du wahr!
Mit dem jetzigen Ziel habe ich eine recht hohe Ausführungsgeschwindigkeit, brauche aber viele Befehle für gewisse Aktionen.Das ist ein generelles CPU-Design-Problem, entweder kleine und schnelle Befehle von dehnen man aber viele für einfache Aufgaben braucht oder mächtige Befehle die aber langsamer sind und auch mehr OpCode-Platz brauchen.
Angenommen, ich muss ständig die Bänke wechseln, das kostet jeweils 5-10 Takte zusätzlich.Ja ich verstehe. Mehr Bänke gleichzeitig kosten Dich mehr Logik. Ich will nicht mit Dir tauschen. :roll:
Mit dem C-Ableger hatte ich eher vorgestellt, dass ich die Overlays selbst basteln muss und im Betriebssystem dann automatische Bankwechsel o.ä. vorsehe, so dass die Anwendungsentwicklung etwas einfacher wird.Ich verstehe immer noch nicht was Du meinst.
Eine Hardvard-Architektur erfordert zusätzliche Lade-/Schreibbefehle für den CodespeicherUnd dafür fehlen Dir wertvolle Bits in den Befehlen.
Über Interrupts habe ich bisher noch nicht nachgedacht, möglicherweise realisiere ich das über ein Flag (und damit Polling); ein Befehl ist nicht vorgesehen. Software-Interrupts kann ich mit JMP auf Kosten eines Registers realisieren; Hardware-Interrupts möglicherweise auch.Polling ist aber was anderes als Interrupts. Naja, Du wirst wissen was Du tust.
Das Problem mit dem Banking bleibt ja erhalten, so oder so; wenn ich aber in der C-Library (bzw. im Kernel) entsprechende Unterstützung aber vorsehe, dann wird es vielleicht etwas weniger schmerzhaft. Die Programme würden dann nur noch eine Aufteilung in Overlays bekommen, sich aber nicht darum kümmern müssen, an welcher Adresse und in welcher RAM-Bank die benötigten Daten nun gerade liegen.Mit dem C-Ableger hatte ich eher vorgestellt, dass ich die Overlays selbst basteln muss und im Betriebssystem dann automatische Bankwechsel o.ä. vorsehe, so dass die Anwendungsentwicklung etwas einfacher wird.Ich verstehe immer noch nicht was Du meinst.
Eben.Eine Hardvard-Architektur erfordert zusätzliche Lade-/Schreibbefehle für den CodespeicherUnd dafür fehlen Dir wertvolle Bits in den Befehlen.
Das war eher entweder-oder gemeint. Möglicherweise realisiere ich keine Hardwareinterrupts (d.h. Pollingbetrieb), möglicherweise lässt sich eine simple "sichere PC nach Register, JMP an fixe Adresse" einfach realisieren, dann tue ich das natürlich. Inwieweit meine CPU überhaupt Hardwarestacks unterstützt, weiß ich bisher noch nicht; nested function calls und Syscalls sind dann halt wieder softwareseitig (z.B. Assemblermakro) realisiert.Über Interrupts habe ich bisher noch nicht nachgedacht, möglicherweise realisiere ich das über ein Flag (und damit Polling); ein Befehl ist nicht vorgesehen. Software-Interrupts kann ich mit JMP auf Kosten eines Registers realisieren; Hardware-Interrupts möglicherweise auch.Polling ist aber was anderes als Interrupts. Naja, Du wirst wissen was Du tust.
Schmerzhaft wird es ohnehin, da hab ich wenig Illusionen.Dann is ja gut wenn Du weißt auf was Du Dich da einlässt. :wink:
Das war eher entweder-oder gemeint. Möglicherweise realisiere ich keine Hardwareinterrupts (d.h. Pollingbetrieb), möglicherweise lässt sich eine simple "sichere PC nach Register, JMP an fixe Adresse" einfach realisieren, dann tue ich das natürlich. Inwieweit meine CPU überhaupt Hardwarestacks unterstützt, weiß ich bisher noch nicht;Da gibt es IMHO 2 Wege: einmal werden für den IRQ-Mode (teilweise) andere Register benutzt so das Du nichts sichern musst aber noch mehr Register brauchst (ARM z.B. geht diesen Weg) oder Du hast einen HW-Stack und die CPU kann "sichere PC auf Stack und springe nach Irgendwohin" (x86 ist da ein klassisches Beispiel). Ich gehe den ARM-Weg aber in etwas anderer Form. ARM hat für jeden System-Mode (IRQ, Exception, SYSCALL ...) eigene Schatten-Register so das man diese Modi (theoretisch) beliebig verschachteln kann, soll heißen das man im SYSCALL-Handler noch ne Page-Not-Present-Exception verarbeiten kann und im Page-Not-Present-Handler könnte noch ne Illegal-Opcode-Exception kommen ohne das da was kollidiert (in echt begrenzt ARM das natürlich auf sinnvolle Möglichkeiten), das kostet aber viele Register (ARM hat über 30 HW-Register obwohl die SW immer nur 16 sehen kann). Ich möchte nur einen Register-Schatten (und auch nur für R56 bis R63) haben was aber bedeutet das im SYSCALL-Handler keine Exception auftreten darf und auch keine IRQs angenommen werden können. Aus einem System-Mode muss die CPU immer erst in den User-Mode zurück um dann wieder einen anderen System-Mode betreten zu können. Ist zwar ne Einschränkung aber ich denke damit kann ich gut leben und es spart reichlich Register. 64 normale Register sind schon ne Menge Zeugs, das kostet auch einiges an Logik-Ebenen bzw. Fan-Out und reduziert im Endeffekt den maximalen Takt für meine CPU.
Genau das ist aber der Grund, weswegen es mich interessiert, wie andere an solche Projekte herangehen - denn an Interruptverarbeitung habe ich wirklich noch nicht gedacht.Ich vermute mal dieser Thread wird noch ne Weile benutzt werden. :-D
Wenn man die CPU hinreichend blöde macht, muss man halt alles in Software nachimplementieren.Es kommt eben auf das richtige Verhältnis an. Was wirklich das Optimum ist merkt man aber erst wenn die ersten großen Programme laufen, bzw. dann stellt man eventuell fest das man nicht das Optimum getroffen hat.
.... Anschließend werden die Code-Segmente assembliert. Dazu gibt es eine Vorrunde in der bei allen Befehlen geprüft wird wie groß sie mindestens werden damit jeder Befehl ein Initial-Offset bekommen kann. Dann soll der gesamte Code so lange immer wieder assembliert und optimiert werden bis sich das best-mögliche Ergebnis eingestellt hat (das Abbruchkriterium wird mir sicher einiges an Kopfzerbrechen bereiten).Da steckt ein Fehler drin. Wenn man die Befehle versucht zu schrumpfen könnte es sein das nicht das kleinst mögliche Ergebnis dabei raus kommt.
Label1:
b Label2
viel_Code;
b Label1
Label2:
Hierbei könnte es passieren das beide Sprünge gerade so nicht in die nächst kleinere Größer überführt werden können weil sie sich quasi gegenseitig blockieren, also die Entfernung auf Grund des "viel Code" gerade so zu groß ist für die nächst kleinere Größe aber wenn die Sprünge kleiner wären dann würde es eben gehen.(plus einen MAC-Befehl, den halte ich für nützlich)Dir ist klar das ein MAC-Befehl ein 3 Operanden-Befehl ist?
Dir ist klar das ein MAC-Befehl ein 3 Operanden-Befehl ist?Ne, war mir nicht klar. :-(
In der Form "rA = rA + (r2 * r3)" must Du mindestens 3 Operanden angeben. Du kannst den Accu gerne auf 1 Bit reduzieren oder auch ganz weg lassen aber r2 und r3 müssen IMHO schon sein.
Mal ne andere Frage:Ich würde das bevorzugt von der Größe der zu erwartenden Codes und Daten abhängig machen. Wenn deine Anwendungen viele und große Daten verarbeiten (Puffer), dann würde ich die Pagegröße schon größer ansetzen als bei Durchlaufverarbeitung (z.B. ständige FFT). Vielleicht so nen Faktor 1,5 auf die Durchschnitts-Puffergröße abgeschätzt auf die nächste Zweierpotenz? :-D
Wie groß würdet ihr die Pages in einem 64 Bit-System mindestens machen?
So rückblickend stellt sich da auch die Frage der Ergebnisvermittlung, da ich mit dem jetzigen Ansatz nur ein Ergebnisregister beschreiben kann, d.h. eine Multiplikation zweier 8-Bit-Register nicht zurücksichern kann.Du könntest für das High-Byte vom Ergebnis auch ein extra Spezial-Register vorsehen (die 8 Bit PICs machen das so), aber um dieses Register lesen und schreiben (ohne schreiben kannst Du in einem IRQ-Handler nicht multiplizieren) zu können wirst Du wieder zusätzliche Befehle brauchen die wiederum Bits benötigen. Ein ekliges Dilemma.
Bäh. Pfui. Wie kannst du nur sowas sagen? :-DIch bin eben ein fieser Mensch. :-D
Allerdings hätte ich bevorzugt die 4-Operandenfassung des Befehls genommen (A = B + C * D), da ich vermute, dass diese nützlicher ist.Die 4 Operandenvariante hat IMHO kaum Vorteile, der MAC-Befehl wird vorwiegend für DSP-Algorithmen und ähnliches Zeugs benutzt und da werden die Ergebnisse vieler Multiplikationen aufakkumuliert. Für sowas hab ich auch extra "R1:R2 = R1:R2 + ( R3 * R4 )", es gibt auch eine Version mit - anstatt +. 6 Operanden (für "R1:R2 = R3:R4 +/- ( R5 * R6 )") hätte ich aber nicht untergebracht, 4 unabhängige Operanden hab ich nur wenn der Akku nicht aus 2 Registern besteht "R1 = R2 +/- ( R3 * R4 )".
Ich würde das bevorzugt von der Größe der zu erwartenden Codes und Daten abhängig machen.Das kann ich aber nicht vorhersagen. Es soll ja ein Allzweck-System werden.
Für ein Allzwecksystem würde ich die Pagegröße klein machen, da du sonst recht viel Speicher verschwendest; das hängt dann aber auch wieder von der Verwaltung im Betriebssystem ab, inwieweit Anwendungen ihre Pages miteinander teilen können.Das mit der Speicherverschwendung (gerade bei kleinen Programmen) ist natürlich ein Problem (erinnert irgendwie an die Cluster-Größe bei FAT). Da die Basis-Adresse der Segmente wohl auf 64 kByte aligned ist (vielleicht auch weniger, da muss ich mal genau überlegen wie viele Bits ich in den Segment-Descriptoren wirklich für Flags brauche) können sich natürlich mehrere Segmente in einer größeren Page befinden.
Wenn du natürlich das Paging nur als Auslagerungsmöglichkeit für deine Segmente nutzt, kannst du als Richtmaß einerseits die Segmentgrößen nehmen, andererseits aber die Pages einfach sehr groß machen, in der Annahme, dass Paging nur bei Speicherknappheit (und damit wahrscheinlich sehr selten) aktiv wird.Paging möchte ich vor allem für die Speicherdefragmentierung und andere Tricks nutzen aber natürlich auch zum swappen, falls ich mit meinem Projekt so weit komme das sich das lohnt.
Ich hatte als Gedanken einen Instruktionsbus, wo auf der steigenden Taktflanke die Daten eingelesen werden und auf der fallenden Flanke das Ergebnis ausgegeben wird. Da die meisten Befehle implizit mit dem Akkumulator arbeiten (der also zusätzlich eigene Leitungen in die ALU hat, aber damit nur lesbar ist), hätte ich für das Ergebnis somit nur 8 Bit (plus die Steuerleitungen zur Registerauswahl).Der Akku sollte aber auch immer beschrieben werden können, er sollte also auch für das Ergebnis eine eigene Verbindung zur ALU haben. Außerdem hattest Du doch mal 2 Akkus im Plan, da könnte ja jeder Akku eigene Verbindungen zur ALU haben, damit währe "A1:A0 = A1:A0 + ( R2 * R3 )" möglich und Du müsstest nur 2 normale Register im Befehl codieren. Ich weiß das kostet Lötarbeit, aber für umsonst gibt es eben auch keine TTL-CPU.
Den Unterschied zwischen signed und unsigned wird die CPU nicht kennen, das ist dann Software.Diese Unterscheidung in SW zu machen hallte ich für ungeschickt, um aus einem Unsigned errechnetem Ergebnis von 2 Signed Faktoren das richtige Ergebnis zu machen braucht der Compiler viele Befehle da kann er auch gleich händisch multiplizieren.
Und wenn ich das Einserkomplement nehme, dann ist das eben so, auch wenn das die Unterscheidung zwischen "-0" und "+0" erlaubt. :-)Was, Du willst ernsthaft über Einerkomplement nachdenken?!? :-o Dann denke vielleicht doch über eine eigene Programmiersprache nach. Ich weiß das C da keine Vorgaben macht aber viele existierende Programme gehen einfach davon aus. Ich hatte es auch noch nie mit einer CPU zu tun die nicht mit dem Zweierkomplement arbeitet, mit so einer Idee wirst Du noch mehr inkompatibel zur restlichen Welt.
Wenn dieses Monster fertig ist, werde ich vielleicht möglicherweise über eine sinnvollere CPU nachdenken, aber Bits sind halt teuer und richtig aufwändig, daher spare ich an jeder Stelle.Vielleicht solltest Du für Deine CPU erst mal einen Simulator programmieren damit Du ein Gefühl für den geplanten Befehlssatz bekommst. Wenn Du dann mit dem Ergebnis zufrieden bist kannst Du immer noch überlegen wie sich das per TTL realisieren lässt. Ein Simulator sollte für Deine CPU ja nun nicht so schwer sein, bestimmt gibt Dir chris12 die nötigen Tipps. :wink:
Mehrere Segmente innerhalb einer Page halt ich für nicht besonders zweckmäßigWarum? Pages dienen doch bei mir nicht direkt der Speicherverwaltung. Und da ich jede beliebige Zweier-Potenz (>= kleinste Page-Size) erlauben will, um die TLB-Effizienz so hoch wie möglich zu halten, wird es sowieso der Regelfall sein das sich in einer Page mehrere Segmente befinden. Zum swappen werden die Pages natürlich auf die kleinste Größe reduziert.
Eine andere Idee wäre es, nicht den gesamten Adressraum in Pages aufzuteilen, sondern nur einen kleinen Teil.Das habe ich eh vor. Der Speicher in dem z.B. die Peripherie liegt benötigt kein Paging.
Wenn du die Programme etwas daraufhin anpasst [vgl. NOMMU-uClinux, welches aber nicht defragmentiert], sollte das ohnehin nicht nötig sein.Die Programme extra dafür anpassen möchte ich eigentlich nicht. Den Speicher von Flat-Memory-Programmen kann man ja auch gar nicht defragmentieren (wegen Pointern usw.), also kann uCLinux das nicht tun.
Swappen kannst du, grundsätzlich genügend RAM vorausgesetzt, auch mit ganzen Segmenten. Das hat natürlich eine gröbere Granularität, wenn du das gesamte Codesegment auslagern musst, weil die Anwendung grad nicht aktiv ist. Bei mehreren aktiven Tasks sollte das aber nicht weiter stören.Meinst Du das ernst?? Komplette Code/Daten-Segmente auszulagern ist schon wirklich sehr strange. Für gewöhnlich ist es so das von allen Programmen immer etwas aktiv ist, und wenn es nur ein Signal-Handler ist der ab und an mal Nachrichten vom OS abhandelt. Das heißt das von jedem Programm wohl ein paar Pages im RAM bleiben müssen. Der Trick des Swapping per Paging ist das von jeden Programm nur das absolute Minimum im RAM bleibt, nur auf 0 wird sich das Minimum nicht reduzieren lassen. Segmentweises Swapping halte ich persönlich für keine gute Idee, auch wenn Du nicht der erste bist der das vorschlägt.
Ich vermute mal, dass die 64 Bit-Variante aus praktischem Ansatz (Befehlslänge usw.) entsteht und nicht aus der Bedingung, da mehr als 4 GB RAM einzubauen.Sicher nicht im ersten Anlauf, da werde ich wohl mit höchstens 1 GByte auskommen müssen :cry:, aber grundsätzlich möchte ich schon mehr als 4 GByte RAM ermöglichen.
Wenn deine MMU also immer nur 4 GB abdecken kann, wäre das also kein Problem. Und wenn du mehr RAM hast, musst du halt die 4 GB-Blöcke getrennt defragmentieren.Das defragmentieren hatte ich mir eher anders vorgestellt. Wenn es nötig ist dann wird ein Paging-Directory erstellt das den physischen RAM 1:1 mappt und zusätzlich nochmal in einem virtuellem Bereich hinter dem RAM, dann wird das Paging eingeschaltet und alle Segement-Basis-Adressen in den virtuellen Bereich verlegt, danach werden die Pages im physischen Speicher umsortiert und das Paging für den virtuellen Bereich immer mit angepasst, als letztes wird der 1:1 Bereich wieder auf 1:1 gebracht, die Segment-Basis-Adressen wieder in den 1:1 Bereich verlegt und das Paging abgeschaltet. Das Problem ist eher das dieser Vorgang Zeit kostet und in dieser Zeit das OS natürlich trotzdem Speicherveraltungsaufgaben (Segmente anlegen/löschen/vergrößern/verkleinern) wahrnehmen muss.
Und wenn du mehr RAM hast, musst du halt die 4 GB-Blöcke getrennt defragmentieren.Das wird nicht klappen wenn Segmente existieren die größer als 4 GB sind, was ja auf einer 64 Bit-Architektur möglich sein soll.
Das defragmentieren hatte ich mir eher anders vorgestellt.....Ich hab da noch mal drüber nachgedacht.
ich hatte vor, den Akku direkt mit der ALU zu verbindenSo soll es ja auch sein, der Akku hat nen direkten Draht zur ALU damit jeder Befehl ihn immer benutzen kann.
Wenn ich keine Hardwaremultiplikation habe, dann brauche ich auch keine Entscheidung über signed/unsigned treffen; das oberste Bit der letzten Operation bleibt dann einfach ein Flag und gut ist.Bei den Strichrechnungen im Zweierkomplement gibt es keinen Unterschied zwischen signed und unsigned, wie das im Einerkomplement ist weiß ich gar nicht. Da fällt mir ein das die ganzen eingebauten Operatoren in VHDL auch alle vom Zweierkomplement ausgehen, eine CPU mit Einerkomplement in VHDL zu entwickeln dürfte ziemlich frustrierend sein.
aber man kann - angenommen, die Segmente sind hinreichend groß - die Segmente im physischen RAM verschieben.Hä?? Was hat die Größe der Segmente damit zu tun?
Dazu benötigt man kein Paging,Dann muss das verschieben atomar sein, das betreffende Programm muss während dieser Zeit schlafen. Bei großen Segmenten ist das schon ziemlich doof, das will ich den Programmen nicht zumuten. Bei SMP muss der OS-Kernel dann auch noch sicherstellen das wirklich keiner der Threads des betreffenden Programms auf anderen CPUs laufen, ist zwar machbar aber umständlich. Bei "beerbten" Segmenten (wegen shared Memory oder IPC) ist das noch schwieriger, weil man noch mehr Programme (alle ihre Threads) deaktivieren muss. Das atomare verschieben von Segmenten würde quasi das gesamte System lahmlegen.
die Programme dürfen dann aber keine Verweise über Segmentgrenzen erlauben.Was genau meinst Du? Offsets für ein bestimmtes Segment dürfen nur vom dessen Minimum bis zu dessen Maximum gehen (beides ist bei mir im Segment-Descriptor eingetragen, bei x86 gibt es kein Minimum dort fangen Segmente immer mit Offset 0 an), alles andere löst beim Speicherzugriff eine Exception aus. So einen Mist wie die unflexieble Segmentierung im x86-Real-Mode will ich definitiv nicht machen.
Das Swappen von ganzen Segmenten ist an sich gar nicht so abwegig, das meinte ich schon ernst.Das bedeutet aber dass das betreffende Programm komplett deaktiviert ist wenn eines seiner Segmente nicht im RAM ist. Wenn z.B. ein großes Office-Programm (falls sowas jemals auf meinem OS laufen sollte) für jeden Tastendruck wieder komplett in den Speicher muss obwohl der Key-Event-Handler nur den Text um einen Buchstaben erweitern müsste und nur das aktualisieren der Bildschirmdarstellung über die GUI antriggern müsste. Das stelle ich mir wahrlich nicht praktikabel vor. Ich möchte den linearen Speicher wirklich im Hintergrund bei laufendem System defragmentieren können, wenn das nicht geht ist das Konzept mit der Segmentierung IMO nicht tragfähig.
Das Umsortieren des Speichers (Verschieben der Segmente) erfordert ohnehin eine wie auch immer geartete Auslagerung.Nicht unbedingt. Das ich die Segmente, während des defragmentierens, virtuell in einen anderen Bereich verlagern möchte dient eher der Performance (hat aber auch noch ein paar praktische Gründe), dann müssen die anderen Segmente, die direkt im physischen Speicher angesprochen werden, nicht das Paging benutzen. Man könnte die Segmente, während des defragmentierens, auch im normalen linearen Speicher lassen aber dann müsste man für diesen Bereich, in dem der physische Speicher eigentlich immer 1:1 gemappt ist, das Paging anschalten und das kostet etwas Performance bei allen Programmen. Wenn nur das eine Segment in einem speziellen Bereich liegt dann ist nur das eine Programm betroffen und das auch nur bei Zugriffen auf das eine Segment welches gerade verschoben/defragmentiert wird.
Swapping effizient zu machen bringt meiner Meinung nach wenig, aber das hängt auch grundsätzlich vom Anwendungszweck ab. Mein System mit derzeit 2 GB RAM unter Linux fängt nur damit an, wenn irgendein Programm Amok läuft. Effektiv wird es nie genutzt.Das Swapping ist sicher nicht besonders performancerelevant aber eine zügige Arbeitsweise wird bestimmt nicht schaden.
Meine Meinung ist es, auf Paging ganz zu verzichten oder aber es vollständig zu implementieren und dann auch zu nutzen. Alles andere ist verschwendetes Silizium.Da hast Du völlig recht. Deshalb möchte ich Paging ordentlich implementieren und es genau dafür nutzen wofür es einst ersonnen wurde "virtuelle Speichermehrung". Das dazu noch ein bisschen sonstige Speicher-Magie kommt ist bei Segmentierung natürlich mehr als nur willkommen.
Zum richtigen Defragmentieren ist es eigentlich nicht nötig, wenn du die gesamte Segmentverwaltung dem Betriebssystem obliegt und Programme nur ihre eigenen Segmente und ein unverschiebbares Systemsegment verwenden können.Ich verstehe nicht was Du damit meinst. Natürlich dürfen Programme nur innerhalb ihrer eigenen Segmente arbeiten (der Kernel darf natürlich mehr). Die eigenen Segmente, welche alle in der zum Programm gehörenden LDT stehen, sind quasi der Kontext. Die Programme haben dabei keinen Einfluss darauf wo sich ihre Segmente im linearen Speicher befinden, das wissen die noch nicht mal (außer natürlich Geräte-Treiber die ihrem Gerät mitteilen müssen wo im physischen Speicher das Gerät Daten ablegen/holen soll, dazu muss ein Segment aber gelockt werden und ist damit nicht mehr verschiebbar, sieh Dir dazu mal XMS (http://de.wikipedia.org/wiki/XMS) an).
Mit Paging könntest du das Defragmentieren durch ein Ummappen sehr schnell lösen und im Hintergrund später die reale Umsortierung machen, das halte ich aber (insbesondere bei mehreren CPUs) für ein sehr kompliziertes Unterfangen.Ja, genau darum geht es mir. Das ich mir da keine leichte Übung vorgenommen hab ist mir bewusst. Warum sollte das mit mehreren CPUs schwerer werden?
Gibt es vielleicht die Möglichkeit, nur sehr wenige Pages (vielleicht 16) zu erlauben ....Das ist viel zu grob. Ein Segment muss bei mir immer mindestens eine minimale Page (also das was das Paging als kleinste Page unterstützt, 4 kByte auf der 32 Bit-Version meiner Plattform, das ist dann auch das minimale Alingment der Segment-Basis-Adresse) ausfüllen, diese minimale Page können sich nicht mehrere Segmente teilen (sonst wäre das defragmentieren nicht möglich). Erst wenn größere Pages benutzt werden können in einer größeren Page auch mehrere Segmente drin liegen. Die Segmentierung weiß davon ja nichts und die größeren Pages dienen nicht der Verwaltung sondern nur der Performance (so wie auf allen anderen Plattformen auch).
Segmentierung finde ich prinzipiell toll, nur die damit einhergehenden Probleme (die es nur für knappes oder zersplittertes RAM gibt) finde ich schwierig zu lösen.Segmentierung, gefällt mir schon lange. Aber ich finde die ist selbst im 386-Protected-Mode nur sehr halbherzig umgesetzt. Theoretisch ließe sich meine OS-Idee auch dort umsetzen aber da gibt es keinen Pfad zu 64 Bit (im Long-Mode gibt es keine Segmentierung mehr), außerdem finde ich die PC-Plattform (mitsamt der CPU-Architektur) einfach nur echt Scheiße.
Dynamische Segmentgrößen vereinfachen das etwas.Dynamische Segmente sind für mein Konzept die Kern-Idee, ohne dem geht nichts davon.
In meiner Implementierung wäre das Defragmentieren allerdings atomisch, so dass der Kernel sich ungestört um die Speicherverwaltung kümmern kann. Damit gibst du dann allerdings die Echtzeitfähigkeit komplett auf - es sei denn, die Programme sind "lieb".Atomische Defragmentierung will ich nicht. Programme die freiwillig auf bestimmte Segmente mal nicht zugreifen stelle ich mir auch sehr schwierig vor, da müsste man vor jedem Speicherzugriff erst den Segment-Selector prüfen ob der auf der roten Liste, mit den momentan verbotenen Segmenten, steht.
Naja, sind alles nur Gedankensplitter.Dafür ist dieser Thread doch da.
Wenn der RAM voll ist, kannst du ganze Segmente nicht mehr ohne Auslagerung verschieben, ausgegangen von "kein Paging".aber man kann - angenommen, die Segmente sind hinreichend groß - die Segmente im physischen RAM verschieben.Hä?? Was hat die Größe der Segmente damit zu tun?
Also mein Core2Duo hat unter Linux eine Speicherperformance von ~2 GB/s. Um also ein paar MB umherzuschieben, gehen dir ein paar Mikrosekunden (maximal ms) verloren.Dazu benötigt man kein Paging,Dann muss das verschieben atomar sein, das betreffende Programm muss während dieser Zeit schlafen. Bei großen Segmenten ist das schon ziemlich doof, das will ich den Programmen nicht zumuten.
Bei SMP muss der OS-Kernel dann auch noch sicherstellen das wirklich keiner der Threads des betreffenden Programms auf anderen CPUs laufen, ist zwar machbar aber umständlich. Bei "beerbten" Segmenten (wegen shared Memory oder IPC) ist das noch schwieriger, weil man noch mehr Programme (alle ihre Threads) deaktivieren muss. Das atomare verschieben von Segmenten würde quasi das gesamte System lahmlegen.
die Programme dürfen dann aber keine Verweise über Segmentgrenzen erlauben.Was genau meinst Du? Offsets für ein bestimmtes Segment dürfen nur vom dessen Minimum bis zu dessen Maximum gehen (beides ist bei mir im Segment-Descriptor eingetragen, bei x86 gibt es kein Minimum dort fangen Segmente immer mit Offset 0 an), alles andere löst beim Speicherzugriff eine Exception aus. So einen Mist wie die unflexieble Segmentierung im x86-Real-Mode will ich definitiv nicht machen.
Wenn der RAM alle ist, verlierst du mindestens einen Faktor 100-1000 für den RAM-Zugriff, ob du nun das Office-Paket komplett oder zu einem Großteil ausgelagert hast, spielt da eine untergeordnete Rolle. Am besten, man sieht genug RAM vor, seit wenigen Jahren ist das ja endlich möglich. Swapping ist eine Notlösung und war (auch) einen Grund für Paging überhaupt. Der einzige Vorteil mit Paging ist die feinere Granlarität und damit die Möglichkeit, geringe Knappheit gut abzufangen.Das Swappen von ganzen Segmenten ist an sich gar nicht so abwegig, das meinte ich schon ernst.Das bedeutet aber dass das betreffende Programm komplett deaktiviert ist wenn eines seiner Segmente nicht im RAM ist. Wenn z.B. ein großes Office-Programm (falls sowas jemals auf meinem OS laufen sollte) für jeden Tastendruck wieder komplett in den Speicher muss obwohl der Key-Event-Handler nur den Text um einen Buchstaben erweitern müsste und nur das aktualisieren der Bildschirmdarstellung über die GUI antriggern müsste. Das stelle ich mir wahrlich nicht praktikabel vor.
Ich möchte den linearen Speicher wirklich im Hintergrund bei laufendem System defragmentieren können, wenn das nicht geht ist das Konzept mit der Segmentierung IMO nicht tragfähig.Hängt vom Anwendungsfall ab.
Wenn du Umsortieren musst, weil der RAM knapp wird, dann schon. Ich hätte dann halt auf ein Zwischenmedium ausgelagert, du lagerst in den virtuellen Adressraum aus. Das Umsortieren bei zu sehr zerstückeltem RAM sollte dagegen auch im physischen Bereich nicht zu lange dauern, da es dort meist um viele sehr kleine Segmente geht, für die noch Platz vorhanden sollte. Sonst ist der RAM voll und es tritt Fall 1 ein.Das Umsortieren des Speichers (Verschieben der Segmente) erfordert ohnehin eine wie auch immer geartete Auslagerung.Nicht unbedingt. ...
Ich würde versuchen vorzusorgen, dass dieser Fall nie eintritt. Er sollte ohnehin sehr selten sein.Swapping effizient zu machen bringt meiner Meinung nach wenig, aber das hängt auch grundsätzlich vom Anwendungszweck ab. Mein System mit derzeit 2 GB RAM unter Linux fängt nur damit an, wenn irgendein Programm Amok läuft. Effektiv wird es nie genutzt.Das Swapping ist sicher nicht besonders performancerelevant aber eine zügige Arbeitsweise wird bestimmt nicht schaden.
Im Endeffekt sortierst du den physischen Speicher ja trotzdem um, auch wenn die MMU dazwischen sitzt. Du müsstest halt atomar dafür sorgen, dass für jedes verschobene Fragment eines Segments die Pagingtabelle korrekt ist. Sonst hat das Programm zwar eine nette virtuelle Adresse zum Arbeiten und greift trotzdem ins Leere, wenn die MMU auf den falschen Speicher zeigt (der da gerade wegverschoben wurde).Mit Paging könntest du das Defragmentieren durch ein Ummappen sehr schnell lösen und im Hintergrund später die reale Umsortierung machen, das halte ich aber (insbesondere bei mehreren CPUs) für ein sehr kompliziertes Unterfangen.Ja, genau darum geht es mir. Das ich mir da keine leichte Übung vorgenommen hab ist mir bewusst. Warum sollte das mit mehreren CPUs schwerer werden?
Zum Defragmentieren würde es aber reichen, wenn du nämlich je zwei Segmente in das Paging aufnimmst und dann im Hintergrund sortierst. Da die Tabelle dann extrem klein bleibt, kannst du sie in ein spezielles Register eintragen, wo die Lookups kostenlos sind und du keine Performance verlierst. Paging kostet ja nur deshalb so viel, weil die Pagingtabelle im RAM liegt und du dadurch Unmengen an zusätzlichen RAM-Zugriffen hast, die vom Cache nur ungenügend abgefedert werden können. Oder seh ich da grundsätzlich was falsch?Gibt es vielleicht die Möglichkeit, nur sehr wenige Pages (vielleicht 16) zu erlauben ....Das ist viel zu grob. ...
Naja, wenn du keinen Speicherschutz hast, dann kann sich jedes Programm eine Segmentnummer zusammenbauen und dort zugreifen. Wenn du das nicht möchtest, dann musst du bei jedem Segmentzugriff nachschauen, ob das betreffende Segment in der weißen Liste des Prozesses steht...In meiner Implementierung wäre das Defragmentieren allerdings atomisch, so dass der Kernel sich ungestört um die Speicherverwaltung kümmern kann. Damit gibst du dann allerdings die Echtzeitfähigkeit komplett auf - es sei denn, die Programme sind "lieb".Atomische Defragmentierung will ich nicht. Programme die freiwillig auf bestimmte Segmente mal nicht zugreifen stelle ich mir auch sehr schwierig vor, da müsste man vor jedem Speicherzugriff erst den Segment-Selector prüfen ob der auf der roten Liste, mit den momentan verbotenen Segmenten, steht.
Wenn der RAM voll ist, kannst du ganze Segmente nicht mehr ohne Auslagerung verschieben, ausgegangen von "kein Paging".Ach so, Du bist von mit ohne Paging ausgegangen. Paging ist für mich integraler Bestandteil meines Konzepts, und damit implizit immer mit dabei. :-D
Also mein Core2Duo hat unter Linux eine Speicherperformance von ~2 GB/s.Meine FPGA-Lösung kommt hoffentlich auf mehr als ein Zwanzigstel davon (also etwa gut 100 MBytes/s Kopier-Transferrate), und dafür werde ich mich schon anstrengen müssen. Damit möchte ich sicher nicht Segmente mit eventuell mehreren GBytes atomar verschieben müssen. Vielleicht wird es mehr wenn der Kopiervorgang von der CPU gemacht wird an welcher der Speicher dran hängt, damit würde man wenigstens die Latenzzeiten der Kommunikation zwischen den CPUs umgehen und die DRAM-Controller im Spartan 6 bringen bestimmt auch deutlich mehr als 200 MBytes/s.
Um also ein paar MB umherzuschiebenDie Segmente können schon richtig groß werden, es hindert doch niemand die Programme daran mal ordentlich Speicher zu allozieren. Zumindest dürfen speicherhungrige Programme kein KO-Kriterium für mein Konzept sein.
Das System sollte an sich schon nicht stehenbleiben,Aber die Auswahl an lauffähigen Prozessen könnte schon erheblich eingeschränkt sein. Außerdem dürften vor allem Programme die gerade im User-Focus stehen (und daher auf User-Aktionen reagieren müssen) am ehesten ihre Segmente vergrößern wollen und da fallen dem User Totzeiten natürlich als erstes auf.
nur hängt bei mir im Hinterkopf halt immernoch der Gedanke, wann denn die Defragmentierung nötig ist. Das ist sie kurz vorm Swappen (oder bei hinreichend langer Uptime mit vielen sich schlecht verhaltenden(*) Programmen). Also im Zweifelsfall fast nie.Naja, das ist wie auf einer Festplatte, nach langer Benutzung wird die Belegung des physischen Speichers schon ziemlich zerklüftet sein. Ich denke es wird ziemlich schnell passieren das Defragmentierung nötig wird, mit steigender Speicherauslastung natürlich noch schneller. Ich befürchte das ich da selbst mit möglichst intelligenter Segment-Allozierung (z.B. Best-Fit oder hinter jedem Segment was frei lassen) nicht lange ohne Defragmentieren auskomme.
Wenn meine Festplatte beschäftigt ist (oder der Auslagerungsmarathon beginnt), dann steht mein Linux auch komplett.Also mein Linux steht da nicht komplett, das Linux versucht doch als erstes Pages auszulagern die schon eine Weile nicht mehr benutzt wurden. Durch das Auslagern wird zwar die Platte beschäftigt (was ansich keine CPU-Zeit kostet) aber wenn Linux die Pages gut wählt dann merken die Programme davon gar nichts. Ich gehe davon aus das die Page-Ersetzungs-Strategien von Linux ziemlich gut sind.
Schlecht verhalten tun sich Programme, welche einen großen Puffer alloziieren, diesen nutzen und etwas ausrechnen, für das Ergebnis aber keinen weiteren Puffer alloziieren, sondern nur den nicht mehr genutzten Teil freigeben. Sehr schlimm wird das, wenn im Puffer dann der letzte Teil behalten und der große erste Teil freigegeben wird.Das wäre aber bei meinen Segmenten eventuell auch ein Problem. Der Heap eines Programms liegt in mehreren Segmenten für unterschiedliche Objekt-Größen (ich denke da so an vielleicht 6 bis 10 Segmente, also von 64 Bytes an für jede weitere 4er-Potenz ein extra Heap-Segment) um die interne Fragmentierung möglichst klein zu halten, damit wäre ein realloc recht aufwendig wenn dabei das Objekt in ein anderes Segment verschoben wird. Diese Probleme treffen aber auch Programme auf Flat-Memory-Systemen, okay da kann man immerhin mitten aus dem virtuellen Raum einfach ein paar Pages freigeben falls diese komplett unbenutzt sind. Wenn wir von einem Programm ausgehen das sehr viele kleine Blöcke, mit leicht unterschiedlicher Größe, alloziert und in ungeordneter Reihenfolge teilweise wieder frei gibt dann dürften ziemlich viele Pages nur teilweise benutzt sein also das Programm deutlich mehr virtuellen Speicher belegen als eigentlich notwendig währe. Diese innere Fragmentierung in Grenzen zu halten ist Aufgabe der malloc-Implementierung in der libc (da kann ich aus einem reichhaltigem Fundus unterschiedlichster Ansätze und Forschungstheorien wählen), gegen die externe Fragmentierung hat mein OS die Segmente die es nach belieben Defragmentieren kann.
Das ist den uClibc-Leuten aufgefallen und stellt für diese ein großes Problem dar.Okay, uCLinux hat schon aufgrund seines Konzeptes ein paar Probleme mehr. Da werden die Nachteile aus verschiedenen Welten kombiniert (und die jeweiligen Vorteile beseitigt), Speicherverwaltung ganz ohne Hardwareunterstützung ist eben nicht so einfach.
Achso, naja ich gehe immer davon aus, dass ein Segment bei 0 anfängt (ist für mich einer der Hauptvorteile) und eine Segmentnummer dazukommt.Das meine Segmente nicht bei 0 anfangen müssen ist eher ein nettes Zusatzfeature, außerdem hatte ich in den Descriptoren noch Platz. Die meisten Segmente werden wohl ihre Offsets ganz normal bei 0 anfangen lassen.
Wenn du den Zugriff auf programmfremde Segmente hardwareseitig unterbindest (Speicherschutz)Ja, das habe ich vor. :-D
Ich kenne halt nur die simple Mechanik des 8086 ...An den x86-Real-Mode denken die meisten Leute wenn das Wort "Segmentierung" fällt (falls sie alt genug sind). Die Möglichkeiten die bereits der 386-Protected-Mode bietet (auch wenn das nur ein bescheidener Vorgeschmack auf richtige Segmentierung ist) sind selbst unter den erfahrenen Programmierern der überwiegenden Mehrheit völlig unbekannt. Wenn ich mit Leuten über meine Ideen rede dann darf ich immer erst mal anfangen zu erklären was dynamische Segmentierung wirklich ist. :(
Wenn der RAM alle ist, verlierst du mindestens einen Faktor 100-1000 für den RAM-Zugriff,Aber nur auf Speicher der auch wirklich ausgelagert wurde.
ob du nun das Office-Paket komplett oder zu einem Großteil ausgelagert hast, spielt da eine untergeordnete Rolle.Das sehe ich anders. Viele Programme benutzen, über einen kurzen bis mittleren Zeitraum betrachtet, nur einen kleinen Teil des allozierten Speichers. Von einem Programm zwangsläufig alles auslagern zu müssen, anstatt nur den tatsächlich unbenutzten Teil rauszuwerfen, erscheint mir wahrlich ungeschickt. Ersteres bedeutet dass das Programm komplett lahmgelegt ist und Letzteres kann bedeuten das es ohne (spürbare) Einschränkungen weiter arbeitet. Für jeden Tastendruck in einem Office-Programm mal eben über 100 MBytes einzulesen (und andere 100 MBytes vorher rauszuwerfen) ist sicher nicht praktikabel.
Swapping ist eine Notlösung und war (auch) einen Grund für Paging überhaupt. Der einzige Vorteil mit Paging ist die feinere Granlarität und damit die Möglichkeit, geringe Knappheit gut abzufangen.Paging ist IMHO die einzigste Möglichkeit Knappheit für alle Programme fair abzufangen.
Mein geplanter Anwendungsfall ist ein allgemein nutzbarer PC. Daher darf ich auch keine Annahmen über die Programme machen. Es muss z.B. auch möglich sein Segmente zu erstellen die größer als der physische RAM sind, dass das nicht unbedingt geschickt/performant ist ist mir klar.Ich möchte den linearen Speicher wirklich im Hintergrund bei laufendem System defragmentieren können, wenn das nicht geht ist das Konzept mit der Segmentierung IMO nicht tragfähig.Hängt vom Anwendungsfall ab.
Wenn du Umsortieren musst, weil der RAM knapp wird, dann schon.RAM-Knappheit ist der Grund fürs Swappen nicht fürs Defragmentieren.
Ich hätte dann halt auf ein Zwischenmedium ausgelagert, du lagerst in den virtuellen Adressraum aus. Das Umsortieren bei zu sehr zerstückeltem RAM sollte dagegen auch im physischen Bereich nicht zu lange dauern, da es dort meist um viele sehr kleine Segmente geht, für die noch Platz vorhanden sollte. Sonst ist der RAM voll und es tritt Fall 1 ein.Ich glaube Du gehst davon aus das jeder Aufruf von malloc in einem User-Space-Programm ein neues Segment liefert. Das wäre echte Speicher-Verschwendung und für das OS ziemlich aufwändig. Den Heap verwalten die Programme selber, mit ihrer libc (die natürlich auch von mir kommen muss), siehe oben. Außerdem würden mir dann ziemlich schnell die Einträge in der LDT ausgehen. Natürlich können die Programme für bestimmte Dinge auch zusätzliche Segmente anfordern, z.B. ein Verschlüsselungsprogramm könnte die Keys in einem extra Segment halten und dieses Segment gegen das Swappen sperren lassen damit die unverschlüsselten Keys nicht auf die Platte kommen. Die Segmente in meinem System werden im Durchschnitt eher ziemlich groß sein.
Wenn in der Zeit des Defragmentierens die Gesamtsystemleistung um 5-25% sinkt (du möchtest sicherlich kein 4stufiges Paging haben), stört das auch nicht weiter, wäre sogar eine starke Verbesserung gegenüber dem Halt der betreffenden Programme. Bei einer angenommenen Performance von 200 MIPS verlierst du dann vielleicht 20-30 MIPS, das merkt man nichtmal - solange man die Anwendung nicht auf die CPU zuschneidet (d.h. Echtzeitbetrieb).Die Performance sinkt beim Defragmentieren nur in den betroffenen Segmenten und das ist natürlich nicht so arg schlimm (vor allem weniger schlimm als wenn alle Programme leiden müssten). Um diesen Verlust so klein wie nur möglich zu halten soll das OS möglichst große Pages verwenden (deshalb will ich möglichst viele Page-Größen unterstützen) und der flexible TLB (der mit allen Page-Größen gut zurecht kommt) soll sein übriges dazu tun. Im 32 Bit-System wird nur ein 2-stufiges Paging verwendet (vom 586 abgeschaut, nur mit mehr unterstützen Page-Größen). Bei der 64 Bit-Ausführung bin ich mir noch nicht sicher, mit einem 3-stufigem Paging läuft das auf 512 kByte als kleinste Page-Größe hinaus (ist dann schon recht oft eine gewisse Speicher-Verschwendung) und mit nem 4-stufigem Paging komme ich auf 64 kByte als kleinste Page-Größe (was im Hinblick auf den Verschnitt schon etwas freundlicher aussieht, aber wohl etwas schlechter performt). Ich möchte mit den guarded-Page-Tables aber Abkürzungen durch die Tabellen ermöglichen, so das die 4 Stufen (bei den 64 kByte-Pages) sich auf 2 reduzieren lassen wenn nur 1 TByte oder weniger virtueller Speicher benötigt wird.
Ja. Ich möchte die betreffende Page (zerstückelt in die kleinste Page-Größe) zum Verschieben als nicht-präsent markieren, alle anderen CPUs per Broadcast-Message informieren diesen Bereich aus dem TLB zu tilgen, den Inhalt kopieren und als letztes die neue physische Adresse in die Page-Table Eintragen und wieder als präsent markieren. Falls während der kurzen Zeit des umkopierens auf einer der anderen CPUs eine Page-Not-Present-Exception auftritt dann wird der Exception-Handler im Paging-Directory feststellen was Sache ist und einfach aktiv warten (pollen) bis der Kopier-Vorgang abgeschlossen ist (also die Page wieder benutzbar ist) und dann wieder zum Programm zurückkehren, wenn die CPU dann den Speicherzugriff noch mal probiert geht wieder alles. Das Programm (bzw. der eine Thread) friert für nicht mal eine Millisekunde (bei 100 MByte/s und 4 kByte/Page sind das 40 µs) ein und schon geht es weiter, ich denke das ist tolerabel und auch sicher recht selten (wäre schon ein guter Zufall wenn das Programm genau in dem Moment was von genau der Page will wo diese gerade verschoben wird).Warum sollte das mit mehreren CPUs schwerer werden?Im Endeffekt sortierst du den physischen Speicher ja trotzdem um, auch wenn die MMU dazwischen sitzt. Du müsstest halt atomar dafür sorgen, dass für jedes verschobene Fragment eines Segments die Pagingtabelle korrekt ist. Sonst hat das Programm zwar eine nette virtuelle Adresse zum Arbeiten und greift trotzdem ins Leere, wenn die MMU auf den falschen Speicher zeigt (der da gerade wegverschoben wurde).
Zum Defragmentieren würde es aber reichen, wenn du nämlich je zwei Segmente in das Paging aufnimmst und dann im Hintergrund sortierst.Hä?? Was hat defragmentieren mit sortieren zu tun und warum soll das genau 2 Segmente betreffen? Es ist durchaus möglich das mehrere Segmente der Defragmentierung bedürfen da sie vor kurzem vergrößert werden sollten aber nicht wachsen konnten weil dahinter schon was anderes lag (und kurzerhand in den virtuellen Speicher umgemappt wurden).
Paging kostet ja nur deshalb so viel, weil die Pagingtabelle im RAM liegt und du dadurch Unmengen an zusätzlichen RAM-Zugriffen hast, die vom Cache nur ungenügend abgefedert werden können. Oder seh ich da grundsätzlich was falsch?Das siehst Du ganz genau richtig.
Naja, wenn du keinen Speicherschutz hast, dann kann sich jedes Programm eine Segmentnummer zusammenbauen und dort zugreifen.Richtig guter Speicherschutz ist das Kern-Ziel welches ich mit meiner Segmentierung erreichen will. :-D
Wenn du das nicht möchtest, dann musst du bei jedem Segmentzugriff nachschauen, ob das betreffende Segment in der weißen Liste des Prozesses steht...Jain, die LDT wird natürlich nicht bei jedem Speicherzugriff befragt sondern nur wenn ein Selector in ein Segment-Register geladen werden soll bzw. wenn der Segment-Descriptor in die zugehörigen Shadow-Register geladen/decodiert wird. Eine Leaf-Funktion die nur mit den als Parameter übergebenen Pointen und globalen Variablen arbeitet braucht sowas überhaupt gar nicht zu tun. Auch sonst werde ich alles unternehmen damit die LDT möglichst selten benötigt wird.
Naja, nur wenn du sehr viele sehr große Segmente hast, ist das Problem der Fragmentierung nicht gegeben, denn die tritt hauptsächlich bei sehr kleinen (bzw. von vorne verkürzten) Segmenten auf. Das sind in der Regel nur ein paar MB oder sogar weniger.Um also ein paar MB umherzuschiebenDie Segmente können schon richtig groß werden, es hindert doch niemand die Programme daran mal ordentlich Speicher zu allozieren. Zumindest dürfen speicherhungrige Programme kein KO-Kriterium für mein Konzept sein.
Das meinte ich mit begrenzter Echtzeitfähigkeit. Den Programmen das beizubringen ist ja kein Problem, gewisse Segmente braucht man nicht verschieben, wenn man sie im Voraus günstig platziert (z.B. Treiber).Das System sollte an sich schon nicht stehenbleiben,Aber die Auswahl an lauffähigen Prozessen könnte schon erheblich eingeschränkt sein. Außerdem dürften vor allem Programme die gerade im User-Focus stehen (und daher auf User-Aktionen reagieren müssen) am ehesten ihre Segmente vergrößern wollen und da fallen dem User Totzeiten natürlich als erstes auf.
Ich befürchte das ich da selbst mit möglichst intelligenter Segment-Allozierung (z.B. Best-Fit oder hinter jedem Segment was frei lassen) nicht lange ohne Defragmentieren auskomme.Ich vermute mal schon, allerdings immer unter der Annahme von genügend physischem RAM.
Das mag sein, aber wenn die Maus ruckelt und auf Tastendrücke keine Reaktion kommt, dann steht für den Nutzer das System (bis der OOM-Killer anläuft). Ansonsten tritt das Phänomen nicht auf. In dem Augenblick, wo Pages ausgelagert werden müssen, die noch gebraucht werden, ist ohnehin alles verloren. Bis zu diesem Zeitpunkt kann man Buffers/Caches einfach wegwerfen.Wenn meine Festplatte beschäftigt ist (oder der Auslagerungsmarathon beginnt), dann steht mein Linux auch komplett.Also mein Linux steht da nicht komplett, das Linux versucht doch als erstes Pages auszulagern die schon eine Weile nicht mehr benutzt wurden. Durch das Auslagern wird zwar die Platte beschäftigt (was ansich keine CPU-Zeit kostet) aber wenn Linux die Pages gut wählt dann merken die Programme davon gar nichts. Ich gehe davon aus das die Page-Ersetzungs-Strategien von Linux ziemlich gut sind.
Besser als uClinux geht es fast nicht. Trotz der ganzen Probleme, die mit der Speicherfragmentierung einhergehen, läuft das System trotzdem. Gut, in der Regel mit relativ einfachen und kleinen Programmen, aber "out of memory" bei zerstückeltem RAM gibt es selten unter 75% RAM-Auslastung. Und solche Situationen merkt man auch schon früher als Fall für den Garbage Collector.Das ist den uClibc-Leuten aufgefallen und stellt für diese ein großes Problem dar.Okay, uCLinux hat schon aufgrund seines Konzeptes ein paar Probleme mehr. Da werden die Nachteile aus verschiedenen Welten kombiniert (und die jeweiligen Vorteile beseitigt), Speicherverwaltung ganz ohne Hardwareunterstützung ist eben nicht so einfach.
Nicht während des Aus- und wieder Einlagerns. Die halte ich für das größte Problem, denn während der Kernel sein Speichermanagement mit der Festplatte abquatschen muss, geht nicht wirklich viel. Besonders, wenn die Page, in der der Taskzustand steht, fehlt und das Programm nicht als schlafend markiert werden kann. Ich halte das Speichermanagement für zu kritisch, als dass man nebenher die unbelasteten Anwendungen weiterlaufen lassen könnte.Wenn der RAM alle ist, verlierst du mindestens einen Faktor 100-1000 für den RAM-Zugriff,Aber nur auf Speicher der auch wirklich ausgelagert wurde.
Muss das Programm denn nur aus drei Segmenten bestehen? Wer hindert dich denn daran, für die Eventhandler ein eigenes Segment vorzusehen (welches dann, da es eh zu klein ist, nicht ausgelagert würde)?ob du nun das Office-Paket komplett oder zu einem Großteil ausgelagert hast, spielt da eine untergeordnete Rolle.Das sehe ich anders. Viele Programme benutzen, über einen kurzen bis mittleren Zeitraum betrachtet, nur einen kleinen Teil des allozierten Speichers. Von einem Programm zwangsläufig alles auslagern zu müssen, anstatt nur den tatsächlich unbenutzten Teil rauszuwerfen, erscheint mir wahrlich ungeschickt.
Ersteres bedeutet dass das Programm komplett lahmgelegt ist und Letzteres kann bedeuten das es ohne (spürbare) Einschränkungen weiter arbeitet. Für jeden Tastendruck in einem Office-Programm mal eben über 100 MBytes einzulesen (und andere 100 MBytes vorher rauszuwerfen) ist sicher nicht praktikabel.Der Tastendruck gehört entweder woanders verarbeitet, oder aber das Office-Paket nicht ausgelagert, wenn es eh noch benutzt wird.
Außerdem gehst du davon aus, dass die Programme von der Segmentierung nichts wissen (oder mitkriegen können), und somit dein System nicht entlasten können. Das sehe ich wie, wenn sich zwei Deutsche auf Englisch unterhalten - es funktioniert, ist aber schon im Ansatz falsch. Wenn du den Programmen keine segmentfreundliche Nutzung zugestehst, dann kann das nicht performant werden, einfach weil du auf der falschen Architektur arbeitest.Mein geplanter Anwendungsfall ist ein allgemein nutzbarer PC. Daher darf ich auch keine Annahmen über die Programme machen. Es muss z.B. auch möglich sein Segmente zu erstellen die größer als der physische RAM sind, dass das nicht unbedingt geschickt/performant ist ist mir klar.Ich möchte den linearen Speicher wirklich im Hintergrund bei laufendem System defragmentieren können, wenn das nicht geht ist das Konzept mit der Segmentierung IMO nicht tragfähig.Hängt vom Anwendungsfall ab.
Vor dem Swappen kommt zwangsweise ein Defragmentieren, um die letzten Speicherreste auszukratzen. Und bei genügend RAM (oder nicht genügend RAM-Auslastung) gibt es keinen Grund zu defragmentieren.Wenn du Umsortieren musst, weil der RAM knapp wird, dann schon.RAM-Knappheit ist der Grund fürs Swappen nicht fürs Defragmentieren.
Die Segmente in meinem System werden im Durchschnitt eher ziemlich groß sein.Womit du innerhalb der Segmente den Aufwand hast. Andererseits sparst du Unmengen FAR-Pointer ein, wenn die Anzahl der Segmente der Anwendung größer wird als die Anzahl der lokalen Segmentregister...
Klingt gut. Wobei ich die möglicherweise auftretenden Probleme bei mehreren CPUs weder ab- noch einschätzen kann.Ja. ...Warum sollte das mit mehreren CPUs schwerer werden?Im Endeffekt sortierst du den physischen Speicher ja trotzdem um, auch wenn die MMU dazwischen sitzt. Du müsstest halt atomar dafür sorgen, dass für jedes verschobene Fragment eines Segments die Pagingtabelle korrekt ist. Sonst hat das Programm zwar eine nette virtuelle Adresse zum Arbeiten und greift trotzdem ins Leere, wenn die MMU auf den falschen Speicher zeigt (der da gerade wegverschoben wurde).
Ich halte defragmentieren als einen Oberbegriff, nämlich wann immer die physische RAM-Aufteilung geändert werden muss. Und das betrifft grundsätzlich immer zwei Segmente - das Quell- und das Zielsegment. Wenn du die beiden in das Paging verlagerst (bzw. ein paar mehr für stark unterschiedliche Größen), kannst du die im Hintergrund deine Speichermagie betreiben.Zum Defragmentieren würde es aber reichen, wenn du nämlich je zwei Segmente in das Paging aufnimmst und dann im Hintergrund sortierst.Hä?? Was hat defragmentieren mit sortieren zu tun und warum soll das genau 2 Segmente betreffen? Es ist durchaus möglich das mehrere Segmente der Defragmentierung bedürfen da sie vor kurzem vergrößert werden sollten aber nicht wachsen konnten weil dahinter schon was anderes lag (und kurzerhand in den virtuellen Speicher umgemappt wurden).
Naja, nur wenn du sehr viele sehr große Segmente hast, ist das Problem der Fragmentierung nicht gegeben, denn die tritt hauptsächlich bei sehr kleinen (bzw. von vorne verkürzten) Segmenten auf. Das sind in der Regel nur ein paar MB oder sogar weniger.Wieso sollte die Fragmentierung bevorzugt mit kleinen Segmenten auftreten? Gerade für kleine Segmente wird sich doch noch eher eine passende Lücke finden lassen als für große Segmente. Ein Segment das nur eine Page (kleinste Page-Größe) belegt kann gar nicht fragmentieren.
Wenn du natürlich möglichst fleißig Segmente zusammenfassen möchtest, damit die Segmente möglichst groß werden und du innerhalb der Segmente den Verwaltungsaufwand hast, dann kommt zu der externen Fragmentierung (Segmente im physischen Adressraum) auch noch interne Fragmentierung (malloc/realloc in den Segmenten) dazu. Stell ich mir unangenehm vor.Ja, das möchte ich. Die interne Fragmentierung werde ich aber mit meinen unabhängigen dynamischen Segmenten deutlich besser in den Griff bekommen als das ein Flat-Memory-Programm je könnte.
gewisse Segmente braucht man nicht verschieben, wenn man sie im Voraus günstig platziert (z.B. Treiber).Die Segmente für .code , .const und .data sind in ihrer Größe fest, die werden von hinten anfangend in den physischen Speicher gepackt. Die Heap-Segmente und Stack-Segmente sind flexibel und kommen von vorne anfangend in den physischen Speicher. Der Micro-Kernel in meinem OS weiß nicht was ein Treiber ist, der wird alle Programme gleich gut behandeln. Es wird aber eine Möglichkeit geben Segmente zu locken damit sie garantiert nicht verschoben werden (Treiber werden das nutzen), so ein Segment wird dann auch sofort nach vorne verlegt (der Treiber blockiert also während seiner Initialisierung ein bisschen wegen dem sofortigem verschieben oder das Segment wird gleich als gelockt erstellt), damit ist das Segment aber auch nicht mehr in seiner Größe änderbar.
Das hängt sehr davon ab wie neue Programme gestartet und beendet werden oder die Programme ihre (Heap-/Stack-)Segmente vergrößern/schrumpfen. Solange die RAM-Auslastung unter 50% bleibt sollte Defragmentieren kaum nötig sein, wird aber trotzdem mal auftreten (vor allem beim wachsen von Segmenten, da kann immer mal was anderes im Weg sein).Ich befürchte das ich da selbst mit möglichst intelligenter Segment-Allozierung (z.B. Best-Fit oder hinter jedem Segment was frei lassen) nicht lange ohne Defragmentieren auskomme.Ich vermute mal schon, allerdings immer unter der Annahme von genügend physischem RAM.
Auf einem normalen PC hast du in der Regel genug Idle-Zeit für solche Spielereien,Du kennst Seti@Home oder seine Nachfolger? Von genügend Idle-Zeiten auszugehen ist meiner Meinung nach so eine Annahme die einem früher oder später richtig Ärger macht. Solche Annahmen will ich nicht treffen und auch nicht ins Design meiner Plattform einfließen lassen!
ansonsten kannst du die natürlich jederzeit auch garantieren.Wie? Mal abgesehen davon das sowas IMHO Quatsch ist.
(Wobei garantierter Leerlauf eher nach Überhitzungsschutz klingt...)lol :-D
In dem Augenblick, wo Pages ausgelagert werden müssen, die noch gebraucht werden, ist ohnehin alles verloren.Das soll ja die Page-Ersetzungs-Strategie möglichst verhindern. Die ist natürlich auch nicht perfekt und dann gibt es ein paar kurze Wartezeiten (wobei wenn die Page-Not-Present-Exception noch während des Schreibens auf die Festplatte kommt könnte der Exception-Handler die Page einfach wieder als gültig markieren und den Schreibvorgang als ungültig), aber im Durchschnitt wird die schon gut funktionieren. Wenn es gar nicht mehr anders geht fliegen IMHO die Programme mit der niedrigsten Priorität als erstes raus (die habens dann natürlich extrem schwer).
Besser als uClinux geht es fast nicht. Trotz der ganzen Probleme, die mit der Speicherfragmentierung einhergehen, läuft das System trotzdem.Klar, in Anbetracht der Möglichkeiten die uCLinux hat ist das Ergebnis natürlich hervorragend.
aber "out of memory" bei zerstückeltem RAM gibt es selten unter 75% RAM-Auslastung. Und solche Situationen merkt man auch schon früher als Fall für den Garbage Collector.Bei mir wird dann das Defragmentieren anspringen wenn sich für ein neues Segment nicht mehr physischer Speicher am Stück findet oder wenn ein Segment nicht einfach wachsen konnte und deshalb physisch zerteilt werden musste. Andere Gründe für das Defragmentieren fallen mir nicht ein. Einen "Garbage Collector" wird es bei mir nicht geben, kann ich mir momentan auch nicht vorstellen was der machen sollte. Das einzigste was mir einfällt sind Programme die ihren Speicher freiwillig, auf Bitte vom OS-Kernel, wieder hergeben. Ein File-System-Cache wäre da ein gutes Beispiel, normale User-Programme trifft das eher nicht.
denn während der Kernel sein Speichermanagement mit der Festplatte abquatschen muss, geht nicht wirklich viel.Das "abquatschen" (Verwaltungsstrukturen im Speicher ändern) dürfte bei der Performance heutiger PC-CPUs vernachlässigbar sein und der eigentliche Datentransfer wird vom Festplatten-Host-Controller selbstständig erledigt, da kann die CPU sich längst wieder um die Programme kümmern.
Besonders, wenn die Page, in der der Taskzustand steht, fehltEin OS das seine eigenen Verwaltungsstrukturen auslagert hat es auch nicht besser verdient. ;)
Ich halte das Speichermanagement für zu kritisch, als dass man nebenher die unbelasteten Anwendungen weiterlaufen lassen könnte.Wieso? Wenn man alles ordentlich mit spinlocks o.ä. absichert sehe ich da keine Probleme. Ein passendes Konzept zu entwerfen ist sicher nicht trivial (das werde ich bestimmt früher spüren als mir lieb ist) aber nicht unmöglich. Im Linux-Kernel versucht man doch auch diesen "Big-Kernel-Lock" zu beseitigen damit der Kernel auch unter Last noch die Wünsche der Programme befriedigen kann, gerade auf einem massiven SMP-System ist das nötig.
das Problem sehe ich heutzutage aber eher darin, dass irgendein Programm amok läuft, schnell viel RAM füllt, das System in die Ecke drängt bis es dann vom Kernel OOM-gekillt wird.Solche Programme sind natürlich ein Ärgernis. Trotzdem soll das OS auch damit einigermaßen geschickt umgehen können. Hier wären vielleicht Memory-Quotas eine Lösung. Sowas wurde, hier im Forum, schon mal diskutiert http://lowlevel.brainsware.org/forum/index.php?topic=2256. Wenn mein OS mit Amok laufenden Programmen einigermaßen (also besser als Windows) umgehen kann dann bin ich zufrieden.
Swap als Arbeitsgrundlage braucht man mMn nicht mehr. Dazu ist RAM zu billig geworden.ACK
Nein, die Programme werden schon ein paar Segmente mehr benötigen. Aber da die Größe der Segmente wohl in Pages (Größe der kleinsten unterstützen Page, in der 64 Bit-Variante also mindestens 64 kBytes) gemessen wird, entsteht eine Menge Verschnitt wenn man alles auf zu viele kleine Segmente verteilt. Außerdem kann die LDT maximal 32767 Segmente aufnehmen, damit will ich schon etwas sparsam umgehen.Muss das Programm denn nur aus drei Segmenten bestehen?ob du nun das Office-Paket komplett oder zu einem Großteil ausgelagert hast, spielt da eine untergeordnete Rolle.Das sehe ich anders. Viele Programme benutzen, über einen kurzen bis mittleren Zeitraum betrachtet, nur einen kleinen Teil des allozierten Speichers. Von einem Programm zwangsläufig alles auslagern zu müssen, anstatt nur den tatsächlich unbenutzten Teil rauszuwerfen, erscheint mir wahrlich ungeschickt.
ein eigenes Segment vorzusehen (welches dann, da es eh zu klein ist, nicht ausgelagert würde)?Wieso sollten kleine Segmente nicht ausgelagert werden?
Der Tastendruck gehört entweder woanders verarbeitetUnd Wo? Wenn nicht im Office-Paket?
oder aber das Office-Paket nicht ausgelagertGenau das ist der Punkt wo ich sage das Paging die einzigste Möglichkeit ist um Knappheit für alle Programme gleich fair abzufangen. Man kann doch schließlich nicht große Programme komplett im RAM lassen nur weil ein kleiner Teil davon benötigt wird. Ich denke jedes Programm muss etwas geben damit auch bei (leichter) Knappheit alle Programme noch einigermaßen arbeiten können.
Außerdem gehst du davon aus, dass die Programme von der Segmentierung nichts wissen (oder mitkriegen können), und somit dein System nicht entlasten können. Das sehe ich wie, wenn sich zwei Deutsche auf Englisch unterhalten - es funktioniert, ist aber schon im Ansatz falsch. Wenn du den Programmen keine segmentfreundliche Nutzung zugestehst, dann kann das nicht performant werden, einfach weil du auf der falschen Architektur arbeitest.Ja, Du hast (in gewissen Grenzen) recht. Solange Segmentierung allgemein belächelt wird hab ich aber keine andere Wahl. Wirklich alles selber programmieren will ich nicht, ich will auch mal ein nützliches Tool portieren können. Natürlich muss die libc genau wissen wie die Segmente zu nutzen sind, auch all die anderen Librarys welche die Dienste des OS, oder der Personality, dem eigentlichen User-Space-Programm zugänglich machen sollten wissen das sie es mit Segmentierung zu tun haben. Wenn ich mir normale C-Programme ansehe dann gehen die erstmal von gar keiner Architektur aus, ich glaube auch nicht das in den C/C++-Specs dazu was drin steht. Es ist Aufgabe des Compilers die tatsächliche Ziel-Architektur angemessen (also möglichst optimal) zu bedienen.
Heutzutage ist es ja (für größere Projekte) auch üblich, dass man möglichst nicht regelmäßig den Cache kaputtmacht. Solche Rücksichtnahme auf die reale Architektur muss vorhanden sein...Da gebe ich Dir natürlich recht. Rücksicht auf Caches und dergleichen nehmen die Programmierer aber nicht für das Betriebssystem oder die Plattform sondern für die Performance des Programms, also aus Eigennutz. Die Existenz und Wirkungsweise von Caches hat physische Ursachen um die auch ich nicht herum komme, so das all diese Tricks eben auch auf meiner Plattform funktionieren werden (wenn auch auf einem deutlich niedrigerem Niveau).
Wieso? Defragmentieren bringt keinen zusätzlichen freien physischen Speicher sondern verteilt diesen nur anders. Defragmentieren und Swappen sind 2 völlig verschiedene Dinge mit völlig verschiedenen Ursachen und völlig verschiedenen Konzepten, die nur beide (zufällig) das selbe Paging benutzen um ihre jeweilige Aufgabe zu erfüllen.RAM-Knappheit ist der Grund fürs Swappen nicht fürs Defragmentieren.Vor dem Swappen kommt zwangsweise ein Defragmentieren, um die letzten Speicherreste auszukratzen.
Und bei genügend RAM (oder nicht genügend RAM-Auslastung) gibt es keinen Grund zu defragmentieren.Jedenfalls nur sehr selten.
Womit du innerhalb der Segmente den Aufwand hast.So wie in einem Flat-Memory-Programm auch, nur das ich mit den unabhängigen Segmenten ein paar Freiheitsgrade mehr hab um die interne Fragmentierung im Zaum zu halten.
Andererseits sparst du Unmengen FAR-Pointer ein, wenn die Anzahl der Segmente der Anwendung größer wird als die Anzahl der lokalen Segmentregister...Hä? FAR-Pointer brauch ich doch trotzdem, der Compiler weiß doch nicht vorher wo die Speicherblöcke real liegen. Ein Pointer hat genau drei verschiedene Quellen: 1. der Compiler muss eine statische Variable referenzieren (aus .const oder .data, das passiert beim Linken), oder der Compiler muss eine lokale Variable referenzieren (also die Adresse von etwas ermitteln das auf dem Stack liegt, dafür muss der Compiler etwas Code erzeugen) und 3. kommt als Rückgabewert von malloc/realloc (das ist mein Part, ob ich da unbedingt Assembler für benötige weiß ich noch nicht). Alles andere ist reine Pointer-Arithmetik.
Ich halte defragmentieren als einen Oberbegriff, nämlich wann immer die physische RAM-Aufteilung geändert werden muss. Und das betrifft grundsätzlich immer zwei Segmente - das Quell- und das Zielsegment. Wenn du die beiden in das Paging verlagerst (bzw. ein paar mehr für stark unterschiedliche Größen), kannst du die im Hintergrund deine Speichermagie betreiben.Wieso "Quellsegment" und "Zielsegment"? Ich kopiere doch nicht den Inhalt von einem Segment in ein anderes sondern verschiebe (in Wirklichkeit natürlich kopieren) die Pages aus denen ein Segment besteht. Das Segment befindet sich für diesen Vorgang zwar im virtuellen Speicher und seine Pages sing möglicherweise total über den physischen Speicher zerstreut, aber es ist trotzdem nur ein Segment an dem gerade gearbeitet wird.
Was ich damit grundsätzlich sagen möchte, wenn du ohne Paging auskommen möchtest (es im Normalfall also eh nicht nutzen möchtest), dann brauchst du auch nicht die volle Flexibilität von Paging. Verschwendetes Silizium.Ohne ein vollwertiges Paging bringt mir die Segmentierung aber zu viele Nachteile (zum DOS will ja niemand zurück). Natürlich kostet das Silizium aber ohne kommt kein gescheites System bei raus.
Für mich klingt es nach einer ausgewachsenen Nutzung von Paging und Segmentierung (was ich persönlich auch sehr gut finde) mit dem Designziel, die Pagingeinheit grundsätzlich abgeschaltet zu lassen.Ganz genau. Ich will das Paging haben weil ich damit ne Menge Magie veranstalten kann aber ich will es nicht ständig benutzen müssen weil es Performance kostet. Wenn ich auf die Magie verzichte dann kostet das aber noch mehr Performance (z.B. atomares Verschieben von Segmenten ist IMHO ein absolutes KO-Kriterium).
Außerdem rede ich (für meine Begriffe) immer von Randbedingungen, seltenen Ereignissen - die zu optimieren bringt mMn wenig.Das Defragmentieren soll natürlich möglichst selten vorkommen aber wenn es dann, wenn es gebraucht wird, nicht richtig funktioniert ist IMHO mein Konzept totaler Mist (mal abgesehen davon das die meisten Leute die Segmentierung eh als totalen Mist bewerten).
Das Problem sind große Segmente, die zu (sehr) kleinen Segmenten schrumpfen, beispielsweise ein Netzwerkpuffer, in dem Netzwerkdaten gesammelt werden, bis dann nach dem Header die Nutzdaten kommen und der Header weggeschnitten wird. Dann hast du nämlich ein kleines Loch vor oder hinter dem nächsten Segment. Mach das so lange, bis du den Speicher voll hast mit kleinen Segmenten, die von kleinen Löchern umgeben sind und der nächste (große) Puffer kann nicht mehr alloziiert werden.Naja, nur wenn du sehr viele sehr große Segmente hast, ist das Problem der Fragmentierung nicht gegeben, denn die tritt hauptsächlich bei sehr kleinen (bzw. von vorne verkürzten) Segmenten auf. Das sind in der Regel nur ein paar MB oder sogar weniger.Wieso sollte die Fragmentierung bevorzugt mit kleinen Segmenten auftreten? Gerade für kleine Segmente wird sich doch noch eher eine passende Lücke finden lassen als für große Segmente. Ein Segment das nur eine Page (kleinste Page-Größe) belegt kann gar nicht fragmentieren.
Genau das meinte ich. Und wenn du systemkritische Segmente in der Form erzeugen kannst, werden die auch von paginglosem Defragmentieren nicht betroffen sein.gewisse Segmente braucht man nicht verschieben, wenn man sie im Voraus günstig platziert (z.B. Treiber).Die Segmente für .code , .const und .data sind in ihrer Größe fest, die werden von hinten anfangend in den physischen Speicher gepackt. Die Heap-Segmente und Stack-Segmente sind flexibel und kommen von vorne anfangend in den physischen Speicher. Der Micro-Kernel in meinem OS weiß nicht was ein Treiber ist, der wird alle Programme gleich gut behandeln. Es wird aber eine Möglichkeit geben Segmente zu locken damit sie garantiert nicht verschoben werden (Treiber werden das nutzen), so ein Segment wird dann auch sofort nach vorne verlegt (der Treiber blockiert also während seiner Initialisierung ein bisschen wegen dem sofortigem verschieben oder das Segment wird gleich als gelockt erstellt), damit ist das Segment aber auch nicht mehr in seiner Größe änderbar.
Ja, und solange es selten auftritt... :-) Das ist ja meine Ausgangssituation.Das hängt sehr davon ab wie neue Programme gestartet und beendet werden oder die Programme ihre (Heap-/Stack-)Segmente vergrößern/schrumpfen. Solange die RAM-Auslastung unter 50% bleibt sollte Defragmentieren kaum nötig sein, wird aber trotzdem mal auftreten (vor allem beim wachsen von Segmenten, da kann immer mal was anderes im Weg sein).Ich befürchte das ich da selbst mit möglichst intelligenter Segment-Allozierung (z.B. Best-Fit oder hinter jedem Segment was frei lassen) nicht lange ohne Defragmentieren auskomme.Ich vermute mal schon, allerdings immer unter der Annahme von genügend physischem RAM.
Klar, läuft bei mir. Und?Auf einem normalen PC hast du in der Regel genug Idle-Zeit für solche Spielereien,Du kennst Seti@Home oder seine Nachfolger? Von genügend Idle-Zeiten auszugehen ist meiner Meinung nach so eine Annahme die einem früher oder später richtig Ärger macht. Solche Annahmen will ich nicht treffen und auch nicht ins Design meiner Plattform einfließen lassen!
Wenn das Betriebssystem sich eine gewisse CPU-Zeit zur Selbstverwaltung genehmigt, sehe ich darin kein Problem. Damit könnte man dann sogar weiche Echtzeitanforderungen bearbeiten; harte Echtzeit kriegst du auf einem Allzweck-System ohnehin nicht hin.ansonsten kannst du die natürlich jederzeit auch garantieren.Wie? Mal abgesehen davon das sowas IMHO Quatsch ist.
Swap als Arbeitsgrundlage braucht man mMn nicht mehr. Dazu ist RAM zu billig geworden.ACK
Nun, die könnte man locken...ein eigenes Segment vorzusehen (welches dann, da es eh zu klein ist, nicht ausgelagert würde)?Wieso sollten kleine Segmente nicht ausgelagert werden?
Im Hauptprogramm aus 50 MB Code? Im Datensegment aus 400 MB Daten? Oder vielleicht doch in einem Event-Handler-Segment (bevorzugt gelockt)?Der Tastendruck gehört entweder woanders verarbeitetUnd Wo? Wenn nicht im Office-Paket?
Ich denke jedes Programm muss etwas geben damit auch bei (leichter) Knappheit alle Programme noch einigermaßen arbeiten können.Ich sehe leiche Knappheit als nicht vorhanden. Entweder die Ressourcen (bes. RAM) sind da, oder sie fehlen komplett. Bei Speichergrößen im GB-Bereich sehe ich ein paar fehlende KB als sehr unwahrscheinlich an. Und wenn es im Zig-MB-Bereich liegt, dann gibt das bereits spürbare Probleme, weil die meisten Programme, die viel RAM belegen, eben auch viel RAM nutzen.
Wie sollten den normale User-Programme mithilfe der Segmentierung mein System entlasten?Wenn sie gewisse Rücksicht auf das System nehmen, z.B. Eventhandler in eigene gelockte Segmente verlagern oder Segmente eben nicht stark verkleinern, sondern wegwerfen und sich ein kleines Segment nehmen, dann entlastet das die Verwaltung auch. Du versuchst ja auch nicht, ein Serversystem auf 'nem DSP laufenzulassen. Der ist dafür halt nicht gebaut. (Es geht, Linux läuft ja auf manchen. Deswegen ist es trotzdem nicht das geeignetste System.)
Da gebe ich Dir natürlich recht. Rücksicht auf Caches und dergleichen nehmen die Programmierer aber nicht für das Betriebssystem oder die Plattform sondern für die Performance des Programms, also aus Eigennutz.Haste Recht. Doof, so ein Egoismus. :x Trotzdem sollte man schon einigermaßen dafür sorgen, dem System nicht mehr Last aufzubrummen als notwendig.
Auf einer segmentierten Architektur führt Fragmentierung dazu, dass du keine großen, zusammenhängenden freien Speicherblöcke mehr hast. Durch Defragmentierung kannst du durch Umschichten der Segmente solche Blöcke erzeugen, die - je nach Stückelung der vorhandenen Segmente - genügend große Blöcke erzeugen und damit das Swapping nicht erforderlich machen.Wieso? Defragmentieren bringt keinen zusätzlichen freien physischen Speicher sondern verteilt diesen nur anders. Defragmentieren und Swappen sind 2 völlig verschiedene Dinge mit völlig verschiedenen Ursachen und völlig verschiedenen Konzepten, die nur beide (zufällig) das selbe Paging benutzen um ihre jeweilige Aufgabe zu erfüllen.RAM-Knappheit ist der Grund fürs Swappen nicht fürs Defragmentieren.Vor dem Swappen kommt zwangsweise ein Defragmentieren, um die letzten Speicherreste auszukratzen.
Wenn die verwendeten Segmentnummern zum Task gehören, braucht das Programm sich im Regelfall nicht um FAR-Pointer scheren (ein Datenzugriff geht nach .data, ein Codezugriff nach .code usw). Problematisch wird es erst, wenn du Segmentregister umladen musst - also FAR-Pointer dereferenzierst - denn das lässt sich nicht vermeiden. Klingt das überhaupt logisch?...Andererseits sparst du Unmengen FAR-Pointer ein, wenn die Anzahl der Segmente der Anwendung größer wird als die Anzahl der lokalen Segmentregister...Hä? FAR-Pointer brauch ich doch trotzdem, der Compiler weiß doch nicht vorher wo die Speicherblöcke real liegen. Ein Pointer hat genau drei verschiedene Quellen: 1. der Compiler muss eine statische Variable referenzieren (aus .const oder .data, das passiert beim Linken), oder der Compiler muss eine lokale Variable referenzieren (also die Adresse von etwas ermitteln das auf dem Stack liegt, dafür muss der Compiler etwas Code erzeugen) und 3. kommt als Rückgabewert von malloc/realloc (das ist mein Part, ob ich da unbedingt Assembler für benötige weiß ich noch nicht). Alles andere ist reine Pointer-Arithmetik.
Du hast ein Quellsegment, welches du verschieben möchtest und (ein oder mehrere) Zielsegmente, die da liegen, wo du das Quellsegment hinhaben möchtest. Die musst du vorher also sichern, ob im virtuellen Adressraum, (im physischen Adressraum, sofern genug Platz) oder auf Platte. So viele müssen das nicht unbedingt sein, außerdem muss oder sollte man große Segmente eh nur selten verschieben müssen, eher die kleinen drum herum.Ich halte defragmentieren als einen Oberbegriff, nämlich wann immer die physische RAM-Aufteilung geändert werden muss. Und das betrifft grundsätzlich immer zwei Segmente - das Quell- und das Zielsegment. Wenn du die beiden in das Paging verlagerst (bzw. ein paar mehr für stark unterschiedliche Größen), kannst du die im Hintergrund deine Speichermagie betreiben.Wieso "Quellsegment" und "Zielsegment"? Ich kopiere doch nicht den Inhalt von einem Segment in ein anderes sondern verschiebe (in Wirklichkeit natürlich kopieren) die Pages aus denen ein Segment besteht. Das Segment befindet sich für diesen Vorgang zwar im virtuellen Speicher und seine Pages sing möglicherweise total über den physischen Speicher zerstreut, aber es ist trotzdem nur ein Segment an dem gerade gearbeitet wird.
Naja, ich würde mit dem Paging gezielt die Probleme der Segmentierung abschwächen/entfernen wollen. Wenn das Konzept sich dann als nicht tragbar erweist, kann man sich immernoch um was anderes bemühen - oder Segmentierung endgültig als Mist abstempeln.Was ich damit grundsätzlich sagen möchte, wenn du ohne Paging auskommen möchtest (es im Normalfall also eh nicht nutzen möchtest), dann brauchst du auch nicht die volle Flexibilität von Paging. Verschwendetes Silizium.Ohne ein vollwertiges Paging bringt mir die Segmentierung aber zu viele Nachteile (zum DOS will ja niemand zurück). Natürlich kostet das Silizium aber ohne kommt kein gescheites System bei raus.
Naja, dann ist das eben so.Für mich klingt es nach einer ausgewachsenen Nutzung von Paging und Segmentierung (was ich persönlich auch sehr gut finde) mit dem Designziel, die Pagingeinheit grundsätzlich abgeschaltet zu lassen.Ganz genau. Ich will das Paging haben weil ich damit ne Menge Magie veranstalten kann aber ich will es nicht ständig benutzen müssen weil es Performance kostet. Wenn ich auf die Magie verzichte dann kostet das aber noch mehr Performance (z.B. atomares Verschieben von Segmenten ist IMHO ein absolutes KO-Kriterium).
Das Problem sind große Segmente, die zu (sehr) kleinen Segmenten schrumpfen, beispielsweise ein Netzwerkpuffer, in dem Netzwerkdaten gesammelt werden, bis dann nach dem Header die Nutzdaten kommen und der Header weggeschnitten wird. Dann hast du nämlich ein kleines Loch vor oder hinter dem nächsten Segment. Mach das so lange, bis du den Speicher voll hast mit kleinen Segmenten, die von kleinen Löchern umgeben sind und der nächste (große) Puffer kann nicht mehr alloziiert werden.Na und? So lange in Summe genügend freier Speicher vorhanden ist kann dieser auch für ein großes Segment alloziert werden. Dieses große Segment hat dann seine lineare Basis-Adresse nicht mehr direkt im physischen Speicher (weil es ja eben nicht mehr an einem Stück im physischen Speicher liegt) sondern im virtuellen Speicher (und dort werden die einzelnen physischen Stücke zu einem virtuellem Ganzen per Paging zusammengesetzt).
Das stimmt, bedeutet aber nicht das der noch vorhandene freie Speicher nicht mehr nutzbar ist. Genau dafür (und zum Swappen) will ich ordentliches Paging haben.Auf einer segmentierten Architektur führt Fragmentierung dazu, dass du keine großen, zusammenhängenden freien Speicherblöcke mehr hast.Vor dem Swappen kommt zwangsweise ein Defragmentieren, um die letzten Speicherreste auszukratzen.Wieso? Defragmentieren bringt keinen zusätzlichen freien physischen Speicher sondern verteilt diesen nur anders. Defragmentieren und Swappen sind 2 völlig verschiedene Dinge mit völlig verschiedenen Ursachen und völlig verschiedenen Konzepten, die nur beide (zufällig) das selbe Paging benutzen um ihre jeweilige Aufgabe zu erfüllen.
Durch Defragmentierung kannst du durch Umschichten der Segmente solche Blöcke erzeugen, die - je nach Stückelung der vorhandenen Segmente - genügend große Blöcke erzeugen und damit das Swapping nicht erforderlich machen.Solange noch genügend freier Speicher vorhanden ist, egal wie zerstreut der auch ist, kann ich den auch nutzen (Dank dem Paging). Swapping ist wirklich nur für tatsächliche RAM-Knappheit erforderlich.
Du hast ein Quellsegment, welches du verschieben möchtest und (ein oder mehrere) Zielsegmente, die da liegen, wo du das Quellsegment hinhaben möchtest. Die musst du vorher also sichern, ob im virtuellen Adressraum, (im physischen Adressraum, sofern genug Platz) oder auf Platte. So viele müssen das nicht unbedingt sein, außerdem muss oder sollte man große Segmente eh nur selten verschieben müssen, eher die kleinen drum herum.Ein Festplatten-Defragmentierer kann die Dateien auch nicht wo anders hin auslagern, der braucht einfach nur mindestens einen freien Cluster zum arbeiten. Wie groß die Segmente sind, die defragmentiert werden müssen, spielt für den Defragmentierer keine Rolle.
Bei Speichergrößen im GB-Bereich sehe ich ein paar fehlende KB als sehr unwahrscheinlich an. Und wenn es im Zig-MB-Bereich liegt, dann gibt das bereits spürbare Probleme, weil die meisten Programme, die viel RAM belegen, eben auch viel RAM nutzen.Wenn auf einem Rechner mit X GByte RAM gerade 32 kBytes fehlen dann ist es eben so und das OS muss schauen was es auslagern kann, was bei dieser kleinen Menge natürlich kein Problem ist. Wenn etliche MBytes fehlen dann muss das OS auch schauen was raus kann und da dürfte es in vielen Programmen einiges geben was nicht ständig gebraucht wird. Klar bei winzigen Programme die mit ein paar kBytes genau eine Aufgabe erledigen ist da wenig zu holen aber bei einem fetten Office-Paket dürfte da bestimmt einiges gehen.
... oder Segmente eben nicht stark verkleinern, sondern wegwerfen und sich ein kleines Segment nehmen, dann entlastet das die Verwaltung auch.Wieso sollte das anlegen eines neuen Segmentes und das löschen eines vorhandenen Segmentes weniger kosten als das einfache schrumpfen eines vorhandenen Segmentes? (mal davon abgesehen das ersteres 2 SYSCALLs und letzteres nur 1 SYSCALL braucht) Beim Verkleinern entsteht eben eine neue Lücke bzw. eine vorhandene Lücke neben dem Segment wird größer, das neu erstellen (und Daten umkopieren?) ist IMHO einiges aufwendiger. Wenn sich für das neue Segment nicht zufällig eine genau passende Lücke findet dann bleibt auch wieder ne Lücke übrig.
Für den Event-Handler extra Segmente anzulegen ist ne Verschwendung (Page-Verschnitt) und stelle ich mir außerdem schwierig/umständlich vor (der bräuchte ein eigenes Heap-System u.a.m., der Compiler müsste den Code/Daten nach X? Kriterien über mehrere Segmente verteilen). Genau da kann das Paging was rausreißen in dem es dafür sorgt das nur die Pages im RAM bleiben müssen die auch tatsächlich benötigt werden. Warum sollte ich die Programme komplexer machen wenn der gewünschte Effekt mit dem Swapping per Paging von ganz alleine kommt? Außerdem wäre diese Komplexität in den Programmen immer drin auch wenn gar nicht geswappt werden muss und swappen ist ja eher die Ausnahme als die Regel und für Ausnahmen muss man doch nicht unbedingt so sehr optimieren. Normale Segmente zu locken ist IMHO kontraproduktiv, das OS sollte selber entscheiden dürfen was es raus wirft und was nicht. Je mehr gelockt wird desto weniger Freiheiten hat das OS.Im Hauptprogramm aus 50 MB Code? Im Datensegment aus 400 MB Daten? Oder vielleicht doch in einem Event-Handler-Segment (bevorzugt gelockt)?Der Tastendruck gehört entweder woanders verarbeitetUnd Wo? Wenn nicht im Office-Paket?
Und wenn du systemkritische Segmente in der Form erzeugen kannst, werden die auch von paginglosem Defragmentieren nicht betroffen sein.Gelockte Segmente sind weder vom Defragmentieren noch vom Swapping betroffen und genau deshalb muss ich damit sehr sparsam sein sonst werden die normalen Programme zu arg blockiert. Sein Segment zu locken ist für die anderen Programme unfair (natürlich haben die trotzdem was von funktionierenden Triebern) und deshalb soll das nur gemacht werden wenn es tatsächlich zwingenst erforderlich ist.
Warum sollten normale User-Programme anfangen dürfen ihre Segmente einfach/unkontrolliert zu locken und damit eventuell wichtigen System-Programmen den Speicher wegnehmen?Nun, die könnte man locken...ein eigenes Segment vorzusehen (welches dann, da es eh zu klein ist, nicht ausgelagert würde)?Wieso sollten kleine Segmente nicht ausgelagert werden?
Dann weißt Du ja das man sich nicht auf Idle-Zeiten verlassen kann. Das einzigste wovon man ausgehen kann ist dass das OS sicherstellen muss das alle Programme einen fairen Zugang zur Ressource "CPU-Zeit" bekommen.Klar, läuft bei mir. Und?Auf einem normalen PC hast du in der Regel genug Idle-Zeit für solche Spielereien,Du kennst Seti@Home oder seine Nachfolger? Von genügend Idle-Zeiten auszugehen ist meiner Meinung nach so eine Annahme die einem früher oder später richtig Ärger macht. Solche Annahmen will ich nicht treffen und auch nicht ins Design meiner Plattform einfließen lassen!
Wenn das Betriebssystem sich eine gewisse CPU-Zeit zur Selbstverwaltung genehmigt, sehe ich darin kein Problem.Ich sehe da schon ein Problem. Natürlich muss man dem OS-Kernel für die Aufgaben die man von ihm erledigt haben will auch angemessene Zeit zugestehen, aber das er von sich aus anfängt die CPU zu benutzen finde ich persönlich falsch.
Trotzdem sollte man schon einigermaßen dafür sorgen, dem System nicht mehr Last aufzubrummen als notwendig.Natürlich, das tun die Programmierer ja auch. Es ist meine Aufgabe als Plattform-Designer dafür zu sorgen das diese Bemühungen auch auf meiner Plattform zum Erfolg führen. Dinge wie ein kleiner Cache-Foot-Print u.ä.m. führen auch bei mir zum Ziel.
Gegenbeispiel dafür ist Java. Hauptsache langsam. Oder Flash.Ich hab selber schon Java-Applets programmiert und bin durchaus der Meinung das ich für den Komfort das mein Applet überall läuft ein klein wenig Performance opfern kann (so groß ist dieser Performance-Verlust bei Java nun wirklich nicht). Eine Wetter-Simulation wird man bestimmt nicht in Java programmieren, wenn jemand extra dafür etliche Millionen Euro für die Hardware ausgibt wird er auch jedes MIPS bis aufs letzte ausquetschen wollen. Ich hab mal gehört das man manchmal auf solchen Super-Computern gar kein richtiges OS hat, einfach damit möglichst wenige CPU-Takte für was anderes als die eigentliche Aufgabe drauf gehen.
Wenn die verwendeten Segmentnummern zum Task gehören, braucht das Programm sich im Regelfall nicht um FAR-Pointer scheren (ein Datenzugriff geht nach .data, ein Codezugriff nach .code usw). Problematisch wird es erst, wenn du Segmentregister umladen musst - also FAR-Pointer dereferenzierst - denn das lässt sich nicht vermeiden.Äh, nein, das klingt für mich nicht besonders logisch. :-D
Klingt das überhaupt logisch?...
Naja, ich würde mit dem Paging gezielt die Probleme der Segmentierung abschwächen/entfernen wollen.Ganz genau darum geht es, aber dafür ist IMHO ordentliches Paging erforderlich. Im Gegensatz zum Paging auf anderen Plattformen ist mein Paging über alle CPUs synchron, es gibt also nur ein Paging-Directory das von allen CPUs benutzt wird, ich hab daher kein TLB-Trashing beim Task-Wechsel. Das ist aber auch der Grund warum ich nie Linux, oder irgend ein anderes Flat-Memory-OS, portieren kann. Es wäre aber auch totaler Unsinn ein ungeeignetes OS zu portieren.
Wenn das Konzept sich dann als nicht tragbar erweist, kann man sich immernoch um was anderes bemühen - oder Segmentierung endgültig als Mist abstempeln.Bis jetzt sehe ich keine Probleme in meinem Konzept, ich denke es wird gut funktionieren.
PPS.: Falls ich oben ein bisschen den Oberlehrer in mir gezeigt hab bitte nicht übel nehmen. Ich hab irgendwie den unbestimmten Eindruck das wir in ein paar Details an einander vorbei reden und da wollte ich noch mal richtig erklären.Äh, ja. Nix Oberlehrer. Danke, das war richtig so.
Also hast du den physischen Speicher in einem 1:1 Mapping in der Page-Tabelle (und das ganze so gebaut, dass damit genau kein Performance-Verlust auftritt)Jain, es gibt in dem 1:1-Bereich gar kein Paging. Ich will dafür extra Range-Register vorsehen die der Paging-Unit nur einen bestimmten Bereich erlauben, alles andere geht immer dran vorbei und ist im Paging-Directory nicht präsent.
und dahinter virtuellen Speicher, der durch die MMU geroutet wird.Genau. Nur Segmente, deren lineare Basis-Adresse nicht im 1:1-Bereich sondern im virtuellen Bereich liegt, sind vom Paging betroffen und können dann auch aus mehreren Teilen bestehen.
Somit ist also Paging mehr oder weniger ständig aktiv, wird aber designtechnisch möglichst umgangen.Ja, das Paging ist immer aktiv aber es liegen meistens keine Segmente in dem Bereich wo Paging auch wirkt so das es meistens gar nicht benutzt wird.
Das einzige, was ich noch sehe, ist der physisch nutzbare Adressraum von nur 1 GB RAM (statt 4 GB) - in der 32-Bit-Variante - aber so viel möchtest du da wohl eh nicht anbasteln.Dieses Problem ist mir auch schon aufgefallen. Irgendeinen Bereich im linearen Speicher brauche ich um dort hin die Segmente virtuell "auszulagern" damit sie noch in einem Stück sichtbar bleiben (das geht im 1:1-Bereich mit fragmentierten Segmenten ja eben nicht). Dieser Bereich sollte möglichst deutlich größer als der physisch vorhandene Speicher sein damit ich dort nicht so schnell in Bedrängnis gerate, schließlich gibt es auch dort externe Fragmentierung wenn ich dort immer Segmente rein mappe und dann wieder raus nehme. Andererseits werden dort immer nur wenige Segmente drin sein (meistens wohl nur ein einziges) so das der Druck wohl doch nicht so hoch ist.
Ich bin mal gespannt, was dabei rauskommt.Ja, gespannt bin ich auch. ;-)
Gut klingen tut es.Das sollte es auch langsam, nach dem ich nun schon 4 Jahre drüber brüte. :-D
Jain, es gibt in dem 1:1-Bereich gar kein Paging. Ich will dafür extra Range-Register vorsehen die der Paging-Unit nur einen bestimmten Bereich erlauben, alles andere geht immer dran vorbei und ist im Paging-Directory nicht präsent.Warum nimmst du nicht ein Flag für die Segmente her, das aussagt, ob Paging für dieses Segment aktiviert ist? Das erscheint mir irgendwie eleganter als irgendwelche magischen Speicherbereiche zu haben.
[...]
Nur Segmente, deren lineare Basis-Adresse nicht im 1:1-Bereich sondern im virtuellen Bereich liegt, sind vom Paging betroffen und können dann auch aus mehreren Teilen bestehen.
Warum nimmst du nicht ein Flag für die Segmente her, das aussagt, ob Paging für dieses Segment aktiviert ist? Das erscheint mir irgendwie eleganter als irgendwelche magischen Speicherbereiche zu haben.Das hab ich doch auch ein paar Sätze später geschrieben.
Das hab ich doch auch ein paar Sätze später geschrieben.Wie gesagt, ihr schreibt mir zu viel, um alles zu lesen. ;)
Das löst aber nicht das Problem das ich die Segmente im linearen Speicher immer am Stück brauche und wenn es im physischen Speicher keinen geeigneten Frei-Bereich gibt (wo ich das Segment kurzerhand einblenden könnte und wo es dann im Hintergrund auch physisch hin defragmentiert/verschoben wird), dann muss das Segment erst mal wo anders hin "ausgelagert" werden (nur dort ist dann das Paging nützlich, weshalb es etwas Sinn macht es auch nur dort aktiv zu haben).Wenn du mit dem Defragmentieren anfängst, ist das Segment doch am Stück. Warum es nicht einfach dort lassen, wo es ist, während Paging aktiv ist? Oder wenn dir der Quellbereich nciht gefällt, nimmst du eben den Zielbereich her - den brauchst du genauso in zusammenhängender Form, wenn du Paging irgendwann wieder ausschalten willst.
Wie gesagt, ihr schreibt mir zu viel, um alles zu lesen. ;)Ich hätte erwartet das Du wenigstens den Beitrag liest den Du zitierst.
Der war doch nun wirklich nicht mehr soooooooo lang. :wink:[.....]
Wenn du mit dem Defragmentieren anfängst, ist das Segment doch am Stück.Nicht immer, das hängt von der Ursache des Defragmentierens ab. Falls ein Segment vergrößert werden soll aber nicht einfach wachsen kann oder falls ein neues Segment angelegt werden soll und es keinen ausreichenden Frei-Speicher in einem Stück gibt dann muss das Segment sofort (sonst würde das System blockiert) in mehreren Teilen entstehen. Dazu benötige ich aber eben auch sofort einen neuen Bereich im linearen Speicher (der gerade beim zweiten Fall) nicht unbedingt im 1:1-Bereich liegt. Daher ist mir die Idee mit der virtuellen "Swap-Out-Arrea" gekommen. Wo die liegt ist erst mal egal und komplett 4 GByte RAM geht auf der 32Bit-Plattform eh nicht.
Warum es nicht einfach dort lassen, wo es ist, während Paging aktiv ist?Das wird wohl nur selten gehen. Das geht IMHO nur wenn ich nicht-fragmentierte Segmente nur verschieben möchte (wegen memory-compaction bzw. free-space-consolidation). Obwohl auch dazu der Ziel-Bereich im linearen Speicher ja bereits frei sein muss oder nur durch das betroffene Segment selber belegt sein darf und ich daher auch das virtuelle verschieben vor dem physischen Verschieben durchführen kann.
Oder wenn dir der Quellbereich nciht gefällt, nimmst du eben den Zielbereich herDas dürfte schon eher was bringen. Wenn ich ein Segment vergrößern muss und es nicht wachsen kann (weil was anderes im Weg liegt) dann kann ich auch gleich einen neuen Bereich für das ganze Segment suchen und dort das Segment gleich virtuell hin verlegen. Der neue Segmen-Teil wird gleich 1:1 genommen (trotz Paging) und der alte Teil wird erst mal über das Paging eingeblendet. Nach dem der Paging-Teil in die neue Heimat umgezogen ist kann dann das Paging-Enable-Flag im Segment-Descriptor einfach aus geschaltet werden.
den brauchst du genauso in zusammenhängender Form, wenn du Paging irgendwann wieder ausschalten willst.Ja, es ist mein Ziel das nach dem defragmentieren jedes Segment an einem Stück im physischen Speicher liegt. Sonst werde ich ja das böse Paging nicht los.
Wenn es keinen passenden Bereich gibt, hast du doch sowieso verloren, oder?Ja, genau das ist mein Problem.
so da ihr hier viel diskutiert habt, wollte ich mal anfragen, ob schon was bei rausgekommen ist, also ob ihr schon ein konzept habt, oder ob ihr euch immernoch mit den ganzen einzelhein rumschlagen müsst?Wir haben vor allem viel über Speicher-Defragmentierung zur Laufzeit diskutiert. Dieses Konzept ist jetzt etwas klarer in meinem Kopf, aber es sind noch ein paar Fragen offen.
ich hab schon was zu stande gebracht:Na das ist doch schon was! :-)
so sieht die cpu in diesem zustand ausAha, ich bin, äh, verwirrt. :wink:
[Bild]
Register | Beschreibung |
pFree | Anfang des freien Bereichs |
pCurrent | Anfang aktueller Chunk |
pNext | Anfang nächster Chunk |
Typ | Typ aktueller Chunk |
Size | Länge aktueller Chunk |
pRead | Leseadresse |
pWrite | Schreibadresse |
das programm nennt sich logisim http://ozark.hendrix.edu/~burch/logisim/ damit kann man diese schönen bildchen erstellen und auch gleich simulieren, ob das auch funktioniertDanke, nur leider kann es wohl keine VHDL/Verilog-Komponenten einbinden. Eine große CPU nur durch gezeichnete Gatter zu bauen finde ich zu anstrengend. :-D
Ich hatte mir das mit der Speicherbereinigung mal wie folgt vorgestellt. .......Ich bin mir zwar nicht sicher ob ich verstanden hab was Du meinst aber es klingt nach Paging mit variablen Page-Größen.
Das zu implementieren wird nicht so leicht ;(Das dürfte auch ziemlich langsam werden, aber ich lass mich da auch gerne vom Gegenteil überzeugen.
dann nimm doch http://inst.eecs.berkeley.edu/~cs61c/sp06/projs/04/logisim-ucb-2.jar da kann man "Software Gates" benutzen, die kann man in c codenAlso in einer "normalen" sequenziellen Programmiersprache kann man IMHO keine parallele Logik beschreiben. Für ein paar Kleinigkeiten wird sowas sicher schon mal gehen, aber für ein richtiges Logik-Design, das man auch in einem FPGA (oder gar in einem ASIC) laufen lassen will, muss man dann doch die passenden Werkzeuge benutzen.
%ifndef 1416
%error "Runs only on the 1416! simply Define the key '1416'."
%endif
%ifdef 1416
%warning "Make sure you assemble for the 1416!"
%define $zero r15
%define si r14
%define outr r13
%define $one r12
ld outr, 0x8000
ld $zero, 0
ld $one, 1
ld si, string
l:
mov r0, outr
sm si, $+2
lm r1, 0
cmp r1, $zero
je end
add r0, r1
out r0
out $zero
add si, $one
j l
end:
hlt
string: db "Hallo Welt!", 0
%endif
das der code gibt auf der 1416 "Hallo Welt!" aus, wenn der bildschirm sachgerecht an den outport angeschlossen ist.Aber denk dir bitte nicht so was verrücktes wie Segmentierung aus. bleib beim Flat Memory Modell. Ich hab vorhin mich wieder mit dem x86 Real Mode auseinandergesetzt. Grausam.Hm? Erstens würde dir Erik da was husten, auch wenn der Realmode nicht unbedingt viel mit der ihm vorschwebenden Segmentierung (die wohl eher dem Protected Mode entspricht) zu tun hat.
gesamtzahl der zeilen: alles in allem nur 2723 zeilenDas klingt nach ziemlich wenig, ich bin bereits bei über 6000 Zeilen und hab gerade mal die wichtigsten Befehle eingelesen, von Binärcode ist bei mir noch nichts zu sehen. Ich vermute aber mal das Du einen einfachen Single-Pass-Assembler gebaut hast. Bei meiner CPU-Architektur würde ich nicht mal mit nem Two-Pass-Assembler hinkommen.
Aber denk dir bitte nicht so was verrücktes wie Segmentierung aus.Was ist daran Bitte "verrückt"? Überlege doch erst mal was man mit richtiger dynamischer Segmentierung alles machen kann. Du kannst z.B. Speicher sharen und es ist völlig egal ob das 3 Byte oder 3 TByte sind (unabhängig ob ausgerichtet oder nicht), das macht im Kernel nicht einen CPU-Takt unterschied. Mit Segmentierung kannst Du den Speicher zur Laufzeit defragmentieren und noch ganz andere tolle Sachen anstellen. Und Du verlierst nicht bei jedem Speicherzugriff Performance, so wie beim Paging. Ließ Dir mal diesen Thread komplett durch.
Ich hab vorhin mich wieder mit dem x86 Real Mode auseinandergesetzt. Grausam.Die Segmentierung im x86-Real-Mode ist auch grausam, da ist nichts dynamisches oder zumindest flexibles dran. Das ist einfach nur ein dämlicher Weg um einer 16Bit-CPU 20Bit-Adressen zu ermöglichen.
bleib beim Flat Memory Modell.Und damit auf der dunklen Seite? Klar mit Flat-Memory kommt man leichter zum Ziel, aber ist nicht auch der Weg das Ziel?
Mit PM-Segmentierung kann man aber noch viel lustigere Sachen machen.Das ist die richtige Einstellung! Willst Du nicht bei mir einsteigen? Ein Paar fähige Hände könnte ich gut gebrauchen und als Gegenleistung darfst Du mit einem signifikant erweiterten Horizont rechnen. ;)
Entschuldigung, das ich deine Segmentierung nicht gut finde.Dafür brauchst Du Dich nicht zu entschuldigen, mit Meinungen hab ich kein Problem. Ich hab ein Problem damit das Du ein Konzept das Du nicht kennst als "verrückt" abstempelst ohne das rational zu begründen.
Ich kenne Nur die X86 RealMode SegmentierungUnd genau da ist das Problem.
und finde die Schei**.Dem kann ich uneingeschränkt zustimmen.
weil ich Flat Memory mit Paging persönlich schöner finde.Nie und nimmer würde ich Dir das ausreden, ich zeige Dir höchstens die belegbaren Nachteile Deiner Entscheidung.
Ich habe deine Threads bisher alle so ziemlich komplett durchgelesen. aber ich glaube ich habe dein Konzept nicht so ganz verstanden. Ich sehe da irgendwie keinen Vorteil gegenüber der x86 Realmode Segmentierung.Entschuldigung, das ich deine Segmentierung nicht gut finde.Dafür brauchst Du Dich nicht zu entschuldigen, mit Meinungen hab ich kein Problem. Ich hab ein Problem damit das Du ein Konzept das Du nicht kennst als "verrückt" abstempelst ohne das rational zu begründen.
Danke, aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)Mit PM-Segmentierung kann man aber noch viel lustigere Sachen machen.Das ist die richtige Einstellung! Willst Du nicht bei mir einsteigen? Ein Paar fähige Hände könnte ich gut gebrauchen und als Gegenleistung darfst Du mit einem signifikant erweiterten Horizont rechnen. ;)
zu der 32 bit cpu: sie kann leider der ram nur mit einem 24 bit adressbus ansprechen, da logisim meint: "Neh du brauchst nicht mehr als 16.777.215 32bit zellen speicher ...."Nimm einfach mehrere RAM Bausteine ein Verteiler und einen Demuxer denn du dann mit denn sel Anschluss des RAM verbindest.
aber ich glaube ich habe dein Konzept nicht so ganz verstanden.Dann habe ich meine Ideen wohl zu kompliziert beschrieben, das tut mir leid.
Ich sehe da irgendwie keinen Vorteil gegenüber der x86 Realmode Segmentierung.Die x86-Real-Mode-Segmentierung ist starr, da gibt es keine Flexibilität: wo das Segment im Speicher liegt ist durch den numerischen Wert des Selector bereits festgelegt und auch die Größe ist fix. Bei dynamischen Segmenten kannst du für jedes Segment beliebig definieren wo es im linearen Speicher liegt und wie groß es ist. Man kann z.B. für einen Stack ein Segment mit 16 kBytes als Startgröße nehmen und wenn der Thread doch mehr Stack benötigt dann kann man das Segment einfach vergrößern und wenn das Vergrößern an der aktuellen Stelle im linearen Speicher nicht geht dann kann man das Segment auch kurzerhand wo anders hin legen (und dann später in aller Ruhe im Hintergrund den linearen Speicher defragmentieren) ohne das der betreffende Thread das überhaupt bemerkt. Und da ja jeder Prozess seine eigene LDT hat kommt da auch nichts durcheinander und es gibt damit einen starken Speicherschutz obwohl alle Segmente von allen Prozessen im selben linearen Speicher liegen. Das geht alles nur mit dynamischen und flexiblen Segmenten. All das kann der 386 im PM prinzipiell auch, wenn auch manches davon nicht so elegant wie meine CPU das können soll (nebst dessen das seine nur 6 Segmentregister einfach ein Performance-Engpass darstellen).
auch verstehe ich nicht, wie du die Segmente verwalten willst, also welche Segmente u welchem Task gehören.Jeder Prozess hat seine eigene LDT und in der stehen alle Segmente des betreffenden Prozess drin. Also ein Execute-Only-Segment mit dem Code, ein Write-Only-Segment für .const ein Read-Write-Segment mit .data und .bss (im Executable muss natürlich nur .data drin sein) und für jeden Thread noch ein Stack-Segment und ein TLS-Segment (bis auf die Stack-Segmente wird die Größe all dieser Segmente zur Laufzeit nicht mehr geändert). Dazu kommen noch die Segmente aus denen die Heap-Verwaltung eben den Heap baut (die Größe dieser Segmente wird natürlich zur Laufzeit dem Bedarf entsprechend angepasst).
Danke, aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)Schade das Du das so negativ siehst (obwohl ich damit eigentlich gerechnet habe), Du weißt ja gar nicht was Dir alles entgeht, Dein Pech. ;)
Eine eigene Archtitektur ist zu kompliziert, die kann man schlecht beschriben. nicht umsonst sind die Intel Manuals so groß. Es ist nicht dein Fehler.aber ich glaube ich habe dein Konzept nicht so ganz verstanden.Dann habe ich meine Ideen wohl zu kompliziert beschrieben, das tut mir leid.
Was ist eine LDT?auch verstehe ich nicht, wie du die Segmente verwalten willst, also welche Segmente u welchem Task gehören.Jeder Prozess hat seine eigene LDT und in der stehen alle Segmente des betreffenden Prozess drin. Also ein Execute-Only-Segment mit dem Code, ein Write-Only-Segment für .const ein Read-Write-Segment mit .data und .bss (im Executable muss natürlich nur .data drin sein) und für jeden Thread noch ein Stack-Segment und ein TLS-Segment (bis auf die Stack-Segmente wird die Größe all dieser Segmente zur Laufzeit nicht mehr geändert). Dazu kommen noch die Segmente aus denen die Heap-Verwaltung eben den Heap baut (die Größe dieser Segmente wird natürlich zur Laufzeit dem Bedarf entsprechend angepasst).
Wenn Du konkrete Fragen hast dann stell sie einfach, wir sind ja hier im richtigen Thread dafür.Ich werde mir nacher alles nochmal durchlesen und dann auch fragen stellen.
Erik deine Architektur ist einfach zu anders. und da hier alle schon mit der x86 RealMode Segmentierung in Berührung gekommen sind, mögen die Leute keine Segmentierung.Danke, aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)Schade das Du das so negativ siehst (obwohl ich damit eigentlich gerechnet habe), Du weißt ja gar nicht was Dir alles entgeht, Dein Pech. ;)
Erik deine Architektur ist einfach zu anders. und da hier alle schon mit der x86 RealMode Segmentierung in Berührung gekommen sind, mögen die Leute keine Segmentierung.
ein Write-Only-Segment für .constSicher? ;)
Naja, im Ernst: Das tolle am x86 ist, dass ich ihn mehrfach vorrätig habe und damit ohne weiteres rumspielen kann. Dass du mehr oder weniger im luftleeren Raum jahrelang rumplanst und bastelst, bevor man an irgendwelche echten Ergebnisse auch nur denken kann, nötigt mir Respekt ab, aber ich kann das nicht. Ich brauche was zum rumspielen.Danke, aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)Schade das Du das so negativ siehst (obwohl ich damit eigentlich gerechnet habe), Du weißt ja gar nicht was Dir alles entgeht, Dein Pech. ;)
Was ist eine LDT?Local Descriptor Table, also sowas wie die GDT, nur hat jeder Prozess eine eigene davon. Lies dir mal das Intel-Manual durch, der Protected Mode hat viele interessante Sachen. Wenn du den PM einigermaßen verstanden hast, wirst du Eriks Architektur auch besser nachvollziehen können.
kann ich machen, nur müsste ich dann den source code bzw das binäre programm entsprechend aufteilen und dazu hatte ich noch keine lust sowas einzubauen ^^zu der 32 bit cpu: sie kann leider der ram nur mit einem 24 bit adressbus ansprechen, da logisim meint: "Neh du brauchst nicht mehr als 16.777.215 32bit zellen speicher ...."Nimm einfach mehrere RAM Bausteine ein Verteiler und einen Demuxer denn du dann mit denn sel Anschluss des RAM verbindest.
Erik deine Architektur ist einfach zu anders.Das soll sie ja auch sein. ;)
und da hier alle schon mit der x86 RealMode Segmentierung in Berührung gekommen sind, mögen die Leute keine Segmentierung.Das ist wohl eher das Problem. Intel hat mit seinem vermurksten Real-Mode die Leute eher abgeschreckt. Im PM blendet man die Segmentierung für gewöhnlich einfach aus und kommt daher quasi gar nicht damit in Berührung. Ich hab im 386-PM schon einiges mit Segmentierung machen dürfen, z.B. im Rahmen von verschiedenen DOS-Extendern, und hab dabei auch die Vorzüge kennen gelernt.
Ich sehe nur alle Vorteile durch die Verwaltung von Segmenten wieder zu Nichte gemacht. Man muss den RAM ständig defragmentieren.Wieso sollte die Verwaltung von Segmenten kompliziert sein? Das sind auch nur zusammenhängende Bereiche im linearen Adressraum, das ist IMHO nicht komplexer als ein HEAP der auch zusammenhänge Bereiche unterschiedlicher Größe in seinem einen virtuellen Adressraum (der ja auch linear ist) verwaltet. Defragmentieren muss man auch nicht "ständig" sondern nur wenn es nötig ist, bei einem kaum belasteten System sollte das entsprechend selten sein.
Dazu sehe ich nur die zwei Möglichkeiten:Exakt. Ich habe mich für Paging entschieden weil die zweite Option wohl wirklich nicht in frage kommt. Damit mir das Paging nicht die Performance (von der ich ja immer schwärme) kaputt macht wird es nur zum defragmentieren/swappen eingeschaltet und auch nur für die Segmente die davon betroffen sind, alle anderen Segmente bleiben davon verschont.
- Paging, womit das ganze transparent ablaufen kann
- oder, das gesamte Segment blockieren bis es fertig verschoben ist.
Und wenn man dann auf 4MB oder sogar 1GB Pages 'hoch schalten' kann müssten sich die TLB'misses beim Pagelookup auch drastisch reduzieren.Genau das kann man aber nur selten machen, das geht für einen großen Batzen an Code oder für die .data-Sektion aber z.B. für Stacks ist das nur wenig praktikabel denn wenn Du im virtuellen Adressraum eine große Page anbietest musst Du auch den physischen Speicher dafür haben (und zwar passend ausgerichtet). Da der virtuelle Adressraum typischer Flat-Memory-Programme oft nur dünn belegt ist kommen die großen Pages eben kaum zum Einsatz.
*Aber ich hab seine Romane auch nicht komplett gelesen.Daraus macht Dir wirklich niemand einen Vorwurf. ;)
Was natürlich ein ganzer Haufen Aufwand ist, nur um diese eine Operation durchzuführen.Wo ist das ein Aufwand? Ich muss doch nur ein Mapping einrichten und dafür ist der Aufwand erstmal nur direkt abhängig von der Größe des betreffenden Speicherblocks, das ist auf Flat-Memory-Systemen doch ganz genau so. Das Paging ist immer da und wird dann nur für das eine Segment eingeschaltet, ich hab da ein extra Bit im Segment-Descriptor vorgesehen. Irgendwo in diesem Thread hab ich mal erklärt wie ich mir das vorstelle was alles passieren muss wenn ein Segment vergrößert werden soll aber etwas im Weg ist, klar ist da erst mal ein gewisser Aufwand aber dann läuft das eigentliche Defragmentieren bequem und unbemerkt im Hintergrund ab.
Mist, das muss natürlich Read-Only heißen. Aber es wird bei mir auch Write-Only-Segmente geben z.B. für IPC wenn der Service in einen Bereich nur Daten rein legen soll.ein Write-Only-Segment für .constSicher? ;)
Das tolle am x86 ist, dass ich ihn mehrfach vorrätig habe und damit ohne weiteres rumspielen kann.Okay, das ist natürlich ein gutes Argument.
Dass du mehr oder weniger im luftleeren Raum jahrelang rumplanst und bastelst, bevor man an irgendwelche echten Ergebnisse auch nur denken kann, nötigt mir Respekt abNaja, das erste mal lief bereits vor gut 2 ein wenig händisch assemblierter Code in einem noch extrem primitiven Simulator, die OpCodes händisch zusammen zu bauen war alles andere als angenehm. Genau deswegen will ich ja jetzt endlich einen Assembler, ich denke wenn der zuverlässig Code ausspuckt geht der Rest wieder deutlich zügiger voran. Nur sollte ich mir bis da hin mit meinen OpCodes und Descriptor-Tabellen recht sicher sein ansonsten muss ich an vielen Stellen Code anfassen.
Lies dir mal das Intel-Manual durch, der Protected Mode hat viele interessante Sachen. Wenn du den PM einigermaßen verstanden hast, wirst du Eriks Architektur auch besser nachvollziehen können.Ja, das stimmt, ich habe vom 386-PM viele Ideen übernommen (eine gewisse Ähnlichkeit ist nicht abzustreiten). Ich denke auch das ein Großteil meiner Ideen für das OS auf dem 386 ebenfalls funktionieren können. Der größte Unterschied ist das ich das Paging pro Segment einschalten kann und das es vor allem nur eines gibt (also alle Prozesse im selben linearen Adressraum existieren), ansonsten existiert noch der Unterschied das ich von vornherein 64Bit mit integriert habe (als AMD den Long-Mode erschaffen hat wurden ja all die tollen Sachen raus geworfen).
Solange man nicht defragmentieren muss, ist alles ganz easy. Nur gehe ich davon aus, dass das mit dem Defragmentieren nicht ganz so selten sein wird, und vor allem immer dann wenn es gerade auf Performance ankommt. Also wenn man z. B. gerade ein 'make -j5 bzimage' im Hintergrund laufen lässt, könnte ich mir vorstellen, dass der RAM ziemlich zerhackt wird. Es sei den bis du fertig bist hat jemand nen GCC-Daemon gebaut, der dir das Erstellen von Tausenden neuen unterschiedlich großen Segmenten erspart. :-)Ich sehe nur alle Vorteile durch die Verwaltung von Segmenten wieder zu Nichte gemacht. Man muss den RAM ständig defragmentieren.Wieso sollte die Verwaltung von Segmenten kompliziert sein? Das sind auch nur zusammenhängende Bereiche im linearen Adressraum, das ist IMHO nicht komplexer als ein HEAP der auch zusammenhänge Bereiche unterschiedlicher Größe in seinem einen virtuellen Adressraum (der ja auch linear ist) verwaltet. Defragmentieren muss man auch nicht "ständig" sondern nur wenn es nötig ist, bei einem kaum belasteten System sollte das entsprechend selten sein.
Code und Datenbereich sind das worauf es ankommt. Der Stack wird sich nur selten in einem Bereich größer 4KB bewegen, und so sowieso nur eine TLB'miss auslösen. Und das mit der passende Ausrichtung lässt sich ja machen.Und wenn man dann auf 4MB oder sogar 1GB Pages 'hoch schalten' kann müssten sich die TLB'misses beim Pagelookup auch drastisch reduzieren.Genau das kann man aber nur selten machen, das geht für einen großen Batzen an Code oder für die .data-Sektion aber z.B. für Stacks ist das nur wenig praktikabel denn wenn Du im virtuellen Adressraum eine große Page anbietest musst Du auch den physischen Speicher dafür haben (und zwar passend ausgerichtet). Da der virtuelle Adressraum typischer Flat-Memory-Programme oft nur dünn belegt ist kommen die großen Pages eben kaum zum Einsatz.
Da bin ich aber beruhigt. :wink:*Aber ich hab seine Romane auch nicht komplett gelesen.Daraus macht Dir wirklich niemand einen Vorwurf. ;)
der dir das Erstellen von Tausenden neuen unterschiedlich großen Segmenten erspart. :-)Warum sollten den "tausende" Segmente für ein paar Compiler-Instanzen nötig sein?
Code und Datenbereich sind das worauf es ankommt.Und was ist mit dem Heap, der ist in klassischen Flat-Memory-System auch oft ziemlich zerklüftet.
Der Stack wird sich aber nur selten in einem bereich größer 4KB bewegen.Das würde ich nicht unbedingt so sehen. Klar wissen viele Programmierer um die Stackknappheit aber es gibt trotzdem viele Situationen wo man mal tiefe Call-Trees erzeugt oder einfach mal einen dicken Batzen Daten als lokale Variablen anlegt. Der Punkt ist das man mit Flat-Memory diese Dynamik nur unzureichend abfangen kann: entweder man macht die Stacks zu klein um mehr Threads zu ermöglichen und riskiert damit eben ein Out-of-Stack oder man macht die Stacks größer und begrenzt dadurch die maximale Anzahl möglicher Threads. Ein Dilemma das eben daraus entsteht das der virtuelle Adressraum in einem Flat-Memory-Prozess nur genau ein mal existiert, das dieses Dilemma keine spürbaren Auswirkungen hat liegt vor allem daran das die meisten Programmierer mit Stack und Threads sparsam umgehen.
Und das mit der passende Ausrichtung lässt sich ja machen.Und wie? Mit defragmentieren? Welches Flat-Memory-OS macht das denn?
Ich denke der Linux-Kernel besteht aus mehr als nur ein paar Dateien. Und ein ich weiß zwar nicht genau welche Prozesse alle beim Kompilieren einer Datei gestartet werden, aber des sind AFAIK mehr als einer. Die beim Parsen der Datei dann feststellen, dass noch zusätzlich ein paar header Dateien nachgeladen werden müssen, so dass das Daten Segment wachsen muss, was zu Problemen führt wenn der darauf folgende Adressraum an eine der Parallel laufenden Compiler Instanzen vergeben wurde.der dir das Erstellen von Tausenden neuen unterschiedlich großen Segmenten erspart. :-)Warum sollten den "tausende" Segmente für ein paar Compiler-Instanzen nötig sein?
Ansonsten hast Du natürlich recht das fragmentierte Segmente gerade dann entstehen wenn Performance benötigt wird. Aber das Problem trifft doch Flat-Memory-Systeme genau so. Speicher wird üblicherweise dann angefordert wenn er auch benötigt wird und dann muss das OS dem so schnell als möglich nachkommen. In einem Flat-Memory-System musst Du ein Mapping anlegen (und dafür im Zweifelsfall ne Menge einzelner physischer Pages aus dem wahrscheinlich ziemlich zerklüfteten physischen Speicher zusammensuchen), das kann man kurzsichtig etwas beschleunigen indem nur der virtuelle Adressraum belegt wird und das eigentliche Mapping dann später über unzählige und teure (daher kurzsichtig) Page-Faults nachholt. Wenn ich ein Segment anlegen muss kann ich prüfen ob das sofort in einem Stück geht (wäre der Optimalfall) oder ob es fragmentiert entstehen muss und dann muss ich ebenso ein Mapping anlegen und hab dabei auch die selben kurzsichtigen Beschleunigungsoptionen. Damit der Optimalfall möglichst oft eintritt kann ich im Hintergrund prophylaktisch defragmentieren aber das lohnt sich IMHO nur wenn der lineare Speicher auch bereits nennenswert ausgelastet oder stark zerklüftet ist (und das ist wohl nur selten).Eine freie Page finde ich mit niedrigem konstanten Aufwand. Und ein Mapping anzulegen ist gegen das Defragmentieren trivial.
Was willst du mir damit sagen?Code und Datenbereich sind das worauf es ankommt.Und was ist mit dem Heap, der ist in klassischen Flat-Memory-System auch oft ziemlich zerklüftet.
[…] aber es gibt trotzdem viele Situationen wo man mal tiefe Call-Trees erzeugt […]Z. B. ein Backtrack-Algorithmus mit hoher Rekursion tiefe? Da wird nur relativ selten zwischen unterster und oberster Ebene wechseln. Von daher ist der Stackbereich auf dem auch gearbeitet wird immer noch nicht so groß.
Das wohl bekannteste wäre da wohl Linux. Wie das bei den NT-Kerneln aus sieht weiß ich nicht.Und das mit der passende Ausrichtung lässt sich ja machen.Und wie? Mit defragmentieren? Welches Flat-Memory-OS macht das denn?
was zu Problemen führt wenn der darauf folgende Adressraum an eine der Parallel laufenden Compiler Instanzen vergeben wurde.Ja, dann muss das Segment fragmentiert werden. Aber wo genau ist das Problem? Ein passendes Mapping für den neuen Speicher muss doch ein Flat-Memory-System auch machen. Ich sehe da erstmal keinen relevanten Performance-Unterschied, nur das ich mit den Segmenten prinzipiell die Möglichkeit hab das Paging wieder los zu werden. Das mag in einem Szenario wo mit recht kurzlebigen Prozessen nur so geschmissen wird, kein Vorteil sein (weil es einfach nicht dazu kommt) aber bei längerlebigen Prozessen, die nicht ständig große Mengen an Speicher allozieren oder freigeben, bringt das IMHO schon einen signifikanten Vorteil.
Eine freie Page finde ich mit niedrigem konstanten Aufwand. Und ein Mapping anzulegen ist gegen das Defragmentieren trivial.Das erste trifft auf mein System doch auch zu und beim zweiten muss ich sagen das mein System im Worst-Case nicht schlechter ist als ein Flat-Memory-System aber im Best-Case eben deutlich schneller.
Den Heap kann und braucht man auch nicht Defragmentieren. Und wenige Byte große Löscher im Heap stören den Einsatz von großen Pages auch nicht.Das man den Heap nicht defragmentieren kann ist mir klar (schließlich gibt es ja Pointer die da hin zeigen) aber es können durchaus große Löcher im Heap entstehen deren Pages dann manchmal frei gegeben werden um eben weniger Speicher zu verbrauchen und diese Löcher können dann dem Einsatz großer Pages schon mal im Weg sein.
.... Von daher ist der Stackbereich auf dem auch gearbeitet wird immer noch nicht so groß.Stimmt.
Echt, das wusste ich noch nicht. Dann unterscheidet sich von den grundlegenden Fähigkeiten her mein OS ja gar nicht so sehr von Linux außer eben in dem Punkt das wenn mein OS diese Fähigkeiten anwendet zum Schluss das Paging abgeschaltet werden kann.Und wie? Mit defragmentieren? Welches Flat-Memory-OS macht das denn?Das wohl bekannteste wäre da wohl Linux. Wie das bei den NT-Kerneln aus sieht weiß ich nicht.
Das man den Heap nicht defragmentieren kann ist mir klar (schließlich gibt es ja Pointer die da hin zeigen) aber es können durchaus große Löcher im Heap entstehen deren Pages dann manchmal frei gegeben werden um eben weniger Speicher zu verbrauchen und diese Löcher können dann dem Einsatz großer Pages schon mal im Weg sein.Ich gehe halt von weniger Sozialen Anwendungen aus, die siech nicht die mühe machen löscher im Heap frei zu geben.
Siehe hier mein Edit. Scheinbar hab ich mich da geirrt.Echt, das wusste ich noch nicht. Dann unterscheidet sich von den grundlegenden Fähigkeiten her mein OS ja gar nicht so sehr von Linux außer eben in dem Punkt das wenn mein OS diese Fähigkeiten anwendet zum Schluss das Paging abgeschaltet werden kann.Und wie? Mit defragmentieren? Welches Flat-Memory-OS macht das denn?Das wohl bekannteste wäre da wohl Linux. Wie das bei den NT-Kerneln aus sieht weiß ich nicht.
Bei was für einer Gelegenheit defragmentiert den Linux? Gibt es da einen Idle-Thread im Kernel?
Das wohl bekannteste wäre da wohl Linux. Wie das bei den NT-Kerneln aus sieht weiß ich nicht.Hm, eventuell im Zusammenhang mit Transparent Huge Pages?
[edit]Das dachte ich zumindest mal gelesen zu haben. Aber ich finde es gerade nicht. Vielleicht hab ich das auch mit KSM(Kernel Samepage Merging) verwechselt.[/edit]
Ja, das entspricht zumindest dem was ich im Hinterkopf hatte.Das wohl bekannteste wäre da wohl Linux. Wie das bei den NT-Kerneln aus sieht weiß ich nicht.Hm, eventuell im Zusammenhang mit Transparent Huge Pages?
[edit]Das dachte ich zumindest mal gelesen zu haben. Aber ich finde es gerade nicht. Vielleicht hab ich das auch mit KSM(Kernel Samepage Merging) verwechselt.[/edit]
Ok. Das du letzt endlich doch Paging verwendest hab ich jetzt nicht beachtet.Ich muss, ansonsten müsste ich das Verschieben/Defragmentieren von Segmenten als atomare Aktion ausführen und dafür eventuell auch noch große Teile des System lahm legen. Das Paging ist ein essentieller Bestandteil meines Konzepts, eben weil ich damit ne Menge Speichermagie machen kann und weil ich es auch zum Swappen benötige.
Ich gehe halt von weniger Sozialen Anwendungen aus, die siech nicht die mühe machen löscher im Heap frei zu geben.Das würde aber bedeuten das Du weniger soziale Programme auch noch mit zusätzlicher Performance belohnst. Das klingt irgendwie sehr nach der realen Welt da draußen. ;)
Siehe hier mein Edit. Scheinbar hab ich mich da geirrt.Dein Edit kam wohl kurz nachdem ich auf den "Zitieren"-Button geklickt hatte, schade dass das Forum da nicht warnt so wie bei neuen Beiträgen.
aber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)Mir ist dieser Satz noch mal durch den Kopf gegangen und ich muss sagen das er ein Widerspruch in sich selbst ist. Wie kann man etwas (als krank) beurteilen das man nicht "einmal annähernd durchgespielt" hat?
Musst du eigentlich immer jedes Wort auf die Goldwaage legen? Das ist mit der Zeit anstrengend. ;)Zitataber i386 ist mir krank genug und ich habe es noch nicht einmal annähernd durchgespielt. ;)Mir ist dieser Satz noch mal durch den Kopf gegangen und ich muss sagen das er ein Widerspruch in sich selbst ist.
Wie kann man etwas (als krank) beurteilen das man nicht "einmal annähernd durchgespielt" hat?Dass man etwas noch nicht in die Praxis umgesetzt ("durchgespielt") hat, heißt doch nicht, dass man automatisch keine Ahnung davon hat? Ja, mir fehlt vielleicht die praktische Erfahrung, aber deswegen ist meine Meinung trotzdem nicht auf Gerüchten und unangenehmen Erinnerungen, sondern auf der offiziellen Dokumentation des Prozessors basiert.
Da wird doch das eigentliche Problem offensichtlich: die Leute haben eine vorgefasste Meinung (die basiert zumeist auf Gerüchten und ein paar unangenehmen Erinnerungen mit etwas völlig anderem aus grauer Vorzeit das zufällig den selben Namen hat) und fertig.
und jede Diskussion mit Svenska sofort beenden (wenn ich mich nicht täusche, gehört der hier nämlich zur Fraktion der reiner Theoretiker).Korrekt. :-P Schlimm?
Musst du eigentlich immer jedes Wort auf die Goldwaage legen? Das ist mit der Zeit anstrengend. ;)Naja, da ist so eine meiner Schwächen. Eigentlich war das auch nicht konkret an Dich gerichtet, ich hab beim Zitat extra absichtlich das Referer weg gelassen. Es ging mir um die generelle Einstellung vieler Leute zur Segmentierung die oft durchscheint.
Dass man etwas noch nicht in die Praxis umgesetzt ("durchgespielt") hat, heißt doch nicht, dass man automatisch keine Ahnung davon hat?Das hab ich auch nicht behauptet, es geht mir um die Abwertung (als krank) die nicht rational begründet wurde.
Ja, mir fehlt vielleicht die praktische Erfahrung, aber deswegen ist meine Meinung trotzdem nicht auf Gerüchten und unangenehmen Erinnerungen, sondern auf der offiziellen Dokumentation des Prozessors basiert.Das ist auch völlig richtig, aber Du hast da IMHO keine Meinung sondern eine (negative) Wertung ausgedrückt (sorry, da ist wieder die Goldwaage).
Wenn du deine hohen Ansprüche behalten willstWas für Ansprüche eigentlich? Das ich es nicht mag wenn die Segmentierung so unbegründet abgewertet wird?
solltest du ab sofort nicht mehr über deine Plattform redenIst das Dein Wunsch?
Fraktion der reiner TheoretikerWas ist so schlimm an Theoretikern? Ohne die hätte heute keiner von uns einen Computer vor der Nase.
Spekulationen über die Kaputtheit von etwas, das du selbst noch nicht implementiert hastAber ich hab Segmentierung und auch Paging bereits mehrfach ausgiebig benutzt. Hat hier schon mal jemand selber Paging implementiert? Muss man dann auch mal ein Auto implementiert haben um sich überhaupt über das Fahrverhalten eines existierenden Autos beschweren zu dürfen?
Stimmst du mir zu, dass deine Aussage Blödsinn war?Nein, meine Aussage war vielleicht nicht perfekt formuliert und sicher auch etwas frustgeladen aber ich stehe dazu! Wenn ich was negatives über Paging gesagt hab dann hab ich das auch immer rational begründet und nicht einfach inhaltslose Attribute wie "krank" oder "kaputt" benutzt.
es geht mir um die Abwertung (als krank) die nicht rational begründet wurde.Hm? Darf ich dich an deine Aussage bezüglich der Real-Mode-„Segmentierung“ erinnern?
und finde die Schei**.Dem kann ich uneingeschränkt zustimmen.
Ich persönlich hätte mir da schon eine Erklärung gewünscht. :-)dann schau mal 2 Beiträge davor:
Die Segmentierung im x86-Real-Mode ist auch grausam, da ist nichts dynamisches oder zumindest flexibles dran.oder 4 Beiträge dahinter:
Die x86-Real-Mode-Segmentierung ist starr, da gibt es keine Flexibilität: ....
Irgendwie schweifen wir hier ziemlich vom Topic ab. Lasst uns die Segmentation Diskussion in einem extra Thread diskutieren.Das fände ich auch gut, meinetwegen ab meinem Beitrag von Sonntag Vormittag.
Ich fand das nur komisch, weil du auch geschrieben hattest (wimre), dass die RM-Segmentierung lediglich ein merkwürdiger Weg zum Erhalten von einem MB Speicher mit 16-Bit-Adressen sei.Ja, dieser Meinung bin ich (nebst dessen das sich das eigentlich nicht unbedingt Segmentierung nennen sollte aber egal). Eines der Probleme mit dem x86-Real-Mode ist das der Befehlssatz einem quasi dazu zwingt sehr häufig die Segment-Register neu zu laden (wenn das Programm mehr als 64kB benötigt), es sind nicht nur die RM-Segmente starr und unflexibel sondern auch der Befehlssatz (z.B. MOVS). Bei Flat-Memory (und im Prinzip ist ein RM-Programm das komplett in 64kB passt ja auch flach) ist das keine Einschränkung aber die Segmentierung wird bei x86 nur recht lieblos unterstützt (in allen Modi). Ich persönlich bin der Meinung das eine größere Verarbeitungsbreite der CPU, z.B. 32 Bit, oder flexiblere Befehle der bessere Weg wären aber das kostet beides auch mehr Transistoren auf dem Chip. Ich kann verstehen warum Intel damals diesen Weg gegangen ist aber toll find ich (als Programmierer) ihn trotzdem nicht (auch wenn diesem Weg vielleicht der 286-PM und damit auch der 386-PM zu verdanken ist).
Den Namen, DPSKA (Dem PNoob Seine Kaputte Architektur), hat sich taljeth ausgedacht. Vieleh Dank nochmals dafür.Und genau da ist wieder dieses bestimmte Attribut "Kaputt". Wieso wird eine Idee bereits als kaputt bezeichnet (bewertet!) bevor überhaupt etwas konkretes bei raus gekommen ist? Hätte nicht erst mal DPSA gereicht?
.... schreibe ich jetzt auch mal was dazu.Genau dazu war dieser Thread ja auch gedacht.
Die CPU wird eine 64 Bit CPU mit 32 Bit Adressbus, was ich erstens für ausreichend halteWenn die CPU intern mit 64Bit (virtuellen) Adressen umgehen kann aber extern nur 32Bit hergibt (also so ähnlich wie ein 386SX) dann ist das keine so schlechte Idee, auch wenn die 32Bit gemessen an aktuellen PCs sicher etwas knapp kalkuliert sind. Der Hyper-Transport von AMD unterstützt wimre auch nur 40Bit physische Adressen.
zweitens würde ich die CPU später mal in Hardware bauen. Also nicht nur in einem FPGA laufen lassen.Was meinst Du mit "in Hardware bauen"? Wenn nicht in einem FPGA.
Die kommunikation mit der Hardware läuft über das Klassische I/O Port Konzept ab.Also einen extra I/O-Adressraum und nicht über MMIO? Alle anderen CPU-Architekturen gehen von den I/O-Ports zu MMIO über (falls sie überhaupt mal I/O-Ports unterstützt haben). Kann dann die Peripherie per eigenständigem Busmastering auf den normalen RAM zugreifen? Das wäre sinnvoll da dort ja die Nutzdaten der User-Mode-Prozesse liegen und um die geht es doch schließlich. Soll die Peripherie überhaupt Speicherbereiche anbieten können (sowas wie der Frame-Buffer einer Grafikkarte)?
Es gibt 256 IRQ lines ...Wirklich als eigenständige Leitungen? Bei PCI-Express hat man darauf komplett verzichtet (an den PCI-Express-Slots gibt es keine IRQ-Pins mehr). Schau Dir mal das MSI(-X) aus der PCI-Spec an, das hat ne Menge Vorteile gegenüber dedizierten IRQ-Signalen.
In dem Usermode, lösen alle Befehle, mit der die möglichkeit besteht die kontrolle über das system zu erlangen, eine Exception aus.Dann solltest Du aber 2 verschiedene Register für den Flags vorsehen, eines wo die normalen Flags (C,S,N,O) drin sind und eines für Flags wie IE, damit nicht ständig normale Befehle potentiell gefährliche Sachen machen können (so wie PUSHF/POPF auf x86).
Die CPU wird eine 64 Bit CPU mit 32 Bit Adressbus, was ich erstens für ausreichend halte und zweitens würde ich die CPU später mal in Hardware bauen. Also nicht nur in einem FPGA laufen lassen.Du hast also 64-Bit-Register, von denen die oberen 32 Bit nicht auf den Adressbus herausgeführt werden. Das ist in Ordnung. Vielleicht solltest du es aber vorsehen, dass man da ein paar Bits noch nachträglich rausführen kann - als Erweiterung.
Außerdem vereinfacht der 32 Bit adressbus breite das Speichermanagment. Als Speichermodell kommt bei mir FlatMemory zum einsatz. Der Speicherschutz wird mit Paging realisiert. Der Befehlssatz wird ein 3 Operanden Befehlssatz werden.Also an sich nichts neues.
Die kommunikation mit der Hardware läuft über das Klassische I/O Port Konzept ab.Machbar, aber ich finde MMIO einfach angenehmer. Mit dem I/O-Konzept verzichtest du übrigens auf DMA... es sei denn, du hinterlegst den I/O-Bereich mit Speicher. Und um dann Vorteile daraus zu kriegen, müsstest du jeden CPU-Befehl doppelt implementieren: Einmal für MEM, einmal für I/O. Das heißt zwei vollständige Adressierungsdekoder und eine Opcodeverschwendung.
SMP mit bis zu 32 CPU's und 64 Kernen pro CPU ist geplant.Du weißt schon, was du dir damit aufhalst?
Es gibt 256 IRQ lines, somit sollte es für jede Situation genügend freie IRQ lines geben, so das keine Geräte sich eine Line teilen müssen. Zur Systemsicherheit, gibt es 2 Privelege Level. In dem Usermode, lösen alle Befehle, mit der die möglichkeit besteht die kontrolle über das system zu erlangen, eine Exception aus.Besitzt die CPU bereits 256 IRQ-Leitungen oder wirst du einen speziellen IRQ-Controller benutzen?
Wieso wird eine Idee bereits als kaputt bezeichnet (bewertet!) bevor überhaupt etwas konkretes bei raus gekommen ist?IMHO ist eine unkonkrete Architektur per Definition Kaputt, bis sie konkret und voll funktionsfähig ist.
zweitens würde ich die CPU später mal in Hardware bauen. Also nicht nur in einem FPGA laufen lassen.Der Sinn und Zweck von FPGAs ist das 'Hardware Bauen' zu vermeiden. Ein Chip in Hardware zu gießen kosten ein paar Millionen $. Und eine 64-Bit CPU lötet man sich auch nicht einfach mal so zusammen. Siehe mycpu.eu, was für ein Klotz, und das (wimre) für nur 8-Bit.
Baust du 1 IRQ-Leitung an, dann ist das Prinzip wie bei heute üblichen CPUs (an dieser Leitung hängt ein IRQ-Controller). Baust du 8 Leitungen, um 256 IRQs zu verwalten, dann kann immer nur ein IRQ gleichzeitig aktiv sein. Oder du baust 256 Leitungen an die CPU an, was enorm viel Lötarbeit ist...Oder er baut einen IRQ-BUS, sowas in der Art wie der dedizierte APIC-BUS den es mal zu PentiumPro-Zeiten gab, das ist wenig Lötarbeit und die Anzahl der verwaltbaren IRQs hängt nur vom Protokoll ab das da drauf läuft. Mit ein wenig Geschick bei der Protokoll-Entwicklung sollten auch die IRQ-Latenzen kaum darunter leiden. Oder er leitet die IRQs als Messages auf dem normalen Bus mit durch, dafür lohnt sich auch mal ein Blick in die Hyper-Transport-Spezifikation (AMD prahlt schließlich damit das der HT deutlich kleinere IRQ-Latenzen hat als PCI-Express, was auch mit Sicherheit nicht gelogen ist).
Ein Chip in Hardware zu gießen kosten ein paar Millionen $.Wenn es nur was einfaches ist (was der Chip-Hersteller aus der Komponenten-Datenbank kurz zusammen klickt) kommt man eventuell auch für um die 500'000 $ zum fertigen Silizium. Aber da würde ich auf jeden Fall vorher die Preise und Leistungen der Chip-Gießereien (http://de.wikipedia.org/wiki/Foundry) vergleichen.
Siehe mycpu.eu, was für ein Klotz, und das (wimre) für nur 8-Bit.Ja, "nur" 8Bit, und trotzdem ist der Fuß kaputt wenn das Ding vom Tisch fällt. ;)
IMHO ist eine unkonkrete Architektur per Definition Kaputt, bis sie konkret und voll funktionsfähig ist.Echt? Ich bin irgendwie sehr enttäuscht so etwas hier zu lesen. Die meisten Leute hier haben doch erst mal nur Ideen vorzuweisen.
Genau so war es gedacht. Die CPU später auf echte 64 Bit aufzubohren ist dann auch kein Problem.Die CPU wird eine 64 Bit CPU mit 32 Bit Adressbus, was ich erstens für ausreichend halteWenn die CPU intern mit 64Bit (virtuellen) Adressen umgehen kann aber extern nur 32Bit hergibt (also so ähnlich wie ein 386SX) dann ist das keine so schlechte Idee, auch wenn die 32Bit gemessen an aktuellen PCs sicher etwas knapp kalkuliert sind. Der Hyper-Transport von AMD unterstützt wimre auch nur 40Bit physische Adressen.
Darüber habe ich mir noch keine Gedanken gemacht. TTL ist glaube ich etwas zu kompliziert dafür.zweitens würde ich die CPU später mal in Hardware bauen. Also nicht nur in einem FPGA laufen lassen.Was meinst Du mit "in Hardware bauen"? Wenn nicht in einem FPGA.
Also Die Geräte können alle selbständig auf den Speicher zugreifen.Die kommunikation mit der Hardware läuft über das Klassische I/O Port Konzept ab.Also einen extra I/O-Adressraum und nicht über MMIO? Alle anderen CPU-Architekturen gehen von den I/O-Ports zu MMIO über (falls sie überhaupt mal I/O-Ports unterstützt haben). Kann dann die Peripherie per eigenständigem Busmastering auf den normalen RAM zugreifen? Das wäre sinnvoll da dort ja die Nutzdaten der User-Mode-Prozesse liegen und um die geht es doch schließlich. Soll die Peripherie überhaupt Speicherbereiche anbieten können (sowas wie der Frame-Buffer einer Grafikkarte)?
Also die IRQ Lines werde nicht alle bis in die CPU geführt, sondern gehen an einen Controller, der die dann an die CPU meldet.Es gibt 256 IRQ lines ...Wirklich als eigenständige Leitungen? Bei PCI-Express hat man darauf komplett verzichtet (an den PCI-Express-Slots gibt es keine IRQ-Pins mehr). Schau Dir mal das MSI(-X) aus der PCI-Spec an, das hat ne Menge Vorteile gegenüber dedizierten IRQ-Signalen.
OK da hast du recht.In dem Usermode, lösen alle Befehle, mit der die möglichkeit besteht die kontrolle über das system zu erlangen, eine Exception aus.Dann solltest Du aber 2 verschiedene Register für den Flags vorsehen, eines wo die normalen Flags (C,S,N,O) drin sind und eines für Flags wie IE, damit nicht ständig normale Befehle potentiell gefährliche Sachen machen können (so wie PUSHF/POPF auf x86).
Darüber solltest du Dir aber mal Gedanken machen. Meiner persönlichen Meinung nach ist TTL keine echte Option (sorry Svenska), das macht extrem viel Aufwand und dürfte auch einiges an Geld verschlingen. Bei einer sehr simplen 8Bit-CPU kann TTL noch in die Kategorie "schwer aber realistisch" fallen aber für eine 64Bit CPU ist das wohl ganz offensichtlich nicht machbar.Was meinst Du mit "in Hardware bauen"? Wenn nicht in einem FPGA.Darüber habe ich mir noch keine Gedanken gemacht. TTL ist glaube ich etwas zu kompliziert dafür.
Also Die Geräte können alle selbständig auf den Speicher zugreifen.Dann nutze den Speicheradressraum auch für die Ressourcen welche von der HW angeboten werden, Du machst Dir damit vieles leichter.
Also die IRQ Lines werde nicht alle bis in die CPU geführt, sondern gehen an einen Controller, der die dann an die CPU meldet.Wenn Du eh einen richtigen IRQ-Controller haben willst dann wäre es geschickter wenn Du die Interrupt-Vektoren (also die Einsprungspunkte Deiner SW-IRQ-Handler) gleich von diesem verwalten lässt und der CPU dann mitteilst wenn diese einen IRQ-Handler ausführen soll. Das hat den Vorteil das die CPU keine Tabelle dafür benötigt wo sie dann erst nachschauen muss von wo sie denn jetzt die Befehle holen soll (verringert die Latenz) und es ist der CPU völlig egal wie viele verschiedene IRQ-Handler es eigentlich gibt (das ist dann eine reine Design-Frage für den IRQ-Controller und damit flexibel ohne die CPU ändern zu müssen). Wenn Du aber ein Micro-Kernel-OS implementieren möchtest ist es auch OKay wenn es aus CPU-Sicht nur einen einzigen IRQ-Handler (im Kernel-Mode) gibt der dann die IRQ-Nummer als Parameter bekommt und damit nur noch eine asynchrone IPC-Message an den entsprechend registrierten IRQ-Handler vom User-Mode-Treiber schicken muss (diesen Weg möchte ich gehen).
Du solltest generell dafür sorgen das normale Befehle möglichst wenige Möglichkeiten haben eine Exception auszulösen, wenn Du mehrere unabhängige Befehle hast die alle garantiert keine Exception werfen kannst du die gefahrlos parallel ausführen ohne komplizierte Dinge wie Registerrenaming zu benötigen. Wenn Du das schaffst ist auch Out-of-Order-Execution nicht mehr weit und damit bekommst Du einen enormen Performance-Schub..... damit nicht ständig normale Befehle potentiell gefährliche Sachen machen können (so wie PUSHF/POPF auf x86).OK da hast du recht.
Also das es nur eine Funktion gibt, die dann als Parameter die IRQ Nummer bekommt. also nicht wie bei x86 wo es eine Tabelle, die IDT gibt, sondern nur eine Funktion?Also die IRQ Lines werde nicht alle bis in die CPU geführt, sondern gehen an einen Controller, der die dann an die CPU meldet.Wenn Du eh einen richtigen IRQ-Controller haben willst dann wäre es geschickter wenn Du die Interrupt-Vektoren (also die Einsprungspunkte Deiner SW-IRQ-Handler) gleich von diesem verwalten lässt und der CPU dann mitteilst wenn diese einen IRQ-Handler ausführen soll. Das hat den Vorteil das die CPU keine Tabelle dafür benötigt wo sie dann erst nachschauen muss von wo sie denn jetzt die Befehle holen soll (verringert die Latenz) und es ist der CPU völlig egal wie viele verschiedene IRQ-Handler es eigentlich gibt (das ist dann eine reine Design-Frage für den IRQ-Controller und damit flexibel ohne die CPU ändern zu müssen). Wenn Du aber ein Micro-Kernel-OS implementieren möchtest ist es auch OKay wenn es aus CPU-Sicht nur einen einzigen IRQ-Handler (im Kernel-Mode) gibt der dann die IRQ-Nummer als Parameter bekommt und damit nur noch eine asynchrone IPC-Message an den entsprechend registrierten IRQ-Handler vom User-Mode-Treiber schicken muss (diesen Weg möchte ich gehen).
Also das es nur eine Funktion gibt, die dann als Parameter die IRQ Nummer bekommt. also nicht wie bei x86 wo es eine Tabelle, die IDT gibt, sondern nur eine Funktion?Ja, genau das meinte ich mit der Idee für ein Micro-Kernel-OS. Aber wenn Du doch einen Monolithen haben willst ist diese Idee weniger gut geeignet, also kein KO-Kriterium aber kostet eben etwas Latenz. Wenn Du etwas mehr Flexibilität möchtest dann lieber die Variante wo der IRQ-Controller die Einsprungspunkte kennt und der CPU bei jedem IRQ sagt wo der zugehörige Handler ist (das kann auch immer der selbe sein). Für ein paar HW-Komponenten wirst du eventuell auch in einem Micro-Kernel-OS dedizierte Handler im Kernel-Mode haben wollen, z.B. für den HW-Timer wo der IRQ-Handler möglicherweise direkt in der Lage sein soll einen Thread auf zu wecken oder eine asynchrone IPC-Message zu versenden.
Das macht man doch momentan fast genauso. man legt für alle Interrupts einen stub an, der dann die eigentliche funktion aufruft. daher sollte man keinen unterschied merken.Also das es nur eine Funktion gibt, die dann als Parameter die IRQ Nummer bekommt. also nicht wie bei x86 wo es eine Tabelle, die IDT gibt, sondern nur eine Funktion?Ja, genau das meinte ich mit der Idee für ein Micro-Kernel-OS. Aber wenn Du doch einen Monolithen haben willst ist diese Idee weniger gut geeignet, also kein KO-Kriterium aber kostet eben etwas Latenz. Wenn Du etwas mehr Flexibilität möchtest dann lieber die Variante wo der IRQ-Controller die Einsprungspunkte kennt und der CPU bei jedem IRQ sagt wo der zugehörige Handler ist (das kann auch immer der selbe sein). Für ein paar HW-Komponenten wirst du eventuell auch in einem Micro-Kernel-OS dedizierte Handler im Kernel-Mode haben wollen, z.B. für den HW-Timer wo der IRQ-Handler möglicherweise direkt in der Lage sein soll einen Thread auf zu wecken oder eine asynchrone IPC-Message zu versenden.
Das macht man doch momentan fast genauso. man legt für alle Interrupts einen stub an, der dann die eigentliche funktion aufruft. daher sollte man keinen unterschied merken.Du meinst das man auf x86 256 winzige Assembler-Stubs anlegt die dann alle auf die selbe Funktion springen welche dann per switch wieder verzweigt? Auf x86 sicher ein guter Weg aber generell finde ich das eher unelegant.
Meiner persönlichen Meinung nach ist TTL keine echte Option (sorry Svenska), das macht extrem viel Aufwand und dürfte auch einiges an Geld verschlingen. Bei einer sehr simplen 8Bit-CPU kann TTL noch in die Kategorie "schwer aber realistisch" fallen aber für eine 64Bit CPU ist das wohl ganz offensichtlich nicht machbar.Dem kann ich uneingeschränkt zustimmen.
Wenn Du eh einen richtigen IRQ-Controller haben willst dann wäre es geschickter wenn Du die Interrupt-Vektoren (also die Einsprungspunkte Deiner SW-IRQ-Handler) gleich von diesem verwalten lässt und der CPU dann mitteilst wenn diese einen IRQ-Handler ausführen soll.Als Augenöffner könnte man die drei verschiedenen Interrupt-Modi des Z80 begutachten:
Als Augenöffner könnte man die drei verschiedenen Interrupt-Modi des Z80 begutachten:Sorry, aber wir wollen uns doch nach vorne entwickeln, der Z80 war doch schon Überholt wo von uns noch keiner gelebt hat, ;)
IRQ-Nummer auf Datenbus, aber feste Sprungadresse - die Nummer taucht dann evtl. in einem Register aufVielleicht sollte der IRQ-Controller trotzdem auch die Sprungadresse mit auf den Bus packen, dann hätte man maximale Flexibilität ohne damit die CPU belasten zu müssen (eine in der CPU implementierte Tabelle ist immer entweder zu groß oder zu klein, eben dafür sollte der IRQ-Controller zuständig sein). Die Interrupt-Nummer sollte dem Handler natürlich trotzdem (in einem System-Register) zur Verfügung stehen.
Fragen über Fragen, eine eigene Plattform zu entwickeln bringt einem eine ganze Reihe kniffliger Fragen auf den Tisch. Was soll z.B. alles passieren von dem Moment an wo das System mit elektrischer Energie versorgt wird bis zu dem Moment wo das OS dem User ein Login bietet? Soll es ein BIOS geben das die HW rudimentär in Betrieb nimmt und ein beliebiges OS von einem beliebigen Massenspeicher holt oder soll das OS tiefer im System verankert sein und alles selber machen?Ich habe das so geplant. wenn Strom kommt, dann wird als erstes ein BIOS geladen, welches den Speicher zählt und die wichtigsten komponenten auf Funktion prüft, dann wird von einem Massenspeicher ein Bootloader geladen und dem die kontrolle übergeben. was dann kommt ist Systemspezifisch.
welches den Speicher zählt und die wichtigsten komponenten auf Funktion prüftDas klingt alles so einfach, ich hab da sicher etliche Monate drüber gebrütet um auch nur mal ein grobes Konzept zu haben.
dann wird von einem Massenspeicher ein Bootloader geladenAlso so wie bei x86. Was denkst du was alles nötig ist um überhaupt von einem Massenspeicher einen OS-Boot-Loader laden zu können? Soll dieser OS-Boot-Loader Dein BIOS auch benutzen können (was wie die BIOS-INTs auf x86) um eben auf Massenspeicher zuzugreifen oder ein paar Buchstaben auf den Bildschirm bringen zu können?
Ich sprach vom Augen öffnen. Ob du nun ARM, MIPS oder x86 anschaust, dann hast du überall Vektorinterrupts, die von einem Interruptcontroller auf eine IRQ-Leitung plus das Bussystem gemultiplext werden. (Bei SoCs ist der Interruptcontroller natürlich auf dem Die mit drauf.) Der Z80 hatte da vollständig andere Ideen.Als Augenöffner könnte man die drei verschiedenen Interrupt-Modi des Z80 begutachten:Sorry, aber wir wollen uns doch nach vorne entwickeln, der Z80 war doch schon Überholt wo von uns noch keiner gelebt hat, ;)
Ein wirklich rundum empfehlenswertes CPU-Vorbild gibt es IMHO einfach nicht, da hilft wohl nur sich selber nen Kopf zu machen.
IRQ-Nummer auf Datenbus, aber feste Sprungadresse - die Nummer taucht dann evtl. in einem Register aufVielleicht sollte der IRQ-Controller trotzdem auch die Sprungadresse mit auf den Bus packen, dann hätte man maximale Flexibilität ohne damit die CPU belasten zu müssen (eine in der CPU implementierte Tabelle ist immer entweder zu groß oder zu klein, eben dafür sollte der IRQ-Controller zuständig sein). Die Interrupt-Nummer sollte dem Handler natürlich trotzdem (in einem System-Register) zur Verfügung stehen.[/quote]Wozu? Jeder sinnvoll genutzte Interrupt ist dem System bekannt, also brauchst du in Hardware nicht doppelt arbeiten, um der CPU einmal eine interruptspezifische Funktion und die Interruptnummer mitteilen zu können.
Feste Adressen (wie bei ARM oder Z80) finde ich persönlich doof weil man dann dafür sorgen muss das an dieser Stelle auch wirklich RAM ist und weil dort eh nur ein Sprung auf den tatsächlichen Handler steht.Bei ARM weiß ich es grad nicht, aber beim Z80 sind die Adressen nicht fest. Sie liegen nur alle zusammen in einem (kleinen) Bereich. Man muss ja die historischen Beschränkungen nicht mit dem Konzept übernehmen, außerdem sind die Grenzen heute ganz andere. Wenn IRQ-Handler in den unteren 4 GB des Speichers liegen müssen, sehe ich da kein Problem. Wenn sie in den unteren 256 Bytes liegen müssen, schon...
Also das klassische Modell vom x86. Was ist an deinem Konzept eigentlich anders als der Rest? Jede Architektur zeichnet sich durch irgendetwas aus, was zeichnet deine aus?Was soll z.B. alles passieren von dem Moment an wo das System mit elektrischer Energie versorgt wird bis zu dem Moment wo das OS dem User ein Login bietet? Soll es ein BIOS geben das die HW rudimentär in Betrieb nimmt und ein beliebiges OS von einem beliebigen Massenspeicher holt oder soll das OS tiefer im System verankert sein und alles selber machen?Ich habe das so geplant. wenn Strom kommt, dann wird als erstes ein BIOS geladen, welches den Speicher zählt und die wichtigsten komponenten auf Funktion prüft, dann wird von einem Massenspeicher ein Bootloader geladen und dem die kontrolle übergeben. was dann kommt ist Systemspezifisch.
In meine Plattform wird es kein BIOS o.ä. geben, da liegt das OS in einem Flash der durch die CPU direkt angesprochen werden kann (als ganz normaler Speicher) und davor kommt ein minimaler Initial-Loader der einfach nur das Image sucht und anspringt.Also baust du ein NOR-Flash an eine Stelle im Adressraum, wo die CPU nach dem Reset die Ausführung beginnt und dort legst du einen Bootloader für z.B. NAND/IDE hin, der Prüfsumme berechnet und startet?
Ich sprach vom Augen öffnen.Schon klar, ich hatte das auch nicht so ganz ernst gemeint. ;)
Geschichte wiederholt sich. ...Wohl war und Dein Beispiel ist sehr gut gewählt.
Es gibt heutzutage viele HW-Komponenten die sehr viele Interrupts auslösen können, schau dir mal dir Gründe für MSI(-X) an, da wird man nicht für jeden Interrupt einen eigenen Handler haben wollen aber der Handler muss wissen für welchen der vielen Interrupts seiner einen HW-Komponente er gerade tätig ist. Auch könnte die selbe HW mehrfach vorhanden sein und auch da möchte man den Code nicht unbedingt doppelt haben. Ich denke den geringen Aufpreis für das Silizium sollte man zahlen wenn man dafür die volle Flexibilität bekommt.Die Interrupt-Nummer sollte dem Handler natürlich trotzdem (in einem System-Register) zur Verfügung stehen.Wozu? Jeder sinnvoll genutzte Interrupt ist dem System bekannt, also brauchst du in Hardware nicht doppelt arbeiten, um der CPU einmal eine interruptspezifische Funktion und die Interruptnummer mitteilen zu können.
Bei ARM gibt es ein paar Vectoren die alle in den ersten wimre 64Bytes drin sind, man kann diese Vectoren auch in die letzte 64kB-Page legen aber man muss dort trotzdem jeweils einen Sprung auf den eigentlichen Handler hin legen und das kostet vor allem Latenz aber auch etwas nutzlosen RAM den man auch noch an einer bestimmten Adresse benötigt. Da ja auf meiner Plattform der Kernel per default ohne Paging arbeitet würde das bedeuten das ich an einer bestimmten physischen Adresse RAM vorsehen muss (x86 definiert ja auch wo überall physischer RAM verfügbar sein muss) und genau das will ich nicht, ich will auf jeden Fall das die CPU absolut keine Bedingen an das physische Speicher-Layout richtet so das der Board-Designer dort absolute Freiheit hat. Auch der Reset-Einsprungspunkt ist auf meiner CPU nicht fest definiert sondern der wird mit einem speziellen Bus-Zyklus vom Chip-Satz (der sein Board ja kennt) an die CPU übermittelt, das heißt die CPU fängt nicht einfach das arbeiten an wenn das Reset-Signal deasserted wird sondern sie bleibt in einem Power-Down-Wartezustand und wartet darauf vom Chip-Satz gesagt zu bekommen wo es los geht.Feste Adressen (wie bei ARM oder Z80) finde ich persönlich doof weil man dann dafür sorgen muss das an dieser Stelle auch wirklich RAM ist und weil dort eh nur ein Sprung auf den tatsächlichen Handler steht.Bei ARM weiß ich es grad nicht, aber beim Z80 sind die Adressen nicht fest. Sie liegen nur alle zusammen in einem (kleinen) Bereich.
Wenn IRQ-Handler in den unteren 4 GB des Speichers liegen müssen, sehe ich da kein Problem.Das ist in meinen Augen schon ein Problem weil das bedeutet das Du in den ersten 4GB überhaupt RAM haben musst was auf einer 64Bit-Plattform eigentlich nicht unbedingt erforderlich ist (da könnte auch der HW-MMIO-Bereich liegen).
Also baust du ein NOR-Flash an eine Stelle im Adressraum, wo die CPU nach dem Reset die Ausführung beginnt und dort legst du einen Bootloader für z.B. NAND/IDE hin, der Prüfsumme berechnet und startet?Nein, dort liegt gleich ein vollwertiges OS-Image mit allem was nötig ist damit das OS auf die Füße kommt. Als Flash erscheint mir das http://www.sst.com/products/?inode=42062 (http://www.sst.com/products/?inode=42062) ziemlich interessant. Wenn man davon 8 Stück parallel schaltet hat man einen 32Bit-Datenbus mit 80MHz, nur ein paar Takte Initial-Latenz (bursten geht dann mit den vollen 80MHz also 320MByte/s und beliebig lang) und 32MByte Kapazität. Das macht schon ordentlich was her und kostet nur 34 Pins am FPGA, nur zum Schreiben von ganzen Blöcken muss man im FPGA einen speziellen Mechanismus einbauen aber das Lesen dürfte ziemlich simpel sein. In diesem Flash sollen mehrere OS-Images drin sein können und eines davon wird von einem Initial-Boot-Loader angesprungen, dieser Initial-Boot-Loader liegt in einem ROM-Bereich. Dazu gibt es noch etwas SRAM der dann als Stack benutzt wird weil das OS ja erst den richtigen DRAM finden und einrichten muss (der darf dann an einer beliebigen physischen Adresse liegen). Unmittelbar nach dem aufwachen aus dem Reset gibt es nur ein einziges Device das im physischen Adressraum vorhanden ist und das darf sich eine beliebige physische Adresse auswürfeln, okay ich (als Board-Designer) lege einfach mal Adresse 0 fest aber es könnte auch jede andere sein. Dieses Device enthält dann den ROM, SRAM und Flash und sendet daher die physische Adresse des ROM an die CPU zum aufwachen. Die PCI-Spezifikation macht extra bestimmte Zugeständnisse an "zum Booten benötigte Devices" die nach dem Reset bereits aktiv sind (also deren Bit 1 im Command-Register auf 1 steht was eigentlich nicht erlaubt ist).
Ein full-featured BIOS finde ich jedenfalls heutzutage übertrieben.Full ACK. Außerdem macht das einen riesigen Haufen an zusätzlicher Programmier-Arbeit.
Das OS implementiert in der Regel alle Treiber selbst.Eben. Das EFI-Konzept, mit den OS-unabhängigen Treibern die direkt bei der HW in einem Flash liegen, ist zwar ganz nett aber so ein interpretierter Treiber, der auch nicht wirklich zu den spezifischen OS-Konzepten passt, dürfte kaum anständige Performance bringen. Das EFI-Konzept taugt um beim OS-Booten überhaupt erst mal an Massenspeicher und Konsole kommen zu können aber zur richtigen Laufzeit vom OS ist das absolut uninteressant und dementsprechend dürftig ist die Unterstützung der HW-Komponenten-Hersteller für EFI. Einer der Gründe warum Intel sich überhaupt das EFI hat einfallen lassen war der das man mal dachte zwischen x86 und Itanium Sockel-Kompatibilität erreichen zu können (es sollte mal Boards geben auf die man einen großen x86-Prozi oder einen kleinen Itanium-Prozi stecken kann und das BIOS mit den rudimentären Treibern nicht als nativer Code doppelt vorliegen muss), der andere wichtige Grund ist natürlich das man das angestaubte RM-BIOS los werden will.
Es gibt heutzutage viele HW-Komponenten die sehr viele Interrupts auslösen können, schau dir mal dir Gründe für MSI(-X) an, da wird man nicht für jeden Interrupt einen eigenen Handler haben wollen aber der Handler muss wissen für welchen der vielen Interrupts seiner einen HW-Komponente er gerade tätig ist. Auch könnte die selbe HW mehrfach vorhanden sein und auch da möchte man den Code nicht unbedingt doppelt haben. Ich denke den geringen Aufpreis für das Silizium sollte man zahlen wenn man dafür die volle Flexibilität bekommt.Okay, daran hatte ich jetzt nicht gedacht. Wobei ein Interrupthandler meist extrem kurz ist, daher ist der RAM-Verbrauch sehr gering. Zumal ich es nicht mag, wenn ein Treiber mehrere identische Hardwarekomponenten verwaltet. Da sollte man (gefühlt) den Treiber mehrfach laden können; andererseits habe ich keine Ahnung, ob sich gewisse Hardware dagegen sperrt.
Bei ARM gibt es ein paar Vectoren die alle in den ersten wimre 64Bytes drin sind, man kann diese Vectoren auch in die letzte 64kB-Page legen aber man muss dort trotzdem jeweils einen Sprung auf den eigentlichen Handler hin legen und das kostet vor allem Latenz aber auch etwas nutzlosen RAM den man auch noch an einer bestimmten Adresse benötigt.Okay, das wusste ich nicht. Wobei ich die Restriktion auf fixe physische Bereiche nicht so problematisch finde, sofern dieser Bereich "hinreichend groß" ist. Schöner dehnbarer Begriff, aber wenn Interrupthandler mit einem 24-Bit-Pointer (auf einer 32-Bit-Architektur) oder 32-Bit-Pointer (bei 64 Bit) physisch erreichbar sein müssen, hab ich damit kein Problem. Die oberen Bits können ja vom Chipsatz kommen, dann verlangst du keinen RAM an einer bestimmten Stelle.
Nein, dort liegt gleich ein vollwertiges OS-Image mit allem was nötig ist damit das OS auf die Füße kommt. Als Flash erscheint mir das http://www.sst.com/products/?inode=42062 (http://www.sst.com/products/?inode=42062) ziemlich interessant. Wenn man davon 8 Stück parallel schaltet hat man einen 32Bit-Datenbus mit 80MHz, nur ein paar Takte Initial-Latenz (bursten geht dann mit den vollen 80MHz also 320MByte/s und beliebig lang) und 32MByte Kapazität.Okay, ich hätte eher daran gedacht, einen Flashbaustein als Speicher zu benutzen und die dann vorhandene Adresse als Resetvektor anzugeben... der Vorteil wäre, dass der Flashbaustein an den ganz normalen Speicherbus angeschlossen ist und du dort dann extern (initial) und intern (vom OS aus) flashen könntest und du dir den ROM sparst. Wenn im FPGA genug Platz übrig bleibt, kannst du ja dort ein bisschen RAM erzeugen, spart den Extrabaustein.
Das EFI-Konzept taugt um beim OS-Booten überhaupt erst mal an Massenspeicher und Konsole kommen zu können aber zur richtigen Laufzeit vom OS ist das absolut uninteressant und dementsprechend dürftig ist die Unterstützung der HW-Komponenten-Hersteller für EFI.In der Firmware braucht man für normale Systeme (deine Entwicklung spielt eher in Richtung leistungsfähiger Mikrocontroller) nur Treiber für Konsole (Tastatur und Textmodusbildschirm) und Bootmedium (so Dinge wie PIO-IDE-Controller, NAND-Flashbaustein und Netzwerkkarte). Das Konzept der Option-ROMs ist eigentlich optimal.
Okay, daran hatte ich jetzt nicht gedacht. Wobei ein Interrupthandler meist extrem kurz ist, daher ist der RAM-Verbrauch sehr gering.Es geht mir nicht primär um den RAM-Verbrauch sondern darum dass das System keine Vorgaben machen sollte wo und wie die IRQ-Handler liegen sollten. Okay so ganz halte ich mich da selber nicht dran weil ich für alle HW-IRQs (und das können extrem viele sein) nur einen einzigen Handler ermögliche (mehr macht bei einem Micro-Kernel ja auch keinen Sinn), aber wenn man eine etwas allgemeinere Plattform entwickelt dann sollte man mehr Flexibilität bieten.
Zumal ich es nicht mag, wenn ein Treiber mehrere identische Hardwarekomponenten verwaltet. Da sollte man (gefühlt) den Treiber mehrfach laden können; andererseits habe ich keine Ahnung, ob sich gewisse Hardware dagegen sperrt.Ich kann ehrlich gesagt Deine Bedenken nicht nachvollziehen. Unter Windows ist es wimre üblich das ein Treiber nur ein einziges mal existiert und die IRQ- und DPC-Handler immer eine Geräte-Individuelle Struktur als Parameter mit bekommen. Aber ich meinte eigentlich das es auch HW-Komponenten gibt die mit recht vielen IRQs umgehen. Da wären z.B. bessere Ethernet-Controller bei denen für einzelne (TCP-)Connections der Treiber einen eigenen IRQ anbindet und diesen IRQ auch auf eine bestimmte CPU fest klemmt damit die Pakete auch gleich auf der CPU im Cache landen wo dann auch der zugehörige Server-Thread läuft, das selbe erlauben auch die guten SAS-Controller welche z.B. 64 IRQs (für jede CPU einen) haben können und für jeden HDD-Zugriff kann individuell ausgewählt werden auf welcher CPU dann der zugehörige "Fertig"-IRQ kommt (das geht übrigens auch mit SATA im AHCI-Modus), auf den besseren Server-Systemen soll sowas auf jeden Fall gut funktionieren.
Was tut man eigentlich in so einem Interrupthandler ....Also das hängt ja wohl stark von der betreffenden HW ab, zumindest simples Daten-Kopieren sollte heute kein IRQ-Handler mehr machen müssen schließlich gibt es dafür ja HW mit Busmasterfähigkeiten.
Wobei ich die Restriktion auf fixe physische Bereiche nicht so problematisch finde, sofern dieser Bereich "hinreichend groß" ist.Also mMn ist dieser Bereich entweder so groß das er den gesamten physischen Adressraum unterstützt oder es ist eine Einschränkung. Auch der Zwang das alle IRQ-Handler in einem bestimmten Bereich drin sein müssen ist IMHO nicht akzeptabel. Was ist wenn eine HW per Hot-Pluging erst später in den PC hinzugefügt (oder eingeschaltet) wird?
Okay, ich hätte eher daran gedacht, einen Flashbaustein als Speicher zu benutzen und die dann vorhandene Adresse als Resetvektor anzugebenDie NOR-Flash-Teile sind schon ganz toll aber eben nicht sonderlich schnell.
der Vorteil wäre, dass der Flashbaustein an den ganz normalen Speicherbus angeschlossen ist und du dort dann extern (initial) und intern (vom OS aus) flashen könntest und du dir den ROM sparst.Ich habe keinen normalen Speicherbus, stell Dir mein System lieber wie ein Opteron-System vor, da hat zwar jede CPU etwas Speicher aber das ist dann DDR2 oder DDR3 Speicher und an deren Datenleitungen kann man keinen NOR-Flash mit ranhängen. Flaschen passiert entweder über den FPGA per JTAG oder bei laufendem System.
Direktes Booten von PCI-Geräten finde ich persönlich etwas übertrieben.Ich nicht, ich denke das ist genau die richtige Lösung. Es gibt ein Boot-Device, das Bestandteil des Chipsatz ist, und dieses Device bietet z.B. einen 32MB-Speicherbereich an. Die Basis-Adresse dieses Bereichs ist hardcodiert im BAR0 und wird benutzt um eine CPU aufzuwecken. In diesem Speicherbereich ist dann vorne der ROM und danach etwas RAM, etwas FRAM (für die Konfiguration) und natürlich der Flash enthalten. So lange es keine anderen Speicherbereiche im System gibt gehen eh alle Speicherzugriffe von der CPU an den Chipsatz (der dieses Boot-Device kennen muss) und wenn dann das OS-Image den RAM (der direkt an den CPUs hängt) in Betrieb nimmt werden auch die Routing-Tabellen in den CPUs konfiguriert und ab dann gibt es erst den DRAM in den dann der eigentliche OS-Kernel entpackt wird. Sobald das OS zum Leben erwacht wird das Boot-Device nicht mehr benötigt, außer noch die Konfigurationsdaten aus dem FRAM. Erst im laufenden OS wird dann der PCI-Service gestartet der den restlichen PCI-Bus nach Geräten absucht und diesen Ressourcen zuweist und Treiber startet.
Mikrocontroller und SoCs tun das ja auch nicht... beim Samsung S3C24xx hast entweder einen NOR-Baustein im Adressraum oder du schließt einen NAND-Baustein an, wo die CPU dann mit einem CPU-internen Mikrocode die ersten 4 KB in einen CPU-internen SRAM kopiert und davon bootet.Das sind aber alles Beispiele wo die CPU-Designer schon sehr viele Vorgaben für den System-Designer machen. Ich möchte das meine CPU von außen gesagt bekommt wo es los geht und der Rest liegt dann in der Verantwortung des System-Designers.
deine Entwicklung spielt eher in Richtung leistungsfähiger MikrocontrollerNa horch mal, ich will einen full-featured General-Purpose-PC entwickeln! Zumindest von den prinzipiellen Möglichkeiten her soll meine Plattform in dieser Liga mitspielen. Außerdem hab ich mir vorgenommen das ich einem vergleichbar ausgestattetem System (also gleiche Menge RAM, gleiche CPU-Taktfrequenz, gleiche CPU-Anzahl) zumindest auf gleicher Augenhöhe Paroli bieten kann. Du hast doch mal geschrieben das Du so verschiedene Serversysteme hast, kannst mir ja mal ne kleine Auflistung (per PM) zukommen lassen und wenn ich soweit bin suche ich mir einen adäquaten Gegner aus und wir lassen mal von http://www.eembc.org/ (http://www.eembc.org/) den CoreMark und den MultiBench gegeneinander antreten. Du darfst auch Dein OS und Compiler selber wählen. ;)
Einschränkung ja, KO-Kriterium nein. Was das Hot-Plugging angeht, hatte ich dazu was geschrieben - der RAM im entsprechenden Bereich wird erst dann alloziiert, wenn der restliche RAM voll ist oder er wird komplett für IRQ-Handler reserviert (16 MB sind da für mich akzeptabel). Ist der entsprechende Bereich belegt und ein Treiber wird geladen, so werden alle IRQ-Handler in einen anderen Bereich umkopiert und der Zeiger mit den in der Adresse fehlenden Bits wird neu geladen (er muss nicht null sein) oder der Treiber kann dann eben nicht geladen werden.Wobei ich die Restriktion auf fixe physische Bereiche nicht so problematisch finde, sofern dieser Bereich "hinreichend groß" ist.Also mMn ist dieser Bereich entweder so groß das er den gesamten physischen Adressraum unterstützt oder es ist eine Einschränkung. Auch der Zwang das alle IRQ-Handler in einem bestimmten Bereich drin sein müssen ist IMHO nicht akzeptabel. Was ist wenn eine HW per Hot-Pluging erst später in den PC hinzugefügt (oder eingeschaltet) wird?
Ist das ein Problem? Der initiale Bootloader kann doch das OS erstmal in den RAM kopieren, ehe du deine Lebendgeburt vornimmst. Wenn du mit 32 MB für mehrere OS-Images rechnest, dann dürfte ein OS-Image vielleicht 8-12 MB haben... bei 1 GB RAM kannst du das nun wirklich locker verschmerzen.Okay, ich hätte eher daran gedacht, einen Flashbaustein als Speicher zu benutzen und die dann vorhandene Adresse als Resetvektor anzugebenDie NOR-Flash-Teile sind schon ganz toll aber eben nicht sonderlich schnell.
Ich habe keinen normalen Speicherbus, stell Dir mein System lieber wie ein Opteron-System vor, da hat zwar jede CPU etwas Speicher aber das ist dann DDR2 oder DDR3 Speicher und an deren Datenleitungen kann man keinen NOR-Flash mit ranhängen. Flaschen passiert entweder über den FPGA per JTAG oder bei laufendem System.Gut, das ist ein KO-Kriterium. Wobei du eine Art Speicherbus sicherlich haben wirst, um MMIO da ranzuhängen; wenn im physischen Adressraum Geräte auftauchen, kann das auch der Flashbaustein.
Ich würde einfach ein Stück Adressraum mit dem ROM vorbelegen. Das ist einfacher. Und wenn einem der Adressraum wirklich zu knapp ist, dann kann das OS ja auch das Adressbit wieder in physischen RAM (oder MMIO-Bereiche) zeigen lassen, statt in den Bootcode.Direktes Booten von PCI-Geräten finde ich persönlich etwas übertrieben.Ich nicht, ich denke das ist genau die richtige Lösung. Es gibt ein Boot-Device, das Bestandteil des Chipsatz ist, und dieses Device bietet z.B. einen 32MB-Speicherbereich an. [...]
Gut, ist eine Designentscheidung. Normalerweise willst du ein Board möglichst einfach stricken und wenn die passende Unterstützung in der CPU schon vorhanden ist, dann ist das halt einfacher und billiger. Je flexibler die CPU ist, umso komplizierter wird der Chipsatz.Mikrocontroller und SoCs tun das ja auch nicht... beim Samsung S3C24xx hast entweder einen NOR-Baustein im Adressraum oder du schließt einen NAND-Baustein an, wo die CPU dann mit einem CPU-internen Mikrocode die ersten 4 KB in einen CPU-internen SRAM kopiert und davon bootet.Das sind aber alles Beispiele wo die CPU-Designer schon sehr viele Vorgaben für den System-Designer machen. Ich möchte das meine CPU von außen gesagt bekommt wo es los geht und der Rest liegt dann in der Verantwortung des System-Designers.
Ääh, das war nicht abwertend gemeint. Du kannst auch einen ARM9 mit VGA-Ausgang, USB-Tastatur/-Maus und einer IDE-Festplatte als General-Purpose-PC nutzen... und trotzdem ist der ARM ein Mikrocontroller. Definiere mir bitte mal den Unterschied, den es da für dich gibt. ;-)deine Entwicklung spielt eher in Richtung leistungsfähiger MikrocontrollerNa horch mal, ich will einen full-featured General-Purpose-PC entwickeln!
Zumindest von den prinzipiellen Möglichkeiten her soll meine Plattform in dieser Liga mitspielen. Außerdem hab ich mir vorgenommen das ich einem vergleichbar ausgestattetem System (also gleiche Menge RAM, gleiche CPU-Taktfrequenz, gleiche CPU-Anzahl) zumindest auf gleicher Augenhöhe Paroli bieten kann. Du hast doch mal geschrieben das Du so verschiedene Serversysteme hast, kannst mir ja mal ne kleine Auflistung (per PM) zukommen lassen und wenn ich soweit bin suche ich mir einen adäquaten Gegner aus und wir lassen mal von http://www.eembc.org/ (http://www.eembc.org/) den CoreMark und den MultiBench gegeneinander antreten. Du darfst auch Dein OS und Compiler selber wählen. ;)Muss ich ablehnen. Mein 286er mit Xenix als Server-OS ist derzeit kaputt und die ganzen dicken Server gehören der Firma, bei der ich gerade Praktikum mache. Da hab ich keinen Shellzugang, ich hab da nur dutzende Festplatten reingeschraubt. ;-)
der RAM im entsprechenden Bereich wird erst dann alloziiert, wenn der restliche RAM voll ist oder er wird komplett für IRQ-Handler reserviert (16 MB sind da für mich akzeptabel).Ich gehe jetzt mal von einem Monolithen aus: Du willst also alle IRQ-Handler von allen Treiber in einem bestimmten Speicherbereich zusammen haben? Virtuell oder physisch? Wie soll das eigentlich gehen? Der IRQ-Handler liegt doch irgendwo mitten im Code vom Treiber, für den Compiler ist das eine Funktion wie jede andere auch. Um das zu bewerkstelligen müsstest Du IMHO vom Compiler bis zum Datei-Format ne ganze Menge Zeugs anfassen. Und das nur um im IRQ-Controller ein paar Bits einzusparen. Das kann ich irgendwie nicht nach vollziehen. Ich verlange z.B. das die IRQ-Handler auf 256Bytes ausgerichtet sind um mir die untersten 8 Bits zu sparen aber die oberen Bits bleiben definitiv erhalten. Ich könnte jetzt überlegen das auf einem konkreten System wo der Bus nur 48Bit an physischen Adressen unterstützt (HyperTransport hat ja auch so eine Beschränkung) das ich mir da bei diesen Registern die oberen Bits sparen könnte, weil so eine Adresse eh nicht möglich ist, aber grundsätzlich finde ich ist das an der falschen Stelle gespart.
Einschränkung ja, KO-Kriterium nein. ;-)Im Prinzip ja, aber zu welchem Preis?
Der initiale Bootloader kann doch das OS erstmal in den RAM kopieren, ehe du deine Lebendgeburt vornimmst.Der initiale Boot-Loader soll nur das OS-Image anspringen und vorher noch den Stack vorbereiten, macht also ungefähr so viel wie eine minimale crt0.asm die mehrere main-Funktionen zur Auswahl hat. Dieser Initial-Boot-Loader soll keine Veränderungen am System vornehmen. Erst der Code im OS-Image initialisiert überhaupt die DRAM-Controller damit überhaupt normaler Speicher verfügbar ist. Der Code im OS-Image soll nur von den Ressourcen abhängen die das Boot-Device grundsätzlich zur Verfügung stellt, alles was er zusätzlich benötigt muss er selber erst vorbereiten.
dann dürfte ein OS-Image vielleicht 8-12 MB habenBis es wirklich so viel ist dürfte es ne Weile dauern, schließlich muss ich das ja alles auch selber programmieren.
Wobei du eine Art Speicherbus sicherlich haben wirst, um MMIO da ranzuhängen;Ja natürlich, ich hab da an PCI oder gar PCI-Express gedacht. Damit kann ich dann ganz normale Standard-HW (wie z.B. SATA-Controller oder Ethernet-Karten) anbinden. Auf meiner Plattform soll jede Ressource (also Speicher) als PCI-Device verwaltet werden, sogar der normale RAM.
wenn im physischen Adressraum Geräte auftauchen, kann das auch der Flashbaustein.Eben deshalb soll das Boot-Device ein PCI-Gerät sein, wenn auch nur logisch weil es ja integraler Bestandteil des Chipsatzes ist.
Ich würde einfach ein Stück Adressraum mit dem ROM vorbelegen. Das ist einfacher.Welches Stück vom Adressraum? Das ist genau die Frage die bei mir nicht der CPU-Designer sondern der System-Designer klären soll. Klar ist es einfacher wenn der CPU-Designer sagt die CPU fängt an der physischen Adresse 0 an zu arbeiten wenn sie aus dem Reset kommt aber es zwingt dem System-Designer dazu dafür zu sorgen das genau dort auch wirklich der Initial-Boot-Loader liegt. Das zweite Problem ist: Was machen die anderen CPUs in einem SMP-System wenn diese auch aufwachen sollen? Ich denke man benötigt eh einem Mechanismus um eine CPU an einer flexiblen Adresse aufwachen zu lassen und da kann ich diesen Mechanismus auch gleich für die erste CPU benutzen und spare mir die CPU-Vorgaben für das physische Speicherlayout.
Ääh, das war nicht abwertend gemeint.Schon klar.
Definiere mir bitte mal den Unterschied, den es da für dich gibt. ;-)Da gibt es absolut keinen Unterschied. Was man aus einer CPU macht hängt in erster Linie vom System ab in dem sie verbaut ist.
Muss ich ablehnen.Schade, nunja da muss ich mir eben jemand anderes suchen.
Du meinst aber so geschätzte 100 MHz?Ja, das wäre so ungefähr die Gewichtsklasse die ich mit nem FPGA einigermaßen realistisch erreichen kann. Wobei wohl ein 486 kaum ein echter Gegner ist weil er doch eine recht simple Pipeline hat und einen recht schwachen Befehlssatz, mein erster FPGA-Versuch wird mit Sicherheit keine Bessere Pipeline haben aber auf jeden Fall einen deutlich leistungsfähigeren Befehlssatz und eben 64 Register. So lange ich keinen zu großen Mist baue müsste der 486 schon mit mindestens der doppelten Taktfrequenz wie mein FPGA kommen wenn er eine reelle Chance haben soll.
Für richtige Multi-CPU-Systeme musst du FlashBurn fragen, der hatte wohl sowas.Also wenn ich doch in die Richtung um 100MHz hin komme dann wäre ein 4fach PentiumPro (mit kleinem Multiplikator) genau das was ich mir als herausfordernden Gegner vorstelle. Der hat zwar nach außen den schwachen x86-Befehlssatz aber intern kann er gut Parallelisieren, kann Out-of-Order-Execution und Register-Renaming. Wenn ich zumindest in gewissem Grad auch Parallelisieren kann dann sollte ein PentiumPro bei gleichem Takt tatsächlich zu schlagen sein weil all die restlichen Dinge die der PentiumPro in HW macht bei mir der Compiler tun kann.
Du willst also alle IRQ-Handler von allen Treiber in einem bestimmten Speicherbereich zusammen haben? Virtuell oder physisch? [...] Und das nur um im IRQ-Controller ein paar Bits einzusparen.Das hängt davon ab, wie teuer die Bits im IRQ-Controller sind. Ansonsten hast du natürlich recht. Du tauschst Softwarekomplexität gegen Hardwarekomplexität ein.
aber grundsätzlich finde ich ist das an der falschen Stelle gespart.Hast du recht.
Der initiale Boot-Loader soll nur das OS-Image anspringen und vorher noch den Stack vorbereiten, macht also ungefähr so viel wie eine minimale crt0.asm die mehrere main-Funktionen zur Auswahl hat. Dieser Initial-Boot-Loader soll keine Veränderungen am System vornehmen. Erst der Code im OS-Image initialisiert überhaupt die DRAM-Controller damit überhaupt normaler Speicher verfügbar ist.Okay, das ist eine Designentscheidung. Der definitiv hardwareabhängige Teil (wie z.B. DRAM-Initialisierung) gehört für mich nicht ins OS, sondern in einen hardwareabhängigen Softwarebereich, d.h. die Firmware.
Auf meiner Plattform soll jede Ressource (also Speicher) als PCI-Device verwaltet werden, sogar der normale RAM.Sehe ich als overkill an, aber OK.
Das ist genau die Frage die bei mir nicht der CPU-Designer sondern der System-Designer klären soll.Gefühlt wird es genau einen einzigen Chipsatz für deine CPU geben, auf einem einzigen Boardtyp. Der Systemdesigner ist der CPU-Designer, also du. Mach es dir doch einfach. ;-)
Klar ist es einfacher wenn der CPU-Designer sagt die CPU fängt an der physischen Adresse 0 an zu arbeiten wenn sie aus dem Reset kommt aber es zwingt dem System-Designer dazu dafür zu sorgen das genau dort auch wirklich der Initial-Boot-Loader liegt. Das zweite Problem ist: Was machen die anderen CPUs in einem SMP-System wenn diese auch aufwachen sollen? Ich denke man benötigt eh einem Mechanismus um eine CPU an einer flexiblen Adresse aufwachen zu lassen und da kann ich diesen Mechanismus auch gleich für die erste CPU benutzen und spare mir die CPU-Vorgaben für das physische Speicherlayout.Du kannst auch einfach sagen, dass die Startadresse auf dem Datenbus liegen muss, wenn CPURESET deaktiviert wird. Wenn du SYSTEMRESET aktivierst, dann wird der Datenbus statisch vorbelegt, wenn du sekundäre CPUs bootest, legt die primäre CPU halt ein Wort auf den Datenbus und löst das CPURESET der anderen CPU.
Finde mal solche Systeme, ich habe jedenfalls keins. Was ich habe, können wir gerne als Vergleich benutzen.Für richtige Multi-CPU-Systeme musst du FlashBurn fragen, der hatte wohl sowas.Also wenn ich doch in die Richtung um 100MHz hin komme dann wäre ein 4fach PentiumPro (mit kleinem Multiplikator) genau das was ich mir als herausfordernden Gegner vorstelle.
Das hängt davon ab, wie teuer die Bits im IRQ-Controller sind.Wenn der IRQ-Controller tausende IRQs verwalten kann und jeder IRQ eine eigenen Adresse haben kann dann kommen da schon ein paar Transistoren zusammen aber da nimmt man dann eh SRAM o.ä. für. Da ich ja nur einen IRQ-Handler (im Micro-Kernel) für alle HW-IRQs hab benötige ich im IRQ-Controller auch nur ein einziges Register dafür, da ist der Preis auf jeden Fall sehr gering.
Allerdings sehe ich kein Problem damit, wenn ein Treiber seinen IRQ-Handler im Betriebssystem registrieren muss (das OS kümmert sich dann um den IRQ-Controller) und dieser Code dann an einer bestimmten Stelle im RAM liegen muss. Das kann ja auch zur Laufzeit geschehen (wie TSR-Programme, die den residenten Teil ja auch erstmal irgendwohin kopieren, damit er nicht im Weg rumliegt).Klar meldet der Treiber seinen IRQ-Handler beim OS an aber er gibt dabei auch nur den Einsprungspunkt für seinen IRQ-Handler bekannt und nicht wie viele Code-Bytes der belegt und was für Funktionen dann sonst noch (von dieser Handler-Funktion) so alles aufgerufen werden können. Den IRQ-Handler aus einem Treiber heraus zu filetieren stelle ich mir extremst kompliziert vor. Bei den TSR (ich nehme mal an Du meinst das was zu DOS-Zeit so üblich war) ist die Sache ganz anders, war war der TSR-Teil quasi ein eigenständiges Programm das in das Haupt-Programm mit integriert wurde.
Der definitiv hardwareabhängige Teil (wie z.B. DRAM-Initialisierung) gehört für mich nicht ins OS, sondern in einen hardwareabhängigen Softwarebereich, d.h. die Firmware.Da bleibt ja eigentlich nur die DRAM-Initialisierung, alles andere wird erst später im laufenden OS in Betrieb genommen. Aber so ein bisschen Recht hast Du schon, wenn ich die Möglichkeit habe in dem Flash mehrere Images zu haben dann könnte da auch eines mit system-spezifischen Funktionen sein. Das heißt das ich eine Art Firmware hätte die nicht per INT o.ä. benutzt wird sondern eine Tabelle mit den unterstützen Funktionen und den zugehörigen Einsprungspunkten am Anfang bietet. Da könnten z.B. Funktionen wie RAM-Größe ermitteln oder RAM initialisieren oder eventuell auch Anzahl der CPUs ermitteln und CPU in Betrieb nehmen drin sein. Diesen Gedanken muss ich mal notieren. So lange ich genau ein OS auf genau einem System habe ist das uninteressant aber wenn ich doch mal unterschiedliche HW-Implementierungen haben sollte könnte diese Idee sich als nützlich erweisen. Danke!
Ich meine ja nicht das ich zur Verbindung der CPUs untereinander und mit dem Chipsatz PCI(e) einsetzen will sondern nur das alle physischen CPUs so einen PCI-Config-Space zur Verfügung stellen. Die CPUs wären dann alle im virtuellen PCI-Bus 0 und das Device 31 wäre der Chip-Satz in dem eine oder mehrere PCI-Funktionen dann als PCI2PCI-Bridges einen Root-Port nach draußen bieten.Auf meiner Plattform soll jede Ressource (also Speicher) als PCI-Device verwaltet werden, sogar der normale RAM.Sehe ich als overkill an, aber OK.
Der Systemdesigner ist der CPU-Designer, also du. Mach es dir doch einfach. ;-)Die Personalunion ist einfach ein Zwang der Umstände, mir hilft ja keiner. ;) Und wenn ich drauf aus wäre es mir einfach zu machen wäre ich nicht intelligent geworden! ;)
Du kannst auch einfach sagen, dass die Startadresse auf dem Datenbus liegen mussIch habe keinen Datenbus der alle CPUs verbindet, ich möchte Punkt-zu-Punkt-Verbindungen benutzen und dafür die tollen GBit-SERDES der aktuellen FPGAs benutzen. Ich muss das also eh alles mit Messages machen. Ich kann Dir nur empfehlen mal die Spezifikationen zu HyperTransport und PCI-Express zu lesen, die bauen beide auf die selben PCI-Konzepten (Management, Config-Space usw.) auf auch wenn die Realisierungen sich in vielen Details doch extrem unterscheiden (auch gegenüber dem klassischen parallelen Original).
Finde mal solche Systeme, ich habe jedenfalls keins.Hab ich bereits bemerkt, man findet keine PentiumPro-Server-Systeme mehr bei ebay & co.
Was ich habe, können wir gerne als Vergleich benutzen.Danke für Dein Angebot.
Mir fallen da spontan noch die Bootmedien ein. Schließlich will man nicht immer ein vorgefertigtes OS booten können (was, wenn man es kaputtkonfiguriert hat?), sondern auch mal von so Dingen wie USB-Festplatten, Speicherkarten, TFTP-Server und so. Wenn du deinen Flashbaustein nur als "ein weiteres Bootmedium" betrachtest, dann hast du die Trennung von Firmware wieder drin.Der definitiv hardwareabhängige Teil (wie z.B. DRAM-Initialisierung) gehört für mich nicht ins OS, sondern in einen hardwareabhängigen Softwarebereich, d.h. die Firmware.Da bleibt ja eigentlich nur die DRAM-Initialisierung, alles andere wird erst später im laufenden OS in Betrieb genommen.
Aber so ein bisschen Recht hast Du schon, wenn ich die Möglichkeit habe in dem Flash mehrere Images zu haben dann könnte da auch eines mit system-spezifischen Funktionen sein. Das heißt das ich eine Art Firmware hätte die nicht per INT o.ä. benutzt wird sondern eine Tabelle mit den unterstützen Funktionen und den zugehörigen Einsprungspunkten am Anfang bietet. Da könnten z.B. Funktionen wie RAM-Größe ermitteln oder RAM initialisieren oder eventuell auch Anzahl der CPUs ermitteln und CPU in Betrieb nehmen drin sein.Du betrachtest also dein gesamtes Betriebssystem wie bei x86 das BIOS. Finde ich unpraktisch. Ich würde im Flash eine Firmware bauen, der hardwarespezifisch ist und DRAM, Console und Bootmedium initialisiert, dann eventuell eine Auswahl erlaubt. Das Bootmedium kann ja dein OS im gleichen Flash sein, das schließt sich nicht aus. Außerdem übergibt diese Firmware an dein OS die elementaren Informationen wie RAM-Größe, Adressräume und so weiter.
Ja, dank des kleinen Teams muss doch eine sinnvolle, gemeinsame, Zusammenarbeit möglich sein! ;-) Oder lebt ihr in verschiedenen Staaten ohne Internet?Der Systemdesigner ist der CPU-Designer, also du. Mach es dir doch einfach. ;-)Die Personalunion ist einfach ein Zwang der Umstände, mir hilft ja keiner. ;) Und wenn ich drauf aus wäre es mir einfach zu machen wäre ich nicht intelligent geworden! ;)
Mit HyperTransport hab ich mich mal am Rande befasst, aber da gibt es natürlich keinen klassischen Bus mehr. Okay.Du kannst auch einfach sagen, dass die Startadresse auf dem Datenbus liegen mussIch habe keinen Datenbus der alle CPUs verbindet, ich möchte Punkt-zu-Punkt-Verbindungen benutzen und dafür die tollen GBit-SERDES der aktuellen FPGAs benutzen. Ich muss das also eh alles mit Messages machen. Ich kann Dir nur empfehlen mal die Spezifikationen zu HyperTransport und PCI-Express zu lesen, die bauen beide auf die selben PCI-Konzepten (Management, Config-Space usw.) auf auch wenn die Realisierungen sich in vielen Details doch extrem unterscheiden (auch gegenüber dem klassischen parallelen Original).
Mir fallen da spontan noch die Bootmedien ein. Schließlich will man nicht immer ein vorgefertigtes OS booten können (was, wenn man es kaputtkonfiguriert hat?), sondern auch mal von so Dingen wie USB-Festplatten, Speicherkarten, TFTP-Server und so. Wenn du deinen Flashbaustein nur als "ein weiteres Bootmedium" betrachtest, dann hast du die Trennung von Firmware wieder drin.So lange ich nur mein eines OS habe bin ich der Meinung das ich keine Firmware benötige aber für später hast Du grundsätzlich recht. Das Problem ist das die Firmware fast alles machen muss was das OS auch tut, also DRAM anschalten, PCI-Devices traversieren und mit Ressourcen versorgen (zumindest die nützlichen) und dann auch einige Devices in Betrieb nehmen (wie z.B. AHCI-SATA-Controller oder USB-Controller). Für den ersten Moment sehe ich da vor allem doppelte Arbeit (oder zumindest einen Mehraufwand) weil die selbe Funktionalität ja mehrfach vorhanden sein muss (sicher kann man in vielen Fällen auf den selben Quell-Code zurückgreifen). Diese zusätzliche Arbeit bringt mir aber im ersten Schritt keinen Nutzwert sondern verlängert nur die Boot-Zeit. Wenn ich irgendwann mal soweit bin das ich auch mal ein OS von woanders booten möchte kann ich dann immer noch ein Firmware-Image parallel zu dem festen OS-Image in den Flash packen. Ob ich dann versuche einen vorhandenen Boot-Loader (wie z.B. uboot) zu portieren oder mich am vorhandenen OS-Quell-Code schadlos halte steht noch in den Sternen aber möglich ist es auf jeden Fall. Schön wäre es auch wenn diese Firmware in der Lage ist eine GUI als Auswahlmenü für die vorhandenen Flasch-Images anzubieten.
Du betrachtest also dein gesamtes Betriebssystem wie bei x86 das BIOS.Ja, eine Trennung macht IMHO für den ersten Anlauf absolut keinen Sinn.
Außerdem übergibt diese Firmware an dein OS die elementaren Informationen wie RAM-Größe, Adressräume und so weiter.Genau da sehe ich ein Problem, ich möchte nicht das irgendeine Firmware dem OS sagt wo was ist sondern ich möchte dass das OS selber bestimmen kann wo was hinkommt. Die einzigste Ressource die da ist wenn das OS-Image angesprungen wird ist das Boot-Device selber und alles andere kann das OS so im physischen Adressraum verteilen wie es das möchte. Deshalb ja auch alles als PCI-Device damit das OS einen generischen Mechanismus hat mit dem jeder Ressource (selbst dem normalen RAM) eine Adresse zugewiesen werden kann.
Ja, dank des kleinen Teams muss doch eine sinnvolle, gemeinsame, Zusammenarbeit möglich sein! ;-) Oder lebt ihr in verschiedenen Staaten ohne Internet?Ich denke dieses Team kann recht gut kooperieren und kommunizieren. Daher wurde beschlossen wer für was zuständig ist und einer dieser Beschlüsse lautet das die CPU absolut keine Vorgaben macht was den physischen Adressraum angeht, es gibt also absolut keine hartcodierten Adressen in der CPU. Ich denke dieser Beschluss ist zum Vorteil aller beteiligten Entwickler. ;)
Das Problem ist das die Firmware fast alles machen muss was das OS auch tut, also DRAM anschalten, PCI-Devices traversieren und mit Ressourcen versorgen (zumindest die nützlichen) und dann auch einige Devices in Betrieb nehmen (wie z.B. AHCI-SATA-Controller oder USB-Controller).Im einfachsten Fall wird der DRAM eingeschaltet und von einem fest vorgegebenen Bootdevice gestartet, welches von der Firmware aktiviert werden muss. Das OS sollte niemals den DRAM einschalten müssen - das wurde von der Firmware getan; keine doppelte Arbeit. Und das Bootdevice zu aktivieren muss ohnehin getan werden; wenn die Firmware im gleichen Flash liegt wie das OS-Image, dann ist das nichtmal nötig. Auch keine doppelte Arbeit. ;-)
Für den ersten Moment sehe ich da vor allem doppelte Arbeit (oder zumindest einen Mehraufwand) weil die selbe Funktionalität ja mehrfach vorhanden sein muss (sicher kann man in vielen Fällen auf den selben Quell-Code zurückgreifen). Diese zusätzliche Arbeit bringt mir aber im ersten Schritt keinen Nutzwert sondern verlängert nur die Boot-Zeit.Ob nun eine Firmware den RAM aktiviert und dann ein OS mit Informationen darüber startet oder ob die Firmware "Code" ausführt, der den RAM aktiviert, Informationen darüber rauspult und dann zu einem OS mutiert, finde ich nicht als Unterschied.
Die Firmware gibt dem OS nicht vor, wo die OS-Bestandteile hingehören, sondern die Firmware verrät dem OS, wieviel RAM vorhanden ist und wo er sich (physisch) befindet. Mal abgesehen ist es nicht "irgendeine" Firmware, sondern genau die Firmware, die als einzige genaue Informationen über die Hardware weiß.Außerdem übergibt diese Firmware an dein OS die elementaren Informationen wie RAM-Größe, Adressräume und so weiter.Genau da sehe ich ein Problem, ich möchte nicht das irgendeine Firmware dem OS sagt wo was ist sondern ich möchte dass das OS selber bestimmen kann wo was hinkommt.
Daher wurde beschlossen wer für was zuständig ist und einer dieser Beschlüsse lautet das die CPU absolut keine Vorgaben macht was den physischen Adressraum angeht, es gibt also absolut keine hartcodierten Adressen in der CPU. Ich denke dieser Beschluss ist zum Vorteil aller beteiligten Entwickler. ;)CPU != Chipsatz != OS.
Das OS sollte niemals den DRAM einschalten müssen - das wurde von der Firmware getanIch möchte aber nicht das jemand anderes als das OS den RAM einschaltet und damit seine physische Adresse festlegt. Da ich im Kernel-Mode per Default keinen virtuellen Speicher hab arbeiten bei meinem OS alle Exception/Syscall/IRQ-Handler (also der gesamte Kernel-Code) direkt mit den physischen Adressen, deswegen sollte das OS da auch die Kontrolle darüber haben wo der RAM liegt. Ich weiß das ist eine merkwürdige Design-Entscheidung aber so ist es nun mal. Bei klassischen Flat-Memory-Systemen liegt der Kernel zur Laufzeit immer an der selben virtuellen Adresse egal was das für ein Rechner ist und egal wie viel RAM dort drin steckt und egal wie der RAM im physischen Adressraum verteilt ist. Diesen Luxus habe ich nicht.
keine doppelte Arbeit. ;-)Es geht mir weniger darum das ich eventuell 2 mal Code für die selbe Sache erstellen muss sondern darum das ich 2 unabhängige Images erstellen müsste. Außerdem bräuchte ich dann auch 2 verschiedene Aufrufkonventionen, da das Firmware-Image ja nur mit funktionierenden Stack aber ohne irgendeine aktivierte HW aufgerufen wird müsste man das OS-Image mit anderen Informationen versorgen da dort ja zumindest schon mal der RAM aktiv ist. Das ist sicher alles nicht so viel Arbeit aber ich habe im ersten Anlauf davon definitiv keinen Nutzen also kann ich auch erst mal alles an einem Stück lassen. Wenn ich später z.B. mal 2 verschiedene HW-Revisionen unterstützen möchte und dafür eine Art BIOS mit system-spezifischen Funktionen (wie RAM aktivieren) anbieten möchte kann ich mir immer noch ein geeignetes Konzept überlegen und aus dem OS-Image etwas Code rauswerfen und dafür BIOS-Calls o.ä. einbauen.
Fass den Begriff "Firmware" nicht zu weitgreifend auf. Unter Firmware verstehe ich den Code, der die Initialisierung der Hardware in einen Zustand, in dem Code ohne tiefgreifende Kenntnis der Hardware ausgeführt werden kann, vornimmt. Mehr nicht. Alles, was darüber geht (also TUI/GUI, Bootmedienauswahl, ...) fällt für mich unter den Begriff BIOS.Okay, dann haben wir gestern ein ganz klein wenig aneinander vorbei geschrieben. Im Endeffekt sind das aber alles nur Namen, was für mich erst mal wichtig ist sind die Funktionalitäten und die will ich erst mal ein einem Stück zusammenhalten. Dann habe ich halt ein OS-Image das ein klein wenig Firmware-Code, etwas Kernel-Geburtshelfer-Code und einen gepackten Kernel plus gepackter Personality enthält. Für den ersten Anlauf ist das sicher komplex genug da möchte ich mir nicht auch noch Gedanken darüber machen wie ich das über mehrere Images verteile und wie die sich gegenseitig benutzen.
Mal abgesehen ist es nicht "irgendeine" Firmware, sondern genau die Firmware, die als einzige genaue Informationen über die Hardware weiß.Welche Informationen denn? Aus meiner Sicht ist der physische Adressraum absolut leer, vom Boot-Device einmal abgesehen, wenn das OS-Image gestartet wird. Selbst wenn der Kernel aktiviert wurde und das OS zum ersten mal 100% einsatzbereit ist ist da nur der RAM hinzugekommen. Alle anderen HW-Komponenten egal ob im Chipsatz integriert, als On-Board-Chip oder als Einsteckkarte sind zu diesem Zeitpunkt noch im Reset. Mit Ausnahme von der Methode mit der der RAM aktiviert werden muss (was ja von der konkreten HW abhängt) gibt es da IMHO keine Information die die Firmware haben könnte. Beim x86-PC wo das BIOS schon alles mögliche gemacht hat bevor auch nur mal ein Boot-Sektor von irgendeinem Massenspeicher geladen wird (und danach noch Unmengen an Services anbietet) ist das natürlich was völlig anderes. Wenn ich irgendwann später wirklich mal eine Art BIOS programmieren will das dann Dinge wie booten von TFTP oder USB-Stick anbieten soll dann muss dieses BIOS eben ein OS-Image von irgendwo laden und anschließend alle HW-Komponenten wieder deaktivieren (das entspricht auch dem Konzept von uboot) und danach dann dieses OS-Image anspringen. Auch dieses OS-Image würde das System so vorfinden wie ein OS-Image aus dem Flash. Wohin dieses Image dafür geladen werden müsste muss ich mir mal noch in Ruhe überlegen, da das Boot-Device nebem ROM, FRAM und Flash ja eh auch normalen RAM anbieten muss könnte ich auch einfach einen kleinen DRAM-Chip anstatt SRAM benutzen und einen simplen DRAM-Controller integrieren der nicht konfiguriert werden muss. Dann hätte ich genug RAM-Platz um so ein externes OS-Image auch im Boot-Device liegen zu haben.
Theoretisch sollte ein OS einigermaßen agnostisch gegenüber der Plattform sein.Ein sehr guter Standpunkt, aber nur wenn es sich nur um schwachen Agnostizismus handelt (ich hab extra mal bei Wikipedia nachgelesen). Auch ein weiterer Grund warum ich jede Ressource als PCI-Device haben möchte, so braucht das OS (bzw. die Personality) am Anfang nichts zu wissen und kann trotzdem alles aus eigener Kraft in Erfahrung bringen.
oder möchtest du die Position des RAMs bei jedem Neustart von einem Zufallsgenerator auswürfeln?Warum nicht? Das wäre doch mal was wirklich neues wenn ASLR sogar so weit geht.
Im Betrieb wirst du die physische Basisadresse des Speichers auch nicht ändern wollen.Warum nicht? Vielleicht möchte ich ja auch beim RAM Hot-Pluging unterstützen.
Lass doch die Firmware einen vernünftigen Zustand herstellen, den das OS ohnehin herstellen würde.Definiere Bitte "vernünftiger Zustand". Dazu müsste die Firmware aber Informationen über das OS haben und genau das will ich nicht. Es ist auf jeden Fall sinnvoll wenn die Firmware nötige Werkzeuge anbietet aber die führende Hand bleibt beim OS.
CPU != Chipsatz != OS.Ganz genau, Teile und Herrsche! Jedes Teil hat ganz genau definierte Zuständigkeiten. CPU und Chipsatz bieten die Mittel und das OS hat das Konzept wie diese Mittel zu benutzen sind.
Meine Empfehlung wäre, diese Vorgabe dort zu machen, wo sie hingehört. Die Firmware wird mit dem Chipsatz getauscht, CPU und OS eher nicht. Also gehört genau sowas in die Firmware und nicht in die CPU und nicht in das OS.Warum soll die Firmware Entscheidungen treffen mit deren Konsequenzen dann das OS leben muss? Nein, das finde ich nicht gut. Die Entscheidungen soll genau der treffen der auch die Konsequenzen tragen muss!
Gibt es irgendwo mal ne Logikschaltung für ne CPU?Auf http://opencores.org/projects (http://opencores.org/projects) gibt es den Unterpunkt "Processor", da sind von ganz Klein bis Mittelgroß ne Menge unterschiedlicher CPUs drin. Wenn Du mal was richtig Großes sehen willst dann musst Du bei SUN nach dem Quell-Code der letzten SPARC-CPUs schauen (da fällt mir jetzt leider nicht der Name ein). Für all das muss man aber VHDL oder Verilog können.
Im speziellen hab ich Interesse daran wie so ein Instructiondecode vor sich geht - und wie gesagt "hardwired"So ein Decoder ist quasi ein switch-case in Transistoren gegossen. Oft generiert man einfach entsprechend viele Signale von denen dann nur eines auf TRUE stehen darf und welches die entsprechende Verarbeitungsschaltung aktiviert. Bei einer CISC-CPU wo die Befehle aus unterschiedlich langen Byte-Sequenzen bestehen können muss davor noch eine State-Maschine kommen die so lange alle Bytes einließt bis der Befehl komplett ist, gerade bei x86 mit den vielen optionalen Präfixen und Adressierungsmodi ist das eine ziemlich komplexe Angelegenheit. Ein kleiner RISC, wie z.B. MIPS, hat einen signifikant einfacheren Decoder der wirklich nur aus einem simplen Demultiplexer (http://de.wikipedia.org/wiki/Demultiplexer#Digitale_Demultiplexer) besteht.
OK, bin jetzt vom Idealfall ausgegangen :DAuch dann wird für ein Objekt mit 21 Bytes sicher mehr als das benötigt, also Verwaltungsinformationen kommen in jedem Fall dazu.
Dabei habe ich mich hauptsächlich mit einem Problem beschäftigt, das bei RISC CPU's auftritt, nämlich die Befehlslänge.Ja, das ist wohl wahr. Aber es gibt auch interessante Lösungen dafür, schau Dir mal den Itanium an. Auch das Problem mit der bedingten Ausführung ist dort auf sehr interessante Art und Weise gelöst.
(Auch wenn die z.B. bei ARM vorgestellten Möglichkeiten schon gut sind.)Ja, der Weg von ARM ist interessant aber er hat auch einen eklatanten Nachteil, er erfordert das der Code immer auch lesbar ist (es werden ja ganz normale Speicherzugriffe nur eben mit IP-relativer Adressierung benutzt). Aus Gründen der Sicherheit ist es aber besser wenn Code nicht lesbar sondern nur ausführbar ist.
Hallo,Naja, ganz grob entspricht das meiner Möglickeit 2. Allerdings halte ich so einen Itanium-Befehlssatz für überflüssig, weil diese Art von Parallelität nur selten vorkommt (entweder will man SIMD oder MISD.) Für SIMD ist der Befehlssatz zu mächig und damit zu teuer (Man nutzt die Möglichkeiten nicht richtig aus). MISD lässt sich, da Abhängigkeiten zwischen den Paralellbefehlen verbotenen sind, nicht schnelle als bei einem konservativen Befehlssatz lösen. (Ein MISD Prozessor wäre auch mal interessant.)Dabei habe ich mich hauptsächlich mit einem Problem beschäftigt, das bei RISC CPU's auftritt, nämlich die Befehlslänge.Ja, das ist wohl wahr. Aber es gibt auch interessante Lösungen dafür, schau Dir mal den Itanium an. Auch das Problem mit der bedingten Ausführung ist dort auf sehr interessante Art und Weise gelöst.
(eigentlich könnte hier meine Antwort schon zu Ende sein aber ich hoffe man nimmt es mir bei diesem Thema nicht übel wenn ich das doch mit ein paar Buchstaben mehr erkläre)[...]
Das Problem ist, das man dadurch einen großen Teil des möglichen Befehlssatzes blockiert. Bisher hab ich mich außerdem nie mit Befehlen die mit doppelter Datenabhängigkeit funktionieren beschäftigt. Die von dir beschriebene Möglichkeit hatte ich auch schon mal, da sie z.B. bei ARM schon vorbereitet wurde. Die never Kondition entspricht quasi deinem nop. Ein weiterer Nachteil deines Vorschlags ist, das man spezielle Befehle hierfür benötigt. Möglichkeit 3 zeichnete sich ja gerade dadurch aus, das man die Immideate wie ein Register nutzten konnte, weil die Daten allein über einen Registerzugriff aktiviert werden. Bei Dissassemblen hab ich eigentlich keine Schwierigkeiten. Der Dissassembler dekodiert dann halt so was wie add r1, r2, nir(0xABCDEF01) und setzt den abhängigen Befehl in Klammern, oder so.(Auch wenn die z.B. bei ARM vorgestellten Möglichkeiten schon gut sind.)Ja, der Weg von ARM ist interessant aber er hat auch einen eklatanten Nachteil, er erfordert das der Code immer auch lesbar ist (es werden ja ganz normale Speicherzugriffe nur eben mit IP-relativer Adressierung benutzt). Aus Gründen der Sicherheit ist es aber besser wenn Code nicht lesbar sondern nur ausführbar ist.
Ansonsten gibt es für Deine Version mit den 32Bit Befehlen noch eine Möglichkeit 4:
Um ein NOP zu kodieren sollten nicht mehr als 8 Bit nötig sein so das in jedem NOP immer 24 Bit an Daten enthalten sein können. Wenn Du nun einen Load-Immediate-Befehl baust der selber noch 16 Bits Daten enthält (die restlichen 16 Bits sollten doch reichen um den Befehl ansich und das Zielregister zu kodieren) dann könnte der im Decoder um 2 Takte verzögert werden (es käme eben für 2 Takte kein Befehl aus dem Decoder raus) und der Decoder würde intern aus diesem Befehl und den 2 folgenden NOPs insgesamt einen Load-Immediate mit vollen 64Bit Nutzdaten bauen. (wenn einer der beiden folgenden Befehle kein NOP ist gibt es eben eine Illegal-OpCode-Exception) Das hätte auch den Vorteil das nichts schlimmes passiert wenn mal einer der beiden Daten-NOPs angesprungen wird und auch der Disassembler (im Debugger) hätte keine Probleme was sinnvolles anzuzeigen. Ein weiterer Vorteil ist das diese Variante allein im Decoder passiert und Dein eigentlicher CPU-Kern davon nicht betroffen ist, spätestens wenn Du irgendwann doch mal das simple In-Order-Pipeline-Konzept verlassen möchtest wird es Dir sehr entgegen kommen Dich im CPU-Kern auf die eigentliche Arbeit konzentrieren zu können.
Das mit dem Skip-Befehl ist keine so schlechte Idee aber auch hier solltest Du das gleich im Decoder lösen und dem CPU-Kern nur Befehle übergeben bei denen die Ausführungsbedingung integraler Bestandteil ist. Es sollte auch möglich sein das sich dieses Condition-Präfix mit jedem Befehl kombinieren lässt, damit lassen sich wieder viele Sprünge vermeiden und das bringt einiges an Performance und spart oft auch Code. [...]Das mit der universellen Zuordnung hatte ich eh so vorgesehen (es hieß ja nicht skip next branch). Überhaupt hab ich mir überlegt, ob ich nicht zwei Dekodierstufen einfügen soll. Das macht die Sache einfacher.
... solltest Du Dir mal anschauen wie das bei Alpha gelöst ist.[...]Die Alpha Methode wäre natürlich auch ne Möglichkeit, allerdings ist die Nutzung eines beliebigen Register natürlich mit viel notwendiger Information verbunden. Dafür ist diese Möglichkeit ziemlich nah an dem System, das auch viele Programmiersprachen anwenden. (von FreeBasic weiß ich, das ein if ausgeführt wir, wenn der Ausdruck ungleich null ist. In C ist es glaub ich genauso.)
Auch ich habe mir über die beiden Punkte (Befehlsgröße und bedingte Ausführung) einige Gedanken gemacht und (wie ich meine) auch ein paar interessante Lösungsmöglichkeiten gefunden, das ist ganz am Anfang dieses Threads recht gut beschrieben.Du weißt aber schon, das du den Decoder damit ziemlich auf blähst, da du eigentlich 3 Instructionsets verwendest. Ich würde den Dekoder in verschiedene Teile aufteilen. Du brauchtest also 1 Meta-Decoder (Trennt das Pakete auf) + 2 60bit-Decoder + 3 42bit-Decoder (39bit+3bit Condition) + 6 20bit Decoder = 12 Teildecoder. Da eigentlich alle Teildecoder das gleiche machen, hättest du ziemlich viele Einzellschaltungen. (zum Vergleich ARM brauch für seine Schaltung nur ein ARM und einen Thumb decoder.) Ich würde mir deshalb überlegen, ob du nicht die kurze Befehle auf den langen Abbilden kannst, z.B. indem du konstante Bits einfügst. Außdem würde ich mich fragen, ob du wirklich 6 20bit Anweisungen simultan ausführen musst, oder ob es reicht, immer nur 3 Befehle gleichzeitig zu bearbeiten und die Pipline entsprechend zu modifizieren, oder die Piplinegeschwindigkeit zu halbieren.
Das Problem ist, das man dadurch einen großen Teil des möglichen Befehlssatzes blockiert.Hä, was meinst Du? Denkst Du das ein NOP mit nur 8 Bits zu kodieren Verschwendung ist?
Möglichkeit 3 zeichnete sich ja gerade dadurch aus, das man die Immideate wie ein Register nutzten konnte, weil die Daten allein über einen Registerzugriff aktiviert werden.Ja, aber wenn Du das erst bei der tatsächlichen Ausführung der Befehle machst verhinderst Du damit effektiv jegliche Form von Instruction-Level-Parallelism weil Du den (über)nächsten Befehl erst dann dekodieren kannst wenn der aktuelle Befehl abgearbeitet wird. Ich kann Dir nur dringlichst empfehlen zu versuchen dafür eine Lösung zu finden die nur im Decoder alleine passiert damit Du Dir später nichts verbaust.
Die Alpha Methode wäre natürlich auch ne Möglichkeit, allerdings ist die Nutzung eines beliebigen Register natürlich mit viel notwendiger Information verbunden.Ja, es wird für die Bedingung nicht nur eine Register-Nummer (die bei Alpha 5 Bit groß ist) benötigt sondern auch die Bedingung als solches (da gibt es auch > 0 oder <= 0 jeweils nach signed und unsigned getrennt, wimre gibt es das bei Alpha auch für Floating-Point) und deswegen gibt es bei Alpha wohl nur bedingte Sprünge und sonst nichts. Wenn man diese Bedingung aber in ein eigenständiges Präfix auslagert, das sich mit jedem Befehl kombinieren lässt, dann hat man ja eigentlich auch genügend Bits zur Verfügung um hier auf möglichst große Flexibilität zu setzen. Wenn das Präfix 32 Bits groß ist könnte man sogar die eigentliche Vergleichsoperation (SUB oder AND) und 2 Register mit hinein kodieren. Aber das bläht die interne Befehlsdarstellung ziemlich auf. Ich benötige 7 Bits für die Bedingung (2 Bits für das benutze Flag-Set und 5 Bits für die eigentliche Bedingung, da sind zum einen die 14 Klassiker und noch einige Floating-Point-Bedingungen) und das auch hinter dem Decoder bei der internen Befehlsdarstellung, was IMHO einen brauchbar guten Kompromiss darstellt.
Naja wenn ich bedenke, das ich nop ohnehin schon indirekt kodiert mit so befehlen wie add 0reg, 0reg, 0reg. Halte ich es für nicht so praktisch wenn ich ein 256tel der möglichen Instructionswörter für nop's hergebe und dann für viele, zusätzliche Befehle, die dann auch noch Platz für die restlichen Bits reservieren müssen, find ich Problematisch. (Natürlich könnt man einen Bareal Shifter verwende und den shift 0 Funktionen, eine Spezialfunktion zuweisen.Das Problem ist, das man dadurch einen großen Teil des möglichen Befehlssatzes blockiert.Hä, was meinst Du? Denkst Du das ein NOP mit nur 8 Bits zu kodieren Verschwendung ist?
Ja, aber wenn Du das erst bei der tatsächlichen Ausführung der Befehle machst verhinderst Du damit effektiv jegliche Form von Instruction-Level-Parallelism weil Du den (über)nächsten Befehl erst dann dekodieren kannst wenn der aktuelle Befehl abgearbeitet wird. Ich kann Dir nur dringlichst empfehlen zu versuchen dafür eine Lösung zu finden die nur im Decoder alleine passiert damit Du Dir später nichts verbaust.Wenn ich mal nur vom Lesezugriff ausgehe, baue ich einfach eine Schaltung ein, die die Verarbeitung die die Verarbeitung des Folgebefehls behindert. Führe ich alle Befehle nacheinander aus, weiß ich zwei Takte bevor ich den Befehl ausführe, ob dieser ausgeführt werden soll. Jetzt baue ich einfach eine Flag ein, die die Ausführung des Befehls unterbindet. Du darfst nicht vergessen, das ich einen Befehl auch dann dekodieren kann, wenn ich ihn gar nicht ausführe. (Die Ausführschaltung ist einfach für einen Takt gesperrt). Umgekehrt weiß ich auch schon nach dem dekodieren, ob ich auf das NIR zugreifen werden, und deshalb den nächsten Befehl nicht ausführen darf.
Ja, es wird für die Bedingung nicht nur eine Register-Nummer (die bei Alpha 5 Bit groß ist) benötigt sondern auch die Bedingung als solches (da gibt es auch > 0 oder <= 0 jeweils nach signed und unsigned getrennt, wimre gibt es das bei Alpha auch für Floating-Point) und deswegen gibt es bei Alpha wohl nur bedingte Sprünge und sonst nichts. Wenn man diese Bedingung aber in ein eigenständiges Präfix auslagert, das sich mit jedem Befehl kombinieren lässt, dann hat man ja eigentlich auch genügend Bits zur Verfügung um hier auf möglichst große Flexibilität zu setzen. Wenn das Präfix 32 Bits groß ist könnte man sogar die eigentliche Vergleichsoperation (SUB oder AND) und 2 Register mit hinein kodieren. Aber das bläht die interne Befehlsdarstellung ziemlich auf. Ich benötige 7 Bits für die Bedingung (2 Bits für das benutze Flag-Set und 5 Bits für die eigentliche Bedingung, da sind zum einen die 14 Klassiker und noch einige Floating-Point-Bedingungen) und das auch hinter dem Decoder bei der internen Befehlsdarstellung, was IMHO einen brauchbar guten Kompromiss darstellt.Naja ich würde ohnehin nur zwei Konditionale Befehle verwenden: Den primitiven Sprung Befehl mit Registerhilfe (oder über NIR auch mit Immidiate.) und halt das Condition Präfix, sowie der evalFLAGS Befehl (setzt ein Register auf 0 oder -1, entsprechend der FLAGS). Da ich mit Nullregister arbeite, sind die bedingungsfindenden Opcodes nicht so speziell.
Das mit dem Fehlen der Division gibt es bei mehren RISC Architekturen.Das hat aber nichts mit RISC oder Microcode zu tun, es geht einfach darum sich die Transistoren für die HW-Divisions-Schaltung einzusparen. Bei den CPUs wo es etwas mehr auf Rechenleistung ankommt (also eben nicht die typischen kleinen Micro-Controller) ist für gewöhnliche ein HW-Divisions-Schaltung drin, auch bei RISC. Die braucht halt einfach nur einige Takte bis das Ergebnis anliegt und solange steht eben die Pipe-Line wenn es keine Out-of-Order-Execution gibt, trotzdem ist diese Zeit immer noch deutlich kürzer als wenn der Compiler eine Division händisch baut (außer natürlich beim Itanium da der spezielle Befehle hat die direkt die einzelnen Iterationsschritte der Division anbieten, was wimre keine andere CPU hat). Auch bei der Verarbeitung von Floating-Point-Zahlen ist bei den meisten CPUs (so sie denn Floating-Point überhaupt können) nicht nur Addition/Subtraktion und Multiplikation in HW verfügbar (üblicherweise benötigen selbst diese 3 Befehle bereits mehrere Takte) sondern oft auch Division und Quadratwurzel (die dann noch mehr Takte benötigen, deswegen sind diese beiden auch beim Itanium nicht vorhanden (ebenso wie die noch komplexeren Operationen wie Logarithmus oder Sinus) sondern nur als Einzelschritte, es gibt beim Itanium also die passenden Rechenwerke aber die State-Machines die das üblicherweise ansteuern fehlen und da muss dann der Compiler nachhelfen).
Naja wenn ich bedenke, das ich nop ohnehin schon indirekt kodiert mit so befehlen wie add 0reg, 0reg, 0reg.Okay, aber so ein Null-Register hab ich nicht und möchte ich eigentlich auch nicht, ich denke der Verlust eines Registers wiegt schwerer als der Verzicht auf ein 256tel des OpCode-Raums.
Wenn ich mal nur vom Lesezugriff ausgehe, baue ich einfach eine Schaltung ein, die die Verarbeitung die die Verarbeitung des Folgebefehls behindert. Führe ich alle Befehle nacheinander aus, weiß ich zwei Takte bevor ich den Befehl ausführe, ob dieser ausgeführt werden soll.Aber was ist wenn Du die Befehl nicht hintereinander sondern gleichzeitig ausführen möchtest? Wäre ja möglich das die alle voneinander unabhängig sind und daher diese Parallelität durchaus erlaubt ist. Ich bleibe vorerst bei der Meinung das Du Dir mit diesem Konzept die nächst höhere Performance-Stufe verbaust oder zumindest erheblich komplizierter machst. IMHO ist es gerade einer der Vorteile wenn man große Befehle hat die komplette Immediates aufnehmen können das der Decoder alle Befehle unabhängig voneinander dekodieren und die Verarbeitungs-Logik diese auch unabhängig voneinander ausführen kann, natürlich soweit keine Datenabhängigkeiten o.ä. vorliegen aber das zu ermitteln ist schon komplex genug so das ich persönlich mir da nicht auch noch so ein NIR aufbürden würde.
sowie der evalFLAGS Befehl (setzt ein Register auf 0 oder -1, entsprechend der FLAGS).Also für so einen Befehl sehe ich keine Verwendungsmöglichkeit. Wenn Du dann dieses Register mit einem folgenden bedingten Befehl prüfen möchtest so könntest Du auch gleich die Flags prüfen und würdest Dir einen Schritt sparen und wenn es Dir um das bedingte Laden von Immediates geht um diese dann einheitlich weiter zu verarbeiten dann benutze doch gleich einen bedingten Load-Immediate-Befehl der die Flags prüft.
Setzten der Flags (Arithmetische Befehle die als Rd das Nullregister verwenden.)Heißt dass das Rechenbefehle die nicht aufs Null-Register schreiben keine Flags ablegen können? Das würde ich als sehr ungeschickt betrachten da es durchaus viele Fälle gibt wo man das Ergebnis haben will und auch die Flags interessieren.
Sprungbefehle fassen hierbei die Aktionen 2-4 oder 3-4 zusammen. Flagorientierte Konditionspräfixe fassen Aktion 2 und 3 zusammen. In beiden Fällen muss der Wahrheitswert dann nicht in notiert werden.Um ehrlich zu sein habe ich das überhaupt nicht verstanden, könntest Du noch mal anders beschreiben wie Du bedingte Befehlsausführung realisieren möchtest?
Im Instruction set, unterscheiden sich evalFLAGS Befehl und Flagorientierte Konditionspräfixe dadurch, dass bei letzteren der Programm Counter als "Evaluationsziel angegeben wird."
Registerorientierte Conditionspräfixe gleichen den 3-4-Sprungbefehlen, nur das der PC als "Sprungziel" angegeben wird.
Beide Präfixe lassen sich deshalb leicht daran erkennen, das sie das PC adressieren.
Naja, so schwer schätz ich den bei 32/64 Registern dann nicht ein. Dafür macht so ein Register viele Sachen einfach einfacher. Man braucht z.B. keine extra Compare Befehle und kann sich auch Sachen wie den mov Befehl sparen. Ohne Null Reg würde der Opcode dann halt einfach "mov anyGPReg1, anyGPReg1" heißen. Ich mein, ich hab mir ja nur ein paar Gedanken gemacht. Noch kein greifbares Design.Naja wenn ich bedenke, das ich nop ohnehin schon indirekt kodiert mit so befehlen wie add 0reg, 0reg, 0reg.Okay, aber so ein Null-Register hab ich nicht und möchte ich eigentlich auch nicht, ich denke der Verlust eines Registers wiegt schwerer als der Verzicht auf ein 256tel des OpCode-Raums.
Wenn ich mal nur vom Lesezugriff ausgehe, baue ich einfach eine Schaltung ein, die die Verarbeitung die die Verarbeitung des Folgebefehls behindert. Führe ich alle Befehle nacheinander aus, weiß ich zwei Takte bevor ich den Befehl ausführe, ob dieser ausgeführt werden soll.Aber was ist wenn Du die Befehl nicht hintereinander sondern gleichzeitig ausführen möchtest? Wäre ja möglich das die alle voneinander unabhängig sind und daher diese Parallelität durchaus erlaubt ist. Ich bleibe vorerst bei der Meinung das Du Dir mit diesem Konzept die nächst höhere Performance-Stufe verbaust oder zumindest erheblich komplizierter machst. IMHO ist es gerade einer der Vorteile wenn man große Befehle hat die komplette Immediates aufnehmen können das der Decoder alle Befehle unabhängig voneinander dekodieren und die Verarbeitungs-Logik diese auch unabhängig voneinander ausführen kann, natürlich soweit keine Datenabhängigkeiten o.ä. vorliegen aber das zu ermitteln ist schon komplex genug so das ich persönlich mir da nicht auch noch so ein NIR aufbürden würde.
Da hab ich mich falsch ausgedrückt. Natürlich kannst du auch mit allen anderen Registern Flags setzten.Setzten der Flags (Arithmetische Befehle die als Rd das Nullregister verwenden.)Heißt dass das Rechenbefehle die nicht aufs Null-Register schreiben keine Flags ablegen können? Das würde ich als sehr ungeschickt betrachten da es durchaus viele Fälle gibt wo man das Ergebnis haben will und auch die Flags interessieren.
Naja, das war eh so ein spontaner Einfall, den ich vermutlich nicht umsetzten würde.Sprungbefehle fassen hierbei die Aktionen 2-4 oder 3-4 zusammen. Flagorientierte Konditionspräfixe fassen Aktion 2 und 3 zusammen. In beiden Fällen muss der Wahrheitswert dann nicht in notiert werden.Um ehrlich zu sein habe ich das überhaupt nicht verstanden, könntest Du noch mal anders beschreiben wie Du bedingte Befehlsausführung realisieren möchtest?
Im Instruction set, unterscheiden sich evalFLAGS Befehl und Flagorientierte Konditionspräfixe dadurch, dass bei letzteren der Programm Counter als "Evaluationsziel angegeben wird."
Registerorientierte Conditionspräfixe gleichen den 3-4-Sprungbefehlen, nur das der PC als "Sprungziel" angegeben wird.
Beide Präfixe lassen sich deshalb leicht daran erkennen, das sie das PC adressieren.
Naja, das fehlende Register kostet sicher etwas Performance wogegen die Art und Weise wie die Befehle codiert sind erst mal keine Performance kostet, solange diese Codierung nicht so ineffizient ist das die Befehle deutlich größer werden.... ich denke der Verlust eines Registers wiegt schwerer als der Verzicht auf ein 256tel des OpCode-Raums.Naja, so schwer schätz ich den bei 32/64 Registern dann nicht ein. Dafür macht so ein Register viele Sachen einfach einfacher.
Man könnte auch unsere beide Stategien kombinieren. Ein Opcodepaket besteht aus meheren Opcodes, die verschiedene Länge annehmen können. Beide Opcodelängen sind identisch, allerdings sind bei den kurzen Opcodes Teile derselbigen abgeschnitten. So enthalten kurze Opcodes zwar vollwertigen Befehl, aber kein Immediate Sektor (der impizit 0 enthält.) Lange Opcodes enthalten eine Immediate, die sich über ein Psydoregister (IR immidete Register, remake von NIR) ansprechen lässt.) Damit würde man variable Opcodelängen ermöglichen und den Decoder einfach halten.Ja, das klingt interessant. Machen Sie es so! ;)
Da hab ich mich falsch ausgedrückt. Natürlich kannst du auch mit allen anderen Registern Flags setzten.Dann hast Du aber bei vielen Befehlen ein extra Bit im OpCode das angibt ob die Flags geschrieben werden sollen oder?
das war eh so ein spontaner EinfallDafür ist dieser Thread ja da. Von dem was ich am Anfang dieses Threads (immerhin vor knapp 2 Jahren) geschrieben habe ist heute auch nicht mehr alles gültig, Konzepte entwickeln sich eben auch mal weiter.
Einen CMP-Befehl habe ich auch nicht (ebenso wie ich keinen TEST-Befehl habe) sondern dafür wird ein ganz normaler SUB-Befehl (oder AND-Befehl) benutzt und das Ergebnis einfach nicht verwendet.Ich hab mir überlegt, das 0reg doch abzuschaffen. Für die nicht vorhandenen TEST/COMP befehle, kann man ja die Daten in das IR schreiben, wo sie quasi weg sind. (Für den Executer ist das IR ein ganz normales Register. Der einzige Unterschied ist, das es extern durch den Pipe Shift geladen werden kann.)
Auf ein MOV würde ich auch grundsätzlich nicht verzichten, klar kann man das mit "ADD RD,RS,R0"Da ich mit IR kein echtes 0-Register mehr habe, brauch ich natürlich auch einen echten MOV Befehl.
Ich denke aber ich werde ehr nicht so viele Befehlslängen anbieten. (Es gibt einen 64 bit Doppelpaket, das entweder einen 60 Bit Befehl, 3 20 Bit Befehle oder nur 1 20 bit Befehl + 40 bit Zusatzinfos enthalten kann. (Die dann die Pipe abkürzen.)Man könnte auch unsere beide Stategien kombinieren. Ein Opcodepaket besteht aus meheren Opcodes, die verschiedene Länge annehmen können. Beide Opcodelängen sind identisch, allerdings sind bei den kurzen Opcodes Teile derselbigen abgeschnitten. So enthalten kurze Opcodes zwar vollwertigen Befehl, aber kein Immediate Sektor (der impizit 0 enthält.) Lange Opcodes enthalten eine Immediate, die sich über ein Psydoregister (IR immidete Register, remake von NIR) ansprechen lässt.) Damit würde man variable Opcodelängen ermöglichen und den Decoder einfach halten.Ja, das klingt interessant. Machen Sie es so! ;)
Ja so umgefähr.Da hab ich mich falsch ausgedrückt. Natürlich kannst du auch mit allen anderen Registern Flags setzten.Dann hast Du aber bei vielen Befehlen ein extra Bit im OpCode das angibt ob die Flags geschrieben werden sollen oder?
Auf der anderen Seite muss ein Schreibzugriff auf den PC natürlich registriert werden.So wie ich mir das denke ist der PC (der bei mir einfach nur R63 ist und damit von jeden Befehl gelesen und beschrieben werden kann) zwar logisch ein normales Register aber nicht physisch. Bei Lesezugriffen auf dieses Register wird auch einfach immer das Offset des entsprechenden Befehls geliefert (es ist gerade bei positionsunabhängigen Code wichtig das der sich auf ein bestimmtes Verhalten beim Lesen des PC verlassen kann) und Schreibzugriffe auf R63 werden nicht vom normalen Register-File entgegengenommen sondern speziell abgefangen (aber außerhalb der ALUs) und dem Dispatcher zugeleitet damit der weiß von wo er als nächstes Befehle holen soll. Für die eigentlichen Ausführungseinheiten ist R63 ein ganz normales Register wie alle anderen auch. Es wird aber trotzdem einige Operationen geben die nicht mit R63 arbeiten können aber die lassen sich alle im Decoder abfangen und machen in der Pipeline keinen Stress.
Meine Pipe sieht wie folgt aus:Also quasi der Klassiker. Aber die erlaubt Dir natürlich keine Parallelität zwischen den einzelnen Befehlen und auch keine Out-of-Order-Exection.
Ich hab da lieber gleich noch ein Bit mehr spendiert und habe dafür 4 unabhängige Flag-Sets, das dürfte gerade bei komplexen if-Abfragen oder auch verschachtelten if-Abfragen und Schleifen usw. einiges bringen.Dann hast Du aber bei vielen Befehlen ein extra Bit im OpCode das angibt ob die Flags geschrieben werden sollen oder?Ja so umgefähr.
Allerdings wollte ich doch eigentlich eine Programmiersprache und keine CPU bauen.Ist doch hoffentlich kein Problem, oder? ;)
Kommt drauf an man könnte auch schreiben:Auf ein MOV würde ich auch grundsätzlich nicht verzichten, klar kann man das mit "ADD RD,RS,R0"Da ich mit IR kein echtes 0-Register mehr habe, brauch ich natürlich auch einen echten MOV Befehl.
http://mycpu.eu/ (http://mycpu.eu/)
Der hat sowas selber gemacht auch mit TTL-Bausteinen. Ist aber glaube ich ein CISC.