Prozesse und Scheduling unter Linux (Kernel 2.4.XX) Vorlesung: Dozent:
Betriebssysteme Odej Kao
Vortragende: Andrè Neubert Alexander Geburzi
Überblick 1. • • •
2. • • •
3. • • •
Prozesse unter Linux ProcessControlBlock Prozessliste / PID-Hashing Prozesshierarchie Lebenslauf von Prozessen Erzeugung & RunQueue / WaitQueues Prozess-Zustände Beenden & Zerstören von Prozessen Scheduling unter Linux Allgemeines Strategie Der Scheduler
Prozesse unter Linux (1) task_struct
ProcessControlBlock (PCB)
volatile long state; Zustandsinformationen
Struktur vom Typ task_struct (derzeit 1680Bytes groß ) Teilweise Integerwerte, enthaltene Strukturen oder Zeiger auf solche Max. Anzahl von Prozessen nur durch die Größe des Hauptspeichers begrenzt seit Kernel 2.4 (512MB RAM = 32k Prozesse) Zusammen mit Prozess-KernelStack in einer 8 KByte großen Seite gespeichert
unsigned long flags; pid_t pid; struct list_head run_list
Prozessverwaltung
struct task_struct *next_task, *prev_task; struct task_struct *pidhash_next; struct task_struct **pidhash_pprev; struct task_struct *p_opptr, *p_pptr;
Prozessbeziehungen
struct task_struct *p_cptr, *p_ysptr, *p_osptr;
Benutzerkontext
struct user_struct *user;
Dateisystem
struct fs_struct *fs;
geö geöffnete Dateien
struct files_struct *files;
Speicherbereich
struct mm_struct *mm; volatile long need_resched; long counter;
Scheduling
unsigned long policy; long nice; unsigned long rt_priority;
…
…
Prozesse unter Linux (2) Prozessliste Doppelt verketteten Ringliste von PCBs Zeiger auf Vorgänger *prev_task und Nachfolger *next_task Der Init_Task (PID 1) bildet Anfang und Ende PID-Hashing Lineares Suchen eines Prozesses zu aufwendig Nahezu konstanter Zugriff durch Hashtable (1024 Einträge) PIDs durch das pid_hashfn(x)-Makro in Klassen unterteilt Einträge sind doppelt verkettete PCB-Listen aller Prozesse aus der korrespondierenden Klasse Realisiert durch die Zeiger *pidhash_next, **pidhash_pprev
Prozesse unter Linux (3) Prozesshierarchie Idle_Task (PID 0)
Wird als erster Prozess von Hand durch INIT_TASK(tsk)-Makro während der Initialisierung von Linux erzeugt Ruft später immer wieder idle() auf um den Prozessor freizugeben
KernelThreads
Erledigen sog. Kerndienste z.B. Pageframe-Swapping, Netzwerk Besitzen daher bestimmte Privilegien z.B. I/O privileged benutzen Adressraum des Kernels Ö Leichtgewichtsprozesse z.B. Init_Task (PID 1) u.a. Zerstörung elternloser Prozesse
UserTasks
Normalen Prozesse des Linux-System Klassischer Unix-Prozess
Lebenslauf von Prozessen (1) Erzeugung von Prozessen mit do_fork() PCB des Vaterprozess wird in ein neues task_struct kopiert Referenzzähler werden erhöht, Flags werden gesetzt Neue Prozesse zunächst im Zustand Task_Uninterruptible Neue PID ermitteln, in Hashtable einfügen und Anzahl der aktiven Prozesse erhöhen Zustand auf Task_Running setzen und seine PID an Vater zurückgeben
RunQueue Enthält Prozesse im Zustand Task_Running Doppelt verkettete Liste mit Zeigern auf vorherigen und nächsten Prozess
WaitQueues Task_Interruptible und Task_Uninterruptible allein zu ungenau Unterschiedliche Listen je nach Art und Kontext des Wartezustandes
Lebenslauf von Prozessen (2) Prozess-Zustände Task_Running • •
Besitzt entweder gerade die CPU oder wartet auf Zuteilung vom Scheduler Befindet sich in der RunQueue
Task_Interruptible •
Wartet auf Ereignis z.B. HardwareInterrupt, Verfüg. einer Systemressource, Signal
Task_Uninterruptible • Wie Task_Interruptible, kann nur nicht geweckt werden bei beiden befindet sich der Prozess in entsprechenden WaitQueues
Task_Zombie • • •
Beendet, aber Vaterprozess hat noch keine Infos vom toten Prozess abgerufen Erst nach Abfrage oder Tot des Vaterprozesses kann er entfernt werden Wird aber schon aus RunQueue / WaitQueues entfernt
Task_Stopped •
Wurde durch überwachenden Prozess (z.B. mittels ptrace()) angehalten
Task_Exclusive •
Bevorzugte Behandlung beim Aufwecken
Lebenslauf von Prozessen (3) Zombie
Interruptible exit()
fork()
I/O
Running
ptrace()
Stopped
usleep()
Uninterruptible
Lebenslauf von Prozessen (4) Beendigung von Prozessen mit do_exit() Benutzte Datenstrukturen entfernen, Referenzzähler dekrementieren Exit-Code setzen, Verwandte benachrichtigen
Kinder erhalten PID 1 als Vaterprozess Vaterprozess erhält Signal SIGCHL
Init_Task erbt Kindsprozesse Ö sauberes Entfernen nach Beendigung Zustand Task_Zombie, Aufruf des Schedulers
Zerstören von Prozessen mit release_task() Anzahl der Benutzerprozesse dekrementieren Prozess aus Hashtable entfernen Nichtverbrauchte CPU-Zeit verschenken PID auf 0 gesetzt Vom Prozess belegte Speicherseite wird freigegeben
Scheduling unter Linux Zeitscheibenverfahren und Prioritäten (statisch u. dynamisch) Epochen
Zu Beginn erhält jeder Prozess eine max. Laufzeit (abhängig von statischer und dynamischer Priorität) Endet, wenn alle lauffähigen Prozesse Laufzeit abgearbeitet haben
Unterbrechung durch Timer-Interrupt (x86 Systeme 10ms) Berücksichtigt sowohl Echtzeitprozesse als auch interaktive bzw. Batch-Prozesse Kernel ist non-preemptive
Nur UL Prozesse können unterbrochen werden KL Prozesse nicht
Die Strategie Realisiert drei verschiedene Schedulingverfahren (FIFO, RR, Multi-Level-Feedback) Jeder Prozess kann festlegen, welches Verfahren für ihn verwendet werden soll Alle aktuell lauffähigen Prozesse werden in einer RunQueue organisiert
Strategie SCHED_FIFO SCHED_FIFO: • FIFO-Prinzip • Echtzeitprozess • benötigt Superuser-Privilegien • Abgabe der CPU: bei Aktivierung eines Prozesses mit höherer Priorität bei Aufruf einer I/O-blockierenden Operation durch eigene Freigabe (Yield)
Strategie SCHED_RR • • • •
SCHED_RR: Round-Robin Echtzeitprozess Benötigt Superuser-Privilegien Abgabe der CPU: Bei Aktivierung eines Prozesses mit höherer Priorität Bei Aufruf einer I/O-blockierenden Operation Durch eigene Freigabe (Yield) Nach Ablauf der Zeiteinheiten
Strategie SCHED_OTHER • • •
SCHED_OTHER: Multi-Level-Feedback „normale“ Prozesse Abgabe der CPU: Bei Aktivierung eines Prozesses mit höherer Priorität Bei Aufruf einer I/O-blockierenden Operation Durch eigene Freigabe (Yield) Nach Ablauf der Zeiteinheiten
Der Scheduler Scheduler bestimmt, welchem Prozess aus der RunQueue als nächstes die CPU-Zeit zugewiesen werden soll Er muss dabei die drei verschiedenen Strategien berücksichtigen Bewertet jeden Prozess mit Hilfe der Funktion goodness() Evtl. wird dabei eine neue Epoche eingeleitet und alle SCHED_RR und SCHED_OTHER erhalten neue Zeiteinheiten: counter = counter / 2 + nice Der Prozess mit dem größten Wert erhält den Zuschlag Evtl. vor dem Umschalten organisatorische Dinge erledigen
Die goodness() -Funktion Bewertet einen Prozess folgendermaßen:
• •
Flag SCHED_YIELD gesetzt: -1 SCHED_FIFO oder SCHED_RR: 1000 + rt_priority SCHED_OTHER: 0, wenn alle Zeiteinheiten verbraucht sind Sonst Summe aus counter, der statischen Priorität (nice) und evtl. einem Bonus von 1, falls der Prozess denselben Adressraum hat wie der abzulösende Prozess