Procesos e hilos
Pablo San Segundo(C-206) [email protected]
KERNEL DEL SISTEMA OPERATIVO
Al arrancar el PC siempre se carga en memoria una copia del Kernel desde disco
El Kernel del sistema operativo es un programa, típicamente programado en C
¿Qué es un Proceso?
`
Un programa en ejecución
Presencia en memoria y tabla BCP
`
Registros de la CPU
` Contexto
`
Entorno
` Identificadores de usuario y grupo ` Identificador de proceso
` Variables de entorno ` Usuario
` Sistema
`
Control (tablas)
` Estado: Bloqueado, ejecución, listo ` Prioridad: cola de prioridades
` E/S: Desc. recursos, ,mensajería etc. ` Presencia en memoria typedef struct bcp { struct bcp *siguiente; struct bcp *anterior; struct bcp *hebra int retardo; palabra marco; palabra pila; palabra marcador; int pri; int estado; struct bcp *todosenl; int mascara; k_ptrmens retenmens procnombre nombre; } bcp; Bloque de Control de Proceso
ESTADOS BÁSICOS DE UN PROCESO
`
EN EJECUCIÓN
` Un proceso por procesador
`
BLOQUEADO
` En espera de completar E/S
`
LISTO
ENTORNO DEL PROCESO
`
Definición
` Tabla NOMBRE-VALOR que se pasa al proceso en su creación
`
Ubicación
` Imagen en Memoria: típicamente en la pila
`
Valor
` Por defecto
` Mandatos Shell (API S.O.)
PATH=/usr/bin:/home/pss/bin TERM=tty100 HOME=/home/pss PDW=/home/pss/ingsw SHELL echo $PATH echo $HOME SHELL PATH=$PATH:XXX
CARACTERÍSTICAS DE PROCESOS
`
Jerarquías (familias)
` Procesos hijo, padre, hermano, abuelo etc..
`
Vida
` Creación ` Ejecución ` Muerte `Tipo de ejecución
` Batch (lotes) ` Interactivo INIINI SES INI SES
SHELL SHELL
USUARIO
`
Definición
`
Persona autorizada a utilizar el sistema
`
Autenticación
` User login (internamente es un número para el S.O.: i.e uid) ` Contraseña (password)
`
Super-usuario
(sudo, su)` Derechos de administrador
`
Grupo de usuarios
` Colectivo de usuarios con los mismos permisos ` Todo usuario ha de pertenecer a un grupo
FIN DE
Fundamento del multiproceso
`
Definición
` Ejecución concurrente real de varios procesos con un solo procesador
`
Bases
` Alternancia de los procesos en la CPU durante la fase de E/S ` Almacenamiento en memoria de varios procesos
Ejemplo
VENTAJAS DE LA MULTITAREA
`
Funcionamiento del SO en modo ‘interactivo’ de
forma eficiente
`
Aprovecha tiempos de espera de procesos durante E/S
`
Aumenta el uso de la CPU
`
Nuevo paradigma de programación
`
Principio de ‘modularidad’:
Un programa-varios
GRADO DE MULTIPROGRAMACIÓN
`
Definición
` Número de procesos activos en un instante de tiempo
`
Recurso
` Memoria principal
GRADO DE MULTIPROGRAMACIÓN
GRADO DE MULTIPROGRAMACIÓN
`
Sistema CON memoria virtual
RENDIMIENTO DEL PROCESADOR
FIN DE S.O.
MULTITAREA
INFORMACIÓN DEL PROCESO EN S.O.
i.e. RIED
Se gestionan fuera del BCP. ¿Por qué? CODIGO
PILA
ESTADO DEL PROCESO : CONTEXTO
` Registros generales
` Motorola 68000: A0-A7, D0-D7
` Contador de programa (PC)
` Siguiente instrucción a ejecutar
` Puntero de Pila (“Call Stack pointer”)
` Referencia la pila en memoria asociado a todo proceso ` Motorola 68000 (USP y SSP)
` Puntero de marco (“Frame pointer”) ` Registro de Estado
` Modo de ejecución, máscaras de interrupciones, información acerca de la
última operación realizada en la ALU, etc…
` Motorola 68000 (SR)
` Registros especiales
` Ejemplo: RIED: Registro que Identifica el Espacio de Direcciones que
PROCESO EN MEMORIA
Pertenece al BCP
Preasignación inicial de zona de swapping
Segmentación fija
Hoy en día la segmentación
TABLAS DEL SISTEMA OPERATIVO
`
Tabla de procesos (BCP)
` Información exclusiva de cada proceso que no se puede
COMPARTIR y cuyo tamaño no varía durante la ejecución de
cada proceso (IMPLEMENTACIÓN)
`
Tabla de Memoria (alias paginado)
` Información para la gestión de memoria `
Tabla de Entrada / Salida
` Información relativa a periféricos y a las operaciones E/S `
Tabla de Ficheros
Ejemplo Tabla de Ficheros
COMPARTEN LECTURA Y ESCRITURA EN FICHERO Puntero de posición TABLA DE FICHEROS Doble direccionamientoEJEMPLO: INFORMACIÓN EN EL BCP
TREEPS (LINUX)FIN DE PRESENCIA
EN MEMORIA DE
NACIMIENTO DE UN PROCESO
Linker A imagen en memoria Transmisión de ficheros al Motorola 68000 e.g. kernel.lib/dllFORMACIÓN DE UN PROCESO
EL EJECUTABLE NO CONTIENE A LAS BIBLIOTECAS DEL SISTEMA
COPIA DE LAS BIBLIOTECAS DEL SISTEMA EN LA IMAGEN DE MEMORIA
(kernel.lib)
Tablas S.O.
SE ACTUALIZA LA PRIMERA VEZ Y EN CADA CAMBIO DE CONTEXTO
FIN DE FORMACIÓN
DE UN PROCESO
SERVICIOS DE GESTIÓN DE PROCESOS
`
Identificación de procesos
`
Entorno de un proceso
`
Creación de procesos
`
Cambio de imagen de un proceso
`
Espera a la terminación de un proceso
`
Finalización de la ejecución de un proceso
SERVICIO POSIX: fork
`
Definición
` pid_t fork (void) : Crea un proceso hijo clonando al padre.
`
Librerías
` #include <sys/types.h> `Retorno
` Identificador de procesoÆpadre ` 0 Æhijo ` -1 si error `Descripción
` Crea un proceso hijo que ejecuta el mismo programa que el padre
` Se heredan los descriptores (i.e. archivos) ` Se desactivan las alarmas pendientes
SERVICIO POSIX: fork
SERVICIO POSIX: exec
`
Definición
` Cambia el programa del proceso
EJEMPLO: Servicio fork
int main() { pid_t pid; pid=fork(); if (pid= = -1) cout<<“Error”<<endl; else if (pid == 0) { cout<<“Proceso hijo”<<endl; } else{ cout<<“Proceso padre”<<endl; } return 0; } #include <sys/types.h> #include <iostream>SERVICIO POSIX: exec
`
Declaraciones
` int execl (const char* path, const char* arg,…) ` int execlp(const char* file, const char* arg,…) ` int execvp(const char* file, const char* arg[])
`
Retorno
` -1 si error o NO RETORNA
`
Descripción
` El mismo proceso ejecuta otro programa
` Los descriptores abiertos permanecen abiertos ` Las señales toman la acción por defecto
Ultimo parámetro NULL
EJEMPLO: Servicio execl
int main() { pid_t pid; if ((pid = fork()) = = -1) cout<<“Error”<<endl; else if (pid == 0) {if(execl ("/bin/ls, “ls", “-l”, NULL)){
cout<<“Error al lanzar la tarea”<<endl; } } int status; wait(&satus); return 0; } #include <sys/types.h> #include <unistd.h> #include <iostream>
EJEMPLO: Servicio execv
int main() { pid_t pid; char * arg[3]; arg[0]=“gedit”; arg[1]=“test.cpp”;arg[2]=NULL; if ((pid = fork()) = = -1) cout<<“Error”<<endl; else if (pid == 0) {if(execvp (arg[0], arg)){
cout<<“Error al lanzar la tarea”<<endl; } } int status; wait(&satus); return 0; } #include <sys/types.h> #include <unistd.h> #include <iostream>
SERVICIO POSIX: int exit (int status)
`
Definición
`
Termina un proceso, devolviendo el parámetro status al
proceso padre
`
Declaración
`
void
exit
(int status)
` EXIT_SUCCESS, EXIT_FAILURE `
Argumento
` Código de retorno al proceso padre `
Descripción
` Finaliza la ejecución del proceso ` Cierra todos los descriptores ` Se liberan todos los recursos
SERVICIO POSIX: wait
`
Definición
`
Espera la terminación de un proceso hijo CUALQUIERA
`
Declaración
` pid_t wait (int* status)
` pid_t waitpid (int pid, int* status, int options)
`
Retorno
`
Identificador del proceso hijo, -1 si error
`
Descripción
`
Permite a un proceso padre esperar hasta que termine un
proceso hijo
`
Devuelve el estado del proceso hijo al terminar
` Macros para analizar el estado: WIFEXITED, WEXITSTATUS
WIFSIGNALED, WTERMSIG, etc…
WUNTRACED WNOHANG
EJEMPLO : Servicio wait con MACROS
int main() { int status; pid_t pid; pid= fork(); if(pid==0){execlp(“gedit”, “main.cpp”, NULL); exit(EXIT_SUCCESS); } wait(&status) ; if(WIFEXITED(status)){ if(WEXITSATUS(status)==EXIT_SUCCESS){ cout<<“OK”<<endl; }else if(WEXITSATUS(status)==EXIT_FAILURE)){ cout<<“ERROR”<<endl; } }
cout<<"Proceso padre terminado"<<endl;
exit(EXIT_SUCCESS);
}
EJEMPLO : Servicio wait multiproceso
int main()
{
int status;
pid_t pid;
for(int i=0; i<10; i++){
pid= fork(); if(pid==0){ cout<<"hilo: "<<i<<endl; exit(EXIT_SUCCESS); } }
//Espera a todos los hijos
while ( wait(&status) !=-1 ){;}
cout<<"Proceso padre terminado"<<endl;
exit(EXIT_SUCCESS);
EJEMPLO: waitpid
int main(void) { int status; pid_t pid; pid= fork(); if(pid==0){ sleep(5);cout<<"proceso hijo terminado"<<endl;
exit (EXIT_SUCCESS);
}
pid_t endid= waitpid(pid,&status, WNOHANG); //no bloqueante
if(endid==pid){
cout<<"status del hijo diposnible"<<endl; } else{
cout<<"padre termina sin conocer el status del hjo"<<endl; }
exit (EXIT_SUCCESS);
Caso de uso típico de exec /fork /wait
EVOLUCIÓN DE PROCESOS: ZOMBIES
`
El hijo muere antes de la ejecución de un servicio
wait(…) del padre
` Hijo: Proceso ZOMBIE
BCP de B sigue existiendo
EJEMPLO: Zombies
int main(void) { pid_t pid; pid= fork(); switch(pid){ case -1:cout<<"Error al creado proceso duplicado"<<endl;
exit(EXIT_FAILURE); break;
case 0:
sleep(1);
cout<<"proceso hijo terminado"<<endl; exit(EXIT_SUCCESS);
break;
}
cout<<“hijo terminará sin wait del padre: ¡ZOMBIE!"<<endl; sleep(30);
int status;
wait(&status);
exit(EXIT_SUCESS);
EJEMPLO: JERARQUÍAS DE PROCESOS
int main (int argc, char** argv )
{
int i, valor;
pid_t pid_1, pid_2;
i=atoi(argv[1]); pid_1=fork(); while(i>0){ pid_2=fork(); if(pid_2!=0) { wait(&valor) break; } i=i-1; } return 0; } #include <sys/types.h> <Nombre Ejecutable> 3 Ejecución desde Terminal ¿JERARQUÍA DE PROCESOS ? A) Figura B) Sin Bloque #include <sys/wait.h>
Ejercicio: Procesos distribuidos (I)
Impleméntese un proceso que lance la tarea de leer un grafo en formato DIMACS de un fichero de texto y devuelva su grado en pantalla.
El proceso principal debe esperar a la terminación de dicha tarea e indicar si se ha resuelto correctamente.
FIN DE SERVICIOS
POSIX PARA
ESTADOS DE UN PROCESO
SWAPPING
Planificador Corto Plazo
PLANIFICACIÓN DE PROCESOS
`
Planificador (
Planner
): Programa del S.O. que determina
que proceso tiene que ejecutarse.
`
Activador (
Dispatcher
) : Programa del SO que carga el
programa seleccionado por el planificador en memoria
`
Formas de planificar del S.O:
` Largo plazo: Decidir que procesos batch entran a ejecutarse ` Medio plazo: Añadir procesos a RAM
` Finalización del estado de suspendido. ` Sistemas con swapping.
` CORTO PLAZO:
NOCIONES BÁSICAS DEL CORTO PLAZO
`
Tipos de planificación
`
Sin expulsión
: El proceso conserva la CPU mientras lo desee
`Con expulsión
: El S.O. quita la CPU al proceso cada cierto
tiempo.
`
Colas de procesos
`
Por prioridad
(niveles)
`Por tipo
` Lotes
`
Reparto de la CPU equitativo
`Eficiencia (optimizar uso CPU)
`Mejorar el tiempo de respuesta
`
Modo interactivo
`Modo lotes
`
Cumplir plazos de ejecución en sistemas de tiempo
real
OBJETIVOS DE LA PLANIFICACIÓN
INCOMPATIBILIDAD DE OBJETIVOS!
COLAS DE PROCESOS
`
EJEMPLO
Implementación de colas de procesos
`
Implementación con punteros a otros BCP
`
Acceso eficiente
ALGORITMOS DE PLANIFICACIÓN
`
CÍCLICA (Round Robin)
`
Asignación de CPU rotatoria
`
Se asigna un tiempo máximo a todos los procesos (
time slot
)
`Los procesos se organizan en una cola circular
` Un proceso que excede su slot se expulsa y se coloca al final de la
cola
SISTEMAS DE TIEMPO COMPARTIDO
REPARTO por UID en vez de por TIEMPO de PROCESAMIENTO
ALGORITMOS DE PLANIFICACIÓN (II)
`
FIFO (‘first in-first out’)
`
Los procesos se organizan según una
cola ‘clásica’
. Entran
por el final y esperan su turno.
`
Política de planificación
SIN EXPULSIÓN
`
El proceso en ejecución sólo será expulsado cuando él
mismo realiza una llamada bloqueante al sistema (i.e. E/S)
`
Problema:
INANICIÓN
` Se suele combinar con política de prioridades (i.e. POSIX)
PROCESAMIENTO POR LOTES
ALGORITMOS DE PLANIFICACIÓN (III)
`
Prioridades
`
Fijas: Problema de inanición (p. ej. RTOS)
`Variables (aumentan con envejecimiento)
`
El trabajo más corto primero
`
Aplicación en sistemas Batch
`Menor tiempo de respuesta
`Penaliza las tareas largas
`
Dificultad en conocer ‘a priori’ las tareas de mayor duración.
ALGORITMOS DE PLANIFICACIÓN RT (IV)
`
Definición Sistemas de Tiempo Real (RTOS)
` Sistemas donde los procesos deben ejecutarse en instantes predeterminados
`
Tipos de RTS
`
Requisitos blandos / duros
`
Ejecución a plazo fijo / periódico
2h:30m:10s
2h:30m:10s 2h:30m:20s
Prioridad Reloj de tiempo
9-2-2006 / 2h:30m:15s
La CAPACIDAD DE PROCESAMIENTO NO es especialmente altaPREPROCESAMIENTO PREDECIBLE: Sin caches, sin
paginación, sin predicción de saltos
REQUISITOS DUROS: En ningún caso de puede rebasar el tiempo de reloj asociado al proceso
EJEMPLOS
QNX RTLinux VxWorks Windows CE
PLANIFICACIÓN EN POSIX
`
Prioridad
` Absoluta (RT)
` (0)-normal | (>0)-RT ` Dinámica
` POSIX 19 a -20 (mayor número Æ menor prioridad)
`
El planificador siempre elige aquellos procesos con mayor
prioridad
`
Políticas de planificación
`
FIFO (procesamiento por lotes)
`Cíclica (modo interactivo)
Ejercicio: Procesos distribuidos (I)
`
Algunos servicios POSIX para prioridad dinámica
` int setpriotiy (PRIO_PROCESS, int pid, int niceval)
` RETURN VALUE: -1 ERROR, 0 OK
` int getpriority (PRIO_PROCESS, int pid)
Escriba un programa en C para POSIX que lance dos tareas P1 y P2 que compartan escritura en un fichero de texto “log.txt”.
Modifique la prioridad de P1 y P2 y compruebe como afecta a la salida.
Solución
int main(void)
{
pid_t pid, pid2;
ofstream f("log.txt", ios::app); //fichero compartido pid=fork(); if(pid==0){ sleep(1); cout<<"Proc 1: "<<getpriority(PRIO_PROCESS,pid)<<endl; //…escribir en fichero exit(EXIT_SUCCESS); } pid2=fork(); if(pid2==0){ sleep(1); cout<<"Proc 2: "<<getpriority(PRIO_PROCESS,pid)<<endl; //…escribir en fichero exit(EXIT_SUCCESS); } setpriority(PRIO_PROCESS,pid,19); setpriority(PRIO_PROCESS,pid2,0); int status;
while(wait(&status)!=-1) continue; f.close();
execlp("gedit", "gedit", "log.txt", NULL); //opcional
exit (EXIT_SUCCESS);
FIN DE POLÍTICAS
DE PLANIFICACIÓN
THREAD (Proceso ligero)
`
Definición
` Un thread es un proceso que comparte un espacio en memoria con otros threads.
`
Estructura de un proceso en WIN NT
COMPARTEN MEMORIA
¡Si termina el hilo principal mueren el resto de hilos!
PROGRAMACIÓN CON THREADS
`
Un thread es equiparable a una función que se ejecuta en
PARALELO
con otras funciones (threads)
`
Programación con hilos
` Elevada dificultad
` Al escribir código es fundamental tener en cuenta un PARALELISMO
REAL (aun cuando se tenga un solo procesador)
` Imaginar LLAMADAS DISTINTAS AL MISMO CÓDIGO
`
Comunicación entre hilos
` Variables compartidas en memoria
` Descriptores de recursos compartidos,
` Elementos de sincronización compartidos etc.
APLICACIÓN: DISEÑO DE SERVIDORES
GESTIÓN DE PROCESOS LIGEROS
SERVICIOS POSIX PARA PROCESOS LIGEROS (I)
`
int
pthread_create
(pthread_t* , const pthread_attr_t*,
void* (*func)(void*), void* arg )
` Crea un proceso ligero que ejecuta func con argumentos arg y atributos attr
` Atributos (modificables mediante servicios)
` Tamaño de la pila
` Prioridad, Política de planificación etc.
` RETURN VALUE: 0-OK o ERRNUM `
void
pthread_exit
(void* value)
` Finaliza la ejecución e indica el estado de terminación
`
pthread_t
pthread_self
(void)
` Devuelve el identificador del thread que realiza la llamada
¿COMPARATIVA CON
SERVICIOS POSIX PARA PROCESOS LIGEROS (II)
`
int
pthread_join
(pthread_t tid, void ** value_ptr)
` Suspende la ejecución de un thread JOINABLE hasta que termina el thread (a no ser que haya terminado)
` Devuelve el estado de terminación del thread en value_ptr según devuelto por pthread_exit o PTHREAD_CANCELED . ` RETURN VALUE: 0-OK
`
int
pthread_detach
(pthread_t pid)
` Convierte un hilo en DETACHED en tiempo de ejecución ` RETURN VALUE: 0-OK
Ejemplos: Hilos-Creación
int main(void)
{
pthread_t thid;
pthread_create(&thid, NULL, sumar_thread, NULL); if(pthread_join(thid,NULL)){ //0-OK
cout<<“Hilo joinable no terminado adecuadamente”<<endl; }
exit(EXIT_SUCCESS); }
void* sumar_thread (void*)
{
int a=5; int b=8;
cout<<“la suma del hilo es:”<<a+b<<endl; phtread_exit(NULL);
Ejemplos: Hilos-Paso de parámetros
struct sSUMANDOS {int a; int b;} ;
int main(void)
{
sSUMANDOS sum; sum.a=10; sum.b=20; pthread_t thid;
pthread_create(&thid, NULL, sumar_thread, &sum); pthread_join(thid,NULL);
exit(EXIT_SUCCESS); }
void* sumar_thread (void*param)
{
sSUMANDOS* p=(sSUMANDOS*) param;
cout<<“la suma del hilo es:”<<pÆa +pÆb<<endl;
phtread_exit(NULL); }
Ejemplos: Hilos-Comunicación (I)
struct sSUMANDOS {int a; int b;} ;
int main(void)
{
sSUMANDOS sum; sum.a=10; sum.b=20; pthread_t thid;
pthread_create(&thid, NULL, sumar_thread, &sum); pthread_join(thid,NULL);
cout<<“Los valores nuevo son:”<<sum.a<“:”<<sum.b<<endl; exit(EXIT_SUCCESS);
}
void* sumar_thread (void*param)
{
sSUMANDOS* p=(sSUMANDOS*) param;
pÆa=8; pÆb=10; //…modifica variable en bloque invocante
phtread_exit(NULL); }
Ejemplos: Hilos-Comunicación (II)
struct sSUMANDOS {int a; int b;} ;
int main(void)
{
sSUMANDOS sum; sum.a=10; sum.b=20; pthread_t thid;
pthread_create(&thid, NULL, sumar_thread, &sum);
void* ret_thread;
pthread_join (thid,&ret_thread); sSUMANDOS ret;
ret.a=((sSUMANDOS*) ret_thread)Æa; ret.b=((sSUMANDOS*) ret_thread)Æb;
cout<<“Los valores devueltos son:”<<ret.a<“:”<<ret.b<<endl; exit(EXIT_SUCCESS);
}
void* sumar_thread (void*param)
{
sSUMANDOS* p=(sSUMANDOS*) param;
sSUMANDOS* pret=new sSUMANDOS;
pret->a=pÆa*2; pret->b=pÆb*2; //mod. de valores
phtread_exit(pret);
Ejemplos: Hilos-Comunicación (III)
struct sSUMANDOS {int a; int b;} ;
int suma=0; //variable compartida en memoria
int main(void)
{
sSUMANDOS sum; sum.a=10; sum.b=20; pthread_t thid;
pthread_create(&thid, NULL, sumar_thread, &sum); pthread_join(NULL);
cout<<“La suma es:”<<suma<<endl; exit(EXIT_SUCCESS);
}
void* sumar_thread (void*param)
{
sSUMANDOS* p=(sSUMANDOS*) param;
suma=pÆa + pÆ b;
phtread_exit(NULL); }
Ejemplos: Hilos-DETACHED
int main(void)
{
pthread_t thid;
pthread_create(&thid, NULL, sumar_thread, NULL);
pthread_detach (thid);
pthread_join(NULL);
cout<<“hilo principal terminado”<<endl; exit(EXIT_SUCCESS);
}
void* sumar_thread (void*param)
{
sleep(10);
cout<<“hilo terminado”<<endl; phtread_exit(NULL);
SERVICIOS POSIX PARA PROCESOS LIGEROS (III)
`
int
pthread_attr_init
(pthread_attr_t* attr)
` Inicializa la estructura de atributos de un thread a sus valores por defecto previstos por el S.O.
` RETURN VALUE: 0-OK
`
int
pthread_attr_setdetachstate
(pthread_attr_t* attr, int
detachstate)
` Establece la forma de terminar de un proceso ligero
` Si detachstate = PTHREAD_CREATE_DETACHED el proceso ligero libera sus recursos cuando finalice su ejecución
` Si detachstate = PTHREAD_CREATE_JOINABLE no se liberan los recursos. Hay que utilizar pthread_join(…)
` En el caso general los hilos deberían ser generados como
joinable (recomendado en el standard de POSIX)
` RETURN VALUE: 0-OK
SERVICIOS POSIX PARA PROCESOS LIGEROS (IV)
`
int
pthread_attr_setschedpolicy
(pthread_attr_t* attr, int
schedpol)
` Establece la política de planificación de un proceso ligero
` Tiene que tener la proiedad de PTHREAD_EXPLICIT_SCHED ` Si schedpol = SCHED_FIFO: política FIFO
` Si schedpol = SCHED_RR : política ROUND ROBIN
`
int
pthread_attr_setinheritsched
(pthread_attr_t* attr, int
inheritsched)
` Establece la política de planificación en relación con el padre
` Si inheritsched = PTHREAD_EXPLICIT_SCHED se permite que tenga
una política de planificación diferente a la del padre
` Si inheritsched = PTHREAD_INHERIT_SCHED se hereda la política
de planificación del padre
Por defecto
EJEMPLO JERARQUÍA DE THREADS (I)
LIBRERÍA
THREAD
EJEMPLO THREADS (II)
ATRIBUTO DETACHED
ESPERA A TERMINACIÓN DE HIJOS
¿PASO DE PARÁMETROS?