Autor Thema: Multitasking und Warten auf Ressourcen  (Gelesen 7380 mal)

Feuermonster

  • Beiträge: 24
    • Profil anzeigen
Gespeichert
« am: 14. May 2009, 22:34 »
Guten Abend,

Das Problem an  präemptivem Multitasking ist einerseits, was passiert wenn zwei Tasks die gleiche Ressource anfordern, bzw. auf das gleiche warten?

In meinem Fallbeispiel gibt es zwei Tasks die immer gerne Zeichen von der Tastatur erhalten moechten.

Task A ruft nun get_key auf.
Task B kommt zum Zug
Task B ruft auch get_key auf -> Booom!
Eine Taste wird gedrueckt

Problem Nr 1.)

Rufen zwei Tasks die gleiche Funktion auf, greifen sie auf die gleichen Adressen zu (int i; in get_key ist ja immer an der gleichen Stelle)

Problem Nr 2.)

Wer bekommt die Taste?


get_key muss auf IRQ1 warten. Der Tastaturtreiber soll dem Scheduler also sagen, dass der aufrufende Task erst wieder gescheduled werden soll, wenn der Tastenanschlag da ist? (Ein Tastaturtreiber???)


Ich konnte mir noch keine Loesung die 100%ig passt zusammen denken, und waere daher dankbar fuer eine kleine Unterstuetzung ;)

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 14. May 2009, 22:52 »
Problem Nr 1.)

Rufen zwei Tasks die gleiche Funktion auf, greifen sie auf die gleichen Adressen zu (int i; in get_key ist ja immer an der gleichen Stelle)
Bei lokalen Variablen in Funktionen sollte es keine Probleme geben, da jeder Task einen eigenen Stack im Kernel haben sollte. Für globale Variablen solltest du dich über kritische Abschnitte und Locks informieren oder einfach interrupts deaktivieren.

Problem Nr 2.)

Wer bekommt die Taste?
Der Vordergrund-Task. Moderne Betriebssysteme (oder der Fenstermanager) definieren einen Task, der den Fokus hat, und mit getkey() (oder stdin oder CON: oder wie man das nennen will) seine Tasten bekommt. Wenn ein Task kein Vordergrund-Task ist, wird der in der getkey()-Methode schlafen gelegt, bis er Vordergrund-Task ist und eine Taste gedrückt wird.

Z.B. kann auch ein Task der einen anderen Task startet ihm automatisch das Attribut "Vordergrundtask" schenken. Das heißt der erstellende Task hat es nicht mehr, der neue dafür schon. So lange der Nutzer nicht Alt+Tab drückt bleibt das dann so ;)

Zitat
get_key muss auf IRQ1 warten. Der Tastaturtreiber soll dem Scheduler also sagen, dass der aufrufende Task erst wieder gescheduled werden soll, wenn der Tastenanschlag da ist? (Ein Tastaturtreiber???)
Jop, in get_key() legst du den aktuellen Task schlafen, und der Tastaturtreiber fragt im IRQ1 das Betriebssystem was der Vordergrundtask ist, und weckt den dann auf und schickt ihm die Taste (als Rückgabewert).

Das verhindert übrigens nicht asynchrone Tastaturabfragen (Hotkeys für deinen Mediaplayer etc.). Da muss der Task dann halt eine andere Methode aufrufen (async_check_key_state() oder was auch immer), oder er lässt sich in seiner Message Loop bedienen, oder er registriert einen Event-Handler (RPC).
« Letzte Änderung: 14. May 2009, 22:58 von PorkChicken »
Dieser Text wird unter jedem Beitrag angezeigt.

Feuermonster

  • Beiträge: 24
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 14. May 2009, 23:03 »
Was waere wenn zwei Tasks auf Daten vom COM1 warten? Da kann ich ja schlecht einen Vordergrund und einen Hintergrundtask definieren.

Da koennte ich Notfalls eine Queue einbauen, die die Daten halt der Reihe nach zurueckgibt.

Das ganze geht dann aber weiter: Angenommen ein Task wartet auf eine Taste und einen Antwort vom COM1. Wenn ich den Prozess bei get_key schlafen lege, kann er keine Antwort mehr vom COM1 erhalten, bis er eine Taste erhalten hat. Wenn ich ihn nicht schlafen lege, returnt get_key ohne dass er eine Taste erhalten hat.


Fraglich ist auch, wie get_key den Task schlafen legen lassen soll?

Task A befindet sich ja in get_key drin. Kann ein Task sich selber schlafen legen?

Mein get_key sieht etwa so aus, taste_angekommen wird auf true gesetzt, wenn irq1 gecallt wurde.

get_key(void* ptr)
  do while taste_angekommen = false
  loop
  *ptr = taste
 return

Das koennte ich erweitern zu

get_key(void* ptr,pid)
  if(pid->has_focus()) {
   while(taste_angekommen == false);
   *ptr = taste; return;
  }
  else {
    dont_schedule(pid);
  }

irq1:
 taste_angekommen = true

Wann weiss ich nun, wann ich den Task wieder schedulen muss?
« Letzte Änderung: 14. May 2009, 23:19 von Feuermonster »

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 14. May 2009, 23:17 »
COM1 und andere ähnliche Resourcen (mir fallen gerade nur listening sockets für Netzwerk ein) sollten nur von einem einzigen Programm geöffnet werden können.

Wenn ein Prozess auf zwei Sachen warten will, muss er mit einer Form von Events arbeiten. Entweder er kann dem Kernel zwei Dateihandles übergeben (eins für stdin und eins für COM1), und ihm dazu sagen: "Sag bescheid, wenn sich was tut" (das macht glaub ich die select-Funktion in Unix, etc. so). Oder er sagt dem Kernel: Wenn sich das in stdin tut, dann ruf meinen Handler X auf, und wenn sich was in COM1 tut, dann ruf meinen Handler Y auf. ( Du kannst dir natürlich auch beliebige Varianten ausdenken.

get_key ist auf jeden fall zu primitiv für sowas.

Du kannst natürlich auch aufs schlafen Legen verzichten und Polling machen. Dann sind natürlich alle anderen Programme blockiert, und dein Computer macht nichts anderes als die Luft aufwärmen.
Dieser Text wird unter jedem Beitrag angezeigt.

Feuermonster

  • Beiträge: 24
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 14. May 2009, 23:25 »
Angenommen ich leg den Task schlafen wenn er get_key aufruft und keinen Fokus hat. So, wann ist es Zeit ihn wieder zu wecken? Theoretisch muesste ich dann bei jedem IRQ1 pruefen, ob ein Task der frueher get_key gemacht hat, mittlerweile den Fokus erhalten hat?


Pseudocodemaessig wohl etwa so:

get_key(void* ptr, pid)
start
  Hat Prozess Fokus?
    Nein:
      In Requestliste fuegen
      Schlafen Legen
      goto start
    Ja:
     taste_angekommen = true?
       Nein:
        In Requestliste fuegen
        Schlafen legen
        goto start
       Ja:
       *ptr = taste
      return
IRQ1:
  taste_angekommen = true
  gehe alle eintrage in der Requestliste durch
     alle prozesse in der warte listen wieder schedulen
« Letzte Änderung: 14. May 2009, 23:29 von Feuermonster »

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 14. May 2009, 23:34 »
Wann weiss ich nun, wann ich den Task wieder schedulen muss?
Im IRQ-Handler solltest du den wartenden Vordergrund-Task, als aktiv markieren bzw. in deine Liste der aktiven Tasks eintragen. Der Task kann sich dann die Daten abholen.

Ich stell mir das so vor (habs nicht wirklich in dieser Form implementiert)
int get_key()
{
     if (current_task->has_focus() && key_ready())
          return fetch_key();
     else
          sleep();

     return current_task->magic_key_buffer;
}

void irq1()
{
     if( focus_task != null && focus_task->state == SLEEPING) {
         // jemand wartet auf uns
         focus_task->magic_key_buffer = inb_60_scancode_to_ascii_etc();
         wake_up(focus_task);
     }
     else {
         // es wartet gerade keiner, aber wird schon wer abholen
         keyboard-puffer-schreiben ( inb_60_scancode_to_ascii_etc() );
     }
}

void sleep()
{
     current_task->state = SLEEPING;
}

void wake_up(task)
{
     task->state = RUNNABLE;
}

int fetch_key()
{
     return keyboard-puffer-auslesen()
}

edit: Ja deine Methode geht natürlich auch. Bei beiden Methoden kann es wohl passieren, dass die Tasks in einer anderen Reihenfolge mit Tasten bedient werden, als sie angefragt haben. Solche Ressourcengeschichten sind immer ein wenig unfair, wenn man sie einfach halten will ;)
« Letzte Änderung: 14. May 2009, 23:36 von PorkChicken »
Dieser Text wird unter jedem Beitrag angezeigt.

Feuermonster

  • Beiträge: 24
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 14. May 2009, 23:48 »
Hier nimmst du einfach an, dass der focus_task wenn er einen Status von SLEEPING hat, eine Taste angefordert hat, muss er ja aber nicht zwingend.

unsigned int request_count = 0;
void get_key(void* ptr, unsigned short process_id) {
request_count++;  //Wir wollen ja den naechsten Key, nicht den, den wir 1 Sekunde
              //Zuvor gedrueckt haben.
start:
  if(process_has_focus(process_id)) {
     if(new_key == 1) {
       *ptr = key;
       new_key = 0;
       request_count--;
     } else {
       add_to_request_list(process_id);
       schd_dont_schedule(process_id);
       goto start;
     }
  } else {
     add_to_request_list(process_id);
     schd_dont_schedule(process_id);
     goto start;
  }
}
 
void irq1() {
  key = read_key();
  if(request_count > 0) new_key = 1;
  reschedule_all_in_requst_list();
}

Koennte das so klappen?

Edit: Nein. new_key = 0; macht Probleme da new_key nicht auf dem Stack liegen darf. Das ist kniffliger als ich dachte.

IRQ1 muesste ueberpruefen, ob jemand eine Taste angefordert hat, wenn nicht, darf es new_key nicht auf 1 setzen.

Edit2: Mit request_count muesste es aber gehen.


Ausserdem gefaellt mir die Idee dass Treiber dem Scheduler Befehle geben irgendwie nicht? Machen das Windows/Linux-Treiber auch so?


In Tyndur laeuft das alles anscheinend ueber Callbacks. Das ist wohl am einfachsten. Jeder Task der Tastendruecke empfangen will, erhaelt sie, ob er sie zu der Zeit braucht, muss der Task dann halt selber entscheiden. (Eg. der GUI-Prozess leitet sie dann selber weiter)


Edit3

Dann bleibt noch die Frage, wie ich die Callbacks aufrufen soll. Wenn ich sie einfach so aufrufe, geht das natuerlich nicht. (Wenn jemand ein while(1); im Callbackhandler hat, steht der Rechner still).

Laeuft das ueber den Scheduler? Wenn der Scheduler einen Task wechselt, schaut er ob Nachrichten fuer ihn da sind und schickt die ihm dann?
« Letzte Änderung: 15. May 2009, 00:10 von Feuermonster »

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 15. May 2009, 00:19 »
Hier nimmst du einfach an, dass der focus_task wenn er einen Status von SLEEPING hat, eine Taste angefordert hat, muss er ja aber nicht zwingend.
Stimmt. Man könnte da aber einfach SLEEPING_IN_GETKEY oder so draus machen ;)

Zitat
Edit: Nein. new_key = 0; macht Probleme da new_key nicht auf dem Stack liegen darf. Das ist kniffliger als ich dachte.

IRQ1 muesste ueberpruefen, ob jemand eine Taste angefordert hat, wenn nicht, darf es new_key nicht auf 1 setzen.

Edit2: Mit request_count muesste es aber gehen.
Da hast du ja praktisch sowas wie ein Spinlock nachgebaut. Sogar mit schlafenlegen des Tasks beim Warten. Ich denke, das könnte so gehen. (wenn new_key wie request_count global ist)

Ausserdem gefaellt mir die Idee dass Treiber dem Scheduler Befehle geben irgendwie nicht? Machen das Windows/Linux-Treiber auch so?
Ich glaube ja. Das sind ja eigentlich keine besonderen Befehle. Jedes Programm kann sagen: "Ich leg mich schlafen bis XY". Durch den Aufruf einer Treiberfunktion hat das Programm den Treiber ermächtigt, dieses ebenfalls zu tun.

Zitat
Laeuft das ueber den Scheduler? Wenn der Scheduler einen Task wechselt, schaut er ob Nachrichten fuer ihn da sind und schickt die ihm dann?
Könnte er machen. Da müsste vielleicht jemand etwas zu sagen, der das schon mal gemacht bzw. den Teil in tyndur gebaut (oder geackt :P) hat.
« Letzte Änderung: 15. May 2009, 00:21 von PorkChicken »
Dieser Text wird unter jedem Beitrag angezeigt.

Feuermonster

  • Beiträge: 24
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 15. May 2009, 00:26 »
Nur kann das nicht funktionieren. get_key wird ueber ein rpc_call_func aufgerufen. (rpc_call_func ist eine Funktion vom Int21h). Solange get_key also nicht returnt, wird kein weiterer Interrupt mehr ausgeloest. D.h schlussendlich klappt das dann doch nicht.

Oder gehen dann nur Hardwareinterrupts nicht mehr? (Was bei get_key auch ziemlich doof waere) Ich meinte jedoch bevor ich ein iret mache, gehe nichts mehr. Und iret kann ich erst machen, wenn get_key fertig ist.

Irgendwie kann Multitasking so gar nicht aufgehen :( Daraus schliesse ich, eine Funktion im Kernel darf keinen Polling enthalten sonst geht Multitasking kaputt. Das einzige was geht sind wirklich Messages irgendwie zu verschicken.
« Letzte Änderung: 15. May 2009, 00:32 von Feuermonster »

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #9 am: 15. May 2009, 10:45 »
Zitat
Solange get_key also nicht returnt, wird kein weiterer Interrupt mehr ausgeloest. D.h schlussendlich klappt das dann doch nicht.

Oder gehen dann nur Hardwareinterrupts nicht mehr?
Software Interrupts geh'n immer. Und hardware Interrupts gehen auch wenn du es erlaubst/nicht verbietest.


Zitat
Daraus schliesse ich, eine Funktion im Kernel darf keinen Polling enthalten sonst geht Multitasking kaputt.
Doch das müsste so z.B. gehen:
while( !key_pressed ) {
  trigger_task_switch();
}
return key;


„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

 

Einloggen