<div class="page"> <div class="cover text-center"> <img class="mx-auto" src=/itb/images/logo_mislata.png alt="logo"> # Progamació de processos <div class="text-end fit-content ms-auto my-3 mt-auto pt-3"> <p><strong>Autor:</strong> Joan Puigcerver Ibáñez</p> <p><strong>Correu electrònic:</strong> j.puigcerveribanez@edu.gva.es</p> <p><strong>Curs:</strong> 2024/2025</p> </div> <div> <p class="fw-bold mb-0">Llicència: BY-NC-SA</p> <p class="d-none d-md-block">(Reconeixement - No Comercial - Compartir Igual)</p> <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.ca" target="_blank"> <img class="mx-auto" src="/itb/images/license.png" alt="Licence"/> </a> </div><!--license--> </div><!--cover--> </div><!--page--> {:toc} ## Conceptes bàsics __Programa:__ Conjunt comprès per codi i dades que es troba al disc que resolen una necessitat concreta dels usuaris. __Procés:__ Quan un programa s'executa, es crea un procés, que controla la seua execució, els recursos que consumeix i el seu estat. Inclou: - __Comptador del programa:__ Indica el punt o la instrucció que esta executant. - __Imatge de memòria:__ Conté tot el espai en memòria utilitzat i les dades emmagatzemades. - __Estat del processador:__ Valor dels registres del processador que s'utilitzen. - __Usuari:__ Usuari que ha iniciat el procés, que marcarà els permisos dels quals disposa el procés (recursos del sistema que pot accedir...) Quan s'interromp l'execució d'un procés per alguna causa, es veuran més endavant, es desa una còpia dels tres elements esmentats a dalt per poder restaurar l'execució del procés al mateix punt en què es va interrompre. Els processos són entitats independents del programa que s'executa. Dos processos que executen el mateix programa poden coexistir. Per exemple, es poden tenir oberts dos fitxers pdf alhora amb el mateixa aplicació. Un altre exemple és el navegador Chrome. Chrome crea un procés diferent per cada pestanya que es té oberta. __Executable:__ És un fitxer que conté el programa en __binari__ (llenguatge màquina) i que es pot executar en el dispositiu. __Sistema operatiu:__ Peça de programari encarregada de gestionar de manera eficient tots els recursos de màquinari i programari del dispositiu, proporcionat un entorn amigable i fàcil d'utilitzar per l'usuari, i una interfície comuna per tot el programari instal·lat en aquest. <img src="/itb/DAM-PSP/UD1/img/os.png" alt="" class="center" height="400"/> __Dimoni (daemon) o servei:__ És un procés que s'executa en segon plà i no és accessible per l'usuari. La seua funció és proporcional alguna funcionalitat o servei al sistema i a la resta de processos. Exemples en Linux: - __systemd__: Dimoni d'administració de sistema dissenyat exclusivament per l'API del nucli Linux. - __networking__: Gestionar les interfícies de xarxa del dispositiu. - __crond__: El dimoni del planificador per a accions basades en el temps, com ara actualitzacions de programari o comprovacions del sistema. - __logind:__ Dimoni que gestiona els inicis de sessió. ## Programació concurrent La __computació concurrent__ permet que multiples programes s'estiguen executant alhora. Per exemple, es pot estar escoltant música, mentre s'utilitza un sistema de compartició de fitxers p2p i es redacta un document de text. Per poder implementar la computació concurrent, cal tindre en compte diferents escenaris: - __Dispositius amb un únic processador__ amb un únic nucli i sense fils: En aquest cas, només es pot executar un procés a la vegada. Per donar la sensació que diversos procesos s'executen a la vegada, el sistema operatiu va canviant de procés en execució en poc de temps (mil·lisegons). - __Diversios nuclis en un mateix processador__: Hui en dia és molt normal que els processadors continguen diversos nuclis (també en mòbils). Cada nucli pot executar un programa diferent. Com tots els processos comparteixen memòria, la comunicació entre ells és molt ràpida i senzilla d'implementar. També es pot donar el cas que cada nucli execute una instrucció diferent del mateix programa. Aquestà tècnica rep el nom de __programació paral·lela__, que permet millorar considerablement el rendiment programa. Per exemple, quan s'edita un document de text, un nucli s'encarrega de gestionar l'edició del text i un altre de revisar la correcció gramatical del text. - __Sistemes distribuïts:__ Sistema que consisteix en diferents equips distribuits en xaxa. Cada equip disposa del seu propi processador i memòria i la gestió conjunta de tots els equips s'anomena __programació distribuïda__. Millora el rendiment considerablement, però la la gestió i comunicació entre processos és més complicada, ja que no comparteixen mèmoria i requereixen d'esquemes de comunicació especiífics i costosos a través de la xarxa. El __cloud computing__ estaria dins d'aquesta categoria. ## Funcionament bàsic del sistema operatiu El __sistema operatiu__ (SO), com qualsevol altre programa, necessita estar en memòria per poder ser executat. Hi ha una part del sistema operatiu que s'encarrega de gestionar tota la resta i que ha d'estar sempre em mèmoria: __el nucli o kernel__. El kernel gestiona els recursos de l'ordinador, permetent l'accés a aquests mitjançant les trucades al sistema. El kernel és una part molt xicoteta del SO, sobretot si es compara, amb tot el necessari per implementar la interfície gràfica. A la resta de SO se'n diu programes del sistema. El nucli del sistema generalment treballa sobre la base d'__interrupcions__ (IRQ – Interrupt ReQuest). Una interrupció és la suspensió temporal d'un procés per executar la rutina que gestiona aquesta interrupció. Mentre s'atén una interrupció, es deshabilita l'arribada d'altres interrupcions. Les interrupcions solen estar generades per trucades al sistema. Les trucades al sistema es programen amb __llenguatges de baix nivell__, com C o C++, pel fet que permeten l'accés a nivells més baixos del maquinari. En els __lenguatges d'alt nivell__, els desenvolupadors fan ús d'__APIs__ del sistema. Les més comunes són Win32 (Windows), API POSIX (per a sistemes Unix en què s'inclou GNU Linux i Mac OS). ## Processos Com hem dit anteriorment, un procés és l'estat d'un programa en un moment donat de la seva execució amb tot el que això significa: comptador, memòria i registres de la CPU. És el SO l'encarregat de posar en execució i gestionar els processos. En els sistemes que suporten programació concurrent, els processos poden passen per diferents estats al llarg del seu cicle de vida. Els canvis d'estat també són gestionats pel sistema operatiu. Hi ha diversos models per implementar el cicle de vida d'un procés. ### Estats d'un procés - __Model de tres estats__ (blanc en la imatge): - _A punt_ o _esperant_: Programa preparat per entrar a la CPU. Ja se li ha assignat zona en memòria. - _Execució_: La CPU està executant el procès. - _Bloquejat_: Sense possibilitat d'entrar a la CPU, per exemple, per què està esperant una operació d'E/S. - __Model de cinc estats__: Afegeix dos estats al model anterior (blanc a la imatge). - _Creat_: S'està creant o s'acaba de crear, però encara no esta llest per ser executat. Per exemple, si s'ha arribat al límit de la memòria. - _Acabat_: El procès ha fintalitzat i allibera els recursos i la memòria utilitzada. També passa a aquest estat si ocurreix un error i deixa d'executar-se. - __Model de set estats__: Afegeix dos estats més (tota la imatge). Hi ha moments en què es poden trobar molts processos bloquejats, ocupant memòria i no deixen que altres processos puguen entrar en execució. En aquests casos, és important que es permitisca realitzar un __intercanvi (swap)__ i que aquests processos bloquejats passen a emmagatzermar-se en disc, alliberant memòria principal. - _A punt i suspés_: Procesos preparats per entrar en execució però que es troben en memòria secundària. - _Bloquejat i suspés_: Procesos bloquejats però que es troben en memòria secundària. <img src="/itb/DAM-PSP/UD1/img/process_states.png" alt="" class="center" height="400"/> ### Cues de processos Una de les funcions del SO és la gestió de processos, i en concret, gestionar els diferents estats dels processos i determinar quins processos passen a executar-se. Per realitzar aquesta tasca, fa ús de diferents cues. - _Cua general de processos_: conté tots els processos del sistema. - _Cua de processos preparats_: conté aquells processos llestos per executar-se. - _Cues de dispositiu_: conté els processos que esperen alguna operació E/S. ### Planificació de processos El SO necessita un __planificador de processos__ que s'encarrega de gestionar les cues de processos. Hi ha dos tipus de planificació: - __Curt termini:__ S'encarrega de triar quin és el següent procés en entrar a la CPU, aquesta operació s'executa moltes vegades i cada poc temps (mil·lisegons), per la qual cosa l'algorisme ha de ser senzill. Hi ha tres tipus d'algorismes per a aquesta planificació. - _Sense desallotjament_: Un procés que entra passa a execució no desallotja la CPU fins que no acaba o es bloqueja. Exemples: FIFO (First-In First-Out) i SJF (Shortest Job First). - _Amb desallotjament_: Permeten desallotjar el procès del processador encara que aquest no haja acabat. Examples: Round Robin o SRT (Shortest Remaining Time) sense prioritat, o planificacions que permeten que els processos tinguen diferents nivells de prioritat, com el Multilevel feedback queue. - __Llarg termini__: S'encarrega de gestionar els processos que passen a la cua de preparats, que s'invoca amb poca frequència. Cada procés és únic i impredictible. Els algorismes de planificació necessiten saber de quin tipus és per planificar els processos de la millor manera possible. Podem trobar processos: - __Orientats a CPU (CPU-bound)__: Normalment sols utilitza el processador per la seua execució. - __Orientats a E/S (IO-bound)__: L'execució depen del sistemà E/S i els seus recursos, com els discs durs o perifèrics. ### Arbre de processos Tots els processos han segut iniciats d'alguna manera. Alguns, s'han llançat per el SO, alguns altres els ha executat l'usuari, però ningút ha aparagut espontàneament. Això significa que qualsevol procés, ha segut iniciat per un altre procés. - Quan el SO arranca, inicia un procés amb la interfície gràfica i el gestor de finestres. - Quan l'usuari clica sobre l'accés directe del navegador, el gestor gràfic inicia el navegador en una nova finestra. - Quan l'usuari obri una nova pestanya, el navegador crea un procés per gestionar aquesta pestanya. Qualsevol procés en execució depen del procès que l'ha creat i estableix un enllaç entre ells. El procés creador s'anomena __pare__ i el creat __fill__. Quan s'inicia l'ordinador, el gestor d'arrancada s'encarrega d'executar el __kernel__ del SO i es crea el procés principal, sobre el qual aniran creant-se la resta de processos de manera jeràrquica. En Linux, aquest procés s'anomena __init__. Podem utilitzar la comanda __pstree__ per veure l'abre de processos. Tots els processos s'identifiquen mitjançant el __PID__ (Process Identifier), que és únic per a cada procés. ## Operacions bàsiques amb processos Les operacions amb processos es realitzen mitjançant trucades al sistema, ja que l'encarregat de gestionar-los és el sistema operatiu, per tant, aquestes operacions poden ser realitzades de diferent maneres depenent del sistema operatiu en el qual es trobeu. Les operacions que es poden invocar des d'un programa son: - __Crear process__: Crea un nou procés. Aquesta operació pot ser: - _Iniciar un programa diferent_: Cal indicar el programa que es desitja executar i els arguments. - _Fork_: Realitza un duplicat del procés que crea el procés, que contínua l'execució des del mateix punt, però continuen per separat. Els processos podern fer ús de __recursos compartits__. La __memòria compartida__ és una regió de la memòria a la qual poden accedir diferents processos que col·laboren. El SO s'encarrega de crear i establir els permisos dels processos que poden accedir a aquest espai de mèmoria. - __Esperar (i bloquejar)__: En alguns casos, els processos necessiten coordinar-se i esperar a que un altre procés acabe la tasca que està realitzant per poder continuar la seua execució. Això es fa mitjançant la operació __wait__. - __Acabar process__: Un procès pot acabar de diferents maneres. Per ell mateix, el procés acaba si arriba a la última instrucció o si es crida a la funció __exit(return_code)__. Aquesta funció rep el paràmetre __return_code__, que és un codi intern per identificar el motiu pel qual ha acabat. Si acaba perquè arriba a la última instrucció, es crida a aquesta funció de manera automàtica amb un valor de 0. Qualsevol altre valor distint a 0, indica que ha acabat amb algun error i el programa ha fallat. El desenvolupador és el responsable de especificar els codis d'error del seu programa. El procés pare té "control" sobre el proces fill, per tant, també pot acabar amb la seua execució. Per fer-ho, existeix l'operació __destroy__, que acaba abruptament l'execució del fill. Es podria donar el cas que el procés pare acabe abans que el procés fill. Si aixó ocurreix, es diu que el procés fill és un __procés orfe__. Aquesta situació es gestionada de diferent manera per cada sistema operatiu. Alguns SO no permeten aquesta situació, i realitzen una __terminació en cascada__, que termina tots els processos fills si el procés pare acaba. ### Creació de processos En Java, existeix la classe [__Process__](https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html) que representa un procés. Existeixen dos métodes que serveixen per crear processos i retornen un objecte de la clase Process, que serveix per gestionar aquest nou procés. - [`public Process ProcessBuilder.start()`](https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html#start()): Aquest mètode inicia el programa especificat en la creció del objecte `ProcessBuilder` i l'inicia. ```java ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2"); Process p = pb.start(); ``` - [`public Process Runtime.exec(String[] cmdarray, String[] envp, File dir)`](https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#exec(java.lang.String[],%20java.lang.String[],%20java.io.File)): Aquest mètode inicia el programa especificat en els paràmetres de la funció. - `String[] cmdarray`: Comanda i arguments de la comanda a executar. - `Sring[] envp`: Variables d'entorn que es dessitjen utilitzar. - `File dir`: Directori de treball del programa ```java Runtime runtime = Runtime.getRuntime(); String[] cmdarray = {"myCommand", "myArg1", "myArg2"}; String[] envp = {"VAR=value"}; File dir = new File("path/to/working_directory") Process p = runtime.exec(cmdarray, envp, dir); ``` Els dos mètodes anteriors poden llançar una exepció d'E/S: [`IOException`](https://docs.oracle.com/javase/7/docs/api/java/io/IOException.html). Això pot ocurrir per diferent motius, com que no es trobe la comanda o que l'usuari no tinga permisos per executar eixe programa, entre altres motius. Per tant, cal crear el procés dins d'un bloc `try{ ... } catch { ... }`. ### Wait El procés pare pot esperar que acabe el procés fill. En Java, aquesta es realitza mitjançant la classe `Process` amb el mètode: - [`public int Process.waitFor()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html#waitFor--): El procés actual espera a que el procés representat per `Process` acabe. Retorna el __codi__ de exida del procés. El valor 0 indica una acabada normal. ```java args = {"myCommand", "myArg1", "myArg2"}; ProcessBuilder pb = new ProcessBuilder(args); Process p = pb.start(); int returnCode = p.waitFor(); System.out.println( "L'execució de " + Arrays.toString(args) + " retorna " + returnCode ); ``` ### Destroy El procés pare pot terminar abruptment (destroy o kill) el procés fill. En Java, aquesta es realitza mitjançant la classe `Process` amb el mètodes: - [`public void Process.destroy()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html#destroy--): Acaba el procés de una manera normal i ordenada. Això pot provocar que el procés no acabé inmediatament. - [`public void Process.destroyForcibly()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html#destroyForcibly--): Acaba el procés de manera forçada. Això pot provocar que el procés no acabe correctament. En els dos casos, és pot utilitzar els mètodes `isAlive()` o `waitFor()` per comprovar si ha acabat o esperar que acabe respectivament. ```java args = {"myCommand", "myArg1", "myArg2"}; ProcessBuilder pb = new ProcessBuilder(args); Process p = pb.start(); p.destroy(); System.out.println( "El procés: " + Arrays.toString(args) + p.isAlive() ? " està viu." : " ha acabat." ); ``` ## Exemples en Java ### RunProcess Aquest programa crea un nou programa a partir dels arguments passats al programa __RunProcess__. El procés és creat mitjançant la classe `ProcessBuilder`. Una vegada creat, el procés pare espera a que acabe el procés fill i imprimeix per pantalla el _codi de retorn_ del procés fill. - <a href="/itb/DAM-PSP/files/ud1/examples/RunProcess.java" download="RunProcess.java">RunProcess.java</a> ```java package ud1.examples; import java.io.IOException; import java.util.Arrays; public class RunProcess { public static void main (String[] args) { // Indica la comanda que utilitza aquest programa per iniciar un nou procés String[] program = {"notepad"}; ProcessBuilder pb = new ProcessBuilder(program); try { // Inicia el procés fill Process process = pb.staet(); // El procés Java (pare) Espera a que el procés fill finalitze int codiRetorn = process.waitFor(); System.out.println("L'execució de "+ Arrays.toString(program) +" retorna "+ codiRetorn); } catch (IOException ex) { System.err.println("Excepció d'E/S."); System.out.println(ex.getMessage()); System.exit(-1); } catch (InterruptedException ex) { System.err.println("El procés fill ha finalitzat de manera incorrecta."); System.exit(-1); } } } ``` _Exmeple d'execució_: - `String[] program = {"notepad"};`: El programa `RunProcess` executa la comanda `notepad`, que crea un process amb l'editor de notes del sistema. Desrpés, espera a que acaba i retorna el codi 0: __èxit__. - `String[] program = {"powershell", "sleep", "5"};`: El programa `RunProcess` executa la comanda `sleep 5` en la línia de comandes `Powershell`. Aquest procés esperarà 5 segons i acabarà amb el codi 0: __èxit__. - `String[] program = {"powershell", "noexisteix"};`: El programa `RunProcess` intenta executar la comanda `noexisteix` en la línia de comandes `Powershell`, que evidentment no existeix. Això fa que el procés acabe en el codi d'error 1: __error__. ### DestroyProcess Aquest programa crea un nou programa a partir dels arguments passats al programa __DestroyProcess__. El procés és creat mitjançant la classe `Runtime`. Una vegada creat, el procés pare termina el procés fill abans de que acabe i comprova si ha acabat. Per últim, espera a que acabe i torna a mostrar si ha acabat. - <a href="/itb/DAM-PSP/files/ud1/examples/DestroyProcess.java" download="DestroyProcess.java">DestroyProcess.java</a> ```java package ud1.examples; import java.io.IOException; import java.util.Arrays; public class DestroyProcess { public static void main (String[] args) { // Indica la comanda que utilitza aquest programa per iniciar un nou procés String[] program = {"powershell", "sleep", "5"}; try { Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(program); System.out.println( "El procés: " + Arrays.toString(program) + (process.isAlive() ? " està viu." : " ha acabat.") ); System.out.println("Destruint..."); process.destroy(); process.waitFor(); System.out.println( "El procés: " + Arrays.toString(program) + (process.isAlive() ? " està viu." : " ha acabat.") ); } catch(IOException ex) { System.err.println("Excepció d'E/S"); System.err.println(ex.getMessage()); System.exit(-1); } catch(InterruptedException ex) { System.err.println("El procés fill ha finalitzat de manera incorrecta."); System.exit(-1); } } } ``` _Exmeple d'execució_: - `String[] program = {"powershell", "sleep", "5"};`: El programa `DestroyProcess` executa la comanda `sleep 5` en la línia de comandes `Powershell`, que hauria d'esperar 5 segons. Acte seguit, comprova si està viu amb `isAlive()`, el destrueix i espera a que el fll acabe. Com es pot observar, el procés fill acaba abans d'hora perquè s'ha terminat. ## Bibliografia - https://ca.wikipedia.org/wiki/Gesti%C3%B3_de_processos - https://www.baeldung.com/cs/cpu-io-bound