Autor Thema: Daten in den Speicher eines Prozesses kopieren  (Gelesen 3460 mal)

TheThing

  • Beiträge: 105
    • Profil anzeigen
Gespeichert
« am: 04. June 2011, 19:27 »
Hi,
ich habe da ein kleines Problem.
Bei meinem Kernel funktionieren bereits Multitasking und alles was "davor" kommt, und gestern hab ich auch endlich den Bug beheben können, der das Paging vermurkst hat. Ich habe also momentan Paging mit einem page-directory für den Kernel und bin gerade dabei den einzelnen Prozessen auch ein page-directory zu verpassen und gleich auch ELF-Dateien zu verwenden.
Mein Problem ist jetzt folgendes:
Ich kann bereits dynamisch Speicher mappen usw., aber wenn ich ELF-Dateien parse muss ich ja die einzelnen Speicherbereiche, die im Programm-Header angeben sind, in den Adressraum des Prozesses kopieren (einfach nur mappen ist ja im Grunde nicht richtig, da auch die Bereiche auch im Speicher evtl. größer sein müssen usw.). Ein einfaches memcpy ist hier ja ungeeignet, da die Daten ja nicht nur an eine andere Adresse, sondern in einen ganz anderen Kontext müssen.

Meine Frage daher: Wie kann ich das einigermaßen gut lösen? Für jedes zu kopierende Byte die Adresse umzurechnen ist ja von der Performance her schon mal ganz schlecht.

Vielleicht stehe ich auch einfach nur auf dem Schlauch ;)

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 04. June 2011, 20:23 »
Ich würde sagen es gibt mindestens 4 Möglichkeiten:

1. Das von dir genannte Kopieren mit Adressumrechnung. Dabei musst du bedenken, dass du das nicht für jedes Byte sondern nur für jede Page machen musst. Dann sieht das mit der Performance schon mal besser aus.

2. Du lädst die ELF-Datei so, dass sie schon im ladenden Prozess/Kernel korrekt ausgerichtet ist, sodass du doch wieder einfach mappen kannst.

3. Du kannst den entsprechenden Teil vom Adressraum des geladenen Programms temporärer (irgendwo) in den des ladenden Prozesses mappen, und dann kannst du drauf mit memcpy arbeiten.

4. Du kannst fürs Laden zum Adressraum des geladenen Programms gewechseln. (Funktioniert vermutlich nur bei einem Monolithen.)

Es kommt natürlich drauf an, wie auf das geladene ELF-Image zugegriffen werden kann. Aber ansonsten kannst du dir ja mal was aussuchen.
« Letzte Änderung: 04. June 2011, 20:25 von PorkChicken »
Dieser Text wird unter jedem Beitrag angezeigt.

TheThing

  • Beiträge: 105
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 04. June 2011, 23:16 »
Ok, ich hab mich jetzt erstmal für die 1. Möglichkeit entschieden. Dazu hab ich jetzt erstmal eine Funktion geschrieben, die mir eine virtuelle in eine physische Adresse umrechnet (damit ich dann die Adresse hab wo die Daten letztendlich hin müssen) und bei Bedarf auch gleich die entsprechende Page reserviert, wenn sie noch nicht vorhanden war.
Die eigentliche Kopierfunktion durchläuft dann den zu kopierenden Speicherbereich und kopiert einzelne Bytes, wobei überprüft wird, ob die Adresse eine Page-Grenze überschreitet. Wenn dem so ist, wird die physische Adresse der neuen Page herausgesucht (und diese evtl. reserviert) und der Spaß geht weiter.
Ich hab das ganze jetzt nur mal testweise kompiliert um zu sehen, ob noch ein paar grobe Fehler drin sind, aber noch nicht getestet.
Momentan sieht es so aus:
   function get_p_addr (page_directory as uinteger ptr, v_addr as uinteger, reserve_if_na as ubyte) as uinteger
        dim pd_index as uinteger = (v_addr shr 22)
        dim pt_index as uinteger = (v_addr shr 12) and &h3FF
        dim page_table as uinteger ptr
        
        if (page_directory[pd_index] = 0) then
            if (reserve_if_na = 1) then
                page_directory[pd_index] = cuint(pmm.alloc())
                pmm.clean(cast(any ptr, page_directory[pd_index]))
                page_directory[pd_index] or= (FLAG_PRESENT or FLAG_WRITE or FLAG_USERSPACE)
            else
                return 0
            end if
        end if
        
        page_table = cast(uinteger ptr, (page_directory[pd_index] and &hFFFFF000))
        
        if (page_table[pt_index] = 0) then
            if (reserve_if_na = 1) then
                page_table[pt_index] = cuint(pmm.alloc())
                pmm.clean(cast(any ptr, page_table[pt_index]))
                page_table[pt_index] or= (FLAG_PRESENT or FLAG_WRITE or FLAG_USERSPACE)
            else
                return 0
            end if
        end if
        
        return ((page_table[pt_index] and &hFFFFF000) or (v_addr and &hFFF))
    end function
    
    sub copy_into_context (page_directory as uinteger ptr, p_start as uinteger, p_end as uinteger, virtual as uinteger)
        dim p_addr as uinteger = p_start
        dim p_v_addr as uinteger = get_p_addr(page_directory, virtual, 1)
        dim p_page_start as uinteger = (p_v_addr and &hFFFFF000)
        dim virtual_temp as uinteger = virtual
        
        while (p_addr<=p_end)
            *(cast(byte ptr, p_v_addr)) = *(cast(byte ptr, p_addr))
            p_addr += 1
            p_v_addr += 1
            if (not(p_v_addr < p_page_start+4096)) then
                virtual_temp += 4096
                p_v_addr = get_p_addr(page_directory, virtual_temp, 1)
                p_page_start = (p_v_addr and &hFFFFF000)
            end if
        wend
    end sub

Ist meine Umsetzung soweit einigermaßen richtig?
« Letzte Änderung: 04. June 2011, 23:18 von TheThing »

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 05. June 2011, 00:10 »
Ich kann mir vorstellen, dass das so klappen kann.

Eine Designentscheidung ist dir hoffentlich bewusst: Wenn du in get_p_addr auf Page Tables zugreifst, erwartest du, dass diese 1:1 gemappt sind (virtuelle Adresse = physische Adresse).
Dieser Text wird unter jedem Beitrag angezeigt.

TheThing

  • Beiträge: 105
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 05. June 2011, 02:21 »
jep, das ist mir klar. Der Kernel kriegt sowieso alles 1:1 gemappt ;)
Irgendwo kracht es aber noch, wobei ich jetzt nicht weiß, ob das beim ELF-Parsen passiert oder irgendwo beim Paging, das muss ich mir noch genauer ansehen. Aber das mach ich vermutlich wenn es wieder hell ist, wenn ich müde bin produziere ich mehr Bugs als ich behebe ;)

TheThing

  • Beiträge: 105
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 06. June 2011, 19:02 »
Ok, irgendwie wollte das einfach nicht klappen, und ich hab (ähnlich wie in der Tutorial-Reihe) jetzt einfach mal nur eine ELF-Datei geparst, die halt direkt in den Speicher kommt. So kann das natürlich nicht bleiben, aber ich habe mich diesmal dafür entschieden, zu versuchen, in den Kontext des zu startenden Programmes zu wechseln.
Was müsste ich denn alles beachten, wenn ich das so mache? Ich muss vermutlich erstmal den Kernel mappen, sonst wird das ganze in einem Desaster enden. Müssen seine Pages da beschreibbar sein? Und wenn ich dann auch das multiboot-modul mappe, wie kann ich sicher sein, dass es da nicht sich selbst in die Quere kommt, wenn es irgendwo da liegt, wo ein Teil von ihm hinkopiert werden soll?

Wie lösen das eigentlich andere OS, z.B. tyndur? Irgendwie müssen die ja auch ihren init-Prozess starten.

/edit: Ich hatte gerade eine Idee, wie ich das kopieren in einen anderen Kontext besser und schneller machen könnte:
sub copy_to_context (page_directory as uinteger ptr, p_start as uinteger, v_dest as uinteger, size as uinteger)
    dim bytes_left as uinteger = size
dim size_for_this_page as uinteger
dim p_addr as uinteger = p_start
dim p_v_addr as uinteger
dim v_addr as uinteger = v_dest
    
while (bytes_left > 0)
   p_v_addr = get_p_addr(page_directory, v_addr, 1)
     size_for_this_page = ((v_addr+4096) and &hFFF) - v_addr
     memcpy(p_v_addr, p_addr, size_for_this_page)
     bytes_left -= size_for_this_page
     p_addr += size_for_this_page
     v_addr += size_for_this_page
wend
end sub

Anstatt jetzt immer zu prüfen, ob ich eine Page-Grenze überschreite, rechne ich jetzt aus, wieviele Bytes es bis zur Page-Grenze sind und lasse meine memcpy-funktion die alle in einem Rutsch kopieren, dann gehts mit der nächsten Page weiter usw.

/edit2:
Noch eine kleine Frage zum Paging: Ich muss ja in jedem Kontext den Kernel (int-handler usw.) gemappt haben. logischerweise schreibgeschützt, damit mir kein Task da reinpfuscht. Wenn jetzt ein Interrupt aufgerufen wird, sollte ich dann gleich in den Kontext des kernels wechseln, damit ich dann auch auf Kernel-spezifische Dinge schreibend zugreifen kann?
« Letzte Änderung: 06. June 2011, 20:34 von TheThing »

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 06. June 2011, 20:52 »
Hallo,


Ich muss ja in jedem Kontext den Kernel (int-handler usw.) gemappt haben. logischerweise schreibgeschützt, damit mir kein Task da reinpfuscht. Wenn jetzt ein Interrupt aufgerufen wird, sollte ich dann gleich in den Kontext des kernels wechseln, damit ich dann auch auf Kernel-spezifische Dinge schreibend zugreifen kann?
Hat Dein Kernel einen eigenen Kontext? Wenn ja dann ist das eh was spezielles, sowas hat hier wimre noch keiner probiert. Ein normaler Kernel sollte in jedem Prozess immer an die selbe Adresse (z.B. 0xC0000000 - 0xFFFFFFFF) gemappt sein und diese Pages müssen als System-Only markiert sein damit der User-Mode-Code gar nichts da drin machen kann (schon Lesen wäre ein Sicherheitsrisiko).


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

TheThing

  • Beiträge: 105
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 06. June 2011, 21:03 »
Ah, natürlich - man konnte ja angeben, dass nur Ring-0-Code darauf zugreifen darf, und bei Interrupts wechselt er ja in Ring 0, was einen Kontextwechsel überflüssig macht. Das hatte ich gerade völlig vergessen, ich werde wohl den üblichen Weg gehen, also den Kernel überall als system-only mappen.
Damit wäre meine letzte Frage wohl geklärt ;)

rizor

  • Beiträge: 521
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 07. June 2011, 08:54 »
Falls du das nicht willst, kannst du dir auch einen Handler schreiben, der in jedem Kontext liegt und dann in den Kernel-Kontext wechselt. Dann musst du halt nur die Bereiche ganz klar definieren, etc. Dann verbraucht dein Kernel aus Sicht der Prozesse maximal 4 MB. Ist halt die Frage, ob die Performance-Einbußen, die man durch dieses Design hinnehmen muss, den größeren Speicher für die Prozesse rechtfertigt.
Programmiertechnik:
Vermeide in Assembler zu programmieren wann immer es geht.

 

Einloggen