Lowlevel
Lowlevel => Lowlevel-Coding => Thema gestartet von: bitmaster am 13. March 2008, 15:25
-
Hi,
ich bastle gerade mal wieder an den Trieibern und am RPC rum.
Also ich rufe mit einem Anwendungsprogramm die Funktion GetDate der Bibliothek time.lib auf. Jetzt soll die time.lib mit dem Treiber der Real Time Clock komunizieren und die Daten per RPC beschaffen. Aber um eine Message zu senden ist die Angabe der PID des Real Time Clock Treibers erforderlich, welche nicht immer gleich ist (logisch). Wie komme ich jetzt an der PID?
danke!!!
-
Indem du sie an irgendeiner zentralen Stelle hinterlegst. Beispiel LOST: init ist immer PID 1. Dort registrieren sich alle Treiber mit ihrem Namen. Wer auf einen Treiber zugreifen will, fragt init nach der PID. Der Kernel wäre aber sicher auch ein geeigneter Ort.
-
Mit welchen Namen denn? Nicht den Dateinamen, oder?
-
Der stimmt zufällig überein (ich nenne die Datei für den Service "floppy" halt normal nicht "network" oder so...), aber grundsätzlich nicht. Wie das im Detail regelst, ist aber deine Sache.
-
Ah OK. Was floppy aber mit network zu tun haben soll habe ich noch nicht herausgefunden. Na ja, dann sage ich auf jeden Fall schon mal danke. Kann aber sein, dass ich demnächst noch ein paar Fragen stellen werde. Ich hoffe du/ihr nehmt mir das nicht übel. tschau
bitmaster
-
Ich hab zur IPC nicht die Prozessnummern verwendet, sondern bei mir hat jeder Prozess der IPC machen wollte einen message port erstellt, welcher eine eindeutige ID hatte. Für bestimmte Sachen hatte ich dann feste IDs, sodass man bei den festen IDs wusste was sich dahinter verbirgt. Siehe hier (https://lightos.bountysource.com/svn/!source/413/trunk/lib/libc/include/lightOS/lightOS.h) (Zeile 123) für eine Liste der fixen IDs.
-
Bei mir gibt es eine Headerdatei mit den Portnummern der einzelnen Server. Jeder Server registriert seine eigene Portnummer im Kernel. Der Prozess muss nun nur gucken welche Portnummer der Server hat und über einen Syscall bekommt er aus der Portnummer die PID.
Das mit der Headerdatei ist vielleicht nicht so optimal, sie ist auch eigentlich nur als Stütze gedacht, da man sich die Portnummern nicht so gut merken kann.
-
Dann suche ich noch genauere Vorschläge zum Aufbau einer Message. Und wie sollte der Empfänger auf eine Message reagieren, die für ihn unbekannt ist?
danke!!!
-
Dann suche ich noch genauere Vorschläge zum Aufbau einer Message.
Art/Typ der Message + Parameter der Message
evtl. brauchst du noch eine ID zum unterscheiden welche Respose auf welchen Request passt.
Und wie sollte der Empfänger auf eine Message reagieren, die für ihn unbekannt ist?
Wenn die Art der Message unbekannt ist, dann einfach ein Message mit einem Fehlercode zurückschicken.
-
OK. Also könnte eine ganz vereinfachte Message z.B. folgenden Aufbau haben:
db 5 ;Typ z.B. 5 = Speicher soll auf einen Sektor geschrieben werden
times: 512 db 0 ;Speicher der geschrieben werden soll
Und dann SendMessage die Parameter der Adresse der Nachricht, den Empfänger mittels PID und die Nachrichtengröße (hier z.B. 513) übermitteln.
?
bitmaster
-
db 5 ;Typ z.B. 5 = Speicher soll auf einen Sektor geschrieben werden
Ein Byte ist evtl. etwas wenig für die Messagetypen, va. wenn du sie so konkret machst wie "Sektor schreiben".
Ich persönlich finde das Adressieren mittels PID nicht so toll, da es Multithreading komplett außer Acht lässt. Aber man kann das natürlich so machen, klar.
Worüber du evtl. nachdenken solltest ist, wie die Antwort auf die Anfrage auszusehen hat und was passiert wenn mehrere Anfragen versendet wurden und wie man dann die Antworten zuordnet.
-
Du meinst, dass man eine Zeichenkette als Typ angeben sollte und kein Byte/Word/DWord/QWord? Aber dann müsste ja zusätzlich auch ein Byte für die Größe der Zeichenkette vorhanden sein.
Ja ich bin am überlegen ob ich immer nur eine Message senden lassen soll oder so viele bis der RAM voll ist.
bitmaster
-
Du meinst, dass man eine Zeichenkette als Typ angeben sollte und kein Byte/Word/DWord/QWord? Aber dann müsste ja zusätzlich auch ein Byte für die Größe der Zeichenkette vorhanden sein.
Nein, ich meine, dass ein byte zu wenig ist. Das ermöglichst nur 256 verschiedene Nachrichtentypen.
Ja ich bin am überlegen ob ich immer nur eine Message senden lassen soll oder so viele bis der RAM voll ist.
hä? Ich meine nur, dass du dir überlegen sollst, wie ein Programm das zwei oder mehr Nachrichten (z.B. lese Sektor 1 und lese Sektor 2) versendet ("gleichzeitig") und danach auf Antwort wartet herausfindet, welche der Antworten auf "Lese Sektor 1" und welche auf "Lese Sektor 2" passt. Wie gesagt, so etwas wie ein Message-ID Feld mag da zu überlegen sein, so verwendet, dass die Antwort die gleiche ID wie die Anfrage haben muss. D.h. dass die Anfrage "Lese Sektor 1" z.B. ID 100 hat und "Lese Sektor 2" die ID 200 hat. Wenn die Antworten dann eintreffen, kannst du anhand der ID entscheiden welche Antwort zu welcher Anfrage passt (also die Antwort mit ID 100 passt zu "Lese Sektor 1" und ID 200 passt zu "Lese Sektor 2").
-
Ah so, jo das muss ich mir auch noch überlegen. Aber jetzt gucke ich BAAAAAAAAYEEEEEEEEEERN. :wink:
-
Ah so, jo das muss ich mir auch noch überlegen. Aber jetzt gucke ich BAAAAAAAAYEEEEEEEEEERN. :wink:
Na ja, dann nehme ich halt ein QWORD anstatt BYTE. ;-) So, ich progge dann mal.
-
Da ist mir noch was eingefallen. Ähm sollte der Empfänger bereits vorher Speicher für die zu empfangende Message reservieren oder soll das die GetMessage machen? Denn sonst müsste man ja vorher erstmal schauen wie groß die Message ist.
danke!!!
-
Weißt du, was mir gerade auffällt, bitmaster? Wenn man 08/15-Stringfunktionen, die sowieso überall gleich sind, kopiert, ist das aus deiner Sicht eine Schandtat, weil man ja sein eigenes OS schreibt. Da, wo es aber wirklich interessant wird, wo man was denken muß und wo sich Betriebssysteme wirklich unterscheiden können, fragst du hier nach jedem Detail, damit du es genau nachprogrammieren kannst.
Kleiner Widerspruch?
Einzelne Nachrichten vorher reservieren wird schwierig, wenn du nicht weißt, wie groß die Nachricht wird. Aber man kann sich da vieles ausdenken.
-
Folgende Möglichkeiten fallen mir da so ad hoc ein:
- Die Nachrichten von dem versendenden Prozess in einem speziellen Bereich allozieren lassen und diesen Bereich dann in den Page-Directories/-Tables aus dem sendenden Prozess austragen und den empfangenden eintragen. Dabei spart man sich das rumkopieren im Kernel, d.h. es ist va. schnell für große Nachrichten und wenn man das noch ein bisschen erweitert, dann hat man shared-memory gleich fast kostenlos dazu. (Das verwende ich, offensichtlich :-D )
- Empfänger holt erst die Paketgröße, dann alloziert er, dann holt er die eigentliche Nachricht.
- Empfänger hat eine maximale Nachrichtenlänge
- Kernel ist dafür zuständig, dass Speicher in einen speziellen Bereich des Prozesses gemappt wird und eingehende Nachrichten dort hinkopiert werden. Bei getMessage gibts dann nur einen Pointer dahin. Bisschen problematisch ist das evtl aber: Was passiert wenn mehrere Nachrichten eingehen (wo wird hinkopiert? wie wird die Nachricht wieder freigegeben? fragmentiert das? etc...)
Taljeth spricht mir aus der Seele, dankeschön. :-)
-
Taljeth spricht mir aus der Seele, dankeschön. :-)
Jo, mir auch, dankeschön. :-)
Hmm... dann müsste ich nach dem Empfang der Nachricht den Speicher explizit wieder frei geben, wenn ich die Nachricht nicht mehr benötige, sonst verbraucht der Prozess ja evtl. ziemlich viel Speicher (bei vielen weiteren Nachrichten).
Nojo donn wollen wor mo proggen gohon.
danke!!!
-
dann müsste ich nach dem Empfang der Nachricht den Speicher explizit wieder frei geben [...]
Momentan hast du aber echt lichte Momente, Hut ab. :-D 8-)
-
sollte ich die Funktion
GetMessage
oder lieber
ReceiveMessage
nennen?
-
Ja.
-
Moin
die Frage sollte sich dadurch klären lassen, wer den speicher wieder aufräumen sollte. Wobei das speichermanagement an der stelle sicher nicht ganz ohne ist. Mehrfaches aufrufen, Prozess oder Programm läuft bei abarbeitung der Message nicht mehr, ...
1. Anlegen einer nachricht inerhalb einer Funktion
2. du absetzen der message (ohne auf antwort zu warten )
3. du vererlässt die funktion.
Mit verlassen der Funktion wird der speicher für deine nachricht auch wieder freigegeben und kann für was auch immer wieder verwendet werden. hat zur folge das dein RPC system die nachricht sichern muss. Kann man natürlich auch umgehen. z.B. dadurch, das der speicher für die nachricht global angelegt ist, ist aber auch nicht gerade schön. Oder sich die Nachricht aus einem Pool besorgt ( nachteil, die grösse muss vorher bekannt sein )
beim emfangen von antworten gibt es ähnliche probleme.
1. kernel legt den speicher an.
Wer gibt den speicher wieder frei? der User (sprich das Programm)? wenn ja wie. wie kann sichergestellt werden, das das nicht vergessen wird? auf welchen speicher wird zugegriffen? User oder kernel speicher?
2. User übergibt den speicher beim aufruf.
Der Kernel arbeitet direckt auf dem Speicher des Users. Frage der Realisirbarkeit kenelcode speichert daten im user speicher ab. Frage der Sicherheit. Darf der Kernel überall auf Userspeicher zugreifen? ( Buffer overflow, Systemsicherheit, ... )
Der Kernel arbeit auf seinem eigenen Speicher, die Daten werden vom Kernel umkopiert.
1. Kernel fordert speicher für die Antwort an.
2. Antwort wird verschickt
3. antwort wird vom kernel space in den user space kopiert
4. kernel space wird wieder freigegeben.
Linux realisier das bei den Treiber z.B. so. Ein kernel mode Treiber arbeitet immer nur auf der Kopie der user mode daten. um diese zu erstellen, gibt es entsprechende funktionen.
Zum aufbau der Massages. Bei der WIN API ist immer auch der user für den speicherplatz der antwort zuständig. viele Datenstrukturen existieren in verschiedenen grössen. so dass fast immer ein feld size existiert, in der die grösse der Datenstruktur mit angegeben werden muss.
gruss
-
Der Kernel arbeit auf seinem eigenen Speicher, die Daten werden vom Kernel umkopiert.
1. Kernel fordert speicher für die Antwort an.
2. Antwort wird verschickt
3. antwort wird vom kernel space in den user space kopiert
4. kernel space wird wieder freigegeben.
Sollte man spätestens ab einer bestimmten Nachrichtengröße nicht mehr machen, denn das wird saulahm. Solange es nur ein paar Bytes Steuerdaten sind, okay, aber für Nutzdaten empfiehlt sich dann irgendein anderen Weg (SHM oder so).
-
Hi
ich weiss nur das das so in linux kernel treibern gehandhabt wird. Der kernel stellt hierzu die funktionen
unsigned long __copy_from_user(void *to, const void *from, unsigned long bytes_to_copy);
und
unsigned long __copy_to_user(void *to, const void *from, unsigned long bytes_to_copy);
zur verfügung.
und was soll langsam sein? das umkopieren? Kommt auf den Prozessor und die speicherbandbreite an. Stimmt man muss die daten nicht umbedingt durch den prozessor nageln. aber das muss man ja nicht. wozu gibt es dma der das für einen erledigen kann?
Anlegen und freigeben der Datenstrukturen im Kernel? ggf ist das sogar gar nicht notwendig, für die RTC könnt ich mir vorstellen, das die Struktur nur einaml existiert und immer wieder verwendet wird. Sprich anlegen und löschen entfällt. Sollte sogar für eine HD funktionieren. Das speichermanagement ist ja user sache. Und sollten doch mehrere benötigt werden könnte ein Pool helfen.
-
Sicher, daß du damit keine unzulässige Verallgemeinerung anstellst?
-
und was soll langsam sein? das umkopieren? Kommt auf den Prozessor und die speicherbandbreite an. Stimmt man muss die daten nicht umbedingt durch den prozessor nageln. aber das muss man ja nicht. wozu gibt es dma der das für einen erledigen kann?
Das ist langsam. DMA kommt eben nicht in Frage weil die Speicher-Speicher-transfere für heutige Verhältnisse zum einen langsam und zum anderen nicht unterstützt sind.
-
Es kommt immer darauf an, wie du das kopieren realisierst.
man kann byte weise kopieren in einer for schleife ( sicher das langsamste )
man kann das ganze auf ein alligment von 4 Byte bringen und die länge auf ein vielfachen von 4 definieren. und dann keine Bytes sonder DWords in einer for schleife kopieren. sind schon mal 3 speicherzugriffe weniger je schleifen durchgang.
man kann auch string befehle des Befehlsatzes verwenden. spart den ganzen zähloverhad den man selber implementiert.
[edit hat sich erledigt. gibt es auf pcs wohl nicht.]
oder ich verwende z.B. DMA. der kopiert mir die daten im hintergrund durch die gegend. Entlastet dadurch den Prozessor bus. und die CPU kann ggf was anderes machen. Sicher nicht interesant für 8 Byte [ jedes billige embeded system bietet mitlerweile GPDMA an mit dem ich von MEM to MEM kopieren kann. teilweise sogar mit richtig inteligenten mechanismen]
Sicher ist nicht kopieren schneller als kopieren.
nur ist es z.B. aus sicherheits sicht gewollt, user speicher im kernel speicher sichtbar zu haben? bzw direckt darauf zu arbeiten?
du must den user speicher im kernel einblenden. ( muss man auch beim kopieren )
du must die addresse umrechnen. ein teil davon ist auch beim kopieren notwendig.
Wie sieht es z.B. mit Treibern aus, die auf grund eines implementierungs fehlers daten auserhalb der gewollten datenstruktur verändert? Der Fehler wird vom Treiber verursacht, wirkt sich aber auf den user space aus. Der Fehler tritt dann auch erst im user space auf. Wo wird man den Fehler als erstes suchen. Im userspace programm oder im treiber?
So ein Fehler kann mit jedem neuen treiber wieder passieren. Wohingegen die Implementierung solcher copy from user / to user funktionen nur einam gemacht werden müssen.
gruss
-
Das ist ja alles schön und gut. Wir alle wissen, daß es effiziente und weniger effiziente Möglichkeiten gibt, ein memcpy zu implementieren.
Sicher ist nicht kopieren schneller als kopieren.
Aber das hier ist der entscheidende Punkt, wenn es um mehr als deine beispielhaften 8 Byte geht. Ja, es macht die Sache schlicht und einfach langsam, wenn man mehr kopiert als unbedingt nötig.
nur ist es z.B. aus sicherheits sicht gewollt, user speicher im kernel speicher sichtbar zu haben? bzw direckt darauf zu arbeiten?
Der Kernel kriegt sowieso alles kaputt, wenn er nur will. Ob man jetzt das Userspaceprogramm oder Kernelstrukturen durch einen Overflow zerschießt ist doch egal, am Ende steht der Absturz. Am besten vermeidet man den Overflow einfach ganz. ;)
-
Stimmt der kernel krigt sicher immer alles kaput.
(ich geh mal davon aus das wir von systemen mit paging und MMU reden)
nur hat der kernel auch immer alle speicherseiten der user tasks mit eingeblendet?
Fehler will man nie machen. Es schleichen sich doch immer wieder mal welche ein.
Und von was für systemen reden wir? 80486 oder aktuelle PC Systeme?
wie gross ist der Speicherdurchsatz so eines systems, (10MB/s oder 3GB/s) und wie gross ist die Datenmenge die ich kopieren will. ( 1kb 100kb oder 10MB ) und was sind hinterher ein paar µs? kleinfieh macht auch mist sicher. nur ist es hinterher würlich spürbar?
-
nur hat der kernel auch immer alle speicherseiten der user tasks mit eingeblendet?
Wenn du nicht bei jedem Syscall einen Kontextwechsel haben willst, ja, zumindest die des aktuellen Tasks.
kleinfieh macht auch mist sicher. nur ist es hinterher würlich spürbar?
Ja, ziemlich sicher.
-
nur hat der kernel auch immer alle speicherseiten der user tasks mit eingeblendet?
Bei einem x64 Kernel würde ich den ganzen physikalischen RAM mappen, dann kannst du locker mal ein bisschen in den PageDirs/Tables rumstochern ohne gleich den Cache zu trashen.
Bei x86 macht es wahrscheinlich Sinn das PageDir/Table kurz zu mappen, bisschen die Pages mappen und das PageDir/table wieder rauszunehmen. Da sollte man dann imho mit einem kleinen Invalidieren auskommen.
Nach meiner Methode hast du natürlich den Overhead des (De)Allozierens speziellen Speichers (für größere Datenmengen, wobei bei mir 16 die Größe der normalen Message war, alles darüber hinaus geht bei mir über 'shm' und verändern von PageTables/Dirs).
edit: Ich glaub ich habe hier zwei verschiedene Probleme durcheinandergebracht: Zum einen den L1-L3 Cache, der mit unnötigen Daten gefüllt wird (Da ja nur kopiert wird, d.h. zwei mal das gleiche im Cache) und zum anderen den TLB, der ja nur zum Speichern des Mappings benutzt wird (und der Adressraumwechsel invalidiert wird).
Aber falls ich da falsch liege bitte sagt was, Cache & TLB sind nicht gerade meine starke Seite... :|
Und von was für systemen reden wir? 80486 oder aktuelle PC Systeme?
wie gross ist der Speicherdurchsatz so eines systems, (10MB/s oder 3GB/s) und wie gross ist die Datenmenge die ich kopieren will.
( 1kb 100kb oder 10MB ) und was sind hinterher ein paar µs?
Ich würde das ehrlich gesagt unabhängig davon sehen, da Speicher bei jedem Desktop-PC der Flaschenhals ist, afaik.
kleinfieh macht auch mist sicher. nur ist es hinterher würlich spürbar?
Bei einem Microkernel sicherlich. Sind ja nicht nur ein oder zwei Programme, die alle Äonen mal IPC verwenden, sondern alle Treiber/Programme für sogut wie alles.
-
So, dann sagt mal an, was hört sich besser an:
NameProcess
oder
LabelProcess
?
Ich möchte nämlich einem Prozess (PID) eine feste Bezeichnung zuweisen können, mit deren Hilfe dann z.B. Treiber die PID erfahren. Ist das eine gute Idee oder eher schlecht. Habe ich an etwas nicht gedacht bzw. gibt es bessere Möglichkeiten?
danke!!!
-
Ich würde zum einen IPC unabhängig von der ProcessId gestalten. Man mag eventuell mal mehrere Services anbieten (zB ATA, mir mehreren Festplatten/CD-ROM-Laufwerken oder auch zwei Netzwerkkarten mit dem gleichen Treiber, etc...).
Zum anderen würde ich es nicht als "process name" bezeichnen, da der Begriff m.E. schon als (Datei)name (zB im Taskmanager) oder evtl. auch als argv[0] (also der Kommandozeilenaufruf des Programms) verwendet wird. Label find ich total 0xd00f.
Ich würde wie zu Anfang gesagt, dass komplett vom Prozesskonzept trennen (mit eigenen IDs) und das dann wahrscheinlich "service" name/id/... nennen. Der Name der Funktion wäre bei mir dann setServiceName, ich find es 0xd00f name/label als Verb zu verwenden.
Ich find deine Fragen nach Funktionsnamen im übrigen 0xd00f :roll:
-
OK vielen dank.
Aber du findest meine Fragen 0xd00f. :cry:
-
Aber du findest meine Fragen 0xd00f. :cry:
Verallgemeinerungen sind im Allgemeinen falsch. :wink: