Practica No 6 - WordPress.com

Anuncio
UNAN-Leon
Sistemas Operativos
Gestión de Procesos
GESTIÓN DE PROCESOS
Introducción
Información sobre procesos
Identificadores del Proceso
Identificadores de usuario y de grupo
Ejecución de programas mediante exec
Creación de procesos fork()
Terminación de Procesos exit() y wait()
Introducción
Un proceso es un programa en ejecución, cargado en memoria. Un programa es una colección de
instrucciones y datos almacenados en un archivo que se usa para iniciar los segmentos de instrucciones y
datos de usuario.
En Linux todo proceso es creado por el núcleo (o kernel) del sistema operativo previa petición de
otro proceso, estableciéndose una relación jerárquica entre el proceso que realiza la petición de creación
(conocido como proceso padre o "creador") y el nuevo proceso (denominado proceso hijo). Un proceso
padre puede tener varios hijos y todo hijo tiene únicamente un padre.
Los procesos se organizan de forma jerárquica. Al arrancar el sistema se crea el proceso init, que es
la raíz del árbol de procesos y su PID es 1. Al igual que otros procesos de sistema su PPID es 0.
Información sobre procesos
Identificadores del Proceso
Para leer los valores del PID y del PPID de un proceso utilizaremos las llamadas:
1
Msc. Rina Arauz
UNAN-Leon
Sistemas Operativos
#include <sys/types.h>
pid_t getpid();
pid_t getppid();
Identificadores de usuario y de grupo
El núcleo le asocia a cada proceso 2 identificadores de usuario y dos de grupo. Los identificadores
de usuario son el identificador del usuario real (UID) y el identificador del usuario efectivo (EUID). Para el
grupo están el GID y el EGID. El UID identifica al usuario que es responsable de la ejecución del proceso y
el GID al grupo al cual pertenece el usuario en cuestión. El EUID se usa para determinar el propietario.
Normalmente el UID y EUID coinciden, pero si un proceso ejecuta un programa que pertenece a otro
usuario, el UID y EUID serán diferentes. Se aplica la misma norma para el identificador de grupo. Para leer
los valores de los identificadores de usuario y grupo de un proceso utilizaremos las llamadas:
#include <sys/types.h>
uid_t getuid();
uid_t geteuid();
gid_t get gid();
gid_t get egid();
Ejemplo: Proceso que imprime sus identificadores.
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
void main(void)
{
int id_proceso;
int id_padre;
id_proceso = getpid();
id_padre = getppid();
printf("Mi dentificador de proceso es : %d\n", id_proceso);
printf("El identificador de mi proceso padre es: %d\n", id_padre);
printf("Identificador de usuario: %d\n", getuid());
printf("Identificador de usuario efectivo: %d\n",
geteuid());
printf("Identificador de grupo: %d\n", getgid());
printf("Identificador de grupo efectivo: %d\n", getegid());
}
Ejecución de programas mediante exec
El servicio POSIX exec() permite cambiar el programa que se está ejecutando, reemplazando el
código y datos del proceso que invoca esta función por otro código y otros datos procedentes de un archivo
ejecutable. El contenido del contexto de usuario del proceso que invoca a exec() deja de ser accesible si la
función se ejecuta correctamente y es reemplazado por el del nuevo programa. Por lo tanto en estas
condiciones el programa antiguo es sustituido por el nuevo y nunca se retornará a él para proseguir su
ejecución, ya que es el programa nuevo el que pasa a ejecutarse, el cual se ejecutará desde el principio.
Existe toda una familia de funciones exec que podemos usar para ejecutar programas. Dentro de
esta familia cada función tiene su interfaz propia, pero todas tienen aspectos comunes y obedecen al
mismo tipo de funcionamiento. La declaración de la familia de las funciones exec es:
2
Msc. Rina Arauz
UNAN-Leon
Sistemas Operativos
#include <unistd.h>
Int execl (char * path, char *arg0…char argn, (char *) 0);
Int execv (char * path, char *argv[ ] );
Int execle (char * path, char *arg0…char argn, (char *) 0), char *envp[ ] );
Int execve (char * path, char *argv [ ], char *envp[ ]);
Int execlp (char *file, char *arg0…char argn, (char *) 0)
Int execvp (char * file, char *arg v[ ])
Path apunta la ruta de un fichero ordinario ejecutable y file apunta al nombre de un fichero
ejecutable. La ruta del fichero se construye buscándolo en los directorios que se indican en la variable de
retorno PATH. Tanto path como file se refieren a ficheros ejecutables o a ficheros de datos (shell scripts).
Los parámetros arg0...argn son punteros a cadenas de caracteres y constituyen la lista de argumentos que
se le pasa al nuevo programa. Por convenio, al menos arg0 esta presente siempre y apunta a una cadena
idéntica a path o a el ultimo componente de path. A continuación de argn, pasamos un puntero NULL (char *) 0 - , para indicar el final de los argumentos. (igual para argv). Si exec no se ejecuto correctamente
devuelve –1.
Resumiendo:




Las llamadas execl*() reciben los argumentos como una "lista" de C, tal como la usamos en el
código fuente: execlp("ls", "ls", "-al", NULL);.
Las llamadas execv*() reciben los argumentos en un arreglo, similar a argv[].
Las llamadas exec*p() buscan en los directorios especificados por la variable de entorno
PATH el binario a cargar.
El primer argumento de exec() es el EJECUTABLE a cargar y luego vienen los argumentos a
pasarle, recordando que el primero es argv[0]: el "nombre" del programa.
Ejemplo de execlp
#include<unistd.h>
main()
{
/* Shell es un archivo shell script con permiso de ejecución*/
execlp(“/home/usuario/shell”,”shell”,0);
}
Ejemplo de execv
#include<unistd.h>
main()
{
char *av[]={“ls”,”-l”,0};
execv (“/bin/ls”,av);
}
Ejemplo de execvp
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
void main(int argc, char **argv)
{
3
Msc. Rina Arauz
UNAN-Leon
Sistemas Operativos
char *argumentos[3];
argumentos[0] = "ls";
argumentos[1] = "-l";
argumentos[2] = NULL;
execvp(argumentos[0], argumentos);
}
Creación de procesos fork()
El servicio POSIX fork(), crea un nuevo proceso. El SO trata este servicio llevando a cabo una
clonación del proceso que lo invoca, conocido como proceso padre del nuevo proceso creado,
denominado proceso hijo. Todos los procesos se crean a partir de un único proceso padre lanzado en el
arranque del sistema, el proceso init, cuyo PID es 1 y que por lo tanto está situado en lo más alto en la
jerarquía de procesos de UNIX.
El proceso que hace la llamada a fork se convierte en el proceso padre del proceso creado.
Una vez realizada la copia, tanto padre e hijo continúan de forma independiente la ejecución en
el mismo punto del programa, es decir, en la siguiente instrucción al fork. Es un error pensar
que el hijo comienza la ejecución por el principio del programa. Esto es así porque el proceso hijo
hereda del padre los datos y la pila que tuviera en el momento de la ejecución del fork, así como el
valor de los registros. fork() crea un nuevo proceso; pero no inicia un nuevo programa.
Todos los procesos del sistema, se crean mediante esta llamada al sistema.
Cuando se duplica un programa, los nombres de las variables, constantes, etc., siguen
llamándose igual pero, en realidad, son distintas ya que se encuentran en zonas distintas de
memoria; cada una correspondiente al área de datos de sus respectivos procesos.
fork() devuelve dos valores:
 0 al proceso creado (hijo) y El PID del proceso creado (hijo) al padre
o -1 en caso de error
La ejecución del hijo es independiente de la del padre y concurrente con ella. El padre debe
realizar una llamada a wait, para esperar la finalización del hijo.
La declaración de fork() es:
#include <sys/types.h>
pid_t fork();
La forma de invocarla es pid = fork(). La llamada a fork() hace que el proceso actual se duplique. A
la salida de fork() los dos procesos tienen una copia idéntica del contexto del nivel de usuario excepto el
valor de PID, que para el proceso padre toma el valor del proceso hijo y para el proceso hijo toma el valor
0, si la llamada a fork() falla devolverá el valor –1. El proceso hijo hereda la mayoría de los atributos del
proceso padre, ya que se copian del segmento de datos del sistema.
4
Msc. Rina Arauz
UNAN-Leon
Sistemas Operativos
Una secuencia de código típica para manejar la llamada a fork() es :
int pid ;
...
if (( pid = fork() ) == -1 )
perror (“ error en la llamada a fork”);
#include <stdio.h>
else if (pid == 0)
#include <unistd.h>
//Codigo del Hijo
#include <stdlib.h>
else
main ()
//codigo del padre
Proceso Padre
pid≠0
{
int pid;
pid= fork();
if (pid == 0)
printf("%d: Soy el hijo!\n", getpid());
else
printf("%d: Soy el padre de %d\n", getpid(), pid);
}
Ejemplo:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
main ()
{
int pid;
pid= fork();
if (pid == 0)
printf("%d: Soy el hijo!\n", getpid());
else
printf("%d: Soy el padre de %d\n", getpid(), pid);
}
Proceso Hijo
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
main ()
pid=0
{
int pid;
pid= fork();
if (pid == 0)
printf("%d: Soy el hijo!\n", getpid());
else
printf("%d: Soy el padre de %d\n", getpid(), pid);
}
Padre
Pid 571
Hijo
Pid 574
5
Msc. Rina Arauz
UNAN-Leon
Sistemas Operativos
Es típico necesitar invocar otro programa y seguir ejecutando código nuestro, Como lo hacemos?
La única manera de hacerlo es creando OTRO proceso con fork() y reemplazar su código con
exec().
main ()
{
int x;
x=fork();
if ( x== 0 )
{
printf(“Soy el proceso hijo y hare una llamada a exec”);
execl (“/bin/date”, “date”, 0);
printf (“ ABC”);
}
No se ejecuta
else
{
printf(“soy el proceso padre”);
sleep(1);
printf(“Adios”);
}
}
Terminación de Procesos exit() y wait()
Una situación muy típica en programación concurrente es que el proceso padre espera a la terminación del
proceso hijo antes de continuar su ejecución. Para sincronizar los procesos padre e hijo se emplean las
llamadas exit y wait. Su declaración es la siguiente:
#include <stdlib.h>
void exit (int status);
Exit finaliza el proceso que le llama. Si hay procesos hijos cuando el padre ejecuta un exit, el
PPID de los hijos se cambia a 1 (proceso init).
#include <sys/types.h>
#include <sys/wait.h>
 pid_t wait(int *status);
 pid_t waitpid(pid_t pid, int *status, int options);
pid_t wait(int *status): El valor del parámetro status se utiliza para comunicar al proceso padre la forma en
la que el proceso hijo termina. Suele ser 0 si el proceso termina correctamente y cualquier otro valor en
caso de terminación anormal. Si queremos ignorar este valor, podemos pasarle a wait un puntero NULL.
 La función wait suspende la ejecución del proceso actual hasta que un proceso hijo ha terminado,
si se ejecuta correctamente, retorna el PID del hijo cuya ejecución ha finalizado. Por el contrario
devuelve -1 sino se crearon procesos hijos o ya no existen procesos por los que esperar.
pid_t waitpid(pid_t pid, int *status, int options): La función waitpid suspende la ejecución del proceso en
curso hasta que un hijo especificado por el argumento pid ha terminado.
6
Msc. Rina Arauz
UNAN-Leon
Sistemas Operativos
El valor de options es un OR de cero o más de las siguientes constantes: WHOHANG,
WUNTRACED, WCONTINUED, WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG,
WCOREDUMP, WIFSTOPPED, WSTOPSIG y WIFCONFIRMED.
waitpid() en caso de éxito devuelve el PID del hijo cuya ejecución ha finalizado y en caso de error -1.
Ejemplo1:
#include<unistd.h>
#include<stdio.h>
main()
{
int x;
x=fork();
if (x==0)
{
printf(“Soy el hijo”);
execlp(“ls”,”-l”,NULL);
}
else
{
wait(NULL); //Sincroniza de manera que el padre espera la terminación de un hijo
printf(“saliendo”);
}
}
Ejemplo2:
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <stdio.h>
main()
{
int p, s, e;
p=fork();
if ( p== 0 )
{ /* hijo */
sleep(3);
printf("Hijo mi id=%d\n",getpid());
}
else
{ /* padre …. Esperamos por el proceso hijo */
printf(“ la variable p=%d\n”,p);
s=waitpid(p, &e, 0);
printf("Padre p=%d , s=%d, e=%d\n",p,s,e);
}
}
7
Msc. Rina Arauz
UNAN-Leon
Sistemas Operativos
Para utilizar FORK() dentro de un ciclo y evitar la propagación de procesos en 2^n procesos, puede
usarse la llamada a EXIT() en los procesos hijos. De la siguiente forma:
for (i=0; i<10; i++)
{
if (fork() == 0)
{
/* código de los hijos */
exit(0);
}
}
Ejercicios
1) Crear un proceso que leerá por el terminal el nombre de un programa (orden de Unix) y
seguidamente lo ejecutará. El programa a ejecutar debe residir en el directorio /bin y no debe
necesitar ningún parámetro (p. ej., ls, date, time, cd, pwd, ...). No se debe de ejecutar un
programa hasta que el anterior no haya acabado. El proceso terminará cuando se introduzca
la cadena “salir”.
2) Escriba un programa donde se muestre un proceso principal que crea 4 procesos hijos. El
proceso tiene una variable global “var” inicializada a uno. El proceso1 suma var=var+1, el
proceso2 suma var=var+2, el proceso3 suma var=var+3, el proceso4 suma var=var+4. Cada
proceso imprime su pid, su ppid y el valor de VAR. El proceso padre deberá esperar por la
finalización de los procesos hijos e imprimir el pid de cada hijo que vaya finalizando y el valor
de la variable var. Explique el resultado de var.
3) Considerando el siguiente fragmento de código:
for (num= 1; num<= n; num++)
{
nuevo= fork();
if ((num== n) && (nuevo== 0))
execlp ("ls", "ls", "-1", NULL);
}
Dibuja la jerarquía de procesos generada cuando se ejecuta y n es 3. Indica en qué procesos
se ha cambiado la imagen del proceso usando la función execlp.
4) Observa el siguiente código y escribe la jerarquía de procesos resultante.
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
8
Msc. Rina Arauz
UNAN-Leon
Sistemas Operativos
main ()
{
int num; pid_t pid;
srandom(getpid());
for (num= 0; num< 3; num++)
{
pid= fork();
printf (" Soy el proceso de PID %d y mi padre tiene %d de PID.\n", getpid(), getppid());
if (pid== 0)
break;
}
if (pid== 0)
sleep(random() %5);
else
for (num= 0; num< 3; num++)
printf ("Fin del proceso de PID %d.\n", wait (NULL));
}
Ahora compila y ejecuta el código para comprobarlo. Presta atención al orden de terminación de los procesos, ¿qué observas? ¿por qué?
9
Msc. Rina Arauz
Descargar