CPU Scheduling este un proces care permite unui proces să
folosească CPU -ul în timp ce execuția unui alt proces este în așteptare (sau mai bine zis, în starea de așteptare), din cauza indisponibilității vreunei resurse precum cele de intrare/ieșire, astfel utilizându-se la maxim CPU -ul. Scopul urmărit de „CPU Scheduling” este acela de a face sistemul eficient, rapid și sigur. Oricând CPU -ul este idle (în starea de așteptare), sistemul de operare trebuie să selecteze unul din procesele din cozile pregătite de a fi executate. Procesul de selecție este realizat de un planificator pe termen scurt ( sau, altfel spus, „CPU Scheduler”). O altă componentă implicată în funcția de CPU Scheduling este “Dispecerul”. Dispecerul este modulul care permite controlul CPU - ului la procesele selectate de planificatorul pe termen scurt. Această funcție implică: • schimbarea contextului • schimbarea la modul utilizator • saltul la locația potrivită în programul utilizator pentru a reporni acel program în momentul în care a fost lăsat ultima dată.
Tipuri de CPU Scheduling
Deciziile de CPU Scheduling ar putea avea loc în unul din următoarele patru circumstanțe: 1. Când un proces se schimbă din starea de rulare în cea de așteptare 2. Când un proces se schimbă din starea de rulare în cea de pregătire 3. Când un proces se schimbă din starea de așteptare în cea de pregătire 4. Când un proces se încheie (este terminat). Originile operației de scheduling preced sistemele computerizate; Primele abordări au fost luate din disciplina de management a operațiilor și aplicarea acesteia la calculatoare. Liniile de asamblare și orice alte operații făcute de om necesită această operație de scheduling și multe din probleme există aici, incluzând dorința pentru eficiență. Procesele care rulează în sistem sunt numite și workload. Determinarea workload -ului este o parte critică de a crea principii de acțiune. Presupunerile care se pot face asupra proceselor, numite și job uri, care rulează în sistem, sunt următoarele: 1. Fiecare job rulează pentru un anume timp prestabilit 2. Toate job -urile sosesc în același timp 3. Odată începute, fiecare job rulează pentru a se termina 4. Toate job -urile pot să utilizeze doar CPU -ul (nu realizează nicio operație de intrare-ieșire) 5. Run-time -ul fiecărui job este cunoscut.
Pentru a face presupuneri asupra workload -ului, este nevoie și de
compararea diferitelor principii de acțiune (scheduling metric). Un indice este doar ceva pe care îl folosim pentru a măsura ceva și există un număr de diferiți indici care au sens în operația de scheduling. Un astfel de indice este cel de turnaround time. Turnaround time - ul unui job este definit ca timpul la care job -ul se finalizează minus timpul la care jobul sosește în sistem.Turnaround time -ul este un indice de performanță. Un alt indice este cel de fairness. Performanța și corectitudinea sunt de obicei în dezacord în ceea ce privește operația de scheduling. Un scheduler, spre exemplu, ar optimiza performanța, dar la costul de a preveni câteva job -uri de a rula, așadar scăzând corectitudinea. Cel mai simplu algoritm de scheduling care se poate implementa este cel de First In, First Out (FIFO), sau altfel spus First Come, First Served (FCFS). FIFO are un număr de proprietăți pozitive. Este ușor de înțeles și prin urmare ușor de implementat. Și, potrivit presupunerilor, lucrează destul de bine. FIFO nu este întotdeauna cea mai bună soluție, această situație fiind cunoscută sub numele de convoy effect, unde un număr relativ mic de consumatori potențiali de resurse sunt lăsați în urma unui consumator major de resurse. Shortest Job First (SJF) s-a dovedit a fi o abordare foarte simplistă care rezolvă această problemă. Este o operație “furată” de la cercetările de operaționalizare și aplicată la job -urile de scheduling din sistemele de calculatoare. Această nouă disciplină este cunoscută ca Shortest Job First (SJF) și numele este ușor de memorat deoarece descrie principii aproape complet : rulează primul job prima dată, apoi următorul cel mai scurt, și așa mai departe. Potrivit presupunerilor cum că toate job -urile ajung în același timp, se poate dovedi că SJF chiar este un algoritm optim privind operația de scheduling. Din fericire, există un scheduler care poate preveni un job și decide să ruleze un alt job, continuându-l pe primul mai apoi. SJF este un scheduler non-preemptive, deci, suferă de anumite probleme. Shortest Time-to-Completion First (STCF), față de SJF, adaugă conceptul de preempțiune la SJF și mai este cunoscut ca și scheduler -ul Preemptive Shortest Job First (PSJF). În orice moment un nou job intră în sistem, scheduler -ul STCF determină care din job -urile rămase (incluzând și noile job -uri) are cel mai puțin timp rămas și îl planifică pe acela. Dacă s-ar cunoaște lungimea job -urilor și dacă acele job -uri ar folosi CPU -ul și singurul indice ar fi turnaround time -ul, STCF ar fi un principiu foarte bun. Astfel a apărut conceptul de response time. Response time se poate defini ca și timpul de când un job ajunge într-un sistem până când este planificat. Una dintre cele mai cunoscute abordări de scheduling este cea de Multi-level Feedback Queue (MLFQ). Scheduler -ul Multi-level Feedback Queue (MLFQ) a fost descris pentru prima dată în 1962 într-un sistem numit Compatible Time-Sharing Systems (CTS), și această lucrare, împreună cu o alta pe Multics, a făcut ca acest sistem să primească premiul Turing. Scheduler -ul a fost ulterior redefinit dealungul anilor până la implementarea cu care avem de a face în sistemele moderne de operare. Problema fundamentală a MLFQ pe care aceste încearcă să o adreseze poate fi privită din două unghiuri. Prima dată, ar încerca să optimizeze turnaround time -ul, care este terminat prin rularea joburilor scurte, prima dată. Din păcate, sistemul de operare nu știe de obicei cât de mult un job va rula, precum algoritmi ca SJF (sau STCF) știu. A doua oară, ar vrea să creeze un sistem care să fie responsive și interactiv pentru utilizator. Din păcate, algoritmi precum Round Robin reduc timpul de răspuns, dar nu sunt deloc buni pentru turnaround time. Problema care se pune este următoarea: de obicei, nu se știe nimic despre un proces, deci trebuie construit un scheduler care să atingă acest obiectiv. Scheduler -ul trebuie să învețe, în timp ce sistemul rulează, caracteristicile job -ului pe care îl rulează, și astfel să facă decizii mai bune de scheduling. Pentru a construi un astfel de scheduler, trebuie mai întâi cunoscut faptul că MLFQ are un număr distinct de stive, fiecare asignată unui priority level diferit. În orice moment, un job care e pregătit să ruleze se află pe o singură stivă. MLFQ folosește priorități pentru a decide ce job ar trebui să ruleze la un anume moment: un job cu o prioritate mai mare este ales să ruleze. În mod normal, mai mult de un job ar exista pe o anumită stivă, deci, au aceiași prioritate.
Regula 1: Dacă Prioritatea (A) > Prioritatea B, A rulează (B nu)
Regula 2: Dacă Prioritatea (A) = Prioritatea (B), A & B rulează Cheia pentru scheduling -ul MLFQ este ascunsă, astfel, în modul în care scheduler -ul setează prioritățile. În loc să fie dată o prioritate fixă pentru fiecare job, MLFQ variază prioritatea unui job bazându-se pe comportamentul său observabil. Dacă, spre exemplu, un job abandonează în mod repetat CPU -ul în timp ce așteaptă ca date să fie introduse de la tastatură, MLFQ va menține prioritatea sa ridicată, căci așa un proces interactiv s-ar comporta. Dacă, în schimb, un job folosește CPU -ul intensiv pentru perioade lungi de timp, MLFQ își va reduce prioritatea. În acest fel, MLFQ va încerca să învețe despre felul în care procesele rulează și astfel va folosi istoricul job -ului pentru a prezice comportamentul său viitor. MLFQ poate să schimbe nivelul de prioritate al unui job (așadar, și stiva pe care se află) pe parcursul vieții sale. Pentru a face acest lucru, trebui avut în vedere workload -ul: un mix de job -uri interactive care rulează pentru perioade scurte de timp (și care ar abandona periodic CPU -ul) și câteva joburi care rulează pentru perioade mai lungi și care folosesc mult timp al CPU -ului, dar unde timpul de răspuns nu este așa de important. Un algoritm care ajustează prioritatea ar fi următorul: - când un job intră în sistem, este plasat ca având cea mai mare prioritate (stiva din vârf) - daca un job folosește în întregime o bucată de timp în momentul când rulează, prioritatea sa este redusă (scade cu o stivă) - daca un job părăsește CPU -ul înainte ca bucata de timp să se irosească, stă la același nivel de prioritate.
Dacă un proces renunță la procesor înainte să își utilizeze bucata sa
de timp, îl putem menține la același nivel de prioritate. MLFQ este interesant din următorul motiv: în loc de a se aștepta o cunoaștere a priori a naturii job -ului, observă execuția unui job și o prioritizează în mod corespunzător. Unul dintre abstractizările fundamentale pe care sistemul de operare le oferă utilizatorilor este procesul. Definiția unui proces, în mod informal, este destul de simplă: rulează un program. Programul nsăși doar ocupă spațiu pe disk și este alcătuit dintr-un set de instrucțiuni (și poate câteva date statice), așteptând să fie rulat. Sistemul de operare este cel care preia biții și îi rulează, transformând astfel programul în ceva folositor. Un sistem obișnuit poate rula ușor zeci sau chiar sute de procese în același timp. În acest fel sistemul de operare poate rula fără probleme, CPU -urile fiind disponibile oricând, programele funcționând și ele. Sistemul de operare crează iluzia existenței mai multor CPU -uri prin virtualizarea acestuia. Prin rularea unui proces, oprirea lui și rularea a unui alt proces, și așa mai departe, sistemul de operare crează iluzia că mai multe CPU -uri virtuale există, când defapt există doar un singur CPU fizic (sau doar câteva). Această tehnică de bază, cunoscută ca time sharing, permite utilizatorilor să ruleze în paralel cât de multe procese au nevoie. Pentru a implementa virtualizarea CPU -ului și a o face în mod corespunzător, sistemul de operare va avea nevoie atât de un limbaj mașină low-level, cât și de o inteligență high-level. Limbajul mașină low- level mai este numit și mecanism; mecanismele sunt metode low-level sau protocoale care implementează o anumită funcționalitate de care este nevoie. Time sharing -ul este o tehnică de bază folosită de către sistemul de operare pentru a partaja o resursă. Prin faptul că resursei i se permite a fi folosită pentru scurt timp de o entitate și apoi de alta, și așa mai departe, resursa în cauză (spre exemplu, CPU -ul sau un link din rețea) poate fi folosit de mai mulți utilizatori. Echivalentul time sharing -ului este space sharing -ul, unde o resursă este divizată (în spațiu) printre cele care doresc să o folosească. Switch este operațiunea care oferă abilitatea sistemului de operare de a opri din rulare un program și a porni un altul pe un anumit CPU. Controlul acestor mecanisme este deservit de inteligența din sistemul de operare, sub forma unor principii de acțiuni. Aceste principii sunt algoritmi care iau anumite decizii în sistemul de operare. Un principiu programat în sistemul de operare va face această decizie, utilizând cel mai probabil informația istorică, cunoașterea volumului de muncă și indici de performanță, pentru a-și lua decizile. Abstractizarea permisă unui sistem de operare care rulează un program se numește un proces. Un proces este pur și simplu un program în execuție; în orice moment, un proces poate fi definit printr-un inventar al diferitelor părți din sistem pe care le accesează sau afectează în timpul execuției sale. Pentru a înțelege exact ce înseamnă un proces, trebuie înțeles și ce înseamnă machine state: ce poate un program citi sau îmbunătăți în timp ce rulează. O componentă ușor de observat a machine state care cuprinde un proces este memoria sa. Instrucțiunile stau ascunse în memorie; datele pe care programul care rulează le citește și scrie sunt, de asemenea, în memorie. Așadar, memoria pe care procesul o poate adresa (numită și spațiu adresă) este parte din proces. Din machine state mai fac parte și regiștrii; multe instrucțiuni citesc sau aduc îmbunătățiri, în mod explicit, la regiștrii și, așadar, sunt evident importanți în execuția procesului. Există și câțiva regiștrii speciale care fac parte din machine state. Spre exemplu, program counter (PC) (uneori numit și instruction pointer sau IP) indică ce instrucțiune din program este executată curent; similar, un stack pointer și un frame pointer asociat sunt folosit la administrarea stivei pentru funcțiile parametru, variabilele locale și adresele returnate. În cele din urmă, programele accesează frecvent și dispozitive de stocare Pentru a putea virtualiza CPU -ul, sistemul de operare trebuie să partajeze cumva CPU -ul fizic printre multiple procese care rulează în același timp. Ideea de bază este simplă: un proces trebuie rulat pentru puțin timp, apoi altul trebuie să facă la fel, și așa mai departe. Există, totuși, câteva provocări în construirea unei mașinării virtuale. Prima ar fi performanța: cum ar putea fi implementată virtualizarea fără a adăuga supraîncărcări excesive la sistem? A doua ar fi controlul: cum ar putea fi rulat un proces în mod eficient, în timp ce s- ar menține controlul asupra CPU -ului? Controlul este destul de important pentru sistemul de operare, deoarece are în responsabilitate resursele; fără control, un proces ar putea rula mereu și ar lua controlul asupra mașinii, sau ar putea accesa informații care nu ar trebuie să le acceseze, în mod normal. Prin obținerea de performanțe înalte, în timp ce este menținut un control, este așadar unul dintre provocările majore în crearea unui sistem de operare. Pentru a face ca un program să ruleze la fel de rapid precum oricine își dorește, nu în mod surprinzător, dezvoltatorii unui astfel de sistem au venit cu o tehnică pe care o numesc limited direct execution. Ideea de „execuție directă” este simplă: un program este pur și simplu rulat direct pe CPU. Deci, când sistemul de operare dorește să pornească un program în execuție, crează o intrare de proces pentru acesta în lista de procese, alocă memorie pentru el, încarcă codul lui în memorie (de pe disk), localizează punctul său de intrare (în rutina main() sau în ceva similar), sare la aceasta, și începe rularea codului de la utilizator. Această abordare aduce, însă, câteva probleme în încercarea de virtualizare a CPU -ului. Prima este relativ simplă: dacă un program este pur și simplu rulat, sistemul de operare trebuie să se asigure că programul nu ar face nimic din ce ar vrea să nu facă, în timp ce ar trebui să ruleze în mod eficient. A doua problemă care se pune este următoarea: când un proces este rulat, sistemul de operare l-ar opri din executare și ar comuta la un alt proces, implementând astfel time sharing -ul de care este nevoie pentru virtualizarea CPU -ului. Fără ca anumite limite să fie setate la programele care sunt în curs de execuție, sistemul de operare nu ar fi în controlul a ceva și, prin urmare, ar fi „doar o librărie”. Execuția directă are avantajul implicit de a fi rapidă; programul rulează nativ pe hardware -ul CPU -ului și astfel se execută rapid la fel de rapid precum e de așteptat. Dar rularea pe CPU poate introduce o problemă: procesul ar putea dori să execute o operație restricționată, precum emiterea unei cereri de intrare-ieșire pe un disk, sau dobândirea accesului la mai multe resurse precum CPU -ul sau memoria. O soluție ar fi ca ,pur și simplu, orice proces să fie lăsat să facă ce dorește în ceea ce privește intrările și ieșirile și alte operații asemănătoare. Soluția care trebuie luată, de fapt, este aceea de a introduce un nou mod de procesare cunoscut ca user mode; codul care rulează în user mode este restricționat în ceea ce poate face. Spre exemplu, când se rulează un program în user mode, un proces nu poate emite cereri de intrare-ieșire; prin această chestiune ar rezulta faptul că procesorul ridică o excepție; sistemul de operare ar termina procesul, astfel. În comparație cu user mode este kernel mode, pe care sistemul de operare (sau kernel -ul) rulează în acesta. În acest mod, codul care rulează poate face ceea ce îi place, incluzând operații privilegiate precum cereri de intrare/ieșire și executarea tuturor tipurilor de instrucțiuni restricționate. În mod virtual, orice hardware modern oferă abilitatea programelor utilizator de a realiza un system call. Acesta permite kernel -ului de a expune cu grijă funcționalități pentru programele utilizator, precum accesul la sistemul de fișiere, crearea și distrugerea de procese, comunicarea cu alte procese și alocarea de mai multă memorie.
Bibliografie
[1] "Operating Systems: Three Easy Pieces," 2018. [Online]. Available: