Autor Thema: Multitasking  (Gelesen 21128 mal)

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« am: 06. June 2008, 14:45 »
Ahoi
Ich habe da mal mehrere Fragen, wegen des Multitaskings. :-D
 
Also bis jetzt habe ich das so verstanden:
Es gibt einen Prozess, der erstellt wird, wenn z.B. ein Programm ausgeführt werden soll.
Dieser Prozess hat nun zur Ausführung Unterprozesse also Threads.

Mit dieser Aussage habe ich nun 2 Strukturen erstellt, einmal für einen Process und einmal für einen Thread.
typedef struct process
{
/* Process Stack */
unsigned int esp;
        /* Kernel Stack */
        unsigned int kernel_stack;
/* Process ID */
unsigned int pid;
/* Process Name */
char *process_name;
/* Page Directory */
page_directory_t cr3;
/* Lists with Threads from this process */
list_t *threads;
/* Max. Execution Time */
unsigned int timer_ticks_for_execution;
/* Execution time left */
unsigned int timer_ticks_left;
/* Stores the Process State */
unsigned int process_state;
} process_t;

typedef struct thread
{
/* Thread Stack */
unsigned int esp;
/* Thread Name */
char *thread_name;
/* stores the thread state */
unsigned int thread_state;
/* Is Thread in Virtual 8086 Mode */
bool is_vm8086;
} thread_t;

Nun muss ich einen Stack anlegen, wo der eip auf start gesetzt wird.
Ist die Struktur so vollständig oder fehlt da noch was, was unbedingt rein muss?
 
Bei den VM86 Tasks wollte ich den Code komplett über Threads ausführen lassen. Kann ich das so machen?
 
Brauche ich nur den einen Stack, weil ich meine wo anders gelesen zu haben, dass ich mehr als einen Stack brauche.  :-o
 
Gruß Christian
« Letzte Änderung: 06. June 2008, 15:56 von ChristianF »

Termite

  • Beiträge: 239
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 06. June 2008, 15:24 »
Jede Task Thread oder auch Prozess braucht seinen eigenen Stack. sonst knallt das ganz böse.


ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #2 am: 06. June 2008, 15:41 »
Mmmhhh...
Nun war aber noch in einem anderen Thread in dem Forum hier die Aussage, dass man neben dem Prozessstack nun auch den Kernelstack brauch.
 
So habe ich das zumindest in dem Code von Lost gesehen.
Dann würde ich gerne auf die TSS komplett verzichten. Geht das?
 
Sie hier: http://lowlevel.brainsware.org/forum/index.php?topic=1485.0
 
Gruß Christian
 
PS: Geplant ist momentan nur die kompatiblität zu 386er kompatiblen Prozessoren.
« Letzte Änderung: 06. June 2008, 15:55 von ChristianF »

Korona

  • Beiträge: 94
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 06. June 2008, 16:22 »
Aufs TSS kannst du nicht verzichten, beim Wechsel Ring3 -> Ring0 wird SS und ESP aus dem TSS gelesen. Ohne TSS kriegst du da eine GPF.
Du brauchst für jeden Thread einen Usermode und einen Kernelstack. Sonst könnte es passieren dass beim Aufruf von Interrupts der Stack überläuft. Da wie gesagt beim Ringwechsel SS und ESP neu geladen werden ist es auch garnicht möglich nur einen Stack zu verwenden.

Ich würde mir aber nochmal überlegen, ob du deiner Prozessstruktur Felder für Stacks gibst. Ich würde eher Threads als "Tasks" (auch kein klar definierter Ausdruck aber mir fält kein besserer ein) betrachten und Prozesse als die Umgebung in der Threads laufen. Sprich: Nicht Prozesse werden gescheduled, sondern Threads und Prozesse fassen lediglich alle Threads eines Address-Spaces zusammen.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #4 am: 08. June 2008, 20:46 »
Okay.
Das würde heißen, dass ich eine TSS für den Kernel einbaue, praktisch also eine struktur "tss_t", die dann mit den 2 Werten befüllt wird.

Des Weiteren brauche ich auch eine TSS für die VM8086 Tasks. Für diese hatte ich mir dass dann so geplant, dass ich einen Deskriptor in der GDT habe, den ich jedes mal nach belieben umbiegen, sollte der aktuelle Task ein Virtueller sein.
 
 
Nicht Prozesse werden gescheduled, sondern Threads und Prozesse fassen lediglich alle Threads eines Address-Spaces zusammen.
Mmmmhhhh....
Ich hatte würde jedem Task einen 4 GB Addressraum geben, ausgenommen der für den Kernel reservierte Platz, was also bedeutet, dass ich bei jedem Taskwechsel das cr3 Register neuladen muss, also so:
 
  • Taskwechsel beginnt
  • Kernel Page Directory laden
  • Neuen Task "suchen"
  • Page Directory des Tasks laden
  • Task starten

 
Da habe ich jetzt noch eine Frage:
Wie sieht dass dan im speziellen aus, wenn ich ein Programm starte, z.B. einen editor?
  • Task wird erstellt
  • Programm wird in eine freie Speicherstelle geladen
  • (und nun ?)

 
Gruß Christian

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 08. June 2008, 20:52 »
Nicht Prozesse werden gescheduled, sondern Threads und Prozesse fassen lediglich alle Threads eines Address-Spaces zusammen.
Mmmmhhhh....
Ich hatte würde jedem Task einen 4 GB Addressraum geben, ausgenommen der für den Kernel reservierte Platz, was also bedeutet, dass ich bei jedem Taskwechsel das cr3 Register neuladen muss, also so:
Alle Threads eines Prozesses teilen sich einen Adressraum. Das ist mehr oder weniger die Definition eines Threads. cr3 wirst du natuerlich trotzdem neu laden muessen, wenn du nicht gerade zwischen Threads desselben Prozesses umschaltest.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Korona

  • Beiträge: 94
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 08. June 2008, 21:12 »
Des Weiteren brauche ich auch eine TSS für die VM8086 Tasks. Für diese hatte ich mir dass dann so geplant, dass ich einen Deskriptor in der GDT habe, den ich jedes mal nach belieben umbiegen, sollte der aktuelle Task ein Virtueller sein.
Ja, ein TSS reicht, du änderst dann einfach bei jedem Threadwechsel die ESP und SS Addressen.

Zitat
  • Taskwechsel beginnt
  • Kernel Page Directory laden
  • Neuen Task "suchen"
  • Page Directory des Tasks laden
  • Task starten
Wenn der Kernel in alle Prozesse gemappt ist entfällt der zweite Schritt. Cr3 Wechsel sind sehr teuer also solltest du den Kernel in alle Prozesse mappen.

Zitat
Da habe ich jetzt noch eine Frage:
Wie sieht dass dan im speziellen aus, wenn ich ein Programm starte, z.B. einen editor?
  • Task wird erstellt
  • Programm wird in eine freie Speicherstelle geladen
  • (und nun ?)
-> Virtuellen Adressraum erstellen
-> Auf der Programmdatei auslesen welche Speicherprogramme das Programm benutzt
-> Diese Speicherbereiche in den neuen Adressraum mappen und das Programm entsprechent dorthin laden
-> Einstiegspunkt aus der Datei lesen und neuen Thread erstellen mit diesem Einstiegspunkt als Startadresse
« Letzte Änderung: 08. June 2008, 21:13 von Korona »

nooooooooos

  • Beiträge: 734
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 08. June 2008, 22:25 »
Ja und schlussendlich musst du den neuen Prozess noch in deine Liste eintragen, damit der Scheduler ihn das nächste Mal drannimmt...

Gruss
Noooooooooos

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #8 am: 09. June 2008, 09:14 »
Ok dann habe ich das so jetzt verstanden.
 
Nun habe ich mir eine Struktur für die TSS mit Hilfe der folgenden Quelle erstellt: Link.
Danach habe ich eine Funktion geschrieben, die eine TSS initialisiert, und zwar in folgenden Schritten:
  • GDT Eintrag setzen
  • GDT Erneuern, mittels gdt_flush()
  • ss0 auf Kernel Data Selector setzen (0x10)
  • ltr 0x28
Nun sollte ich allerdings den Wert esp0 noch befüllen, doch mit was ???
Soll ich den aktuellen esp-Wert verwenden, oder irgendwie was anderes ???
 
Hier ist nochmal der Code, von dem oben erklärten:
typedef struct tss
{
unsigned short __blh, link;
unsigned int esp0;
unsigned short __ss0, ss0;
unsigned int esp1;
unsigned short __ss1, ss1;
unsigned int esp2;
unsigned short __ss2, ss2;
unsigned int cr3;
unsigned int eip;
unsigned int eflags;
unsigned int eax, ecx, edx, ebx;
unsigned int esp, ebp, esi, edi;
unsigned short __es, es;
unsigned short __ds, ds;
unsigned short __fs, fs;
unsigned short __gs, gs;
unsigned short __ldtr, ldtr;
} tss_t;

tss_t kernel_tss;
void load_kernel_tss(void)
{
gdt_set_gate(5, &kernel_tss, (&kernel_tss+sizeof(tss_t)), 0x89, 0xCF);
gdt_flush();

kernel_tss.ss0 = KERNEL_DATA_SEL;
// kernel_tss.esp0 =

LTR(5*0x8)
}
Anmerkung: LTR() ist ein Makro.
 
 
Gruß Christian

Korona

  • Beiträge: 94
    • Profil anzeigen
Gespeichert
« Antwort #9 am: 09. June 2008, 13:40 »
Mit dem Stack der bei einem Interrupt benutzt werden soll (= dem Kernel Stack des jeweiligen Threads).
Für jeden Thread erstellst du einen Kernelthread und setzt vor jedem Taskwechsel den ESP Wert des TSS auf den Anfang des Kernelstacks des Threads der als nächstes laufen soll.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #10 am: 09. June 2008, 14:04 »
Das würde heißen, dass dieser Stack nur Interrupts verarbeitet, z.B.: System Calls.
Folglich würde mein Stack, der in esp0 gespeichert wird, also so aussehen:
/* ToDo: Check value (4096) */
unsigned int *stack = kmalloc(4096)+4096;

*--stack = 0x0202; //EFLAGS
*--stack = 0x08;   //CS
*--stack = (unsigned int)interrupt_handler; //EIP
//'pusha'
*--stack = 0; //EDI
*--stack = 0; //ESI
*--stack = 0; //EBP
*--stack = 0; //Just an offset, no value
*--stack = 0; //EBX
*--stack = 0; //EDX
*--stack = 0; //ECX
*--stack = 0; //EAX
// data segments pushed by IRQ handler
*--stack = 0x10; //DS
*--stack = 0x10; //ES
*--stack = 0x10; //FS
*--stack = 0x10; //GS

Aber das kann doch nicht so einfach sein oder doch ???
 
 
Gruß Christian

bluecode

  • Beiträge: 1 391
    • Profil anzeigen
    • lightOS
Gespeichert
« Antwort #11 am: 09. June 2008, 16:51 »
Zu deiner Struktur tss: Du solltest evtl. dem Compiler mitteilen, dass er die Struktur nicht padden sollte, bei gcc geht das folgendermaßen:
struct foo
{
} __attribute__((packed));

Ich hab mir das Erstellen deines Kernelstacks jetzt nicht ìm Detail angesehen (Da gibts ja auch bei genug Hobby-OS Code dazu), aber jo, das funktioniert eigentlich schon so. Welche Probleme siehst du denn dabei?
« Letzte Änderung: 09. June 2008, 18:05 von bluecode »
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

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #12 am: 09. June 2008, 18:05 »
Ich sehe keine Probleme, nur manchmal denke ich um 10 Ecken und kann dann nicht glauben, dass es mit einer solch simplen Lösung getan ist
 :-D

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #13 am: 16. June 2008, 14:12 »
Ich habe da noch eine Konzeptionelle Frage:
Ist es sinnvoll Treiber-Tasks in Ring 0 laufen zulassen, oder sollte das doch besser Ring 3 sein?

Gruß Christian

bluecode

  • Beiträge: 1 391
    • Profil anzeigen
    • lightOS
Gespeichert
« Antwort #14 am: 16. June 2008, 16:48 »
Kommt drauf an ob man einen Microkernel oder einen monolithischen Kernel haben möchte. Beim Microkernel wären die Treiber in eigenen Prozessen (und damit in Ring3) bei einem monolithischen Kernel hingegen innerhalb des Kernels (und damit Ring 0).
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

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #15 am: 17. June 2008, 07:30 »
Nun ja....
ich versuche mich ja an einem Microkernel.
Würde ich die Treiber allerdings als Ring 3-Tasks laufen lassen, braucht die Struktur noch eine Menge Zusatz.
So müsste ich irgendwie standardmäßig den Portzugriff verhindern.
 
Des Weiteren habe ich noch eine Frage zur IPC. Durch das Shared Memory, könnten nur innerhalb des Tasksdie Threads untereinander kommunizieren, da der Speicher, der dafür verwendet wird anderswo vielleicht belegt ist...
Habe ich das so richtig verstanden?
Wenn ja, dann wäre das doch eigentlich recht sinnlos oder  :?
 
 
Gruß Christian

bluecode

  • Beiträge: 1 391
    • Profil anzeigen
    • lightOS
Gespeichert
« Antwort #16 am: 17. June 2008, 08:38 »
So müsste ich irgendwie standardmäßig den Portzugriff verhindern.
:? Der Portzugriff ist standardmäßig in Ring3 verboten, es sei denn du setzt das IOPL oder erstellst eine I/O Permission Bitmap.
 
Zitat
Des Weiteren habe ich noch eine Frage zur IPC. Durch das Shared Memory, könnten nur innerhalb des Tasksdie Threads untereinander kommunizieren, da der Speicher, der dafür verwendet wird anderswo vielleicht belegt ist...
Habe ich das so richtig verstanden?
Nein, das wäre natürlich Blödsinn. Der Speicher innerhalb eines Prozesses ist logischerweise immer zwischen den Threads geshared, zumindest wenn man vom klassischen Model eines Prozesses als Ressourcencontainer ausgeht. Man kann das natürlich wie immer auch anders machen, ob das Sinn macht ist dann natürlich wieder eine ganz andere Frage. Unter Threads des gleichen Prozesses ist es natürlich klar, dass der ges. Speicher geshared wird.
Wenn man das allerdings zwischen mehreren Prozessen machen will ist halt etwas mehr Aufwand notwendig, da wie du schon richtig bemerkt hast, man die Speicherseiten die geshared werden gesondert verwalten muss, damit sie nicht mehrfach bzw. zu früh freigegeben werden, etc... Wenn man das dann nun gesondert verwaltet, dann wird der Speicherbereich einfach irgendwo in die Adressräume der beiden Prozesse eingeblendet. Man muss logischerweise selbst für korrekte Synchronisation von Zugriffen sorgen. Man kann logischerweise auch keine Pointer über den shared-memory Bereich übergeben, da Pointer normalerweise nur innerhalb eines Prozesses gültig sind.
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

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #17 am: 27. June 2008, 09:10 »
Morgen

So müsste ich irgendwie standardmäßig den Portzugriff verhindern.
:? Der Portzugriff ist standardmäßig in Ring3 verboten, es sei denn du setzt das IOPL oder erstellst eine I/O Permission Bitmap.
ok.
 
Nun ist mir eben eine Idee gekommen :evil:
Also folgendes:
Ich habe einen Prozess/Task. Dieser hat einen stack, der einfach auf eine Funktion im Kernel zeigt, die dann zwischen den einzelnen Threads wechselt, also für jeden Task praktisch eine Funktion change_thread(..).

Ist das sinnvoll, also kann ich das verwenden?
So würde ich nämlich die Funktion zum wechseln des Tasks change_task(...) "entlasten".
 
Gruß Christian
 
PS: Die Funktionsnamen sind nur Beispiele.

bluecode

  • Beiträge: 1 391
    • Profil anzeigen
    • lightOS
Gespeichert
« Antwort #18 am: 27. June 2008, 15:18 »
Dein Kernel wird/sollte normalerweise (sobald du Paging aktivierst) als Supervisor gemappt sein, d.h. du hast mit Ring3 Code keinen Zugriff, weder lesen noch schreibend noch (und ganz besonders nicht) ausführend. Außerdem kannst du bestimmte Instruktionen, die bei einem Threadswitch interessant sind aus Ring3 Code nicht verwenden.
Deine verwendete Technik würde man übrigens cooperative multitasking nennen (zumindest so wie ich das jetzt von dir verstanden habe) und ist nicht sonderlich sinnvoll, da ein Thread dann durch "nicht-aufrufen" der Funktion das OS blockieren könnte.

Normalerweise wird man eben bei einem IRQ0 (also dem Timerinterrupt) oder anderen geeigneten Interrupts (zB HPET, Local APIC Timer, RTC Timer wenn man zu viel getrunken hat) machen oder aber ein Thread sagt von sich aus "ich hab keinen Bock mehr" durch einen Syscall, welcher dann diesen Thread blockiert (da er zB auf ein anderes Ereignis warten möchte) und zu einem anderen Thread wechselt. Syscalls werden meisten aber über spezielle Instruktionen (int n, syscall oder auch sysenter) implementiert und nicht über einen normalen Funktionsaufruf (siehe auch oben zur Begründung).

Ich hoffe ich konnte helfen. :-)
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

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #19 am: 30. June 2008, 10:18 »
Mmmhhh....
Eigentlich wollte ich ja preemptives Multitasking einsetzen. Aber deine Antwort hat auf jeden Fall geholfen.
 
Nun ist folgendes:
Ich möchte alle Tasks als 32-Bit Tasks aufrufen und dann, falls es nötig sein sollte 16-Bit Code auszuführen 16-Bit Threads erstellen. Daraus folgt dann, dass es keine 16-Bit Programme gibt.
 
Wie mache ich das aber dann, wenn ich irgendwann sowas wie z.B. *.com Programme ausführen möchte, also komplett 16-Bit?
Gibt es eine möglichkeit zu prüfen, ob ein Programm nun 16-Bit oder 32-Bit Code verwendet? Denn in dem Fall, vorrausgesetzt, dass das geht, würde ich dann auch wieder einen normalen 32-Bit Task erstellen, jedoch gleich mit einem VM8086-Thread.
 
Gruß Christian

 

Einloggen