Prosesser og tråder
Definisjon av prosess Enkel definisjon En prosess er et program som kjører Mer presis definisjon En prosess er en samling av ressurser som er nødvendige for å utføre en oppgave beskrevet med programkode Ressursene til en prosess er: Programkoden (kompilerte maskininstruksjoner) Minne/RAM og evt. diskplass OS-kontrolldata som f.eks. prioriteter og rettigheter En eller flere tråder ( threads of execution ) som utfører maskininstruksjoner
Analogi med et kjøkken Kjøkken Formål: Lage mat Kokker Råvarer og utstyr Oppskrifter Prosess Formål: Løse oppgave Tråder Minne, disk, kontrolldata Programkode Merk at begrepene program, prosess og tråd / thread ofte brukes om hverandre og upresist
Tråder / Threads I gamle dager hadde ikke OS prosesser med mer enn én tråd : Begrepet threads fantes ikke, bare separate prosesser Deling av data mellom prosesser er vanskelig Prosesser stopper når de venter på en ressurs Tråder og internett/distribuerte systemer: Multi-thread OS ble vanlige for 20-25 år siden Trådene i en prosess deler på det samme minneområdet Gjør at f.eks. nettlesere og mailklienter lett kan fortsette selv om en server ikke svarer Både Windows og Linux er multi-thread
Tråd vs. prosess Tråder er sekvensielle flyt av utførelse inni i en prosess, som kan kjøre parallelt/ samtidig Trådene er lettvekts-prosesser Mye raskere å opprette ny tråd enn ny prosess Oftest mye raskere kommunikasjon mellom samarbeidende tråder enn prosesser Tråder er utfordrende for programmereren: Trådene deler samme minne, mye feil/inkonsistens mulig Samarbeidende tråder må synkroniseres i koden Tråd-API'er i både C (POSIX-threads aka Pthreads) og i Java (java.lang.thread)
Prosessenes tilstand En prosess har hele tiden en tilstand, som endres etter som programmet kjøres/tiden går Tilstanden til en prosess bestemmes av: Dataene Prosessomgivelsenes tilstand Trådenes tilstand Hele tilstanden til en prosess må kunne lagres unna ved f.eks. avbrudd i CPU eller stand-by Prosessen må kunne hentes frem igjen og gjenskapes slik at den fortsetter å kjøre med nøyaktig samme tilstand som da den ble lagret
Lagring av prosesstilstander i OS Tilstanden til hver enkelt prosess lagres i en prosessdeskriptor, som bl.a. inneholder: Prosessnavn og -eier Prioritet og rettigheter Informasjon om minneområde(r) som er tildelt Trådliste Informasjon om selve programmet for prosessen Informasjon om forelder- og barneprosesser Deskriptorene for alle prosessene som finnes på maskinen lagres i prosesstabellen i OS-et
Lagring av trådtilstander Tilstanden til hver av trådene i en prosess lagres i en tråddeskriptor, som bl.a. inneholder: Instruksjonspeker som inneholder adressen til instruksjonen som tråden nå utfører Innholdet i CPU-registre Trådens utføringsstatus, som er en av tre mulige: ready klar til å kjøre i CPU, venter på tur running kjører i CPU blocked kan ikke kjøre, venter på f.eks. I/O Tråddeskriptorene lagres lokalt i prosessene
Forelder- og barneprosesser Alle prosesser skapes/opprettes ved at en tråd i en prosess oppretter en ny prosess gjennom systemkall til OS-et * Prosessen som lages er barn av prosessen som opprettet den, forelder-prosessen OS-et holder rede på barn-forelder forholdene mellom prosesser Forelderprosessen kan kontrollere, stoppe, starte og slette barna Alle shell-kommandoer starter prosesser som blir barn av shellet Tilsvarende slektskap finnes mellom trådene i en prosess * Med unntak av den aller første prosessen som kjøres ved oppstart og som startes av ROM-kode stamfaren til alle andre prosesser på systemet
Brukerhåndtering av prosesser i Linux Alle prosesser som opprettes i Linux tildeles en unik (gjenbrukbar) prosessidentifikator i form av et heltall som kalles PID. PPID er PID til forelder. PID 1 : init Brukere kan håndtere sine egne prosesser fra shellet bl.a. med kommandoene: & CTRL Z fg bg top pstree ps kill nice
Forgrunns- og bakgrunnsprosesser Linux shellkommandoer kjøres i forgrunnen shellet venter inntil kommandoen er ferdig '&' etter en Linux-kommando kjører kommandoen i bakgrunnen : Prosessen kjøres samtidig med shellet Shellet venter ikke til kommandoen er ferdig Kommandoen fg brukes for å flytte prosesser fra bakgrunn til forgrunn Nåværende prosess (i forgrunnen) kan suspenderes (tråden settes i wait-tilstand) ved å taste CTRL Z Kommandoen bg vil starte opp siste suspenderte jobb som en bakgrunnsprosess
Prosessinformasjon i Linux top : Løpende systemstatus og info. om alle prosesser på systemet, sortert på ressursbruk (top H for tråd-info). pstree : Hele slektstreet for prosessene (init er stamfar) ps : Skriver ut (deler av) prosesstabellen e Alle prosesser på systemet u Prosessene til en bestemt bruker m Informasjon om tråder [PID] Informasjon om en bestemt prosess-id For å få info. om bestemte programnavn/kommandoer: ps grep programnavn pgrep
Linux-prosesser og signaler Alle prosesser i Linux responderer på signaler Signaler er beskjeder fra OS-et om at en prosess skal avslutte eller endre oppførsel Signalene identifiseres med nummer eller navn Brukere kan sende signaler til prosesser med kommandoen kill Liste over alle signaler: kill l (/bin/kill L) Spesielt signal: KILL (signal 9) for å drepe en prosess: KILL sendes ikke til prosessen, men bare til OS-et sure kill : Prosessen kan ikke selv stoppe en KILL
kill - kommandoen Brukere kan sende signaler til egne prosesser: kill PID Terminér en bestemt prosess kill <signal> PID Send et bestemt signal til prosess kill HUP PID Brukes ofte for å restarte servere kill KILL PID Sure kill kill KILL 1 Massemord Shellprogrammer kan håndtere / ignorere signaler med trap trap "echo SIGINT d***er vi i" 2 Se også: pkill killall
Prioritering av prosesser i Linux Prosesser får en nice-verdi ved oppstart: -20 <= nice-verdi <= 19 19: Prosessen er super-nice og prioriteres lavt av OS langsom utførelse -20: Prosessen er grådig og prioriteres på topp rask utførelse Default er oftest 0 (null) Brukere kan sette nice-verdi med f.eks: nice 15 kommando Delte Linux-systemer er ofte satt opp slik at bare sys.adm./root kan bruke negative nice-verdier Prioriteten til en kjørende prosess kan endre med renice
Time-sharing og scheduling Time-sharing (med én enkel CPU): CPU kan bare utføre én instruksjon om gangen Alle prosesser som ønsker å kjøre en tråd i CPU ligger lagret i en kø/liste OS velger en tråd fra køen, som får kjøre i CPU i en kort periode (en time-slice, typisk 20 millisek.) Prosessen tas ut og må igjen vente i kø. Scheduling-algoritme i OS ( kjøreplan i læreboken): Bestemmer når bytte av prosess/tråd (context switch) skal gjøres må være svært rask for å unngå trege systemer Velger ut neste prosess/tråd som skal få kjøre i CPU i henhold til gitte kriterier og/eller prioriteter
Context switch Prosessen som lagrer og gjenoppretter tilstanden (konteksten) til en prosess/tråd, slik at utførelsen kan gjenopptas korrekt og på samme sted senere. Utføres av OS og består av: Tråden som er running stoppes og lagres, trådens utføringsstatus settes til ready Scheduling-algoritmen velger en av alle trådene med ready-status som den neste som skal få kjøre Tilstanden til valgt tråd og prosess hentes frem og gjenopprettes, trådens status settes til running Context switch ferdig, valgt tråd kjøres i CPU frem til neste context switch
Ulike typer scheduling preempt : ordne på forhånd/reservere/forhindre Preemptive scheduling: Timer starter context switch med jevne mellomrom, f.eks. 50-100 ganger per sekund Scheduling-algoritme velger neste tråd som kjøres Rettferdig fordeling av CPU-tid mellom trådene Nonpreemptive scheduling: Kjørende tråd bestemmer når context switch skjer, OS kan ikke avbryte. Krever at trådene/prosessene samarbeider og ikke er grådige cooperative multitasking Ikke for vanlige OS, finnes oftest i real-time systemer der presisjon er spesielt viktig (eks. styring av romfartøy)
Enkel preemptive scheduling: Round Robin Kjør prosess i én time-slice, legg deretter bakerst i kø Alle behandles likt Bruker en timer som sender avbruddsignal f.eks. 50 ganger i sekundet for å bytte prosess/tråd i CPU med en context switch Relativt lett å modifisere til å håndtere prioriteter for tråder/prosesser og flere enn en CPU De fleste OS bruker en eller annen variant av Round Robin scheduling
Hvordan velge tråder i scheduling? Alle tråder kan ha en prioritet: Velg alltid den som har høyest prioritet Like prioriteter behandles med Round Robin Alternativ: Variér lengden på time-slice iht. prioritet Store jobber kan nedprioriteres: Lagre brukt CPU-tid for alle tråder Velg alltid den som har brukt minst CPU-tid Prioriterer interaktive brukere fremfor tunge jobber Estimert gjenværende kjøretid: Prosessene gjetter på hvor mye CPU-tid som gjenstår Velg den med minst gjenværende tid for å prioritere småjobber
Scheduling i Linux * Bruker p.t. en relativt komplisert algoritme som kalles Completely Fair Scheduler Prøver å utnytte CPU så effektivt som mulig og samtidig prioritere interaktivitet Hovedregelen er å velge prosessen som har brukt minst CPU-tid til nå Interaktive prosesser prioriteres med sleeper fairness": Prosesser som er blocked er typisk interaktive programmer som sover fordi de venter på input fra bruker Disse behandles på samme måte som prosessene som er ready og venter på å kjøre i CPU igjen Felles scheduling av tråder og prosesser tråder betraktes som spesielle prosesser som deler ressurser. *: Beskrivelsen i læreboken er utdatert
Scheduling og avbruddsrutiner Avbrudd skjer f.eks. når en prosess kaller en systemrutine for å lese fra en I/O-enhet CPU avbryter kjøring av programmet og hopper til koden for avbruddsrutinen som håndterer I/O Det gjøres ikke context switch eller scheduling: Avbruddsrutinen er ikke en egen prosess Tråden som forårsaket avbruddet er fortsatt running Dette betyr at avbruddsrutinen har tilgang til både instruksjonen og dataene for prosessen, som er lagret i RAM disse må ikke endres Det er strenge krav til kvalitet og sikkerhet (og hastighet) ved (assembler)programmering av avbruddsrutiner
Virkemåte for avbruddsrutiner Avbruddsrutinen lagrer alle prosessdata som er nødvendig (CPU-registre) for å kunne restarte prosessen/tråden nøyaktig slik den var før avbrudd Dette må ikke lagres i prosessens minne-område, men i egne statiske minneceller satt av spesielt for avbruddsrutinen Avbruddsrutiner gjør alltid bare svært enkle operasjoner som f.eks. å starte en I/O-enhet, flytte data, eller endre status til en tråd. CPU må være i kjernemodus. Avslutter ved å sette lagrede data tilbake i CPU-registre og utføre en return from interrupt, slik at avbrutt prosess kan fortsette utføring.
Kjernetråder og brukertråder Kjernetråder: Kernel-level threads / Native threads Tråder som opprettes og håndteres av OS-et selv Brukertråder: User-level threads, Green threads Tråder som lages og håndteres av et run-time system som f.eks. Java Virtual machine
Kjernetråder Operativsystemets API tilbyr systemkall (C-funksjoner i Linux) for å opprette nye tråder inne i en prosess kjernetråder. Disse trådene er kjente for OS-et og håndteres med scheduling og context switch. Fordeler: OS-et kan utnytte maskiner med flere prosessorer/cpu-kjerner effektivt ved å kjøre kjernetråder parallellt, og kan f.eks. prioritere tunge prosesser med mange tråder. Kjernetråder er blokkeringsfrie, dvs. at en tråd som f.eks. venter på I/O ikke stopper andre tråder i samme prosess fra å kjøre. Ulemper: Opprettelse og context switch er relativt tidkrevende.
Brukertråder i Java Opprettes og håndteres av JVM Enklere med trådhåndtering i høynivå, API: java.lang.thread Tidligere versjoner av JVM (læreboken): Java-trådene fantes ikke i OS-et, som bare så en prosess JVM gjorde selv scheduling av trådene Bytte av brukertråd i JVM raskere enn OS context switch Kan ikke utnytte flere prosessorer effektivt (men virker også for single-thread OS) Ikke blokkeringsfrie: En brukertråd som trenger I/O kan stoppe alle andre brukertråder, fordi OS-et bare ser én prosess I nyere Java-systemer: JVM oppretter en kjernetråd som svarer til hver brukertråd i Javakoden (standard i Windows og Linux)
Programmering med tråder i Java Java-tråder er vanlige klasseobjekter i Java Trådene er er objekter av klassen java.lang.thread, eller av subklasser av denne I tillegg til å være objekter, kan Java-tråder også eksekvere kode. Opprettelse av ny tråd: Thread thread = new Thread(); Starte kjøring av den nye tråden: thread.start(); thread.start setter i gang metoden void run() i trådobjektet
To måter å starte tråden på 1.Lag en subklasse av Thread som definerer sin egen void run() metode. 2.Bruk et objekt som implementerer Runnable, som parameter til konstruktøren til Threadobjektet. Runnable-objektet må selv implementere metoden void run(), og kjøring av tråden vil starte i denne metoden.
Metode 1 public class MyThread extends Thread { public void run() { System.out.println("MyThread running"); } } public class ThreadTest1 { public static void main(string[] args) { MyThread mythread = new MyThread(); mythread.start(); } }
Metode 2 public class MyRunnable implements Runnable { public void run() { System.out.println("MyRunnable running"); } } public class ThreadTest2 { public static void main(string[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); } }
Sette navn på tråder Thread thread = new Thread("Mittnavn"); System.out.println(thread.getName()); MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable, "Mittnavn"); System.out.println(Thread.currentThread().getName ());* *: MyRunnable er ikke subklasse av Thread, og kan derfor ikke kalle getname i Thread. Løsningen er å bruke metoden Thread.currentThread() som alltid refererer til den tråden som til enhver tid er aktiv.
Eksempel: Tråder som skriver ut navnet public class Thread2 extends Thread { Thread2(String S) { super(s); } public void run() { System.out.println("Thread: " + getname() + " Running"); } } public class ThreadNames { public static void main(string[] args) { // Skriver ut navn til tråden som kjører main System.out.println(Thread.currentThread().getName()); // Oppretter og starter 10 nummererte tråder for(int i = 0; i < 10; i++) { Thread2 T = new Thread2("" + i); T.start(); } } }