Está en la página 1de 11

!!

Describir

y operar con las llamadas Win32


para gestionar procesos
"! Crearlos
"! Terminarlos
"! Esperar su terminacin
"! Obtener su informacin

Estudio de un Sistema Operativo


U. D. Sistemas
DSIC
Curso 2009/10

Tema 2: Gestin de Procesos

!! Johnson

M. Hart: Win32 System Programming,


2 ed., Addison-Wesley, 2001, ISBN
0-201-70310-6, 508 pgs.

1.!
2.!

"! Captulo 7.

!! Microsoft:

3.!

Win32 SDK (edicin 1997).

4.!

"! Libro Win32 Programmers Reference.


#!

5.!

Captulo: Processes and Threads.

!! Microsoft:

6.!

MSDN de Visual Studio .NET 2003.

Introduccin
Repaso llamadas POSIX.1
Creacin de procesos
Terminacin de procesos
Esperar terminacin
Otras llamadas

"! Programacin en Windows -> Windows Base Services > Archivos DLL, procesos y subprocesos ->
Documentacin del SDK -> Processes and Threads.

Tema 2: Gestin de Procesos

Tema 2: Gestin de Procesos

!! Elementos

bsicos

!! Contenido

"! Proceso: unidad de asignacin de recursos


#!

Espacio de direcciones, objetos abiertos, hilos...

"! Hilo: unidad de ejecucin y planificacin


#!

Cada proceso tiene un hilo primario al crearlo

"! Fibra: un hilo puede descomponerse en mltiples fibras


#!

Planificacin a nivel de usuario

Tema 2: Gestin de Procesos

1.!
2.!
3.!
4.!
5.!
6.!

Introduccin
Repaso llamadas POSIX.1
Creacin de procesos
Terminacin de procesos
Esperar terminacin
Otras llamadas

Tema 2: Gestin de Procesos

"!
"!
"!
"!
"!
"!
"!

Uno o ms hilos
Espacio virtual de direcciones propio
Uno o ms segmentos de cdigo, incluyendo DLLs
Uno o ms segmentos de datos (variables globales)
Bloques de entorno, con informacin variable (ej: path)
La pila del proceso
Recursos: descriptores abiertos, otras pilas, etc.

Tema 2: Gestin de Procesos

POSIX
fork()
exec()
exit()
waitpid()
get()
set()

de un proceso (para el programador)

Win32 DescripcinDescripcin
Creacin de procesos, y
CreateProcess()
Creacin
de procesos
sustitucin del programa
Sustitucin del programa
Terminar el proceso
Terminar
el proceso
ExitProcess()
WaitForSingleObject()
Esperarprocesos
terminacinhijos
procesos hijos
Esperar
terminacin
GetCurrentProcessId()
Obtencin
atributos
Obtencin
atributos
(PID,
UID,(PID,
) )

Fijacin
atributosFijacin
(SID,atributos
PGID, )

Tema 2: Gestin de Procesos

1.!
2.!
3.!
4.!
5.!
6.!

Introduccin
Repaso llamadas POSIX.1
Creacin de procesos
Terminacin de procesos
Esperar terminacin
Otras llamadas

!! CreateProcess: Crea un proceso y su hilo primario


BOOL CreateProcess(
LPCTSTR lpszImageName,
//programa a ejecutar
LPTSTR lpszCommandLine,
//lnea de rdenes
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles, //heredar handles
DWORD fdwCreate,
//opciones creacin
LPVOID lpvEnvironment, //variables entorno
LPCTSTR lpszCurDir,
//directorio actual
LPSTARTUPINFO lpsiStartInfo, //ventana proceso y redirecciones
LPPROCESS_INFORMATION lppiProcInfo);

//descriptores e identificadores del nuevo proceso y su hilo principal

!!

Tema 2: Gestin de Procesos

!!

Similar fork()+ exec()


"! Se complican las redirecciones

!!

En lppiProcInfo devuelve 2 descriptores:

Tema 2: Gestin de Procesos

"! lpszImageName: nombre (absoluto o relativo) del


programa a ejecutar
#!
#!

"! Handle de su hilo primario

#!

No hay relacin padre-hijo tan fuerte como en POSIX


"! No hay llamadas para obtener el PID del padre
"! Emplear descriptor de un proceso
#!
#!

Hay que incluir la extensin


Si NULL, nombre del programa en primer elemento de
lpszCommandLine
Si nombre de ruta relativo, asigna unidad y directorio actuales

CreateProcess(
LPCTSTR lpszImageName,
LPTSTR lpszCommandLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID lpvEnvironment,
LPCTSTR lpszCurDir,
LPSTARTUPINFO lpsiStartInfo,
LPPROCESS_INFORMATION
lppiProcInfo);
Tema
2: Gestin de Procesos

BOOL

Obtener sus atributos


Realizar determinadas operaciones sobre ese proceso

Tema 2: Gestin de Procesos

10

!! Parmetros:

"! Handle del nuevo proceso

!!

Retorna: TRUE si el proceso y el hilo se crearon con xito, FALSE en


caso contrario.

11

12

"!

lpszCommandLine: lnea de rdenes a ejecutar


#!
#!

1.!
2.!
3.!
4.!
5.!
#!
#!

"! lpsaProcess, lpsaThread: atributos de seguridad del


proceso y tarea (hilo primario)

Nombre del programa seguido por todos sus argumentos


Ruta absoluta del programa buscada en este orden:
Directorio donde se inici el proceso
Directorio actual del proceso padre
Directorio del sistema (GetSystemDirectory() )
Directorio Windows (GetWindowsDirectory() )
Directorios de la variable PATH

#!

typedef struct _SECURITY_ATTRIBUTES {


DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;

Asume .EXE para programas sin extensin indicada


Si NULL, lpszImageName debe indicar el nombre del
programa (sin argumentos)

CreateProcess(
LPCTSTR lpszImageName,
LPTSTR lpszCommandLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID lpvEnvironment,
LPCTSTR lpszCurDir,
LPSTARTUPINFO lpsiStartInfo,
LPPROCESS_INFORMATION
lppiProcInfo);
Tema
2: Gestin de Procesos

BOOL

CreateProcess(
LPCTSTR lpszImageName,
LPTSTR lpszCommandLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID lpvEnvironment,
LPCTSTR lpszCurDir,
LPSTARTUPINFO lpsiStartInfo,
LPPROCESS_INFORMATION
lppiProcInfo);
Tema
2: Gestin de Procesos

BOOL

13

"! fInheritHandles: herencia de descriptores


#!

14

"! fdwCreate: aspectos de funcionamiento del proceso


y tarea (hilo)

Indica si el proceso hijo podr heredar copias de los


descriptores del padre

#!
#!
#!

#!

NULL: seguridad por omisin

CREATE_SUSPENDED: hilo principal comienza suspendido


DETACHED_PROCESS:crear el proceso sin consola
CREATE_NEW_CONSOLE: proporcionar una consola nueva
! Mutuamente excluyentes
! Si no se ponen, la consola (shell) se hereda

Solamente heredar los descriptores que se hayan marcado


como heredables
#!

CREATE_NEW_PROCESS_GROUP: proceso ser raz de un grupo


de procesos
! Reciben seales Ctrl-C y Ctrl-Break de la consola

#!

CreateProcess(
LPCTSTR lpszImageName,
LPTSTR lpszCommandLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID lpvEnvironment,
LPCTSTR lpszCurDir,
LPSTARTUPINFO lpsiStartInfo,
LPPROCESS_INFORMATION
lppiProcInfo);
Tema
2: Gestin de Procesos

BOOL

Otros flags para la prioridad del proceso

CreateProcess(
LPCTSTR lpszImageName,
LPTSTR lpszCommandLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID lpvEnvironment,
LPCTSTR lpszCurDir,
LPSTARTUPINFO lpsiStartInfo,
LPPROCESS_INFORMATION
lppiProcInfo);
Tema
2: Gestin de Procesos

BOOL

15

16

"! lpvEnvironment: variables de entorno del proceso


nuevo (ej: path de bsqueda)
#!

"! lpsiStartInfo: aspectos relativos a la ventana del proceso


(aspecto grfico) y redirecciones estndar para el proceso nuevo
#! Obtener informacin del padre: GetStartupInfo()
#! Modificacin de redirecciones:

Si NULL, se heredan las variables del padre

!
!
!
!

"! lpszCurDir: especifica unidad y directorio actual para


el proceso
#!

NULL significa heredar los valores del padre

CreateProcess(
LPCTSTR lpszImageName,
LPTSTR lpszCommandLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID lpvEnvironment,
LPCTSTR lpszCurDir,
LPSTARTUPINFO lpsiStartInfo,
LPPROCESS_INFORMATION
lppiProcInfo);
Tema
2: Gestin de Procesos

typedef struct _STARTUPINFO {


DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
BOOL CreateProcess(
LPTSTR lpTitle;
LPCTSTR lpszImageName,
DWORD dwX;
LPTSTR lpszCommandLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
DWORD dwY;
LPSECURITY_ATTRIBUTES lpsaThread,
DWORD dwXSize;
BOOL fInheritHandles,
DWORD dwYSize;
DWORD fdwCreate,
LPVOID lpvEnvironment,
DWORD dwXCountChars;

BOOL

17

"! lppiProcInfo: salida de CreateProcess

#!
#!

Algunas llamadas Win32 usan identificadores y otras descriptores

#!

typedef struct _PROCESS_INFORMATION {


HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;

BOOL

CreateProcess(
LPCTSTR lpszImageName,
LPTSTR lpszCommandLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID lpvEnvironment,
LPCTSTR lpszCurDir,
LPSTARTUPINFO lpsiStartInfo,
LPPROCESS_INFORMATION
lppiProcInfo);
Tema 2: Gestin de Procesos

LPCTSTR lpszCurDir,
LPSTARTUPINFO lpsiStartInfo,
LPPROCESS_INFORMATION
lppiProcInfo);
Tema 2: Gestin de Procesos

!!

Devuelve descriptores e identificadores del proceso e hilo creados


Identificadores nicos en todo el sistema
Descriptores privados a cada proceso

#!

Rellenar hStdInput, hStdOutput y hStdError


Activar flag STARTF_USESTDHANDLES del campo dwFlags
Activar herencia de los descriptores estndar
Activar fInheritHandles de la llamada CreateProcess()
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
18

Ejemplo: Proceso hijo ejecuta tree c:\windows\temp

#include <stdio.h>
#include <windows.h>

createprocess1.c
createprocess2.c

int main (int argc, char* argv[]) {


BOOL retval;
STARTUPINFO si;
PROCESS_INFORMATION pi;
LPVOID lpMsgBuf;
GetStartupInfo(&si);
// El proceso ejecuta el programa tree.com con parmetro c:\windows\temp
// Atencin! Se debe poner la extensin .COM !!!

retval = CreateProcess( NULL,


"tree.com c:\\windows\\temp",
NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);

19

Tema 2: Gestin de Procesos

20

1.!
2.!
3.!
4.!
5.!
6.!

!! ExitProcess: terminar el proceso voluntariamente

Introduccin
Repaso llamadas POSIX.1
Creacin de procesos
Terminacin de procesos
Esperar terminacin
Otras llamadas

VOID ExitProcess(UINT uExitCode);

No retorna nada (no falla)


Informar a DLLs de terminacin proceso -> garantizar desconexin
!! Terminar un proceso no implica terminacin de sus hijos
!! No implica que su objeto sea eliminado del ncleo del sistema
"! Slo cuando se cierre el ltimo descriptor que lo referencie
!!
!!

ExitProcess(0);

Tema 2: Gestin de Procesos

!!

21

Tema 2: Gestin de Procesos

1.!

Que un proceso termine implica:


"! Todos sus descriptores se cierran automticamente

2.!

"! Todos sus hilos terminan

3.!

"! Notifica evento (terminacin proceso) a hilos esperndolo


"! Notifica evento (terminacin de sus hilos) a hilos esperndolos

4.!

"! Valor de terminacin del proceso: indicado en ExitProcess

5.!
6.!

Tema 2: Gestin de Procesos

23

ExitProcess(1);

22

Introduccin
Repaso llamadas POSIX.1
Creacin de procesos
Terminacin de procesos
Esperar terminacin
Otras llamadas

Tema 2: Gestin de Procesos

24

!!
!!

POSIX: waitpid()
Win32: WaitForSingleObject + GetExitCodeProcesss

!! WaitForSingleObject:

esperar evento

DWORD WaitForSingleObject(
HANDLE hObject, //objeto a esperar
DWORD dwTimeOut); //tiempo mximo de espera

"! WaitForSingleObject()
Esperar a que un determinado objeto termine
#! Objeto: procesos, hilos
#!

Retorna: La causa del fin de la espera, o 0xFFFFFFFF (-1) si hubo error.

!!

"! WaitForMultipleObjects()
#!

Esperar a ms de un proceso

"! dwTimeOut: intervalo mximo de espera (milisegundos)


#! Valor 0 = no hay espera
#! Valor INFINITE = esperar indefinidamente

"! GetExitCodeProcess()
#!

Obtener valor de terminacin del proceso (hijo) que


haya terminado

Tema 2: Gestin de Procesos

!! Valores

Espera a que el objeto indicado notifique algn evento


relevante

!!

Descriptor objeto con flag SINCHRONIZE activo


"! Activo en descriptores de procesos por omisin

25

Tema 2: Gestin de Procesos

!! WaitForMultipleObjects

de retorno:

DWORD WaitForMultipleObjects(
DWORD cObjects, //nmero objetos a esperar
LPHANDLE lphObjects, //vector con descriptores
BOOL fWaitAll, //esperar a todos?
DWORD dwTimeout); //tiempo mximo de espera

"! WAIT_FAILED (-1): hubo un error


"! WAIT_TIMEOUT: tiempo lmite expir sin ocurrir el
evento esperado
"! WAIT_OBJECT_0: proceso ha terminado (ocurri el
evento)

!!

Esperar a todos los procesos de un conjunto o a uno cualquiera


de ellos
"! fWaitAll
#!

!!

Esperar eventos de:

#!

"! Procesos, mtex, semforos, hilos, temporizadores, notificaciones de


cambio, entrada desde teclado, eventos (mecanismo de sincronizacin)
!!

!!

27

Si TRUE, se esperar a que todos terminen


Si FALSE, slo al primero que termine

Valores de retorno:
"!
"!
"!
"!

Ejemplo de utilizacin:
WaitForSingleObject(pi.hProcess, INFINITE);

Tema 2: Gestin de Procesos

26

WAIT_FAILED (-1): hubo un error


WAIT_TIMEOUT: tiempo lmite expir
WAIT_OBJECT_0: (si fWaitAll==true) todos han terminado
WAIT_OBJECT_0+n: (si fWaitAll==false) proceso n ha terminado

Tema 2: Gestin de Procesos

28

!! GetExitCodeProcess:

obtener valor terminacin

BOOL GetExitCodeProcess(
HANDLE hProcess, //descriptor proceso
LPDWORD lpdwExitCode); //valor de terminacin

!!
!!

!!

Ejemplo: Se crea un proceso hijo (que ejecuta tree c:\windows\temp)


y se espera a su terminacin
t2ejemplo1.c

#include <stdio.h>
#include <windows.h>

Si falla, retorna 0.
lpdwExitCode: valor de terminacin
"! Si lpdwExitCode== STILL_ACTIVE, proceso todava no ha terminado

int main (int argc, char* argv[]) {


BOOL retval;
STARTUPINFO si;
PROCESS_INFORMATION pi;
LPVOID lpMsgBuf;
GetStartupInfo(&si);

!!

Descriptor con flag PROCESS_QUERY_INFORMATION activo.

retval = CreateProcess( NULL, "tree.com c:\\windows\\temp",


NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);

"! En CreateProcess() est activo por omisin

Tema 2: Gestin de Procesos

29

!!

if (!retval) {
fprintf(stderr, "Fallo en CreateProcess\n");
ExitProcess(1);
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
ExitProcess(0);

Ejemplo: se obtiene ahora el valor de terminacin del hijo


#include <stdio.h>
#include <windows.h>
int main (int argc, char*argv[]) {
BOOL retval;
STARTUPINFO si;
PROCESS_INFORMATION pi;
LPVOID lpMsgBuf;
DWORD dwValor;

Notas:

GetStartupInfo(&si);

"! CloseHandle() cierra el descriptor indicado, pero no destruye el


objeto asociado
"! Ej: al cerrar descriptor de un hilo, no termina el hilo. Se elimina la
referencia o puntero al hilo
Tema 2: Gestin de Procesos

30

t2ejemplo2.c
wait.c

!!

Tema 2: Gestin de Procesos

31

retval = CreateProcess(NULL,
"tree.com c:\\windows\\temp",
NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);

Tema 2: Gestin de Procesos

32

1.!

if (!retval) {
fprintf(stderr, "Fallo en CreateProcess()\n");
ExitProcess(1);
}
WaitForSingleObject(pi.hProcess, INFINITE);
if (GetExitCodeProcess(pi.hProcess, &dwValor))
printf("Valor de terminacin: %d.\n", dwValor);
else
fprintf(stderr, "Error en GetExitCodeProcess().\n");
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
ExitProcess(0);

2.!
3.!
4.!
5.!
6.!

Tema 2: Gestin de Procesos

!! Otras

33

Tema 2: Gestin de Procesos

!! GetCurrentProcess:

llamadas sobre procesos en Win32:

"! GetCurrentProcess(): obtener un descriptor (no heredable) al


propio proceso
"! GetCurrentProcessId(): obtener el PID propio

Instante de inicio y terminacin de un proceso


Tiempos de ejecucin a nivel de ncleo y a nivel de usuario

obtener pseudodescriptor

HANDLE GetCurrentProcess(VOID);

Devuelve un pseudodescriptor para el propio proceso invocante


!! Caractersticas:
"! No heredable
"! Usarlo en llamadas que requieran un descriptor
"! Tiene todos los derechos de acceso activos

"! GetProcessTimes(): obtener tiempos del proceso


#!

34

!!

"! OpenProcess(): obtener un descriptor a partir del PID

#!

Introduccin
Repaso llamadas POSIX.1
Creacin de procesos
Terminacin de procesos
Esperar terminacin
Otras llamadas

!!

Obtencin de un descriptor normal (heredable): usar funcin


DuplicateHandle() o bien OpenProcess()

!!

Ejemplo:
"! GetCurrentProcess();
"! DuplicateHandle(GetCurrentProcess(), ProcInfo.hProcess,
GetCurrentProcess(), &hProc, PROCESS_QUERY_INFORMATION
| SYNCHRONIZE , FALSE, 0);

Tema 2: Gestin de Procesos

35

Tema 2: Gestin de Procesos

36

!! GetCurrentProcessId:

obtener identificador

!! OpenProcess:

HANDLE OpenProcess(
DWORD fdwAccess, //tipo de acceso
BOOL fInherit, //heredable??
DWORD dwProcessId ); //PID proceso

DWORD GetCurrentProcessId(VOID);

!!

Devuelve el PID del proceso invocante

!!

"! Identificador global, vlido en todo el sistema


!!

!!

!!

37

#!

PROCESS_CREATE_PROCESS: Usado internamente por el ncleo. Crear procesos

#!

PROCESS_CREATE_THREAD: usar descriptor como destino en CreateRemoteThread()

#!

PROCESS_DUP_HANDLE: permite duplicar el descriptor usando DuplicateHandle()

#!

PROCESS_QUERY_INFORMATION: usarlo en GetExitCodeProcess(), GetPriorityClass()

#!

PROCESS_SET_INFORMATION: usarlo en SetPriorityClass()

#!

PROCESS_TERMINATE: usarlo en TerminateProcess()

#!

PROCESS_VM_OPERATION: utilizar llamadas gestin espacio de memoria del proceso

#!

PROCESS_VM_READ: utilizar llamadas ReadProcessMemory()

#!

PROCESS_VM_WRITE: utilizar llamadas WriteProcessMemory().

38

DWORD GetProcessTimes(
HANDLE hProcess,
// Proceso a interrogar
LPFILETIME lpCreationTime, // Instante de creacin
LPFILETIME lpExitTime,
// Instante de terminacin
LPFILETIME lpKernelTime,
// Tiempo en ncleo
LPFILETIME lpUserTime);
// Tiempo en esp. usuario
Retorna: TRUE en caso de xito, FALSE en caso contrario.
!!
!!

Tipo FILETIME de 64 bits, similar al LONGLONG


Ejemplo:
"!

SYNCHRONIZE: utilizar llamadas de espera, como WaitForSingleObject() ,


WaitForMultipleObjects()

!!

GetProcessTimes(hProc, &CreateTime.ft, &ExitTime.ft, &KernelTime,


&UserTime);

Tiempo expresado en unidades de 100 nanosegundos (10-7 segundos) desde el 1 de


enero de 1601

PROCESS_ALL_ACCESS: Suma de todos los tipos de acceso indicados antes

Tema 2: Gestin de Procesos

Tema 2: Gestin de Procesos

!! GetProcessTimes: obtener tiempos

"! fdwAccess: flags que podemos incluir:

#!

Ejemplo:

"! Hproc= OpenProcess( PROCESS_ALL_ACCESS,


FALSE, JobRecord.ProcessId);

Parmetros OpenProcess

#!

Devuelve un descriptor para el proceso indicado


"! NULL si fallo (ej: PID invlido)

Conociendo el PID, se puede obtener un descriptor de


un proceso con OpenProcess()

Tema 2: Gestin de Procesos

obtener descriptor proceso

"!
"!
39

Convertir al tipo SYSTEMTIME: FileTimeToSystemTime(LPFILETIME, LPSYSTEMTIME)


Y a la inversa: SystemTimeToFileTime(LPSYSTEMTIME, LPFILETIME).

Tema 2: Gestin de Procesos

40

!! FileTimeToSystemTime cambiar unidades tiempo


BOOL FileTimeToSystemTime(
FILETIME *lpFileTime, //filetime a convertir
LPSYSTEMTIME lpSystemTime //salida (en tiempo sistema)
);

typedef struct _SYSTEMTIME {


WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;

Tema 2: Gestin de Procesos

41

También podría gustarte