Autor Thema: Programm laden und ausführen  (Gelesen 26896 mal)

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #20 am: 18. May 2009, 22:02 »
Frage: Wie führt man programme richtig aus?
Das fragen wir uns alle.

Angenommen du hast die Konzepte Prozess, Adressraum, Systemaufruf, usw. (halt das was Windows/Unix/tyndur... im Groben machen) bietet es sich an einfach das Programm an seine Wunschstelle in den Speicher zu laden, einen Task zu erstellen, und diesen in den Scheduler einzutragen. Der Prozess gibt dann dem Kernel über Systemaufrufe Anweisungen für I/O bzw. Interprozesskommunikation.
« Letzte Änderung: 18. May 2009, 22:04 von PorkChicken »
Dieser Text wird unter jedem Beitrag angezeigt.

ehenkes

  • Gast
Gespeichert
« Antwort #21 am: 18. May 2009, 22:09 »
Das verstehe ich alles - auch praktisch - soweit außer:

Adressraum - was meinst Du da ganz genau? Mein OS kann Paging, Heaps und RAM Disk erstellen und verwalten. VFS ebenfalls vorhanden. Syscalls/User Mode auch kein Problem.

Programm an seine Wunschstelle in den Speicher zu laden.
Passt das immer? Nehmen wir an, es sei ein xxx.com (alte DOS-Datei) mit ORG 0x100. Wo lade ich das hin? Man kann natürlich mittels Paging zaubern. Was sind denn typische Adressen von Programmen? Man liest da auch den Begriff "Relokation".

Prozess == Task?
« Letzte Änderung: 18. May 2009, 22:11 von ehenkes »

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #22 am: 18. May 2009, 22:16 »
Man kann nicht nur mit Paging zaubern, man macht das auch. Dafür ist es ja schließlich da. Die Programme liegen typischerweise alle an derselben Adresse (aber jeweils in ihrem eigenen Adreßraum, d.h. sie haben ein eigenes Page Directory, das für jedes Programm unterschiedliche Mappings enthält), die für dein OS spezifisch ist. Bei tyndur ist das 0x40000000 (also ab 1 GB, alles drunter ist Kernel).

Und Task = Prozeß könnte für dein OS im Moment noch gelten, aber sobald du Threads hast, hast du mehrere Tasks pro Prozeß.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #23 am: 18. May 2009, 22:23 »
Zitat
aber jeweils in ihrem eigenen Adreßraum, d.h. sie haben ein eigenes Page Directory, das für jedes Programm unterschiedliche Mappings enthält
Das habe ich schon bei jeder Task so eingefädelt:

task_t* create_task(void* entry)
{
    cli();
    page_directory_t* directory = clone_directory(current_directory);
    task_t* new_task            = (task_t*)k_malloc_stack(sizeof(task_t),0,0);

    new_task->id  = next_pid++;
    new_task->page_directory = directory; // Das ist der "Adressraum"
    new_task->kernel_stack   = k_malloc_stack(KERNEL_STACK_SIZE,1,0)+KERNEL_STACK_SIZE; // Der Kernelstack
    new_task->next = 0;

    // Das hier ist die Task-Liste (momentan einfaches Round-Robin)
    task_t* tmp_task = (task_t*)ready_queue;
    while (tmp_task->next)
        tmp_task = tmp_task->next;
    tmp_task->next = new_task;
Momentan ist nur der Kernel-Stack unterschiedlich. Für jede Task ein eigener Bereich.

ehenkes

  • Gast
Gespeichert
« Antwort #24 am: 18. May 2009, 23:10 »
Theoretisch ist mir das nun soweit klar. Ich würde mir das nur mal gerne praktisch anschauen. Gibt es denn dazu wirklich kein praktisches Tutorial mit Sourcecode. Irgendwie seltsam. Vielleicht könnt ihr mich bei tyndur zu der Stelle linken, an der das "Abspielen" eines Programmes läuft.

bluecode

  • Beiträge: 1 391
    • Profil anzeigen
    • lightOS
Gespeichert
« Antwort #25 am: 19. May 2009, 11:44 »
modules.c, die Funktion  load_multiboot_modules(struct multiboot_info * multiboot_info) wird von der main-Funktion aufgerufen und macht aus den Modulen die durch Grub geladen wurden Tasks. Alle davon aufgerufenen Funktionen findest du sicherlich selbst.

Das ganze in gelb kannst dir auch bei lightOS anschauen: kernel.cpp hier werden die Module geladen. Wieder gilt, alle davon aufgerufenen Klassen/Funktionen findest du sicherlich selbst. :-)
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 #26 am: 19. May 2009, 19:20 »
Zitat
und macht aus den Modulen die durch Grub geladen wurden Tasks
Wie sieht so ein "Modul" in tyndur aus? Ist das eine elf-Datei?

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #27 am: 19. May 2009, 20:11 »
Ja, ein ganz normales tyndur-Programm, also eine ELF, die nach 0x40000000 gelinkt ist.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #28 am: 19. May 2009, 23:38 »
Ich habe nun zunächst ein Virtuelles Filesystem und eine Initial RAM Disk eingebaut. Damit kann ich überhaupt Fileformate und FileNodes verwalten.

Bisher habe ich mein kleines Programm mit incbin eingelinkt, mit memcpy direkt hinter die Initial RAM Disk verschoben und dort mit
asm volatile ("call *%0"::"r"( ramdisk_start + (ULONG)&prog_start - (ULONG)&file_data_start ) ); innerhalb einer Task gestartet. In Assembler:
; data for ramdisk
global _file_data_start
global _file_data_end
global _prog_start
_file_data_start:
incbin "file_data.da1"; file_data
_prog_start:
incbin "file_data.dat"; program
_file_data_end:

Wenn ich nun mehrere Programme in die RAM Disk mittels Fileformat (proprietär oder ELF) einbinde, könnte ich doch mit einer Routine run(name) das entsprechende Programm starten. Der nächste Schritt wäre dann die Ressourcenbereitstellung für diese Programme (I/O, lokale Daten, Heap, ...).
Sehe ich das richtig?
« Letzte Änderung: 20. May 2009, 00:00 von ehenkes »

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #29 am: 20. May 2009, 19:51 »
Bisher habe ich mein kleines Programm mit incbin eingelinkt, mit memcpy direkt hinter die Initial RAM Disk verschoben und dort mit
asm volatile ("call *%0"::"r"( ramdisk_start + (ULONG)&prog_start - (ULONG)&file_data_start ) ); innerhalb einer Task gestartet.
Huch? Wieso startest du den Task nicht einfach gleich mit dem richtigen eip?

Zitat
Wenn ich nun mehrere Programme in die RAM Disk mittels Fileformat (proprietär oder ELF) einbinde, könnte ich doch mit einer Routine run(name) das entsprechende Programm starten. Der nächste Schritt wäre dann die Ressourcenbereitstellung für diese Programme (I/O, lokale Daten, Heap, ...).
Sehe ich das richtig?
Ist es möglicher Weg, ja. Wenn du ein Programm laufen hast, willst du vermutlich als erstes ein paar Syscalls implementieren, z.B. um eine Meldung auszugeben.

Ab dieser Stelle treibt dann eigentlich der Userspace deine Entwicklung - der Kernel bekommt immer diejenigen Syscalls neu dazu, die irgendein Programm gerade gebraucht hat. Das heißt, du solltest ungefähr wissen, was dein erstes Programm eigentlich machen soll. Eine Shell, eine Art init, das andere Programme nachlädt, was auch immer. Und spätestens hier solltest du eine Idee haben, wie du persönlich dein OS haben willst - ansonsten wird es nur noch ein langweiliges OS, womöglich der siebenhundertvierunddrölfzigste Unix-Klon...
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #30 am: 20. May 2009, 19:56 »
Da ich noch keine shell habe, wäre dies ein sinnvolles Anwendungsprogramm.  :-) Wie man aus dem user mode syscalls aufruft, weiß ich, aber wie erstellt man dies in Programmen? Da benötigt man doch so etwas wie eine API. In C hat man da typsicherweise eine api.h/api.lib, die man dann einbindet. Wie geht das in Assembler?

Wie baut man die API von innen heraus auf?
« Letzte Änderung: 21. May 2009, 11:42 von ehenkes »

ehenkes

  • Gast
Gespeichert
« Antwort #31 am: 21. May 2009, 12:21 »
Zitat
Wieso startest du den Task nicht einfach gleich mit dem richtigen eip?
Das Programm liegt in den mit incbin eingeschleusten Daten am Offset &prog_start minus &file_data_start. Nach dem Verschieben mittels memcpy muss ich dann noch die Basis ramdisk_start addieren, um die Startadresse zu finden. Vielleicht denke ich da auch zu kompliziert.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #32 am: 21. May 2009, 13:05 »
Deine Berechnung ist ja schön und gut - ich habe sie mir nicht näher angeschaut und gehe einfach davon aus, dass sie so stimmt.

Wenn ich es richtig verstanden habe, führst du als allererstes in dem neu erstellten Task ein call aus:
asm volatile ("call *%0"::"r"( ramdisk_start + (ULONG)&prog_start - (ULONG)&file_data_start ) );Was ich mich frage, ist, warum du nicht einfach direkt den Task an dieser Adresse startest:
create_task(ramdisk_start + (&prog_start - &file_data_start));
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #33 am: 21. May 2009, 15:17 »
Problem 1: Eine aufgerufene Task muss bisher noch vom nächsten task_switch, also z.B. von IRQ0, umgeschaltet werden. Ein einfaches "Aufhören" durch ret geht noch nicht. Das knallt noch, vermutlich auf dem Stack mit den Tasks.  :-D Daher die Endlos-while-Schleife in der von der Task aufgerufenen Funktion.

Wie werde ich die while-Schleife los? Dann kann ich auch direkt mit create_task ohne Zwischenfunktion in einer Endlos-while-loop an den Start springen.

Problem 2: Ich möchte einen Task im User Mode (Ring 3) durchführen, was ich bisher nicht schaffe.


//in main():
void test1(ULONG addr, ULONG dummy)                 
{
  while(TRUE)
  {
      asm volatile ("call *%0"::"r"(addr));
  }
}

task_t* task6 = create_task2(test1,0,ramdisk_start+(ULONG)&prog_start-(ULONG)&file_data_start,0);

//in task.c:
task_t* create_task2(void* entry, UCHAR privilege, ULONG arg1, ULONG arg2){...} //siehe paralleler Thread
Das klappt leider nur mit Privileg 0, noch nicht mit 3. Bei 3 endet es mit einem GPF. Dabei verwende ich innerhalb der Funktion test1 doch gar nichts vom Kernel, oder etwa doch? Liegt das daran, dass test1 im Kernel definiert ist? 

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #34 am: 21. May 2009, 15:54 »
Zum Beenden des Tasks willst du wahrscheinlich einen Syscall benutzen. Und bei Exceptions wirst du den Task wahrscheinlich auch beenden wollen.

Wegen Ring 3: Der einzige wirkliche Unterschied ist, dass das TSS benutzt wird. Hast du das korrekt eingerichtet und ss0/esp0 sinnvoll belegt? (Wobei letzteres eigentlich erst beim Rücksprung aus dem Task Probleme macht)
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #35 am: 21. May 2009, 15:59 »
Ich habe nun folgendes probiert:
in main (extra die Konstante verwendet:
void test()                   
{
  while(TRUE)
  {
      asm volatile ("call *%0"::"r"( 0x40082372 ) ); // <- program from outside "loaded" by incbin ...
  }
}

task_t* task5 = create_task (test, 3);

in syscall.h/syscall.c
DECL_SYSCALL1(puts, UCHAR*)
DECL_SYSCALL0(test)
DECL_SYSCALL2(test1, ULONG, ULONG)

DEFN_SYSCALL1(puts,  0, UCHAR*);
DEFN_SYSCALL0(test,  1)
DEFN_SYSCALL2(test1, 2, ULONG, ULONG)

ULONG num_syscalls  = 3;
static void* syscalls[3] =
{
    &puts,
    &test,
    &test1
};

Dennoch klappt es nicht mit dem User Mode. Woran liegt das?
Im Kernel Mode übrigens kein Problem.
Die Konstante habe ich durch Screen-Ausdruck kontrolliert:
printformat("%x\n",ramdisk_start + (ULONG)&prog_start - (ULONG)&file_data_start);

ehenkes

  • Gast
Gespeichert
« Antwort #36 am: 21. May 2009, 16:12 »
Zitat
Zum Beenden des Tasks willst du wahrscheinlich einen Syscall benutzen.
Wie sieht die Funktion da aus? Normalerweise endet doch ein Programm, das man aufruft, mit ret. Wie führe ich da einen syscall aus bei create_task(entry)?
Bei create_task(moo) und moo(){...;syscall_BeendeTaskOrdentlich;} kann ich mir das vorstellen. BeendeTaskOrdentlich wäre dann z.B. ein task_switch generiert durch den Interrupt des syscalls.

Zitat
Und bei Exceptions wirst du den Task wahrscheinlich auch beenden wollen.
Das passiert jetzt schon weil ich den task_switch direkt im Interrupt analog tyndur durchführe.

Zitat
Wegen Ring 3: Der einzige wirkliche Unterschied ist, dass das TSS benutzt wird. Hast du das korrekt eingerichtet und ss0/esp0 sinnvoll belegt? (Wobei letzteres eigentlich erst beim Rücksprung aus dem Task Probleme macht)
Das mache ich nur in task_switch:
ULONG task_switch1(ULONG esp)
{
    if(!current_task) return esp;

    current_task->esp = esp;   

    current_task = current_task->next;
    if(!current_task) current_task = ready_queue;
 
    current_directory = current_task->page_directory;
    asm volatile("mov %0, %%cr3" : : "r" (current_directory->physicalAddr));
    tss_entry.esp0 = (current_task->kernel_stack)+KERNEL_STACK_SIZE;

    return current_task->esp; 
}
Ich setze also nur cr3 (nicht im TSS) und esp0 im TSS.

In create_task (siehe Threads) verwende ich TSS überhaupt nicht, sondern generiere nur einen adressraum (PD) und kmalloc'e den kernel stack. Alle Infos gehen in meine Task-Struktur.

« Letzte Änderung: 21. May 2009, 16:16 von ehenkes »

ehenkes

  • Gast
Gespeichert
« Antwort #37 am: 21. May 2009, 16:30 »
Folgendes führt leider auch noch zum GPF:
task_t* create_task(void* entry, UCHAR privilege)
{
    cli();
    page_directory_t* directory = clone_directory(current_directory);
    task_t* new_task            = (task_t*)k_malloc(sizeof(task_t),0,0);

    new_task->id  = next_pid++;
    new_task->page_directory = directory;
    new_task->kernel_stack   = k_malloc(KERNEL_STACK_SIZE,1,0)+KERNEL_STACK_SIZE;
    new_task->next = 0;

    task_t* tmp_task = (task_t*)ready_queue;
    while (tmp_task->next)
        tmp_task = tmp_task->next;
    tmp_task->next = new_task; // ... and extend it

    ULONG* kernel_stack = (ULONG*) new_task->kernel_stack;

    *(--kernel_stack) = 0x0202; // eflags = interrupts activated and iopl = 0

    ULONG code_segment=0x08, data_segment=0x10;
    if(privilege == 0) code_segment = 0x08;
    if(privilege == 3) code_segment = 0x1B; // 0x18|0x3=0x1B

    *(--kernel_stack) = code_segment; // cs

    *(--kernel_stack) = (ULONG)entry; // eip

    *(--kernel_stack) = 0; // error code
    *(--kernel_stack) = 0; // interrupt nummer

    // general purpose registers w/o esp
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;

    // data segment registers
    if(privilege == 0) data_segment = 0x10;
    if(privilege == 3) data_segment = 0x23; // 0x20|0x3=0x23

    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;

    //setup TSS
    tss_entry.ss0   = 0x10;
    tss_entry.esp0  = new_task->kernel_stack;
    tss_entry.ss    = data_segment;

    //setup task_t
    new_task->ebp = 0xd00fc0de; // test value
    new_task->esp = (ULONG)kernel_stack;
    new_task->eip = (ULONG)irq_tail;

    sti();
    return new_task;
}
Ich habe
    //setup TSS
    tss_entry.ss0   = 0x10;
    tss_entry.esp0  = new_task->kernel_stack;
    tss_entry.ss    = data_segment;
ergänzt.
Der GPF gibt mir folgende Daten aus meinem OS:
TSS log:
esp0: 40212000h ss0: 00000010h cr3: 00000000h eip: 00000000h eflags: 00000000h eax: 00000000h ecx: 00000000h edx: 00000000h ebx: 00000000h esp: 00000000h ebp: 00000000h esi: 00000000h edi: 00000000h es: 00000010h cs: 00000008h ss: 00000010h ds: 00000010h fs: 00000010h gs: 00000010h

err_code: 00000020h address(eip): 0000829Bh edi: 0000832Ch esi: 00002420h ebp: 4020D6C8h eax: 402117C0h ebx: 000001EDh ecx:
000B8DB3h edx: 00000020h cs: 8 ds: 16 es: 16 fs: 16 gs 16 ss 35
int_no 13 eflags 00010012h useresp 00000023h

General Protection Fault >>> Exception. System Halted!
Der GPF entsteht beim Wechsel auf den task5, wenn ich Privileg 3 verwende:
task_t* task5 = create_task (test, 3);
Bei 0 ist alles o.k.

Die Werte in der GPF sehen noch voll nach Kernel aus?  :?

Ist das richtig, dass ich z.B. das Datensegment auf 0x23 anstelle 0x20 setze, ebenso das Code-Segment 0x1B anstelle 0x18?
SS0 ist doch 0x10 (wie data segment) für den Kernel-Stack?
« Letzte Änderung: 21. May 2009, 16:38 von ehenkes »

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #38 am: 21. May 2009, 16:40 »
Zitat
Zum Beenden des Tasks willst du wahrscheinlich einen Syscall benutzen.
Wie sieht die Funktion da aus? Normalerweise endet doch ein Programm, das man aufruft, mit ret. Wie führe ich da einen syscall aus bei create_task(entry)?
Bei create_task(moo) und moo(){...;syscall_BeendeTaskOrdentlich;} kann ich mir das vorstellen. BeendeTaskOrdentlich wäre dann z.B. ein task_switch generiert durch den Interrupt des syscalls.
Genau so macht man es auch. Ein Programm endet nie mit ret. Was du vielleicht meinst, ist dass main() eine ganz normale Funktion ist, die entsprechend auch ein ret am Ende hat. Aber main ist normal nicht der Entrypoint, sondern es gibt noch ein _start, das die libc initialisiert und dann erst main aufruft (und hinterher eben den Syscall zum Beenden).

Zitat
In create_task (siehe Threads) verwende ich TSS überhaupt nicht, sondern generiere nur einen adressraum (PD) und kmalloc'e den kernel stack. Alle Infos gehen in meine Task-Struktur.
Ist auch nicht zwingend nötig. Wenn du im TSS einmal gültige Werte stehen hast, brauchst du die nicht mehr zu ändern.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #39 am: 21. May 2009, 16:52 »
TSS log:
esp0: 40212000h ss0: 00000010h cr3: 00000000h eip: 00000000h eflags: 00000000h eax: 00000000h ecx: 00000000h edx: 00000000h ebx: 00000000h esp: 00000000h ebp: 00000000h esi: 00000000h edi: 00000000h es: 00000010h cs: 00000008h ss: 00000010h ds: 00000010h fs: 00000010h gs: 00000010h

err_code: 00000020h address(eip): 0000829Bh edi: 0000832Ch esi: 00002420h ebp: 4020D6C8h eax: 402117C0h ebx: 000001EDh ecx:
000B8DB3h edx: 00000020h cs: 8 ds: 16 es: 16 fs: 16 gs 16 ss 35
int_no 13 eflags 00010012h useresp 00000023h

General Protection Fault >>> Exception. System Halted!
Der GPF entsteht beim Wechsel auf den task5, wenn ich Privileg 3 verwende:
task_t* task5 = create_task (test, 3);
Bei 0 ist alles o.k.

Die Werte in der GPF sehen noch voll nach Kernel aus?  :?
Okay, machen wir mal eine Runde Exception-Lesen. Deine Segmentregister sind alle Kernelspace, bis auf ss, das ist 0x23 (Hexzahlen als Ausgabe wären hier eigentlich sinnvoller). Der Errorcode ist 0x20 (Ringnummer wird rausmaskiert), also wirst du vermutlich versuchen, ds oder es zu setzen. Normal hätte ich jetzt an den GDT-Einträgen gezweifelt, aber ss ist ja anscheinend schon auf denselben Wert gesetzt. Sind vielleicht die Segmentregister für ds/es noch auf Ring 0 und beim Ringwechsel kracht es dann?

Zitat
Ist das richtig, dass ich z.B. das Datensegment auf 0x23 anstelle 0x20 setze, ebenso das Code-Segment 0x1B anstelle 0x18?
SS0 ist doch 0x10 (wie data segment) für den Kernel-Stack?
Ja, alles richtig.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

 

Einloggen