Autor Thema: Supervisor and User Mode  (Gelesen 3449 mal)

ehenkes

  • Gast
Gespeichert
« am: 21. May 2009, 12:15 »
Zur Zeit habe ich syscalls (software interrupt 0x79) und switch_to_user_mode() implementiert. Der Ablauf funktioniert bisher nur im Kernel Mode (Privileg 0).

Mein Aufbau zum Testen der beiden Modes:

in os.h:
//#define USERMODE
#define KERNELMODE

in gdt.c:
void gdt_install()
{
    /* Setup the GDT pointer and limit */
    gdt_register.limit = (sizeof(struct gdt_entry) * NUMBER_GDT_GATES)-1;
    gdt_register.base  = (ULONG) &gdt;

    /* GDT GATES -  desriptors with pointers to the linear memory address */
    gdt_set_gate(0, 0, 0, 0, 0);                // NULL descriptor
    gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // CODE, privilege level 0 for kernel code
    gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // DATA, privilege level 0 for kernel code

    #ifdef KERNELMODE // in os.h
    gdt_set_gate(3, 0, 0xFFFFFFFF, 0x9A, 0xCF); // CODE, privilege level 0 for kernel code
    gdt_set_gate(4, 0, 0xFFFFFFFF, 0x92, 0xCF); // DATA, privilege level 0 for kernel code
    #endif

    #ifdef USERMODE   // in os.h
    //gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // User mode code segment
    //gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // User mode data segment
    #endif

    write_tss(5, 0x10, 0x0);                    // num, ss0, esp0

    gdt_flush((ULONG)&gdt_register); // inclusive gdt_load() in assembler code
    tss_flush();                     //in flush.asm: privilege level 0 for kernel mode 0x28

in task.c:
void switch_to_user_mode()
{
    set_kernel_stack(current_task->kernel_stack+KERNEL_STACK_SIZE); //tss_entry.esp0 = ...;

    // Set up a stack structure for switching to user mode (ring 3 => RPL = 0x3)
    // 0x200 is IF flag (enabling interrupts)
    // 0x20|0x3=0x23  // 0x18|0x3=0x1B
    // $1F means "the address of the next label '1:', searching forward"

#ifdef USERMODE    // in os.h
      asm volatile("  \
      cli; \
      mov $0x23, %ax; \
      mov %ax, %ds; \
      mov %ax, %es; \
      mov %ax, %fs; \
      mov %ax, %gs; \
                    \
      mov %esp, %eax; \
      pushl $0x23; \
      pushl %esp; \
      pushf; \
      pop %eax; \
      or $0x200, %eax; \
      push %eax; \
      pushl $0x1B; \
      push $1f; \
      iret; \
    1: \
      ");
#endif
#ifdef KERNELMODE  // in os.h
      asm volatile("  \
      cli; \
      mov $0x20, %ax; \
      mov %ax, %ds; \
      mov %ax, %es; \
      mov %ax, %fs; \
      mov %ax, %gs; \
                    \
      mov %esp, %eax; \
      pushl $0x20; \
      pushl %esp; \
      pushf; \
      pop %eax; \
      or $0x200, %eax; \
      push %eax; \
      pushl $0x18; \
      push $1f; \
      iret; \
    1: \
      ");
#endif
}

in flush.asm:
_tss_flush:
   mov ax, 0x2B      ; Load the index of our TSS structure - The index is
                     ; 0x28, as it is the 5th selector and each is 8 bytes
                     ; long, but we set the bottom two bits (making 0x2B)
                     ; so that it has an RPL of 3, not zero.
   ltr ax            ; Load 0x2B into the task state register.
   ret

Problem:
Wenn ich in main() switch_to_user_mode() aufrufe, endet das Programm sofort mit einem GPF. Das nachfolgende syscall_puts("Hello, user world!\n"); wird nicht mehr erreicht.

Fragen:
Ob ich in tss_flush auf 0x2B oder 0x28 (RPL 3 oder 0) setze, spielt keine Rolle. Könnt ihr zur Bedeutung des Privilegs im TSS bitte etwas erläutern?

Was muss ich ändern, damit syscall_puts(...) erreicht und ausgeführt wird?

Was muss man in create_task(...) beachten, damit eine Task im User Mode lauffähig ist?

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 21. May 2009, 13:09 »
Wieso in aller Welt willst du denn im Kernel in den Usermode springen? Du legst einfach passende Segmentregister auf den Kernelstack des Tasks und dann passiert der Ringwechsel automatisch beim ganz normalen Taskswitch-iret.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #2 am: 21. May 2009, 13:39 »
Zitat
Wieso in aller Welt willst du denn im Kernel in den Usermode springen?
Diesen Blödsinn habe ich aus dem Tutorial (Kap. 10) von James Molloy.  :-D
In Sachen Multitasking (Kap. 9) und User Mode (Kap. 10) hat er offensichtlich nur unsinniges Zeug verzapft, von dem man wenig lernen kann, außer wie man PF und GPF analysiert.  :-o

Wenn ich in main() - also im Kernel - auf switch_to_user_mode() verzichte, kann man #define USERMODE verwenden, und die Funktion syscall_puts funktioniert auch.
Zitat
Du legst einfach passende Segmentregister auf den Kernelstack des Tasks und dann passiert der Ringwechsel automatisch beim ganz normalen Taskswitch-iret.
Klingt gut.

Kannst Du noch etwas zum Privileg in TSS sagen? Beim Multitasking wird es Tasks sowohl im Kernel als auch im User Mode geben.
« Letzte Änderung: 21. May 2009, 13:52 von ehenkes »

ehenkes

  • Gast
Gespeichert
« Antwort #3 am: 21. May 2009, 14:31 »
Ich habe das nun nach create_task(...) verlagert, indem ich dort noch einen Parameter 'privilege' geschaffen habe. Dementsprechend wird cs, ds, es, fs, gs gesetzt:
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;

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

    sti();
    return new_task;
}
Nun kann ich Tasks beim Erstellen auf Privileg 0  oder 3 einstellen.
Wo wird eigentlich ss eingestellt?
« Letzte Änderung: 21. May 2009, 14:34 von ehenkes »

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #4 am: 22. May 2009, 13:15 »
Zitat
Wieso in aller Welt willst du denn im Kernel in den Usermode springen?
Diesen Blödsinn habe ich aus dem Tutorial (Kap. 10) von James Molloy. :-D
In Sachen Multitasking (Kap. 9) und User Mode (Kap. 10) hat er offensichtlich nur unsinniges Zeug verzapft, von dem man wenig lernen kann, außer wie man PF und GPF analysiert. :-o
Ich denke mal, dass dieses "switch_to_user_mode" einfach nur dazu da ist, um zu Zeigen, dass die eingebauten System Calls funktionieren, mehr nicht.
Außerdem steht dort, dass es bei dem Umschalten in den User mode und dem Ausführen der System Calls zu Problemen führen kann (Abschnitt 10.3.1).

ehenkes

  • Gast
Gespeichert
« Antwort #5 am: 22. May 2009, 22:25 »
Genau nix als Probleme! :-D
Inzwischen habe ich einen ersten kleinen Erfolg erreicht. Das Thema User Mode wird in keinem Tutorial vernünftig behandelt, soweit diese mir bekannt sind. Leider stehe ich auch noch am Anfang.   

 

Einloggen