Salta el contingut
 

Programació de processos

Autor: Joan Puigcerver Ibáñez

Correu electrònic: j.puigcerveribanez@edu.gva.es

Llicència: CC BY-NC-SA 4.0

(Reconeixement - NoComercial - CompartirIgual) 🅭

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 maquinari 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.

Estructura d'un sistema operatiu

Figura 1. Estructura d'un sistema operatiu

Dimoni (daemon) o servei: És un procés que s'executa en segon pla 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 de dimonis a 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 processos s'executen a la vegada, el sistema operatiu va canviant de procés en execució en poc de temps (mil·lisegons).

  • Diversos 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. Aquesta 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 distribuïts en xarxa. 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 memòria i requereixen d'esquemes de comunicació especí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 memòria: 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 llenguatges 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 (groc a 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 finalitzat i allibera els recursos i la memòria utilitzada. També passa a aquest estat si ocorre 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 emmagatzemar-se en disc, alliberant memòria principal.

    • A punt i suspés: Processos preparats per entrar en execució però que es troben en memòria secundària.
    • Bloquejat i suspés: Processos bloquejats però que es troben en memòria secundària.

Estats d'un procés

Sergi Tur Badenas, Wikimedia Commons

Figura 2. Estats d'un procés

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 freqüè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ó depén del sistema 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ò cap d'ells ha aparegut espontàniament. 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ó depén 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'arbre 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 poden 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 memòria.

  • 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 procés 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ò ocorre, 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 que representa un procés del sistema. Existeixen dos mètodes que serveixen per crear processos i retornen un objecte de la classe Process, que serveix per gestionar aquest nou procés.

  • public Process ProcessBuilder.start(): Aquest mètode inicia el programa especificat en la creació del objecte ProcessBuilder i l'inicia.

    ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2");
    Process p = pb.start();
    
  • public Process Runtime.exec(String[] cmdarray, String[] envp, File dir): 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 desitgen utilitzar.
    • File dir: Directori de treball del programa
    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 excepció d'E/S: IOException. Això pot ocórrer 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(): El procés actual espera a que el procés representat per Process acabe. Retorna el codi de eixida del procés. El valor 0 indica una acabada normal.

    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 abruptament (destroy o kill) el procés fill. En Java, aquesta es realitza mitjançant la classe Process amb el mètodes:

En els dos casos, és pot utilitzar els mètodes isAlive() o waitFor() per comprovar si ha acabat o esperar que acabe respectivament.

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.

RunProcess.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.start();
            System.out.printf("S'ha iniciat el procés: %s\n", Arrays.toString(program));
            // 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);
        }
    }
}

Exemple d'execució:

  • String[] program = {"notepad"};: El programa RunProcess executa la comanda notepad, que crea un process amb l'editor de notes del sistema. Despré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.

DestroyProcess.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);
        }
    }
}

Exemple 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 fill acabe. Com es pot observar, el procés fill acaba abans d'hora perquè s'ha terminat.

Bibliografia

Comentaris