Curs 03 - Procese

  • Suport curs
    • Operating System Concepts Essentials
      • Capitolul 3 - Processes
    • Modern Operating Systems (2nd Ed., 3rd Ed.)
      • Capitolul 2 - Processes and Threads - Secțiunea 1
    • Linux System Programming
      • Capitolul 5 - Process Management
      • Capitolul 6 - Advanced Process Management
      • Capitolul 9 - Signals
    • Windows System Programming
      • Capitolul 6 - Process Management
      • Capitolul 11 - Interprocess Communication

Demo-uri

Pentru parcurgerea demo-urilor, folosim arhiva aferentă. Demo-urile rulează pe Linux. Descărcăm arhiva folosind comanda

wget http://elf.cs.pub.ro/so/res/cursuri/curs-03-demo.zip

și apoi decomprimăm arhiva

unzip curs-03-demo.zip

și accesăm directorul rezultat în urma decomprimării

cd curs-03-demo/

Acum putem parcurge secțiunile cu demo-uri de mai jos.

Informații despre shell-ul curent

Pentru a afișa informații despre procesul shell curent, folosim comenzile de mai jos:

ps -f $$
ps -F $$
lsof -p $$
pmap $$
ls /proc/$$/
cat /proc/$$/status

Aceste comenzi ne afișează infomații precum PID-ul procesului, comanda de la care a pornit, PID-ul procesului părinte, timpul de rulare, fișierele deschise (lsof), spațiul de adresă al procesului (pmap).

Informații despre un proces din sistem

Dorim să aflăm informații complete despre un proces creat. Pentru aceea pornim în shell o comandă (find) care să creeze un proces:

/usr/bin/time -v find /usr/share > /dev/null

Comanda time trebuie dată în cale completă (/usr/bin/time) pentru a nu rula comanda time internă shell-ului.

Comanda permite rularea altei comenzi și afișarea de informații despre procesul creat de aceasta. Astfel de informații sunt cel de timp:

  • User time este timpul petrecut de proces în user space
  • System time este timpul petrecut de proces în kernel space
  • Elapsed (wall clock) time este timpul trecut pe ceas.

Procesul rulează pe procesor timp de User time + System time. Observăm că Elapsed time este mai mare decât timpul efectiv de rulare a procesului pe procesor. Acest lucru se întâmplă întrucât în acest timp au mai rulat și alte procese procesor, timp în care procesul curent a așteptat.

Ierarhia de procese a sistemului

Pentru a afișa ierarhia de procese a sistemului folosim comenzile de mai jos:

pstree
ps -H

Rădăcina ierarhiei este procesul init (PID-ul 1), procesul părinte al sistemului.

Vrem să vizualizăm evoluția arborelui de procese. Vom vizualiza subarborele unui proces shell.

Pentru început, aflăm PID-ul procesului shell curent:

echo $$

Fie $PID valorea afișată de comanda de mai sus. Deschidem un shell nou și rulăm comanda:

watch -n 1 pstree -a -p $PID

Comanda de mai sus afișează, cu refresh de o secundă, ierarhia de procese începând cu shell-ul inițial.

Pentru a altera ierarhia, rulăm comenzile de mai jos:

sleep 20 &
bash
sleep 30       # asteptati terminarea comenzii
exit

Observăm evoluția ierarhiei în al doilea shell:

  • La început se creează un proces sleep pe post de proces copil. Este rulat în background deci revine controlul shell-ului curent.
  • Shell-ul curent creează un alt shell. Acum shell-ul curent are două procese copil: procesul sleep 20 și procesul bash nou creat.
  • Shell-ul cel nou creează și el la rândul său un proces sleep și așteaptă încheierea sa. În acest moment shell-ul inițial are un proces copil sleep, un proces copil bash și un proces “nepot” sleep.
  • La încheierea rulării execuției proceselor, ierarhia se destramă.

Informații despre schimbările de context

Pentru a urmări numărul de schimbări de context ale unui proces, consultăm fișierul /proc/$PID/status. Ne interesează câmpurile voluntary_ctxt_switches și nonvoluntary_ctxt_switches. Pentru a urmări aceste valori pentru shell-ul curent folosim comanda:

cat /proc/$$/status

Câmpul voluntary_ctxt_switches (schimbări de context voluntare) se referă la schimbările în care procesul lasă de bună voie procesorul, de obicei acțiuni blocante de intrare/ieșire. Câmpul nonvoluntary_ctxt_switcher (schimbări de context nevoluntar) se referă la schimbările în care procesul este dat la o parte de pe procesor; de obicei acest lucru înseamnă expirarea cuantei curente de rulare; poate fi vorba și de apariția unui proces de prioritate mai bună.

Dacă rulăm de multe ori comanda de mai sus (cat /proc/$$/status) vom observa alterarea celor două câmpuri. Se alterează preponderent câmpul voluntary_ctxt_switches întrucât procesul curent, shell-ul, așteaptă în general intrare de la utilizator. Făcând în majoritate acțiuni de intrare/ieșire (blocante), cele mai dese schimbări de context sunt cele voluntare.

I/O bound vs. CPU bound

Vrem să observăm diferența tipurilor de schimbări de context pentru procese I/O bound și procese CPU bound. Vom folosi directorul ctxt-switch/ din arhiva cu demo-uri a cursului.

Parcurgem fișierele cpu.c și io.c. Observăm că cpu.c face multe acțiuni (consumă procesor), în timp ce io.c face operații blocante. Compilăm cele două programe folosind comanda

make

și obținem executabilele cpu și io.

Întâi rulăm executabilul cpu:

./cpu

Într-o altă consolă urmărim numărul de schimbări de context realizate de procesul nou creat:

cat /proc/$(pidof cpu)/status

Observăm că se modifică preponderent valoarea câmpului nonvoluntary_ctxt_switches. Acest lucru se întâmplă pentru că avem un proces CPU-bound. Acesta va consuma timp de procesor ori de câte ori prinde ocazia și va fi dat afară în majoritar la expirarea cuantei (nu face operații blocante). Un astfel de proces poartă numele de CPU hog.

Apoi rulăm executabilul io:

./io

Într-o altă consolă urmăriți numărul de schimbări de context realizate de procesul nou creat:

cat /proc/$(pidof io)/status

Observăm că se modifică preponderent valoarea câmpului voluntary_ctxt_switches. Acest lucru se întâmplă pentru că avem un proces IO-bound. Acesta va executa multe operații blocante în cadrul cărora va ceda de bună voie procesorul.

La o privire mai atentă la câmpul voluntary_ctxt_switches pentru procesul creat din executabilul io, observăm că acesta se modifică doar la apelul sleep nu și la apelul write, deși write este un apel I/O cu probabilitate de a se bloca. În realitate însă un apel write nu va copia date pe disc (sau la alt dispozitiv I/O) ci într-un buffer intern al nucleului. După copierea datelor în buffer-ul intern al nucleului, apelul write se întoarce, neexistând partea de blocare.

Cursorul de fișier la fork

Pentru a urmări comportamentului cursorului de fișier (file pointerului) vom folosi directorul fork-file-pointer/ din arhiva cu demo-uri a cursului.

Vom parcurge fișierul fork-file-pointer.c. Observăm că același descriptor de fișier este folosit și de procesul copil și de procesul părinte. Vrem să vedem dacă aceste două procese partajează cursorul de fișier.

Pentru început compilăm programul folosind comanda:

make

Obținem executabilul fork-file-pointer. Rulăm executabilul:

./fork-file-pointer

Într-o altă consolă urmărim evoluția cursorului de fișier pentru procesul creat (procesul părinte) folosind comanda:

lsof -a -o -d 0-1023 -p $(pidof fork-file-pointer | cut -d ' ' -f 1)

Observăm modificarea cursorului de fișier de la valoarea 0 la valoarea 10 apoi la valoarea 20 apoi la valoarea 30.

Cursorul de fișier este indicat de coloana OFFSET.

După încheierea procesului urmărim conținutul fișierului f.txt:

cat f.txt

Observăm că fiecare proces (părinte sau copil) a scris în continuarea celuilalt. Adică, în urma fork(), se partajează cursorul de fișier al fișierelor deschise înainte de fork(). Procesul părinte partajează cursorul de fișier cu procesele copil.

Procese orfane și procese zombie

Vrem să urmărim apariția proceselor orfane și a proceselor zombie și să le investigăm. Pentru aceasta vom folosi directorul orphan-zombie/ din arhiva cu demo-uri a cursului.

Parcurgem fișierele orphan.c și zombie.c. Primul program creează un proces orfan, adică își încheie execuția procesul părinte, dar procesul copil continuă execuția. Al doilea program creează un proces zombie, adică procesul copil își încheie execuția, dar procesul părinte continuă execuția fără a aștepta procesul copil.

Compilăm cele două programe folosind comanda

make

și obținem executabilele orphan și zombie.

Rulăm executabilul orphan:

./orphan

Într-o altă consolă urmărim PID-urile și PPID-urile celor două procese (părinte și copil):

watch -n 1 ps -f -C orphan

Urmărim mesajele afișate de procesul copil. Mesajele vor fi afișate și după terminarea procesului părinte. Observăm actualizarea PID-ului procesului părinte (PPID) pentru procesul copil: după ce a rămas orfan, procesul copil a fost adoptat de init (procesul cu PID-ul 1).

Rulăm executabilul zombie:

./zombie

Într-o altă consolă urmărim cele două procese (părinte și copil):

watch -n 1 ps -f -C zombie

Observăm trecerea procesului copil în starea zombie; în ieșirea comenzii ps apare șirul <defunct>. Observăm eliminarea procesului zombie după așteptarea sa de procesul părinte.

Un proces este zombie pentru că reține niște informație reziduală legată de modul în care și-a încheiat execuția; această informație reziduală este utilă procesului părinte. În momentul în care procesul părinte citește această informație reziduală folosind un apel din familia wait, procesul copil, acum zombie, își încheie complet execuția și dispare din sistem.

so/cursuri/curs-03.txt · Last modified: 2019/03/02 09:07 by razvan.deaconescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0