Autor Thema: Wo Programme laufen lassen?  (Gelesen 7921 mal)

ehenkes

  • Gast
Gespeichert
« am: 21. June 2009, 01:28 »
Folgende noch experimentelle Situation:

1) Kleines Programm wird via incbin zunächst im Kernel eingeschleust, dann in die RAM Disk ins Filesystem eingeschleust
2) Beim Durchforsten des Filesystems wird der Name des Programms gesucht und das Programm beim Auffinden zunächst in eine Stack-Variable "geladen". Das könnte man bereits einen kleinen "Ladevorgang" nennen (RAM Disk --> Speicher).
3) Vom Stack sichere ich die Programm-Daten in ein statisches Array, das ich als globale Variable (im bss-Bereich bei 0x0000d800; Kernel beginnt ab 0x00008000) angelegt habe.
4) Dann greift ein im Multitasking erzeugter Prozess auf die Basisadresse bei  0x0000d800 zu und lässt dieses Programm laufen.

Solche RAM Disks werden ja offensichtlich verwendet, um primäre Gerätetreiber zu laden.

Dabei stelle ich mir die Frage, wo man solche kleinen Programme am besten hin bewegt und ausführt.


« Letzte Änderung: 21. June 2009, 01:31 von ehenkes »

bluecode

  • Beiträge: 1 391
    • Profil anzeigen
    • lightOS
Gespeichert
« Antwort #1 am: 21. June 2009, 03:31 »
Naja, der erste Schritt wäre wohl das Dateiformat der ausführbaren Datei zu parsen, die entsprechenden Segmente in die aus der Speicherverwaltung allozierten physischen Speicherseiten kopieren (dazu muss man sie eventuell erstmal temporär mappen, um darauf zugreifen zu können). Dann mappt man diese Speicherseiten in einen neuen Prozesskontext (also eigenes PageDir), wobei der Kernel natürlich auch in diesem Prozesskontext gemappt ist. Anschließend erstellt man einen Thread für diesen Kontext.
Und wo genau man seine Programme im virtuellen Adressraum hinlinkt bleibt jedem selbst überlassen (wobei man nichts an Adresse 0 linken sollte, sonst könnte das in Konflikt mit einem Nullpointervergleich kommen), manche bevorzugen es den Kernel virtual im oberen GB (also an 0xC0000000) zu haben (afaik Linux, lightOS, pedigree, kA wer noch) und die Programme in den unteren 0-3GB (beginnend eventuell bei 4MB oder sowas), andere bevorzugen es den Kernel virtual da zu haben wo er auch physisch liegt (tyndur zB).
lightOS
"Überlegen sie mal 'nen Augenblick, dann lösen sich die ganzen Widersprüche auf. Die Wut wird noch größer, aber die intellektuelle Verwirrung lässt nach.", Georg Schramm

ehenkes

  • Gast
Gespeichert
« Antwort #2 am: 21. June 2009, 12:18 »
Zitat
Naja, der erste Schritt wäre wohl das Dateiformat der ausführbaren Datei zu parsen
Momentan verwende ich aus didaktischen Gründen (siehe: http://www.henkessoft.de/OS_Dev/OS_Dev2.htm#mozTocId216641) noch ein sehr rudimentäres Dateiformat in der RAM Disk:
// Filesystem-Header:
ULONG nfiles;            // The number of files in the ramdisk.
--------------------------------------------------------------------------
//File-Header:
ULONG magic;             // Magic number, for error checking.
CHAR  name[64];          // Filename.
ULONG off;               // Offset in the initrd that the file starts.
ULONG length;            // Length of the file.
Momentan hole ich über die Funktionen des VFS und der RAM Disk (via Offset und Anzahl) die Daten in ein Array.

Zitat
die entsprechenden Segmente in die aus der Speicherverwaltung allozierten physischen Speicherseiten kopieren
Zur Zeit verwende ich bereits im Paging gemappte Bereiche, ist aber kein Problem, habe auch Heap (kmalloc/kfree).

Zitat
Dann mappt man diese Speicherseiten in einen neuen Prozesskontext (also eigenes PageDir), wobei der Kernel natürlich auch in diesem Prozesskontext gemappt ist. Anschließend erstellt man einen Thread für diesen Kontext.
Das mache ich genau so, nur in leicht umgekehrter Reihenfolge, ist aber egal, funktioniert. Eigener Prozess, eigenes PD, kernel stack.

Zitat
Und wo genau man seine Programme im virtuellen Adressraum hinlinkt, bleibt jedem selbst überlassen (wobei man nichts an Adresse 0 linken sollte, sonst könnte das in Konflikt mit einem Nullpointervergleich kommen), manche bevorzugen es den Kernel virtual im oberen GB (also an 0xC0000000) zu haben (afaik Linux, lightOS, pedigree, kA wer noch) und die Programme in den unteren 0-3GB (beginnend eventuell bei 4MB oder sowas), andere bevorzugen es den Kernel virtual da zu haben wo er auch physisch liegt (tyndur zB).
Zur Zeit läuft das bei mir eher "evolutiuonär" als sauber geplant. Gibt es zu diesem Thema einen Übersichtsartikel?













bluecode

  • Beiträge: 1 391
    • Profil anzeigen
    • lightOS
Gespeichert
« Antwort #3 am: 21. June 2009, 13:38 »
Zitat
die entsprechenden Segmente in die aus der Speicherverwaltung allozierten physischen Speicherseiten kopieren
Zur Zeit verwende ich bereits im Paging gemappte Bereiche, ist aber kein Problem, habe auch Heap (kmalloc/kfree).
Ich meinte definitv nicht malloc/free, sondern schon den Teil der Speicherverwaltung der Blöcke von Pagegröße allozieren kann. malloc/free kann man da zum allozieren nicht verwenden, man möchte ja Speicherseiten die man anschließend in den virtuellen Adressraum des Prozesses mappen kann. Mit malloc/free bekommt man aber (zumindest garantiert das Standard C offensichtlich nicht) 4kB Blöcke mit dem richtigen Alignment.

Zitat
Zitat
Und wo genau man seine Programme im virtuellen Adressraum hinlinkt, bleibt jedem selbst überlassen (wobei man nichts an Adresse 0 linken sollte, sonst könnte das in Konflikt mit einem Nullpointervergleich kommen), manche bevorzugen es den Kernel virtual im oberen GB (also an 0xC0000000) zu haben (afaik Linux, lightOS, pedigree, kA wer noch) und die Programme in den unteren 0-3GB (beginnend eventuell bei 4MB oder sowas), andere bevorzugen es den Kernel virtual da zu haben wo er auch physisch liegt (tyndur zB).
Zur Zeit läuft das bei mir eher "evolutiuonär" als sauber geplant. Gibt es zu diesem Thema einen Übersichtsartikel?
Nein, finde ich persönlich auch nicht sonderlich interessant, es ist ja im Prinzip egal wie du es machst und es entwickelt sich definitiv bei allen evolutionär (wie sowieso das ges. OS eigentlich) :wink:
lightOS
"Überlegen sie mal 'nen Augenblick, dann lösen sich die ganzen Widersprüche auf. Die Wut wird noch größer, aber die intellektuelle Verwirrung lässt nach.", Georg Schramm

ehenkes

  • Gast
Gespeichert
« Antwort #4 am: 21. June 2009, 16:16 »
Zitat
den Teil der Speicherverwaltung der Blöcke von Pagegröße allozieren kann. malloc/free kann man da zum allozieren nicht verwenden, man möchte ja Speicherseiten die man anschließend in den virtuellen Adressraum des Prozesses mappen kann.
Dieses Thema liegt wohl noch vor mir. Zur Zeit rufe ich mit einem durch Clonen erzeugten Prozess (Test-Programm hat momentan lediglich 29 Bytes, keine Daten, keinen Stack, keine Wunschadresse, ...) das auszuführende Programm mit call auf. Als Beispiel-Programm verwende ich:
[BITS 32]
start:
   Mov [0x000b8000], byte 'T'
   Mov [0x000b8002], byte 'e'
   Mov [0x000b8004], byte 's'
   Mov [0x000b8006], byte 't'
   Ret
http://www.henkessoft.de/OS_Dev/OS_Dev2.htm#mozTocId216641

ehenkes

  • Gast
Gespeichert
« Antwort #5 am: 01. July 2009, 21:42 »
Ich wundere mich gerade über Folgendes:

Eine Task gestartet im Kernel mit Supervisor code/data Segment ruft mit jmp ein Programm auf einer Page mit User Page auf. Dieses Programm greift auf Daten im Kernel  (pages im supervisor mode) zu und kann HLT (privilegierte Instruktion) ausführen, was mich etwas wundert, dachte das geht nicht von einer User Page aus. Kann mir das jemand erklären? (Intel Manual: erst Berücksichtigung von segment protection, dann page protection) Heißt das, dass man mit Supervisor Segment Privileg auch mit Programmen von user pages aus alles machen kann?

Ich hatte das anders verstanden:
If the processor detects a protection violation at either the segment level or the page level, the memory access is not carried out and an exception is generated. 

Sobald man die gestartete Task auf User Segment umschaltet, klappt die Protection bestens. Nun ergibt sich allerdings ein Problem: Die syscalls sind ja im Kernel Space, also nicht zugänglich für user. Daher zwei Fragen:
1) Wie kann man die syscalls im Linker nur auf eine Page stellen, die man dann user/read-only schalten kann (für Experimente aus dem Kernel heraus)
2) Wie kann man User-Programme mit einer einfachen userlib linken, die genau die syscalls des Kernels enthält?     

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 01. July 2009, 21:53 »
In welchem Ring du bist, hängt allein von cs ab. Page Level Protection heißt schlicht und einfach, dass ein Page Fault fliegt, wenn du gerade in Ring 3 bist und auf einer Kernelpage zugreifen willst.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #7 am: 01. July 2009, 21:56 »
Endlich mal eine klare Aussage. Das Gewäsch im Intel Manual ist diesbezüglich unklar. Wahrscheinlich soll dies das ausdrücken: "Page-level protections cannot be used to override segment-level protection." wird aber an einem anderen Beispiel vorgeführt:
"For example, a code segment is by definition not writable. If a code segment is paged, setting the R/W flag for the pages to read-write does not make the pages writable. Attempts to write into the pages will be blocked by segment-level protection checks."

So wie Taljeth es erklärt hat, ist es absolut klar.

Nun habe ich allerdings das Problem, wie ich bei gegenüber usern geschütztem Kernel einen user task von dort starten soll, der dann auf den Kernel zugreifen muss. Die syscalls sind z.B. auch im Kernel, wobei man diese per page protection frei geben könnte, wenn man sie nur in einer page hätte. Wäre für Experimente interessant. Wie kann ich per Linkerskript gewisse Variablen und die syscall-Funktionen alle in eine oder wenige Page schaffen, die ich dann user stellen kann? Wäre echt hilfreich für Experimente. 
« Letzte Änderung: 01. July 2009, 22:24 von ehenkes »

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 01. July 2009, 23:26 »
Das klingt kaputt.

Usertasks haben per Definition nichts im Kernelspace verloren. Man hat die Trennung ja nicht eingeführt, um sie gleich wieder aufweichen zu können. Das Spiel funktioniert normal genau umgekehrt: Der Userspace-Task lädt irgendwelche Parameter in die Register und evtl. auf seinen Stack und springt dann per Software-Interrupt (oder syscall/sysenter oder wie auch immer) in den Kernel. Der Kernel läuft dann in Ring 0 und hat auf alles Zugriff, kann sich also auch die Parameter aus den Seiten des Userspace-Tasks holen.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #9 am: 01. July 2009, 23:40 »
Zitat
Das klingt kaputt.
Nicht so langweilig! Ich lade Programme noch per incbin in den Kernel, schiebe diese dann in die RAM Disk, lade sie von dort mittels VFS in ein Array und schiebe sie von dort in den User Space.

Im Kernel starte ich im User Mode eine Task, die dann mittels syscall_function eine Funktion im Kernel (muss user-zugänglich sein) startet, die mittels jmp ein Programm im User Space startet.

Für diesen Mischmasch benötige ich im Kernel einen Bereich, den ich user-zugänglich machen kann.

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #10 am: 01. July 2009, 23:45 »
Man nimmt für den Wechsel aus dem User-Mode in den Kernel-Mode Interrupts (oder ähnliche Konstrukte). Da muss nichts vom Kernel in irgendeiner Form zugänglich sein.

Deine seltsame Trennung zwischen "Programmstart" und "Taskstart" solltest du mal überdenken. Ich weiß nicht, was das genau bezweckt, aber wie bereits gesagt:
Das klingt kaputt.
Dieser Text wird unter jedem Beitrag angezeigt.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #11 am: 01. July 2009, 23:50 »
Nicht so langweilig! Ich lade Programme noch per incbin in den Kernel, schiebe diese dann in die RAM Disk, lade sie von dort mittels VFS in ein Array und schiebe sie von dort in den User Space.
Spricht ja nichts dagegen und entspricht soweit auch noch dem, was ich gesagt habe.

Zitat
Im Kernel starte ich im User Mode eine Task, die dann mittels syscall_function eine Funktion im Kernel (muss user-zugänglich sein) startet, die mittels jmp ein Programm im User Space startet.

Für diesen Mischmasch benötige ich im Kernel einen Bereich, den ich user-zugänglich machen kann.
An dieser Stelle hast du aber ein Problem. Denn wenn die Syscalls ihre Arbeit machen sollen, brauchen sie Zugriff auf die Kerneldatenstrukturen. Entweder du definierst also den ganzen Kernel als Mischmasch  und hebst damit den Speicherschutz effektiv komplett auf, oder du hast keine Syscalls, die etwas sinnvolles tun.

Dadurch, dass dein Usertask auf eine Seite springt, die du persönlich als zum Kernel gehörig ansiehst, heißt ja noch lange nicht, dass der Code im Kernelmodus läuft. Er ist nach wie vor in Ring 3 und hat keinen Zugriff auf die Kernelinterna. An irgendeiner Stelle brauchst du den Ringwechsel und der geht am einfachsten mit einem Interrupt.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #12 am: 02. July 2009, 00:32 »
OK, ich habe zu kompliziert gedacht. Syscalls sollten nicht auf User Pages stehen, da man diese ja via Interrupt im Kernelmodus aufrufen kann.

Ich habe nun vereinfacht und die User-Funktion direkt aufgerufen und den Kontext mittels Interrupt gewechselt. Hat bestens geklappt!

Multitasking im Wechsel zwischen Supervisor- und User-Segment/-Page läuft  hervorragend.

Die Herausforderung besteht aber noch darin, das der "User"-Task nur Funktionen starten kann, die in User Pages stehen. Dazu muss die Startadresse in einem solchen Bereich stehen. Daher habe ich per Linkerskript versucht, nur ein bestimmtes Objektmodul in einer Page zu haben, damit man diese gezielt auf User-Page setzen kann. Das habe ich sinnloserweise mit syscall.o gemacht, wollte das Linkerskript hier mal zeigen, da es nicht völlig trivial ist, falls das mal jemand braucht:

OUTPUT_FORMAT("binary")
ENTRY(KernelStart)
SECTIONS
{
  . = 0x8000;
  .text   : { *(EXCLUDE_FILE(syscall.o).text) }
  . = 0xe000;
  .text1  : { syscall.o(.text)                }
  . = 0xf000;
  .data   : { *(.data)                        }
  .rodata : { *(.rodata)                      }
  .bss    : { *(.bss)                         }
}


Zitat
.text1          0x0000e000       0xd8
 syscall.o(.text)
 .text          0x0000e000       0xd8 syscall.o
                0x0000e091                _syscall_handler
                0x0000e015                _syscall_putch
                0x0000e081                _syscall_switch_context
                0x0000e061                _syscall_f4
                0x0000e051                _syscall_getpid
                0x0000e02f                _syscall_settextcolor
                0x0000e071                _syscall_nop
                0x0000e000                _syscall_puts
                0x0000f000                . = 0xf000

.data           0x0000f000       0xb8
 *(.data)
 .data          0x0000f000        0x0 syscall.o
 .data          0x0000f000        0x8 ckernel.o
                0x0000f004                _address_user
                0x0000f000                _ramdisk_start

In diesem Fall eben syscall.o (besser ein user.o) wurde hier sauber zwischen 0xe000 und 0xf000 eingelinkt.  :-)

Alles bestens. Nun kann die User-Programm-Welt starten.  :-D


« Letzte Änderung: 02. July 2009, 00:43 von ehenkes »

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #13 am: 02. July 2009, 00:40 »
Die Herausforderung besteht aber noch darin, das der "User"-Task nur Funktionen starten kann, die in User Pages stehen.
Was versuchst du zu schreiben? Einen Kernel oder eine Shared Library?

Ein Task startet keine Funktionen. Er macht Systemaufrufe über Interrupts.
Dieser Text wird unter jedem Beitrag angezeigt.

ehenkes

  • Gast
Gespeichert
« Antwort #14 am: 02. July 2009, 00:52 »
Zitat
Ein Task startet keine Funktionen. Er macht Systemaufrufe über Interrupts.
Völlig richtig.

Es geht nur um den Start eines User-Programms, das ich z.B. nach dem Laden aus der RAM Disk als ELF-Datei nach 0x400000 geschickt habe (0x400000 bis 0x500000 sind User Pages). Ich starte dies nun direkt so:
create_task (0x400060,3); 3 bedeutet Ring 3
Bisher hatte ich hier eine Funktion im Kernel-Bereich stehen, die dann u.a. einen Jump nach 0x400060 ausführte. In dem User-Programm führe ich dann Interrupts aus, um Kernel-Funktionen zu starten. Klappt alles gut.

Wie sehen nun solche Libs mit den syscalls aus, die man zu User-Programmen linkt. Eigentlich bereitet man doch nur Register (EAX Nummer des Syscalls im Funktionszeiger-Array) und den Stack oder Register für Argumente vor? Oder sehe ich das zu einfach?

So sieht momentan der fault- und syscall-handler aus (r entspricht genau esp):
ULONG fault_handler(ULONG esp)
{
    ULONG retVal;
    struct regs* r = (struct regs*)esp;

    if(!pODA->ts_flag)
    {
        retVal = esp;
    }
    else
    {
        if(r->int_no == 127) //syscall
        {
            /// TODO: decision about task_switch
            retVal = esp;
            //retVal = task_switch(esp);
            /// TODO: decision about task_switch
            //printformat(" nr: %d esp: %x ", r->eax, esp);
        }
        else
        {
            retVal = task_switch(esp); //new task's esp
        }
    }
//...
    if (r->int_no == 127) //sw-interrupt syscall
    {
        syscall_handler(r);
    }
    return retVal;
}

void syscall_handler(struct regs* r)
{
    // Firstly, check if the requested syscall number is valid. The syscall number is found in EAX.
    if( r->eax >= NUM_SYSCALLS )
        return;

    void* addr = syscalls[r->eax]; // Get the required syscall location.

    // We don't know how many parameters the function wants, so we just push them all onto the stack in the correct order.
    // The function will use all the parameters it wants, and we can pop them all back off afterwards.
    int ret;
    asm volatile (" \
      push %1; \
      push %2; \
      push %3; \
      push %4; \
      push %5; \
      call *%6; \
      add $20, %%esp; \
    " : "=a" (ret) : "r" (r->edi), "r" (r->esi), "r" (r->edx), "r" (r->ecx), "r" (r->ebx), "r" (addr));
    r->eax = ret;
}
« Letzte Änderung: 02. July 2009, 01:10 von ehenkes »

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #15 am: 02. July 2009, 19:23 »
Wie sehen nun solche Libs mit den syscalls aus, die man zu User-Programmen linkt. Eigentlich bereitet man doch nur Register (EAX Nummer des Syscalls im Funktionszeiger-Array) und den Stack oder Register für Argumente vor? Oder sehe ich das zu einfach?
Genau. Das komplizierteste Beispiel in tyndur dürfte der Syscall für RPC sein und selbst der ist noch relativ übersichtlich: Klick! (send_message meine ich damit).
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #16 am: 02. July 2009, 19:43 »
Danke für den Direkt-Link. syscalls Klappen nun bestens bei mir.
Ich habe mir vorsichtshalber noch eine "shared page" geschaffen:
OUTPUT_FORMAT("binary")
ENTRY(KernelStart)
SECTIONS
{
  . = 0x00008000;
  .text   : { *(EXCLUDE_FILE(shared_pages.o).text) }
  . = 0x0000d000;
  .text1  : { shared_pages.o(.text)                }
  . = 0x0000e000;
  .data   : { *(.data)                             }
  .rodata : { *(.rodata)                           }
  .bss    : { *(.bss)                              } 
}
bisher aber noch nicht verwendet.  :-)

« Letzte Änderung: 02. July 2009, 22:25 von ehenkes »

ehenkes

  • Gast
Gespeichert
« Antwort #17 am: 03. July 2009, 18:05 »
Dieses User-Programm ...
[BITS 32]
start:
   mov ebx, 0x4  ; 1. Argument
   mov ecx, 0x0  ; 2. Argument
   mov eax, 2    ; settextcolor
   int 0x7F
   
   mov ebx, 0x54 ; 'T'
   mov eax, 1    ; putch
   int 0x7F
   
   jmp start
... erzeugt z.B. diesen Output:
http://www.henkessoft.de/OS_Dev/Bilder/userprogram001.PNG

Gutes Gefühl, wenn man endlich Programme im User Land erzeugen kann.
Nun kommt natürlich die Frage nach dem sinnvollen Aufbau der syscalls als Dienstleistung für die User-Programme auf. Wie geht man das am besten an?


Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #18 am: 03. July 2009, 18:14 »
Was meinst du mit Aufbau der Syscalls? Welche Syscalls angeboten werden?
Dieser Text wird unter jedem Beitrag angezeigt.

ehenkes

  • Gast
Gespeichert
« Antwort #19 am: 03. July 2009, 18:25 »
Zitat
Welche Syscalls angeboten werden?
Ja, nach welchem System man das am besten aufbaut.
Standard scheint zu sein, dass man den POSIX-Standard anbietet.
Wie geht man das Thema aber allgemein an? Zielrichtungen und Vorteile/Nachteile einzelner Lösungen.

Der wiki-Artikel zu diesem Thema ist ja auch nicht gerade tiefschürfend.
http://lowlevel.brainsware.org/wiki/index.php/Systemaufruf
« Letzte Änderung: 03. July 2009, 19:55 von ehenkes »

 

Einloggen