Beiträge anzeigen

Diese Sektion erlaubt es dir alle Beiträge dieses Mitglieds zu sehen. Beachte, dass du nur solche Beiträge sehen kannst, zu denen du auch Zugriffsrechte hast.


Themen - erik.vikinger

Seiten: [1] 2
1
Offtopic / neue? Automatismen im Forum?
« am: 12. November 2011, 11:58 »
Hallo,


seit einiger Zeit ist mir, immer wenn ich hier im Forum angemeldet bin, etwas merkwürdiges aufgefallen (siehe angehängten Screen-Shot): Svenska wird fett geschrieben (falls er ebenfalls gerade angemeldet ist).
Nach ein wenig suchen hab ich festgestellt das Svenska seit einiger Zeit mein "Freund" ist (auch das ist im Screen-Shot erkennbar, ist mir aber nicht sofort aufgefallen). Grundsätzlich hab ich damit keine Probleme, so extrem unsympathisch das ich ihn von dieser Freundes-Liste sofort wieder löschen müsste ist er mir dann doch nicht, aber ich wüste gerne wieso er dort gelandet ist. Da ich mir ziemlich sicher bin das nicht selber von Hand gemacht zu haben und ich mir auch recht sicher bin das ich Svenska kurz nach der Umstellung der Forum-SW zum ersten mal in fett gesehen hab habe ich den Verdacht das es sich dabei um einen neuen(?) Automatismus seitens der Forum-SW handelt, vermutlich weil ich mit Svenska ein paar mal PM's ausgetauscht habe.
Kann das jemand bestätigen oder hat jemand ähnliche Beobachtungen gemacht?


Grüße
Erik
2
Offtopic / meine Paging-Ideen
« am: 31. July 2011, 19:39 »
Hallo,


da ich darum gebeten wurde (und heute etwas Langeweile hatte) schreibe ich hier mal etwas über die Art und Weise wie ich auf meiner Plattform Paging realisieren und benutzen möchte:

Die nötigen Descriptoren findet man da http://www.megaupload.com/?d=IASLIBGX (Stand 2011-07-31).
Die Paging-Directory-Ebenen hab ich von "Root" angefangen mit L0 bis L? durchnummeriert. Paging-Directory-Tables sind immer genau eine Page groß und müssen auch entsprechend ausgerichtet sein.

Paging möchte ich vor allem für 2 Dinge benutzen: einmal um die Segmente im Hintergrund defragmentieren zu können (ohne Paging geht das zwar auch aber ich müsste dafür das ganze System einfrieren und das wäre insbesondere bei großen Segmenten eher doof) und dann noch zum Swappen (das hat bei mir zwar erst mal keine hohe Priorität aber es muss trotzdem von der Plattform angemessen unterstützt werden und Swappen in ganzen Segmenten ist auch eher doof). Paging ist für meine Plattform also nicht unbedingt zwangsweise nötig aber es macht das Leben an ein paar Stellen doch etwas leichter so das ich es dann doch als integralen Bestandteil meiner Plattform betrachte. Ich versuche von Segmentierung und Paging jeweils die Vorteile zu übernehmen aber ohne mir die Nachteile mit aufzuhalsen (zumindest nicht in vollem Umfang).

Ich möchte meine Plattform in 2 Ausführungen bauen: als 32Bit-System und als 64Bit-System. Beides wird aus dem selben VHDL-Code generiert nur eben mit unterschiedlichen Parametern. Auch der OS-Kernel wird aus dem selben Source kompiliert nur eben mit unterschiedlichen Compiler-Parametern. Daher wird es auch das Paging in verschiedenen Ausführungen geben, darüber hinaus möchte ich mir für die 64Bit-Variante auch die Option offen halten dort verschiedene Paging-Varianten zu implementieren. In der Descriptor-Beschreibung sieht man für die 64Bit-Plattform auch 2 verschiedene Varianten, einmal mit 512kB als kleinste Page-Größe und einem 3-stufigem Paging und zum anderen mit 64kB als kleinste Page-Größe und einem 4-stufigem Paging. In den 64Bit-Varianten sind die Page-Descriptoren jeweils 128Bit groß so das der Page-Table-Index immer um 4 Bit kleiner ist als das Page-Offset: 15-15-15-19 oder 12-12-12-12-16. Das sind die 2 besten Varianten die exakt aufgehen und den vollen 64Bit-Adressraum anbieten können. Sollten die 64kB-Pages immer noch zu groß/grob sein kann ich auch noch ein 10-10-10-10-10-14 Paging mit 16kB-Pages und 5 Stufen machen aber ich denke das wird sich langfristig kaum lohnen. Das Paging auf x86-64 kann nur einen 48Bit-Adressraum anbieten (9-9-9-9-12), da ist das aber auch Okay weil das ja immer nur den virtuellen Adressraum eines einzelnen Prozesses betrifft da bei Flat-Memory ja jeder Prozess seinen eigenen virtuellen Adressraum hat. Auf meiner Plattform gibt es immer insgesamt nur einen virtuellen Adressraum da es systemweit nur ein einziges Paging-Directory geben kann, mein CR3-Äquivalent gilt also nicht nur für eine CPU sondern immer für das gesamte System, inklusive Chipsatz da auch dieser auf das Paging-Directory zugreift, so das es im globalen Bereich der Control-Register liegt.

In allen Segment-Descriptoren ist an Bit 7 ein PE-Bit (Paging-Enable) womit man die Benutzung von Paging pro Segment individuell anschalten kann. Daraus ergibt sich das es auf meiner Plattform quasi 2 unabhängige lineare Adressräume gibt. Einmal ohne Paging wo die lineare Adresse immer 1:1 eine physische Adresse ist und zum anderen mit Paging wo die lineare Addresse auf eine beliebige physische Adresse gemappt werden kann. Das Paging als solches wird immer aktiv sein und es wird auch immer ein halbwegs vollständiges Paging-Directory geben in dem aber alle Einträge auch auf 1:1-Mapping stehen (zumindest in den Bereichen wo auch echter physischer RAM vorhanden ist), nur wird es die meiste Zeit über komplett ignoriert werden. Selbst wenn das Paging von einzelnen Segmenten benutzt wird so trifft die Bremswirkung des Paging auch nur diese Segmente und nicht das gesamte System.


Das Paging für 32Bit sieht ziemlich gewohnt aus (10-10-12 mit 4kB als kleinste Page-Größe) und ist auch im wesentlichen von x86 abgeschaut. Der offensichtlichste Unterschied sind die Flags im Descriptor. Es gibt keine Access-Control-Flags u.ä. da das ja schon von den Segmenten erledigt wird, dafür einen richtigen Typ-Eintrag (der auch stimmen muss sonst gibt es eine Paging-Exception beim Zugriff) und ansonsten nur einen Eintrag für die Page-Größe. Das mit der Page-Größe hab ich mir von ARM abgeschaut und funktioniert ganz einfach: wenn dort drin 0b0010 steht dann bedeutet dass das die Page nicht 4kB sondern 16kB groß ist (der Wert gibt an wie viele Bits von der physischen Page-Adresse ignoriert werden sollen) und das 4 gleiche Einträge hintereinander in der L1-Table liegen müssen (an 16kB ausgerichtet) so das unabhängig davon mit welcher linearen Adresse der Page-Table-Walk sucht (der geht ja erst einmal von einer 4kB-Page aus) immer das korrekte Mapping gefunden wird. Im TLB landet dann ein 16kB-Mapping-Eintrag der nur die Bits 31..14 der physischen Adresse enthält (Bit 13 und 12 vom Descriptor werden ignoriert). Wie viele verschiedene Page-Größen ich unterstützen möchte habe ich noch nicht wirklich entschieden aber jede Zweierpotenz ist wohl doch etwas übertrieben (wenn auch theoretisch möglich). Das Problem ist das umso mehr Page-Größen möglich sind auch der TLB-LookUp aufwendiger wird und das im Endeffekt nur noch zusätzliche Performance kostet anstatt was zu bringen. Der gleiche Mechanismus ist auch bei den Einträgen in der L0-Directory-Table vorgesehen aber ob Pages mit mehr als 4MB auf einer 32Bit-Plattform überhaupt Sinn machen würde ich dann doch bezweifeln, vor allem da Paging ja eh nur die Ausnahme sein soll. Wahrscheinlich werde ich wohl nur 4kB, 16kB und 64kB, zuzüglich 4MB damit ich auch mal eine Ebene sparen kann, unterstützen. Auch sind in den Einträgen keine Statistik-Flags wie Accessed und Modified drin, da ich auf der 32Bit-Version eh nicht vor habe zu swappen (es wird immer genug physischer RAM vorhanden sein) hab ich das dort einfach komplett gelassen.

Das Paging für 64Bit ist schon deutlich komplexer und man sieht auch sofort das die Einträge die doppelte Größe als üblich haben. Der zusätzliche Platz wird für einige interessante Features genutzt. Das erste neue Feature ist "Guarded Pages" (wird da erklärt http://www.technovelty.org/code/guarded-page-table.html und dort noch eine Umsetzung für Alpha os.inf.tu-dresden.de/~schoenbg/papers/gpt.pdf). Das wird gerne bei CPUs benutzt die keinen Page-Walk in Hardware haben sondern wo das die SW erledigen muss (das trifft nicht nur auf viele kleine CPUs wie AVR32, MIPS und die meisten Soft-CPUs zu sondern auch auf viele große CPUs wie Alpha, Sparc und PA-RISC, selbst der Itanium unterstützt software-based Paging optional). Damit kann man den Page-Table-Baum in vielen Fällen um einiges verkleinern und spart sich auch gleich ein paar Speicherzugriffe ein. Falls ich das tatsächlich hinbekomme wäre ich meines Wissens nach der Erste der das in HW realisiert, auch wenn ich davon erst mal nur eine Light-Version realisieren möchte in der nur Directory-Ebenen übersprungen werden können (ich hab das fürs erste nicht für Leaf-Nodes vorgesehen). Das zweite wesentliche Feature sind die Access-Observer-Bytes in den Descriptoren. Das ist mein Äquivalent zu den Accessed/Modified-Bits aus x86. Der entscheidende Punkt ist das es Bytes sind die mit einem simplen Schreibzugriff, im Gegensatz zu einem Read-Modify-Write-Zugriff bei einzelnen Bits, aktualisiert werden können. Dadurch ergeben sich auch weitere Vorteile bei der Statistik, anhand der Werte in diesen Bytes kann man ungefähr ablesen wie lange der letzte entsprechende Zugriff zurück liegt so das man fürs Auslagern eine gute Wahl treffen kann. Gute Page-Ersetzungsstrategien sind keine triviale Angelegenheit (was einem ein Blick in den Source-Code von OSen die sowas haben schnell verdeutlicht) und kosten auch immer etwas an CPU-Leistung. An dieser Stelle möchte ich auch die Unterstützung der Hardware in Anspruch nehmen. Wenn ich eh ein festes PTE-Layout benutzen will dann kann das auch im Chipsatz benutzt werden damit dort eine kleine Komponente in regelmäßigen Abständen den kompletten Page-Directory-Baum durchtraversiert und alle Access-Observer-Bytes in den gültigen Einträgen dekrementiert, dazu nutzt der Chipsatz semaphorische Speicherzugriffe damit ein paralleles Rücksetzen (auf 0xFF) durch eine CPU immer Vorrang hat. Dieses komplette Durchtraversieren kann in regelmäßigen Abständen passieren und kostet natürlich auch ein klein wenig Speicherbandbreite aber zumindest keine CPU-Zeit. Dafür kann der Chipsatz beim traversieren auch eine interne Liste mitpflegen in der die Pages mit den kleinsten Werten in den Access-Observer-Bytes geloggt werden und wenn dann doch mal geswappt werden muss braucht der Kernel einfach nur dort nachsehen und hat sofort einen guten Kandidaten fürs Auslagern. Auch in der 64Bit-Version des Paging möchte ich fexible Page-Größen unterstützen aber auch hier gilt das ich da einen ausgewogenen Kompromiss aus Flexibilität und Hardware-Aufwand (gerade der zusätzliche Zeitaufwand beim TLB-LookUp ist da relevant, wenn das nicht in einem einzelnen Takt passieren kann wäre das echt problematisch) finden muss, also diese Entscheidung wird wohl erst dann endgültig getroffen wenn ich soweit bin und das Synthese-Tool mir sagt was für diese Komponente der maximale Takt ist.

Der Chipsatz soll auf der 32Bit-Plattform und auf der 64Bit-Plattform bei der Verwaltung des Paging-Baums helfend mitarbeiten. Das betrifft auch das Defragmentieren wo der Chipsatz nur eine lineare Adresse mit der Quelle und die physische Adresse des Ziels und die Größe bekommt und dann selbstständig Page für Page umkopiert und immer die PTEs passend mitändert.


Wenn ein Segment vergrößert werden soll aber dahinter etwas anderes im Weg ist dann wird zuerst ein neuer zusammenhängender Platz im linearen (physischen) Adressraum gesucht und dieser Alloziert, danach wird im zugehörigen gepagten Adressraum für den bereits vorhandenen Segment-Bereich ein Mapping auf den alten Speicher eingerichtet (der neue Rest des Segments zeigt bereits auf den neuen Speicher), danach wird die Segment-Basis auf den neuen linearen (noch virtuellen) Speicher umgestellt und gleichzeitig das PE-Bit gesetzt und das Limit passend vergrößert, dann muss nur noch an alle CPUs ein Segment-Shadow-Renew-Kommando für den betreffenden Selector gesendet werden und schon arbeiten alle CPUs mit dem selben Segment aber an einer anderen linearen Adresse und das ohne das die SW das merken würde. Der einzigste Unterschied ist dass das Segment jetzt etwas langsamer ist weil ja nun Paging benutzt wird, durch dieses Paging zeigt der alte Bereich noch auf den alten physischen Speicher und der neue Bereich schon auf den neuen physischen Speicher (1:1). Nun muss nur noch im Hintergrund Page für Page des alten Bereichs umkopiert werden (so das am Schluss alle Pages 1:1 gemappt sind) und wenn das erledigt ist kann das PE-Bit wieder gelöscht und der alte lineare Speicherbereich freigegeben werden (anschließend noch ein Segment-Shadow-Renew-Kommando und schon arbeiten alle CPUs auch in diesem Segment wieder mit voller Performance).

Ein Segment das teilweise Ausgelagert ist liegt nicht mehr in dem linearen Speicherbereich der physischem RAM enthält sondern in einem zusätzlichen Bereich in dem dann immer Paging benutzt werden muss. Es soll auf meiner Plattform auch möglich sein Segmente zu erstellen und zu benutzen die größer sind als überhaupt physischer RAM vorhanden ist (nur die Summe aller Segmentgrößen darf nicht die Summe aus RAM und Swapp-Space übersteigen). Das Mapping für so ein Segment zeigt dann teilweise auf physischen RAM und teilweise nicht (da wird dann einfach der Descriptor-Typ auf einen ungültigen Wert gesetzt und der restliche Inhalt kennzeichnet dann den Ort im Swapp-Space). Im physischen RAM liegen dann etliche Segmente jeweils noch am Stück (für die auch kein Paging benutzt wird) und dazwischen unabhängige Pages von fragmentierten bzw. teilweise ausgelagerten Segmenten. Desto mehr geswappt werden muss desto mehr sieht der physische RAM dann so aus wie in einem klassischen Flat-Memory-Sytem und desto größer ist der Performanceverlust durch das Paging. Wenn das Swapping aber später nicht mehr benötigt wird (also die Summe aller Segmentgrößen kleiner als der physische RAM ist) kann der Kernel die Segmente einfach defragmentieren und sie alle wieder in den normalen linearen Adressraum holen um das Paging jeweils abzuschalten, so dass das System Stück für Stück wieder die volle Performance (ohne dem Paging) bekommt.


Ich hoffe dieser recht kurze Umriss meiner Paging-Ideen/Phantasien ist wenigstens ein wenig interessant. ;)
Zumindest das Attribut "dumm" ist damit jetzt für mein Paging erledigt.


Grüße
Erik
3
OS-Design / Emulation von fork/exec in der libc
« am: 16. July 2011, 10:25 »
Hallo,


zu dem oben genannten Thema ist mir vor kurzen was gutes eingefallen: Ich kann mir vorstellen das es möglich ist das Pärchen fork/exec in der libc recht brauchbar zu emulieren. Der fork-Stub müsste dafür nur eine Kopie des aktuelles Stacks anfertigen und ein Flag setzen, alle weiteren Aufrufe in die libc welche File-Handels u.ä. betreffen müssten dann mit einem zweiten Datensatz bearbeitet werden (deswegen das Flag) und die exec-Funktion müsste dann einen neuen Prozess erzeugen und den geänderten zweiten Handle-Datensatz dem neuen Prozess geben und als letztes im eigenen Prozess den alten Stack restaurieren um damit wieder aus dem fork-Aufruf zurück zu kommen (mit dem passenden Return-Wert für den Eltern-Prozess). Mir ist klar dass das nicht immer funktioniert, spätestens wenn zwischen fork und exec die beiden Prozesse miteinander kommunizieren wollen geht das mit meiner Idee nicht weil der neue Prozess ja erst bei exec erstellt wird und der Code zwischen fork und exec noch im alten Prozess läuft während der eigentliche Ausführungspfad des alten Prozesses noch nicht ausgeführt wird. Das das nur mit Single-Thread-Programmen funktioniert ist auch klar aber wer Threads benutzt benötigt IMHO auch kein fork.

Es ist offensichtlich das dieser dirty Hack in einigen Situationen nicht das tut was unter POSIX passieren würde und es da eine Menge Risiken gibt, z.B. könnten andere statische Variablen zwischen fork und exec manipuliert werden und diese Manipulation wäre dann im Elternprozess sichtbar (sie passiert ja auch noch im Elternprozess) obwohl das eigentlich nicht sein darf. Trotzdem könnte diese Krücke für viele einfache Programme durchaus brauchbar sein. Es würde einem zumindest ersparen beim Portieren eine Menge Code anzufassen obwohl da nur nach klassischer POSIX-Manier neue Prozesse erstellt werden. Ich hab noch nicht nachgesehen aber ich kann mir vorstellen das damit über 90% aller fork-Aufrufe in den Core-Utils gut abgedeckt wären und man sich so eine Menge Portierungsaufwand erspart. Da man diese Funktionalität nur dann in der libc bräuchte wenn es sich um ein POSIX-Programm handelt sollte das ganze eventuell über ein #define zuschaltbar sein bzw. die libc müsste doppelt vorhanden sein damit dann der Loader die jeweils passende benutzen kann.


Ich hoffe ich habe meine Idee verständlich ausgedrückt ansonsten Bitte nachfragen.

Was haltet Ihr von meiner Idee?
Seht Ihr irgendwelche Probleme oder Lücken (aus Sicht der einfachen Standard-Fälle)?


Grüße
Erik
4
Offtopic / Supervisor Mode Execution Protection
« am: 08. June 2011, 11:55 »
Hallo,


aus aktuellem Anlass reaktiviere ich mal diesen Thread, ein eigenes Thema erscheint mir da unnötig.


In der c't kann man folgendes lesen (http://www.heise.de/ct/artikel/Prozessorgefluester-1252938.html) :
Zitat
.... noch Intels neuestes Sicherheitsfeature namens SMEP (Supervisor Mode Execution Protection) beizubringen, das in der nächsten Prozessorgeneration Ivy Bridge eingebaut sein wird. Damit kann man verhindern, dass Code im ungeschützten User-Bereich aus dem Supervisor Mode (Ring 0) heraus ausgeführt wird. Solche Callbacks sind nämlich willkommene Haken für bösartige Software, um sich dort einzuklinken und sich Supervisorrechte zu erschleichen.

Allzu schwierig dürfte so etwas nicht sein, früher einmal hätte man das mit den Segmenten des Protected Mode wohl auch hinbekommen, aber dieser bereits mit dem 286-Prozessor eingefügte Modus ist inzwischen bei fast allen Betriebssystemen im Abfalleimer obsoleter Technologien gelandet. Bezüglich Sicherheit war der Protected Mode dem flachen Adressiermodell mit Paging, das sich nunmehr durchgesetzt hat, deutlich überlegen. Und so muss man wie mit No Execute oder jetzt SMEP eben nachbessern.
Ich bin mir jetzt zwar nicht sicher ob man mit den x86-PM-Segmenten wirklich verhindern kann das Ring-3-Code mit Ring-0-Rechten ausgeführt wird aber das man solche Dinge bei Paging immer erst noch dranflicken muss ist schon wahr. Paging wurde einst zur virtuellen Speichermehrung ersonnen (damals war echter Speicher noch teuer) und nicht zur Speicherverwaltung, für erstes ist Paging auch wirklich hervorragend gut geeignet aber für letzteres eben nicht.


Wollte ich nur mal so loswerden, es muss sich niemand genötigt fühlen hierauf zu antworten.


Grüße
Erik
5
Lowlevel-Coding / Wofür hab Ihr bisher den TSC benutzt?
« am: 09. April 2011, 16:34 »
Hallo,


meine Frage steht ja schon im Betreff. Es geht mir darum mal einen Überblick zu bekommen wofür so alles der TSC (bei x86) benutzt wird.
Dazu dann gleich noch die Frage ob Ihr mit dem TSC auch zufrieden wart oder ob Ihr lieber was anderes benutzt hättet, z.B. den HPET, bzw. welche Ansprüche Ihr an eine gute Zeitquelle stellt.


Mir ist nämlich aufgefallen das ich sowas wie den TSC bei meiner CPU gar nicht habe und wollte einfach mal wissen ob man sowas überhaupt wirklich benötigt oder ob nicht ein guter HPET auch ausreicht. Einen guten HPET möchte ich schon implementieren. Das Problem was ich beim HPET sehe ist das dieser im Chipsatz (oder einer anderen zentralen Stelle) ist und ein Lesezugriff immer locker über 100 CPU-Takte kostet wogegen der RDTSC-Befehl fast gar nichts kostet. Des weiteren ist der HPET eine System-Ressource die gar nicht so einfach von normalen User-Mode-Programmen benutzt werden kann wogegen der TSC eigentlich jedem Programm zur Verfügung steht (ich glaube nicht das es überhaupt ein OS gibt das den RDTSC-Befehl für den User-Mode abschaltet). Zusätzlich hat der TSC eine höhere zeitliche Auflösung wobei man sich da auch fragen muss wozu man irgendetwas genauer als auf 100 ns (was ein HPET mit 10MHz bietet) timen können muss. Der große Nachteil des TSC ist natürlich das er keine Interrupts generiert und das es mehrere unabhängige davon gibt die auch auseinander driften können. Also für echtes Timing ist der TSC damit wohl sowieso nicht qualifiziert oder seit Ihr da anderer Meinung? Wenn ja Warum?


Wie hoch seht Ihr allgemein den Bedarf an einer streng monotonen Zeitquelle in einem OS?
Wie genau sollte die sein?
Sollte sich so eine Zeitquelle von der Sommerzeit/Winterzeit-Umstellung oder von Schaltsekunden beeinflussen lassen?


Grüße
Erik
6
OS-Design / IRQs ganz ohne EOI?
« am: 25. February 2011, 19:40 »
Hallo,


ich bin beim nachdenken über meinen IRQ-Controller auf ein interessantes Problem gestoßen:  benötigt man eigentlich unbedingt einen EOI-Mechanismus?

Beim klassischen PIC (für x86) ist es ja so das wenn der einen Interrupt an eine CPU ausgeliefert hat das er dann erst mal keine weiteren IRQs meldet, auch nicht von anderen Geräten aber gerade letzteres ist ja bei SMP eher doof. Das heißt das mein IRQ-Controller eigentlich nur den gerade ausgelieferten IRQ sperren darf und dieser dann mit seinem individuellen EOI am Ende seines IRQ-Handlers wieder frei gegeben werden müsste. Aber gerade individuelle EOIs kosten wieder zusätzliche Logik im IRQ-Controller und auch ein paar zusätzliche Befehle im Kernel da der ja wissen muss welchen IRQ er da gerade als beendet melden möchte.
Da stellt sich dann aber die Frage:  kommen eigentlich vom selben Gerät weitere IRQs während der IRQ-Handler bereits aktiv ist?
Ganz ausschließen kann man das sicher nicht also muss man das per SW abfangen (wenn man das nicht per HW macht). Da ich die IRQs bei meinem Micro-Kernel-OS eh per asynchronem IPC an die Treiber-Prozesse liefern möchte ist es eigentlich zuverlässig ausgeschlossen das ein und der selbe IRQ-Handler (als PopUp-Thread) mehrfach aufgerufen wird, es wird dann im Kernel gequeued (da eine IRQ-Message ja nur konstant ein paar Bytes benötigt ist das auch kein Speicherproblem). Dafür kann ich mir das EOI-Zeugs komplett sparen und das spart dann bei jedem IRQ etliche Takte ein.

Seht Ihr in meinen Gedanken da irgendwo einen Fehler oder würdet Ihr aus anderen Gründen vom Verzicht auf EOI eher abraten?
Sonstige Anregungen oder Vorschläge dazu?


Grüße
Erik
7
Hallo,


ich habe in den letzten Tagen versucht die Frage aus dem Betreff für mich persönlich zu beantworten und bin dabei leider nicht zu einem ordentlichen/eindeutigen Ergebnis gekommen.

In den entsprechenden Wikipedia-Artikeln (deutsch wie englisch) finden sich teilweise widersprüchliche aber auch teilweise sehr weit gefasste Erklärungen.

Zählt schon ein Mechanismus der einem Treiber die physische Adresse von Geräte-Speicher verrät (so das der Treiber dann direkt mit seiner HW sprechen kann) als HAL oder muss ein richtiger HAL wirklich als "Vermittler" zwischen Treiber und Hardware fungieren (so das der Treiber für jeden HW-Zugriff durch den HAL muss)? Oder zählt eher der OS-Kernel zusammen mit den Treibern als HAL auf dem dann die normalen Programme laufen können ohne sich um HW-Details sorgen zu müssen?


Ich würde dazu gerne mal Eure Meinungen lesen.
Was muss ein HAL Eurer Meinung nach mindestens können damit er diesen Namen verdient?


Grüße
Erik
8
OS-Design / aktivieren des Kernel bei SMP (beim booten)
« am: 23. January 2011, 01:39 »
Hallo,


ich bin gerade auf ein kleines Design-Problem in meinem Boot-Prozess gestoßen: Wie aktiviert man am besten die einzelnen CPUs bei einem SMP-System wenn damit das aktivieren des Kernels verbunden ist?

Meine Situation ist folgende:
Der Kernel liegt im RAM und ist komplett vorbereitet (also alle internen Datenstrukturen sind einsatzbereit und 3 User-Mode-Prozesse sind auch eingerichtet, die Lebendgeburt könnte also jetzt stattfinden) aber die eine aktive CPU (BSP, ist noch im Code vom Boot-Loader) muss nun erst noch die anderen (APs) aktivieren weil der Kernel selber das nicht kann (warum sollte der Code für diese Funktionalität auch Platz im Kernel belegen wenn das doch nur ein einziges mal beim booten gemacht werden muss). Ich hatte erst die Idee alle CPUs der Reihe nach aufzuwecken und alle warten zu lassen bis wirklich alle da sind so das alle CPUs gleichzeitig per IRET in den User-Mode springen (auf jeder CPU läuft dann ein individueller Thread vom idle-Prozess) aber das erscheint mir irgendwie unpraktisch. Ich würde lieber eine CPU nach der anderen Aufwecken und die springen sofort in den User-Mode und von dort dann nach der kurzen initial-Zeitscheibe der idle-Threads zurück in den Scheduler welcher dann den init-Prozess ans laufen bringt. Es kann also passieren das einige CPUs schon voll normalen User-Mode-Code und normalen Kernel-Code (IRQ-Handler usw.) abarbeiten während andere CPUs noch deaktiviert sind oder sich noch im INIT-Mode befinden (und Boot-Loader-Code ausführen). Bei der ersten Möglichkeit würde das nicht passieren weil alle CPUs gleichzeitig mit der richtigen Arbeit anfangen so das mein OS ein einem einzigen Moment zum Leben erwacht und nicht in mehreren kleinen Häppchen. Ich möchte trotzdem lieber die zweite Möglichkeit benutzen, schon weil da schneller angefangen wird das eigentliche System hochzufahren (die APs warten eben nicht bis der BSP alle APs aktiviert hat sondern es gibt sofort Action).
Seht Ihr da irgendein Problem?

Ich hoffe ich habe mich einigermaßen verständlich ausgedrückt (trotz der Uhrzeit). Wenn nicht dann Bitte einfach nachfragen.

Wie gestalten andere eigentlich das Aufwecken der restlichen CPUs? Wimre haben doch hier ein paar Leute SMP ans laufen bekommen.


Grüße
Erik
9
OS-Design / Kompatibilität zwischen 32Bit-OS und 64Bit-OS
« am: 16. November 2010, 20:39 »
Hallo,


falls ihr ein OS entwickelt das unter 64Bit genau so laufen soll wie unter 32Bit (also mit fast identischer Code-Basis und äquivalenter Syscall-API), soll dann das 64Bit-OS auch die 32Bit-Programme ausführen können (wäre bei x86 ja eigentlich möglich) oder verlangt ihr das auch die Programme für 64Bit neu kompiliert werden müssen?

Für ein reines Open-Source-OS ist wohl die zweite Variante die bessere, weil ja wirklich alle Programme als Source-Code vorliegen, aber hat sich schon mal jemand Gedanken darüber gemacht was es bedeuten würde wenn ein unverändertes 32Bit-Programm unter dem 64Bit-OS laufen soll? Also was das OS alles tun muss damit ein 32Bit-Programm so läuft wie unter dem 32Bit-OS (ich meine damit jetzt nicht nur die CPU-spezifischen Sachen).


Grüße
Erik
10
Offtopic / Warum wurde ich im IRC gebannt?
« am: 16. November 2010, 09:03 »
Hallo,


kann mir jemand von Euch sagen warum ich im IRC gebannt wurde?
Die (leicht sarkastische) Frage nach der Überspannung war doch sicherlich nicht der Grund.

Es war mir trotz mehrfachen IP-Wechsel nicht möglich wieder in den IRC zu kommen, ich hab es allerdings auch nicht zu oft probiert.

Kann das ganze irgendeine technische Ursache haben?
Ich benutze Mibbit um am IRC teil zu nehmen, falls das eine Rolle spielt.

Es wäre schön wenn mir jemand diese doch recht unangenehme Situation erklären könnte.


Danke und Grüße
Erik
11
Hallo,


wir haben in der letzten Zeit ja reichlich über die Implementierung von IPC diskutiert aber eine Frage ist da für mich immer noch nicht ganz klar:
Wie findet ein Programm den richtigen Port/Message-Target/sonstwas um darüber die gewünschten Dienste in Anspruch zu nehmen?
In einem Micro-Kernel-OS sind ja VFS, TCP, UDP oder Console getrennte Services da müssen die Programme ja immer den richtigen finden.

In meinem OS möchte ich das so lösen das diese Infos bei der Prozesserstellung immer mitgegeben werden. Die libc kümmert sich darum das diese Infos dem nativen CreateProcess mitgegeben werden und die libc holt diese Infos auch im neuen Prozess aus den Parametern raus bevor main aufgerufen wird. Auf diese Art kann ich wahrscheinlich sogar Ressourcen-Vererbung usw. komplett innerhalb der libc abhandeln ohne das die Services (oder gar der Kernel) davon etwas mitbekommen.

Es wäre aber auch möglich das man eine Liste pflegt (möglicherweise direkt im Kernel) in der alle öffentlich erreichbaren Services drin stehen und die Programme sich anhand eines Kriteriums, z.B. ein Name, den gewünschten Service geben lassen.


Wie löst ihr dieses Problem?


Grüße
Erik
12
Hallo,


xHCI-Spezifikation 1.0 für USB3-Hostcontroller ist endlich öffentlich zugänglich.
http://www.intel.com/technology/usb/xhcispec.htm

Die Unterschiede zur xHCI-Spezifikation 0.96 (nach der alle derzeit erhältlichen USB3-Hostcontroller arbeiten) sollen nur marginal sein aber wir werden ja noch erleben mit welchen unerwarteten Einschränkungen die Käufer der ersten Stunde leben müssen. Wenigstens kann die SW dem xHCI die Version ablesen und dann die Zusammenarbeit verweigern (oder wenigstens vor potentiellen Problemen warnen), sicher haben da die Early-Adopters volles Verständnis für. :(


Über das Kapitel 5.2.6 freue ich persönlich mich ganz besonders, damit ist auf meiner Plattform zumindest USB3 ohne Einschränkungen/Hilfskrücken möglich. :)


Grüße
Erik
13
Offtopic / Spam-Links im Forum
« am: 23. June 2010, 22:47 »
Hallo,


ich habe das Gefühl in diesem Forum könnte ein Spam-Problem entstehen.
Wenn ich mir http://forum.lowlevel.eu/index.php?action=mlist;sort=registered;start=0;desc ansehe dann sind in den User-Profilen ne Menge Potenzmittelchen und anderer Schweinkram verlinkt.
Es wäre sicher gut wenn sich das mal ein Verantwortlicher ansieht.
Ich hab den Eindruck das da jemand das LowLevel-Forum als Link-Farm missbrauchen will.


Grüße
Erik
14
Offtopic / Wie GRUB in eine Partition installieren
« am: 20. May 2010, 20:26 »
Hallo,


da es hier ja so viele GRUB-Experten gibt frage ich einfach mal: wie installiert man zuverlässig den GRUB in eine reiserfs-Partition (nicht in den MBR)?
Mein Problem ist das mein PC nicht mehr will. Ich habe vor 3 Wochen von Kubuntu 9.10 auf 10.04 geupdatet und damit hat das GRUB-Unheil seinen Lauf genommen. Zuerst musste ich auf GRUB legacy zurückwechseln, siehe http://forum.kubuntu-de.org/index.php?topic=13722, und nach einer Woche ohne Probleme wollte plötzlich und gänzlich unerwartet der GRUB legacy auch nicht mehr arbeiten. Er hat zwar das Auswahlmenü angezeigt aber nach dem Betätigen der Enter-Taste startete der Computer einfach so neu. Selbst wenn ich den memtest86 auswähle. Beim Versuch über die Konsole mein Linux manuell zu starten wurde der Computer selbst mit dem Befehl 'initrd' einfach nur neu gestartet. Es war nicht möglich irgendwelche Dateien zu laden. Das automagische Vervollständigen von Datei-Namen per Tab hat aber funktioniert, File-System-Access war also prinzipiell gegeben.

Nach ner Weile sinnlosem rumfummelns hab ich einfach einen etwas älteren GRUB legacy von einer alten Linux-Notfall-CD drüberinstalliert (damit hatte ich 2 Wochen zuvor ja noch Erfolg). Seit dem geht es nur noch bis zur Meldung "Loading Stage 1.5." und dann startet der Computer sofort neu, er ist quasi in einer Endlosschleife gefangen. Bis zur Auswahlliste (oder Notfall-Konsole) geht es nicht mehr, es ist also noch schlimmer geworden. Von CD booten, per BIOS-Auswahl-Menü, geht aber noch. Der MBR ist auch noch völlig intakt. Das Problem muss also beim GRUB liegen.

Ich hoffe mal das ein paar von Euch sich gut genug mit GRUB auskennen um mir sagen zu können wie ich den GRUB legacy zuverlässig, von der CD aus, in meine Linux-Partition installiere. Leider bekomme ich von 'grub-install' keine Erfolgsmeldung mehr aber auch keine Fehlermeldung.


Wenn auch ihr keinen Rat wisst das muss ich wohl das Kubuntu komplett neu installieren, ohne funktionierenden PC geht es jedenfalls nicht mehr (ich vernachlässige sogar schon dieses Forum ;) ).


Grüße
Erik
15
Lowlevel-Coding / Konzept für periodische Events
« am: 22. April 2010, 20:53 »
Hallo,


ich möchte hier mal ein Konzept für periodische Events vorstellen. Dieses habe ich selber schon mal implementiert und war damit recht zufrieden. Falls jemand bessere Vorschläge oder Fragen hat dann sind die hier natürlich gern gesehen.


Für periodisch auszulösende Events benötigt man natürlich einen passenden Timer der genau und monoton läuft. Da der klassische PIT nur mit einer festgelegten Frequenz IRQs generieren kann ohne von der CPU ständig neu konfiguriert zu werden fällt dieser als alleinige Basis aus. Man kann diesen aber auf z.B. 1kHz programmieren und den eigentlichen Zähler und die zugehörigen Comparators in SW implementieren, man verliert damit nicht an Langzeitgenauigkeit sondern bekommt nur einen höheren Jitter und eine kleinere zeitliche Auflösung (z.B. der HPET arbeitet normalerweise mit einigen MHz).

Der Einfachheit halber bleibe ich erst mal bei der PIT-Variante:
Man programmiert ihn eben auf 1kHz und lässt sich so jede ms einen IRQ generieren. Im IRQ-Handler zählt man einen simplen Counter nach oben, dafür sollte man gleich einen 64Bit-Wert nehmen um nicht zu schnell mit Überläufen hantieren zu müssen (welche ein ordentlicher Programmierer natürlich trotzdem berücksichtigt, auch wenn ein 64Bit-Zähler bei 1kHz erst in etwa 600 Millionen Jahren überläuft und der verantwortliche Programmierer bis da hin bestimmt was besseres macht), und schaut nach ob irgendwas gemacht werden muss also ob der aktuelle Zählerwert dem nächsten Event-Zeitpunkt entspricht.
Den Zählerwert verarbeitet man im OS-Kernel am besten so wie er ist als allgemeine absolute Zeitbasis. Wenn nun ein Programm einen periodischen Event beantragt, ich gehe jetzt mal von 64 Hz aus, dann schaut der SYSCALL-Handler nach dem aktuellem Zählerstand und vermerkt diesen als Zeit-Basis, als nächstes wird die nächste absolute Event-Zeit des ersten Events errechnet, in diesem Fall also von X+15.625s, was gerundet dann X+16ms ergibt. Beim Zählerwert X+16 generiert dann der IRQ-Handler einen Event (beim HPET wäre das dann erst ein IRQ, das zählen und vergleichen wird dort in Hardware erledigt) und errechnet vorher noch den nächsten absoluten Zählerwert für den nächsten Event welcher bei X+31.25ms liegt und zu X+32ms gerundet wird. Das Spiel geht dann immer so weiter und man bekommt einen periodischen Event welcher mit exakt 64Hz aber bis zu 1ms Jitter getriggert wird.
Das es wirklich exakt 64 Hz (mit einer durchschnittlichen Periodenlänge von exakt 15.625ms) sind ergibt sich aus folgender Liste:
  • 1. Event:   X+15.625ms wird zu X+16ms : also Periode gegenüber letztem Event : 16ms (okay, das zählt nicht weil es bei der Erstellung keinen Event gab)
  • 2. Event:   X+31.25ms wird zu X+32ms : also Periode gegenüber letztem Event : 16ms
  • 3. Event:   X+46.875ms wird zu X+47ms : also Periode gegenüber letztem Event : 15ms
  • 4. Event:   X+62.5ms wird zu X+63ms : also Periode gegenüber letztem Event : 16ms
  • 5. Event:   X+78.175ms wird zu X+79ms : also Periode gegenüber letztem Event : 16ms
  • 6. Event:   X+93.75ms wird zu X+94ms : also Periode gegenüber letztem Event : 15ms
  • 7. Event:   X+109.375ms wird zu X+110ms : also Periode gegenüber letztem Event : 16ms
  • 8. Event:   X+125.0ms wird zu X+125ms : also Periode gegenüber letztem Event : 15ms
  • 9. Event:   X+140.625ms wird zu X+141ms : also Periode gegenüber letztem Event : 16ms
Wie bestimmt schon aufgefallen ist hat hier jeder 8te Event eine glatte errechnete Zeit, das ist ein guter Zeitpunt um X im Event-Descriptor (die Datenstruktur im OS welche diesen periodischen Event beschreibt) auf X+125 zu setzen damit man auf lange Sicht kein Überlaufproblem mit dem rechnen bekommt. Beim erstellen von Events sollte der OS-Kernel also die gewünschte Frequenz so hinrechnen das eine vertretbare Häufigkeit von solchen Übereinstimmungen entsteht. Also genau 30 Hz geht so nicht, aber ganz sicher werden 29.99999******Hz oder 30.00000******Hz was gutes ergeben. Damit muss die Applikation auf einem Computer der nur mit endlicher Genauigkeit rechnen kann eben leben. Wie man diesen Wert intern darstellt bleibt den Fähigkeiten des OS-Programmierers überlassen, ich würde vorschlagen als Periodenlänge mit zusätzlichen 32 Nachkommabits (damit ergibt sich automatisch eine glatte Übereinstimmung nach gut 4 Milliarden Events), das hat den Vorteil das man nicht mit Gleitkommazahlen hantieren muss (der Grund dieses Vorteils sollte offensichtlich sein).

Die errechneten absoluten Event-Zeitpunkte trägt man in eine geordnete Liste ein welche dann vom IRQ-Handler für die Vergleiche benutzt wird. Damit lassen sich auch beliebig viele solcher periodischen Events (mit unabhängigen Frequenzen) verwalten und auch die einmaligen Events kann man dort problemlos eintragen. Damit das einsortieren möglichst schnell geht habe ich dafür einen Baum benutzt, das Element unten rechts war der nächste Timer der abläuft. Die absoluten Zeiten muss man natürlich in Relation zum aktuellen Timer-Zählerwert als quasi Vorzeichenbehaftet betrachten. Hinter der ganzen Mathematick stecken an einigen Stellen ein paar Tricks aber da ist nichts wovor ein geübter Programmierer Angst haben muss.

Wenn man dazu den HPET verwendet, was auf jeden Fall zu empfehlen ist, dann bekommt man nicht ständig IRQs sondern nur dann wenn wieder ein Event dran ist, dazu trägt der IRQ-Handler immer die nächste Event-Zeit in das/die Comparator-Register des HPET ein. Hier muss man beachten das man beim Eintragen prüfen sollte ob der gewünschte Zeitpunkt für den Counter noch in der Zukunft liegt und nicht zwischenzeitlich schon überzählt wurde (der Comparator im HPET prüft nur auf Gleichheit, in SW kann man das etwas besser lösen), sonst wartet man bis der Counter im HPET ein mal komplett umgelaufen ist (was bei einem 64Bit Counter selbst mit extrem hoher Frequenz sehr lange dauern kann). Der HPET-IRQ-Handler muss also möglicherweise mehrere Event auslösen (wenn deren Zeitpunkte dicht bei einander liegen).


So, ich hoffe ich hab nichts wichtiges vergessen.
Falls jemand Fragen hat dann nur zu.


Grüße
Erik
16
Offtopic / Anregungen für IRC
« am: 21. March 2010, 12:32 »
Hallo,


ich hab mal ein paar "Verbesserungs"-Vorschläge für die IRC-Verwaltung.
Da man immer nie weiß wann was los ist wäre eine kleine History nützlich, welche auf die 7 Wochentage und 24 Stunden verteilt eine Nutzungsintensität (Anzahl Posts in der betreffenden Stunde) widerspiegelt. Sowas ähnliches wie die "Aktivität nach Zeit" in den "Benutzerstatistiken" hier im Forum, nur für die 7 Wochentage getrennt. Auf http://irc.tyndur.org/ läuft doch eh ein komplettes Log, das könnte man doch zu diesem Zweck auswerten. Zur besseren Aussagekraft sollte diese Statistik 2 mal da sein, einmal für die letzten 7 Tage und einmal als Mittel über die letzten 10 Wochen (natürlich auch für die 7 Wochentage getrennt).

Was haltet Ihr von diesem Vorschlag?

Eine Auswertung nach Nutzer-Namen o.ä. will ich nicht, eine Stasi 2.0 braucht wohl keiner von uns.
Aber eine Auswertung nach Nützlichkeit bzw. Dummschwätzigkeit der Posts wäre schon was sehr feines. :-D


Grüße
Erik
17
OS-Design / IPC-Konzept
« am: 05. February 2010, 19:03 »
Hallo,


ich möchte Euch mal mein (vorläufiges) IPC-Konzept vorstellen und hoffe auf zahlreiche Meinungen bzw. Verbesserungsvorschläge (oder gerne auch Lobeshymnen :-D ).


Ich möchte synchrone und asynchrone Messages unterstützen.
Die synchronen gehen direkt an ein bestimmtes Empfänger-Objekt und liefern immer eine eindeutig zugehörige Antwort zurück, also ein zusammenhängendes Frage/Antwort-Spiel wo der fragende Thread blockiert bis seine Antwort eintrifft.
Die asynchronen gehen entweder auch an ein konkretes Empfänger-Objekt oder an einen bestimmten Prozess und liefern keine Antwort, der Sender wird nicht blockiert sondern kann sofort weiterarbeiten falls er das kopieren der Message-Daten gewünscht hat oder er muss warten bis der Empfänger die Daten freigibt.
Für die meisten Anwendungsfälle, vor allem für RPC, werden wohl die synchronen Messages benutzt. Die asynchronen Messages dienen eher System-Nachrichten/Signale vom Kernel an Prozesse und IRQ-Messages.
Die Message-Verarbeitung erfolgt in PopUp-Threads welche vom OS-Kernel automagisch, im Prozess des betreffenden Service, erstellt werden und als Einsprungspunkt die zugewiesene Message-Handler-Funktion nutzen (vielen Dank an taljeth für diese grandiose Idee, welche wohl noch nie jemand benutzt hat). Es können auch mehrere Threads pro Message-Empfänger-Objekt gleichzeitig aktiv sein so das z.B. das TCP-Modul von mehreren Prozessen gleichzeitig benutzt werden kann. Diese PopUp-Threads können ganz normal unterbrochen werden und auch alle üblichen Synchronisations-Mechanismen benutzen (auch im Zusammenspiel mit den normalen Threads im Service-Prozess).

Die IPC-SYSCALL-Schnittstelle ist folgende:/**
 * Function-Prototyp for Message-Handler-Function for synchrounus Messages
 * called directly in a Kernel generatet Popup-Thread (need the exact Calling-Conventions)
 *  - Handler-Parameter (from Message-Target-Creater, the pointed Data must not modified)
 *  - Sender-ID (Process-ID if from an other Process or Thread-ID if from current Process)
 *  - Question-1 (the pointed Data must not modified)
 *  - Question-1-Size
 *  - Question-2 (the pointed Data must not modified)
 *  - Question-2-Size
 *  - Answer-1
 *  - Answer-1-Maximum-Size
 *  - Answer-2
 *  - Answer-2-Maximum-Size
 *  the Message-Handler can not simply return, it must call 'syscall_msg_sync_handler_end()' to give the Controll back to the Kernel, the Link-Register is set to -2 by kernel and the Stack is clear
 */
typedef void msg_sync_func(const void* const param,const long sender_id,const void* const question1,const ulong question1_length,const void* const question2,const ulong question2_length,void* const answer1,const ulong answer1_max_length,void* const answer2,const ulong answer2_max_length);

/**
 * Function-Prototyp for Message-Handler-Function for asynchrounus Messages
 * called directly in a Kernel generatet Popup-Thread (need the exact Calling-Conventions)
 *  - Handler-Parameter (from Message-Target-Creater, the pointed Data must not modified)
 *  - Sender-ID (Process-ID if from an other Process or Thread-ID if from current Process)
 *  - Message-1 (the pointed Data must not modified)
 *  - Message-1-Size
 *  - Message-2 (the pointed Data must not modified)
 *  - Message-2-Size
 *  the Message-Handler can not simply return, it must call 'syscall_msg_asyn_handler_end()' to give the Controll back to the Kernel, the Link-Register is set to -2 by kernel and the Stack is clear
 */
typedef void msg_asyn_func(const void* const param,const long sender_id,const void* const message1,const ulong message1_length,const void* const message2,const ulong message2_length);

/**
 * create a Message-Target for synchrounus Messages
 *  - Handler-Function for the Message-Popup-Thread
 *  - Parameter for the Handler (the pointed Data must not modified)
 *  - maximum allowed Question/Answer-1-Size (4kBytes...Kernel-Depended-Maximum)
 *  - maximum allowed Question/Answer-2-Size (0 or 4kBytes...Kernel-Depended-Maximum)
 *  - maximum allowed Popup-Threads that runs simultaneously for this Message-Target (1...Kernel-Depended-Maximum)
 *  - start Stack-Size for the Popup-Thread (Message-Sizes are not included), this is not a real Limit, stack can ever grow up to an OS-Depended Maximum-Limit (0 means an Kernel-spezific Default-Value)
 *  - Priority for the Popup-Thread : offset to the Process-Base-Priority (if the Priority of the calling Thread is not inherited)
 *  returns the Message-Target-ID or Error-Code
 */
long syscall_msg_sync_create(msg_sync_func* const func,const void* const param,const ulong message1_max_length,const ulong message2_max_length,const uint max_threads = 1,const ulong stack_size = 0,const int priority = 0);

/**
 * create a Message-Target for asynchrounus Messages
 *  - Handler-Function for the Message-Popup-Thread
 *  - Parameter for the Handler (the pointed Data must not modified)
 *  - maximum allowed Message-1-Size (4kBytes...Kernel-Depended-Maximum)
 *  - maximum allowed Message-2-Size (0 or 4kBytes...Kernel-Depended-Maximum)
 *  - maximum allowed Popup-Threads that runs simultaneously for this Message-Target (1...Kernel-Depended-Maximum)
 *  - start Stack-Size for the Popup-Thread (Message-Sizes are not included), this is not a real Limit, stack can ever grow up to an OS-Depended Maximum-Limit (0 means an Kernel-spezific Default-Value)
 *  - Priority for the Popup-Thread : offset to the Process-Base-Priority
 *  returns the Message-Target-ID or Error-Code
 */
long syscall_msg_asyn_create(msg_asyn_func* const func,const void* const param,const ulong message1_max_length,const ulong message2_max_length,const uint max_threads = 1,const ulong stack_size = 0,const int priority = 0);

/**
 * create (open) the Process-Message-Target for asynchrounus System-Messages to the current Process
 *  ever asynchrounus : simple Messages
 *  ever maximum 64kBytes per Message-1
 *  ever 0Bytes per Message-2 (no additional Data)
 *  ever only one Popup-Thread
 *  - Handler-Function for the Message-Popup-Thread
 *  - Parameter for the Handler (the pointed Data must not modified)
 *  - start Stack-Size for the Popup-Thread (Message-Sizes are not included), this is not a real Limit, stack can ever grow up to an OS-Depended Maximum-Limit (0 means an Kernel-spezific Default-Value)
 *  - Priority for the Popup-Thread : offset to the Process-Base-Priority
 *  returns the Message-Target-ID or Error-Code
 */
long syscall_msg_proc_create(msg_asyn_func* const func,const void* const param,const ulong stack_size = 0,const int priority = 0);

/**
 * create a Message-Target for asynchrounus IRQ-Messages
 *  for receiving IRQ-Messages spezial Privileges are needed
 *  ever asynchrounus : simple Messages
 *  ever maximum 32Bytes per Message-1 (contains a simple Struct with Infos about the IRQ, ever copyed ontu Popup-Thread-Stack)
 *  ever 0Bytes per Message-2 (no additional Data)
 *  - Handler-Function for the Message-Popup-Thread
 *  - Parameter for the Handler (the pointed Data must not modified)
 *  - IRQ-Number
 *  - maximum allowed Popup-Threads that runs simultaneously for this Message-Target (1...Kernel-Depended-Maximum , the Kernel can fix this to one Thread)
 *  - start Stack-Size for the Popup-Thread (Message-Sizes are not included), this is not a real Limit, stack can ever grow up to an OS-Depended Maximum-Limit (0 means an Kernel-spezific Default-Value)
 *  - Priority for the Popup-Thread : offset to the Process-Base-Priority
 *  returns the Message-Target-ID or Error-Code
 */
long syscall_msg_irq_create(msg_asyn_func* const func,const void* const param,const uint irq_num,const uint max_threads = 1,const ulong stack_size = 0,const int priority = 0);

/**
 * kill a Message-Target for synchrounus Messages
 *  - Message-Target-ID
 *  - correct Handler-Function as Access-Secret
 *  - correct Parameter as Access-Secret (the pointed Data can modified after the return of this SYSCALL)
 *  returns Success/Error-Code
 */
long syscall_msg_sync_kill(const long msg_id,msg_sync_func* const func,const void* const param);

/**
 * kill a Message-Target for asynchrounus Messages (or System-Messages or IRQ-Messages)
 *  - Message-Target-ID (or Process-ID)
 *  - correct Handler-Function as Access-Secret
 *  - correct Parameter as Access-Secret (the pointed Data can modified after the return of this SYSCALL)
 *  returns Success/Error-Code
 */
long syscall_msg_asyn_kill(const long msg_id,msg_asyn_func* const func,const void* const param);

/**
 * End of a Message-Handler for synchrounus Messages
 * this SYSCALL can only called from a Message-Handler (inside of the Popup-Thread)
 *  - real Answer-1-Size (must be smaller or equal to the maximum Answer-1-Size)
 *  - real Answer-2-Size (must be smaller or equal to the maximum Answer-2-Size)
 *  do not return (the Popup-Thread is killed/recycled by the Kernel)
 */
void syscall_msg_sync_handler_end(const ulong answer1_length,const ulong answer2_length);

/**
 * End of a Message-Handler for asynchrounus Messages
 * this SYSCALL can only called from a Message-Handler (inside of the Popup-Thread)
 *  do not return (the Popup-Thread is killed/recycled by the Kernel)
 */
void syscall_msg_asyn_handler_end();

/**
 * free the Message-Memorys for asynchrounus Messages (for copyed messages it has no effect)
 * this SYSCALL can only called from a Message-Handler (inside of the Popup-Thread)
 * the Message-Pointers are invalid after this SYSCALL
 */
void syscall_msg_asyn_handler_free_msg();

/**
 * send a synchrounus Question and wait for its Answer
 *  - Message-Target-ID
 *  - Question-1 (tx) (the pointed Data must not modified while this SYSCALL)
 *  - Question-1-Size (tx)
 *  - Question-2 (tx) (the pointed Data must not modified while this SYSCALL)
 *  - Question-2-Size (tx)
 *  - Answer-1 (rc) (the pointed Data must not modified while this SYSCALL)
 *  - Answer-1-Size (rc) (the pointed Data must not modified while this SYSCALL)
 *  - Answer-1-Maximum-Size
 *  - Answer-2 (rc) (the pointed Data must not modified while this SYSCALL)
 *  - Answer-2-Size (rc) (the pointed Data must not modified while this SYSCALL)
 *  - Answer-2-Maximum-Size
 *  - inherit the absolute Priority of the calling Thread to the PopUp-Thread
 *  - do not share the Question/Answer-Memorys, Kernel must copy if true, small Messages are ever copyed
 *  returns Success/Error-Code
 */
long syscall_msg_sync_send(const long msg_id,const void* const question1,const ulong question1_length,const void* const question2,const ulong question2_length,void* const answer1,ulong* const answer1_length,const ulong answer1_max_length,void* const answer2,ulong* const answer2_length,const ulong answer2_max_length,const bool prio_inherit = true,const bool no_sharing = false);

/**
 * send an asynchrounus Message and do not obligatory wait (typical the current Thread is shedulled and the new Popup-Thread becomes the current CPU-Thread, if a new Popup-Thread is possible)
 *  - Message-Target-ID
 *  - Message-1 (tx) (the pointed Data must not modified while this SYSCALL)
 *  - Message-1-Size (tx)
 *  - Message-2 (tx) (the pointed Data must not modified while this SYSCALL)
 *  - Message-2-Size (tx)
 *  - do not share the Message-Memorys, Kernel must copy if true, small Messages are ever copyed
 *  - wait for the real finish of the Message-Handler (independed of the Sharing-State of the Message-Memorys)
 *  returns Success/Error-Code
 */
long syscall_msg_asyn_send(const long msg_id,const void* const message1,const ulong message1_length,const void* const message2,const ulong message2_length,const bool no_sharing = false,const bool wait = false);

/**
 * get Infos about a synchrounus Message-Target
 *  - Message-Target-ID
 *  - count of current working Threads
 *  - start Stack-Size for PopUp-Thread
 *  - Handler-Function-Pointer
 *  - Handler-Parameter-Pointer
 *  returns Success/Error-Code
 */
long syscall_msg_sync_info(const long msg_id,ulong* const thread_count,ulong* const thread_stack_size,const msg_sync_func** const func,const void** const param);

/**
 * get Infos about an asynchrounus Message-Target (or the Process-Message-Target)
 *  - Message-Target-ID or Process-ID
 *  - count of current working Threads
 *  - start Stack-Size for PopUp-Thread
 *  - Handler-Function-Pointer
 *  - Handler-Parameter-Pointer
 *  returns Success/Error-Code
 */
long syscall_msg_asyn_info(const long msg_id,ulong* const thread_count,ulong* const thread_stack_size,const msg_asyn_func** const func,const void** const param);
auch wenn es nicht so aussieht aber alle Parameter werden komplett in Registern übergeben und die Kommentare sind noch nicht im Doxygen-Style. 13 SYSCALLs nur für IPC sind zwar recht viel aber ich denke damit lassen sich alle nur erdenklichen Anwendungsfälle abdecken.
Wie man sieht möchte ich immer 2 zusammengehörige Message-Daten-Blöcke benutzen so das RPC-Kommando und RPC-Nutzdaten getrennt voneinander übergeben werden können, es müssen aber nicht immer beide benutzt werden.
Die Message-Daten-Blöcke sollen nicht kopiert werden sondern der OS-Kernel soll in der LDT des Message-Empfängers (Service) ein neues Segment anlegen das genau auf den linearen Speicherbereich vom Message-Sender (Client) zeigt, da das nicht auf beliebige Bytegenauigkeit sondern nur mit 256Byte-Alignment geht sieht der Service maximal 2 * 255 Bytes pro geteiltem Speicherbereich aus dem privaten Speicher des Client-Prozess was sicher verschmerzbar ist (wenn der Client das nicht will muss er das kopieren anfordern oder seine Daten passend ausrichten bzw. Platz lassen). Am Ende des PopUp-Threads werden die Alias-Segmente wieder aus dem Kontext des Service gelöscht.
Die Pointer welche der Service bekommt sind nicht die selben wie jene die der Client benutzt, der Kernel muss hier passend umsetzen. In den übermittelten Daten können natürlich auch keine Pointer benutzt werden (so wie bei Shared-Memory üblich).

Ein kleines Beispiel:
Ein Programm will Daten (100000Bytes) über eine Serielle Schnittstelle senden.lib_function_ser_tx(ser_dev_handle,&data_array,100000);in der Application-Library gibts dann dasextern int lib_function_ser_tx(const int ser_dev_handle,const void* const data,const ulong data_length)
{
  byte cmd[??];
  //'cmd' mit nem Kommando befüllen, in Abhängigkeit von 'ser_dev_handle'
  byte ans[??]; ulong ans_length=0;
  const long retval = syscall_msg_sync_send(ser_handler_msg_id,&cmd,sizeof(cmd),data,data_length,&ans,&ans_length,sizeof(ans),NULL,NULL,0);
  //check 'retval' und 'ans' ob alles okay ist
  return 0; //all okay
}
der Treiber für die Serielle Schnittstelle sieht dann etwa so ausvoid ser_handler_msg_func(const void* const param,const long sender_id,const void* const question1,const ulong question1_length,const void* const question2,const ulong question2_length,void* const answer1,const ulong answer1_max_length,void* const answer2,const ulong answer2_max_length)
{
  //check Kommando :
  switch(question1)
   {
     ???:
       break;

     ???:
       break;

     senden:
       {
       //für die Nutzdaten 'question2' die physikalische Adresse ermitteln, sind eventuell mehrere Pages
       //dem BUS-Master-fähigem UART diese Bereiche ans Ende seiner TX-Queue anhängen, im letzten Abschnitt das "Interrupt wenn alles gesendet"-Flag setzen
       //PopUp-Thread schlafen legen bis vom IRQ-Handler wieder aufgeweckt wird, der UART holt die Daten selbstständig aus dem RAM und sendet diese ohne jegliches zutun der CPU
       //Antwort 'answer1' passend befüllen
       syscall_msg_sync_handler_end(???/*Antwortgröße*/,0); //kommt nicht zurück
       }
       break;
   }
}
Das schöne an diesem Szenario ist das die Daten niemals kopiert werden müssen. Das lässt sich auch problemlos auf mehr Ebenen ausdehnen z.B. auf Datei-Zugriffe. Da könnte der VFS-Service das Kommando entgegennehmen und der PopUp-Thread called dann seinerseits den Service für das entsprechende Dateisystem welcher dann wieder den Block-Device-Treiber per RPC aufruft, auch hier müssen die Userdaten nicht unbedingt kopiert werden so das der (S)ATA-Controller die eigentlichen Datei-Daten direkt in/aus dem Speicher der Zielanwendung schreibt/ließt. Das alles kostet nur etwas Speicher für die Stacks der schlafenden PopUp-Threads welche selber RPC benutzen.


Ich währe sehr dankbar wenn Ihr da mal drüber schaut ob euch noch offensichtliche Fehler auffallen.

Das ganze sollte sich identisch für Flat-Memory umsetzen lassen, also wenn jemand mir seine praktischen Erfahrungen mit diesem Konzept mitteilen möchte dann nur zu. :wink:


Grüße
Erik
18
Softwareentwicklung / Calling-Conventions
« am: 11. January 2010, 20:07 »
Hallo,


ich hab mal drei Fragen zu Calling-Conventions. Jene möchte ich gerade für meine Plattform spezifizieren und bin da auf ein paar Problemchen gestoßen.

1. Wenn eine Funktion sehr viele Parameter, oder eine variable Anzahl an Parametern, hat dann können die ja nicht mehr alle per Register übertragen werden. Dafür gibt es 2 Möglichkeiten, entweder alle überzähligen bzw. variablen Parameter der Reihe nach auf den Stack legen (an Top-of-Stack ähnlich cdecl), wobei dann wieder geklärt werden muss wer den Stack frei gibt (am besten der Aufrufer dann funktionieren auch variable Parameter), oder alle überzähligen Parameter in einen passenden Speicherbereich legen (was normalerweise auch der Stack ist aber eben nicht Top-of-Stack sondern irgendwo im Stack-Frame des Callers) und die Adresse dieses Bereichs als unsichtbares Parameter im Register übergeben (also wie FastCall). Die zweite Variante hat den Nachteil das ein zusätzliches Parameter ein Register belegt und man zum Zugriff auf die zusätzlichen Parameter immer auch eine zusätzliche Info (eben diesen Pointer) benötigt was sicher oft zu einem zusätzlichen Befehl führt und manchmal, falls dieser Pointer mal aus den Registern verdrängt werden muss, sogar ein zusätzlicher Speicherzugriff ist. Ich möchte mich lieber für die erste Variante entscheiden bin mir aber nicht sicher ob ich was übersehen hab. Die FastCall-Variante wird z.B. vom Linux-Kernel (intern und beim SYSCALL) verwendet und ich gehe mal davon aus das die sich dabei was gedacht haben. Gerade auf x86 mit den wenigen Registern muss man sich schon ordentlich Mühe geben damit die Parameterübergabe nicht zum kostspieligen Fiasko wird. Auf meiner Plattform hab ich zwar deutlich mehr Register, so das dieser Schuh nicht so sehr drückt. Welche von beiden Varianten haltet Ihr für besser? Gibt es noch andere Möglichkeiten? Ich möchte dieses Problem schon ordentlich lösen und auch nur eine Calling-Convention haben die alle Anwendungsfälle mit möglichst maximaler Performance abdeckt.

2. Wie sieht das mit dem this-Pointer bei Klassen-Methoden aus? Dazu hab ich kaum Infos gefunden (außer den thiscall von MS und den will ich nicht kopieren). Ich würde diesen unsichtbaren Parameter gerne als erstes im Registerbereich ablegen und den auch bei der Rückkehr dort lassen so das dieses Register gar nicht verändert werden muss wenn Methoden einer Klasse sich gegenseitig aufrufen (was ja nicht unbedingt selten ist), also ein Register exklusiv für diese Verwendung reserviert wird (bei insgesamt 64 Registern tut das nicht weh).

3. Was haltet ihr eigentlich davon kleine Strukturen (bis 32 oder 64 Byte) direkt in den Registern zu übergeben oder würdet ihr einen Pointer bevorzugen?


Ich hoffe ihr habt ein paar Anregungen für mich. Danke schon mal im Voraus.


Grüße
Erik
19
OS-Design / Wie Ressourcenverwaltung im Micro-Kernel OS?
« am: 31. December 2009, 11:00 »
Hallo,


ich bin beim nachdenken (über mein Micro-Kernel OS) auf ein Problem gestoßen.
Wie werden in einem Micro-Kernel OS offene Ressourcen (Dateien, Sockets u.ä.) geschlossen wenn ein Prozess außerordentlich gekillt wird? :?
Der eigentliche Kernel weiß doch nichts von dem ganzen Zeugs. In einem Monolithen ist es ja oft so das wenn ein Prozess beendet wird (oder sich selbst beendet) das offene Dateien, Sockets usw. vom Kernel geschlossen werden. In einem Micro-Kernel OS hingegen weiß der Kernel ja gar nichts von all diesen Ressourcen und kann sie demzufolge auch nicht selber schließen. Wenn man aber gar keine derartigen Mechanismen einbaut dürften nach ein paar abgestürzten Programmen ein Haufen Ressourcen (und damit vor allem Speicher) noch belegt sein. Das könnte langfristig zum Absturz o.ä. führen.

Da ich davon ausgehe das ich nicht der erste bin dem dieses Problem auffällt hoffe ich mal das es bereits erprobte Lösungswege gibt. Ich finde diese nur nicht.

Hat da jemand ein paar Vorschläge oder Suchbegriffe für mich?


Grüße
Erik


und rutscht gut aber unfallfrei ins Jahr 2010 :-D
20
OS-Design / Shared IRQs bei Micro-Kernel-OS
« am: 18. October 2009, 09:47 »
Hallo,


ich wüste gerne wie man in einem Micro-Kernel-OS mit shared IRQs umgeht.
Wenn ich das richtig verstehe sind in einem Micro-Kernel-OS die Device-Treiber auch nur ganz normale User-Space-Applicationen die den IRQ per RPC oder einem anderen Inter-Process-Comunication-Mechanismus erhalten müssen da sie ja nicht im Kernel-Space laufen. Ich wollte das so implementieren das ein User-Space-Treiber einen Signal-Handler registriert und diesen Signal-Empfänger nicht für andere Programme freigibt sondern einem IRQ zuweißt. Im Kernel gibt es dann einen (nicht mehrere) generischen IRQ-Handler der nachschaut für welchen IRQ er gerade aufgerufen wurde und dann für den entsprechenden Signal-Handler eine Nachricht generiert. Wenn nun aber mehrere physische Geräte sich einen IRQ teilen dann müsste man ja bei jedem IRQ an mehrere Signal-Handler eine entsprechende Nachricht schicken.

Habe ich da irgendwo einen Denkfehler oder wie löst Ihr dieses Problem?

Eigentlich wollte ich auf meiner Plattform shared IRQs generell verbieten und statt dessen konsequent auf MSI/MSI-X in Zusammenarbeit mit einem ordentlichen IRQ-Controller, der 4096 verschiedene IRQs (zumindest theoretisch, man muss es ja nicht vollständig implementieren) verwalten kann, bauen. Aber leider unterstützt nicht jede PCI/PCI-Express-Hardware MSI/MSI-X so das ich eventuell doch gezwungen bin shared IRQs zu unterstützen.


Grüße
Erik
Seiten: [1] 2

Einloggen