You are on page 1of 11

Procesos

i
Índice

1. INTRODUCCIÓN ....................................................................................................................... 1
2. SISTEMA OPERATIVO Y PROCESOS.................................................................................. 1
3. IDENTIFICACIÓN DE PROCESOS........................................................................................ 1
4. ESTADOS DE UN PROCESO ................................................................................................... 2
5. MULTIPROGRAMACIÓN ....................................................................................................... 3
6. IDENTIFICADORES DE USUARIOS Y PROCESOS ........................................................... 3
7. EJECUTANDO COMANDOS DE UNIX DESDE C ............................................................... 4
8. CREACIÓN DE PROCESOS: FORK().................................................................................... 4
8.1. HERENCIA DE DESCRIPTORES ................................................................................................ 5
9. EJECUCIÓN DE PROCESOS: EXEC....................................................................................... 5
10. LA LLAMADA WAIT() ...................................................................................................... 8
11. LA LLAMADA EXIT() ...................................................................................................... 8
12. LA TERMINACIÓN DE PROCESOS ................................................................................. 8
13. EJERCICIOS.......................................................................................................................... 8

ii MGB
Procesos

1. Introducción
Un programa se compone de un conjunto de instrucciones (operaciones aritméticas, bucles de control y órdenes
de entrada y salida) que se ejecutan siguiendo una secuencia. Una vez que hemos conocido estas operaciones en un
curso de programación básica, aprenderemos a programar la ejecución de varios programas o procesos al mismo
tiempo.
Se verá como un programa (proceso padre) puede crear, terminar y controlar otros procesos (procesos hijos).

2. Sistema operativo y procesos


Cuando un programa es leído del disco por el núcleo y se carga en la memoria para su ejecución, se convierte en
un proceso. Esto no significa que el proceso sea una simple copia del programa, pues el núcleo le añade información
adicional para poder manejarlo. En este sentido, un proceso es una entidad activa que cuenta con un contador ordinal
que especifica la siguiente instrucción que debe ejecutar y con un conjunto de recursos asociados.
Los procesos los podemos clasificar en:
• Procesos de usuario: se crea a partir de una ejecución directa o mediante una aplicación originada por el
usuario.
• Procesos de sistema: forman parte del sistema operativo, es decir, realizan operaciones de acceso a
recursos de entrada/salida o de apoyo a la ejecución de otros procesos. Estos pueden ser:
o Permanentes: arrancan con el sistema y permanecen activos hasta que éste es apagado.
o Transitorio: su duración viene determinada por la realización de una tarea específica, de manera que
su ejecución finaliza cuando la tarea se ha completado.

3. Identificación de procesos
El sistema asigna a cada proceso, en el momento de su nacimiento, un número entero (mayor que cero) que lo
identifica de forma unívoca y que recibe el nombre de pid. El sistema operativo UNIX proporciona la orden ps que
nos da información relativa a cada uno de los procesos que existen en el sistema:

ps [-aA] [-G grouplist] [-o format] ... [-p proclist][-t termlist] [-U userlist]

Muchas de las implementaciones que hacen los distintos vendedores del comando ps no cumplen con el estándar
POSIX, un ejemplo claro es Sun Solaris (SO del servidor de prácticas de la escuela) que emplea la opción -e en lugar
de -A para mostrar la información de todos los procesos. La versión larga del comando ps de Solaris muestra mucha
información interesante sobre los procesos asociados a un terminal (la figura 1 muestra algunos).

• USER: el propietario del proceso.


• PID: el identificador del proceso.
• PPID:
• % CPU: porcentaje de CPU consumida.
• % MEM: porcentaje de memoria consumida.
• SIZE: tamaño total del proceso (Kilobytes).
• RSS: Kilobytes del programa en memoria. (El resto estará en disco (swap)).
• TTY: identificador del terminal desde donde se lanzó el proceso.
• STAT: estado del proceso.
• START: hora en la se lanzó el proceso.
• TIME: tiempo de CPU consumido.
• COMMAND: nombre del proceso.

MGB 1
Procesos

Ejemplo 1 El siguiente comando muestra, en formato largo, todos los procesos cuyo usuario propietario es i5599:
murillo:/export/home/cursos/so> ps -fu i5590
UID PID PPID C STIME TTY TIME CMD
i5590 12246 12195 0 20:53:16 pts/15 0:02 clips
i5590 12164 12150 0 20:30:56 pts/15 0:00 -ksh
i5590 12194 12164 0 20:35:23 pts/15 0:00 -ksh
i5590 12175 12152 0 20:31:12 pts/14 0:00 -ksh
i5590 12195 12194 0 20:35:23 pts/15 0:00 /export/home/cursos/CCIA/bin/tcsh
i5590 12176 12175 0 20:31:12 pts/14 0:00 /export/home/cursos/CCIA/bin/tcsh
i5590 12205 12150 0 20:37:29 pts/16 0:00 -ksh
i5590 12152 12150 0 20:30:39 pts/14 0:00 -ksh
i5590 12192 12176 0 20:34:47 pts/14 0:01 /opt/sfw/bin/emacs oo.clp

Este comando es prácticamente equivalente a ps –ef | grep i5590.

4. Estados de un proceso
La idea principal de un sistema multiproceso, tal y como es UNIX, es que el sistema operativo gestione los
recursos disponibles (memoria, CPU, etc) entre los procesos que en ese momento trabajan en el sistema, de tal forma
que, para ellos, el sistema se comporte como si fuera monousuario. Así que, en un sistema monoprocesador, la CPU
se reparte entre los procesos que se tengan en ese momento. Como es lógico, sólo un proceso puede estar
ejecutándose, los demás estarán esperando para poder ocupar la CPU, esta forma de operar recibe el nombre de
ejecución entrelazada.
A continuación, se enumeran los distintos estados en los que se puede encontrar un proceso en este tipo de
sistemas:

• Preparado (R).- Proceso que está listo para ejecutarse. Simplemente está esperando a que el sistema
operativo le asigne un tiempo de CPU.
• Ejecutando (O).- Sólo uno de los procesos preparados se está ejecutando en cada momento
(monoprocesador).
• Suspendido (S).- Un proceso se encuentra suspendido si no entra en el reparto de CPU, ya que se encuentra
esperando algún tipo de evento (por ejemplo, la recepción de una señal software o hardware). En cuanto
dicho evento se produce, el proceso pasa a formar parte del conjunto de procesos preparados.
• Parado (T).- Un proceso parado tampoco entra en el reparto de CPU, pero no porque se encuentre
suspendido esperando algún evento. En este caso, sólo pasarán a estar preparados cuando reciban una señal
determinada que les permita continuar.
• Zombie (Z).- Todo proceso al finalizar avisa a su proceso padre, para que éste elimine su entrada de la tabla
de procesos. En el caso de que el padre, por algún motivo, no reciba esta comunicación no lo elimina de la
tabla de procesos. En este caso, el proceso hijo queda en estado zombie, no está consumiendo CPU, pero sí
continua consumiendo recursos del sistema.

Figura 4.1: Estados de un proceso

2 MGB
Procesos

Un ejemplo que permite aclarar la diferencia entre procesos suspendidos y procesos preparados es el siguiente: se
tiene un proceso que debe esperar a que el usuario introduzca un valor por teclado. En principio, pueden proponerse
dos soluciones diferentes:

Espera activa.

El proceso está codificado utilizando un bucle, en el cual sólo se comprueba el valor de una variable para saber si
el usuario ha introducido el valor o no. En este caso, el proceso es un proceso preparado que está consumiendo CPU
para realizar la comprobación de la variable continuamente.
Interrupción.
El proceso está codificado de forma que el proceso se suspende. De esta forma, ya no consume CPU porque no tiene
que comprobar en cada ejecución del bucle si el usuario ha introducido un valor. Sólo espera a que sea el propio
usuario el que le “avise” y lo despierte para, así, pasar a estar preparado.

5. Multiprogramación
En un ordenador con un único procesador, en un instante de tiempo dado, hay un solo proceso en ejecución;
dicho proceso se conoce como proceso actual.
A cada proceso le corresponde un espacio de tiempo. Linux elige un proceso y deja que se ejecute durante un
periodo establecido. A continuación, el sistema pasa el proceso actual al estado de a punto, y elige otro proceso para
que se ejecute durante otro espacio de tiempo. Este tiempo es tan corto que el usuario tiene la sensación de que los
distintos procesos se ejecutan simultáneamente.

6. Identificadores de usuarios y procesos


Las funciones C que se utilizan para obtener el identificador de proceso (PID) o el identificador de usuario (UID)
son getpid, getppid y getuid:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
uid_t getuid(void);

• pid t es un entero largo con el ID del proceso llamante (getpid) o del padre del proceso llamante
(getppid)
• uid t es un entero con el ID del usuario propietario del proceso llamante.
• En caso se error se devuelve -1.

Ejemplo 2 El siguiente programa imprime el identificadores del proceso llamante, del proceso padre y del
propietario:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
printf("ID de proceso: %ld\n", (long)getpid());
printf("ID de proceso padre: %ld\n", (long)getppid());
printf("ID de usuario propietario: %ld\n", (long)getuid());
return 0;
}

MGB 3
Procesos

7. Ejecutando comandos de UNIX desde C


Se pueden ejecutar comandos desde un programa de C como si se estuviera en la línea de comandos de UNIX
usando la función system(). NOTA: se puede ahorrar bastante tiempo y confusión en vez de ejecutar otros
programas, scripts, etc. para hacer las tareas.
int system(char *mandato) -- donde mandato puede ser el nombre de una utilería de UNIX, un shell
ejecutable o un programa del usuario. La función regresa el status de salida del shell. La función tiene su prototipo en
<stdlib.h>
Ejemplo: llamada del comando ls desde un programa
main()
{
printf("Archivos en el directorio son:\n");
system("ls -l");
}

La función system es una llamada que esta construida de otras 3 llamadas del sistema: execl(), wait() y
fork() (las cuales tienen su prototipo en <unistd.h>).
Desventajas de utilizar la función system:
• Resulta poco eficiente, pues cada vez que se invoca no sólo comienza a ejecutarse la orden deseada, sino
que también se ejecuta una copia del shell. En consecuencia, si el programa tuviera que ejecutar muchos
comandos, sería más oportuno buscar otra forma de hacerlo.
• Las llamadas al sistema y las rutinas de biblioteca son siempre más eficientes que system. Ejemplo:

En lugar de Es mejor utilizar


system(“rm –f fichero”); unlink (“fichero”);
system(“mkdir directorio”); mkdir(“directorio”);
system(“mv nomantig nomnuevo”); rename (“nomantig”, “nomnuevo”);

8. Creación de procesos: fork()


Los procesos de un sistema UNIX tienen una estructura jerárquica, de manera que un proceso (proceso padre)
puede crear un nuevo proceso (proceso hijo) y así sucesivamente. Para la realización de aplicaciones con varios
procesos, el sistema operativo UNIX proporciona la llamada al sistema1 fork().

Cabecera:

#include <unistd.h>

int fork(void);

Comportamiento de la llamada:

fork() crea un nuevo proceso exactamente igual (mismo código) al proceso que invoca la función. Ambos
procesos continúan su ejecución tras la llamada fork(). En caso de error, la función devuelve el valor -1 y no se
crea el proceso hijo. Si no hubo ningún error, el proceso padre (que realizó la llamada) obtiene el pid del proceso
hijo que acaba de nacer, y el proceso hijo recibe el valor 0.
Ejemplo:

4 MGB
Procesos

En el ejemplo que se muestra a continuación, se crea un proceso hijo que imprime en pantalla el pid de su
proceso padre, mientras que el proceso padre imprime en pantalla su propio pid y el del proceso hijo que ha creado.
Para ello, se utilizan las llamadas al sistema getpid() y getppid(). El proceso padre, antes de finalizar se
suspende hasta que el hijo acaba, para evitar que éste se quede zombie. Para ello, utiliza la llamada al sistema
wait(), que recibe en la variable status el estado en que el proceso hijo finalizó.

Ejemplo3:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{

int pid = 0, status = 0;


pid = fork() ;
switch( pid)
{
case -1:
printf(“Error al crear proceso hijo\n”);
exit(1);
break ;
case 0:

/* Proceso Hijo */
printf(“El PID de mi proceso padre es %d\n”, getppid());
exit(1);
break ;
default:
/* Proceso Padre */
printf(“Mi PID es el %d y he creado un proceso hijo cuyo pid es %d\n”, getpid(), pid);
wait( &status);
printf(“\nEl proceso hijo finalizo con el estado %d\n”, status);
exit(0);
}
}

8.1. Herencia de descriptores


Cuando fork crea un proceso hijo, éste hereda la mayor parte del entorno y contexto del padre, que incluye el
estado de las señales, los parámetros de la planificación de procesos y la tabla de descriptores de archivo. Hay que
tener cuidado ya que las implicaciones de la herencia de los descriptores de archivos no siempre resultan obvias, ya
que el proceso padre e hijo comparten el mismo desplazamiento de archivo para los archivos que fueron abiertos por
el padre antes del fork.

9. Ejecución de procesos: exec


Una llamada al sistema que se utiliza normalmente en combinación con fork() es execl(), la cual permite
ejecutar un código (previamente compilado) desde otro programa.

Cabecera:

#include <unistd.h>

int execl (const char *path, const char *arg0, ...,


const char *argn, char * /*NULL*/);
int execv (const char *path, char *const argv[]);
int execle (const char *path, const char *arg0, ...,
const char *argn, char * /*NULL*/, char *const envp[]);
int execve (const char *path, char *const argv[], char *const envp[]);
int execlp (const char *file, const char *arg0, ...,
const char *argn, char * /*NULL*/);

MGB 5
Procesos

int execvp (const char *file, char *const argv[]);

• Las seis variaciones de la llamada exec se distinguen por la forma en que son pasados los argumentos de
la línea de comando y el entorno, y por si es necesario proporcionar la ruta de acceso y el nombre del
archivo ejecutable.
• Las llamadas execl (execl, execle y execlp) pasan la lista de argumentos de la línea de comando como
una lista y son útiles sólo si se conoce a priori el número de éstos.
• Las llamadas execv (execvl, execvp y execve) pasan la lista de argumentos en una cadena (un array de
punteros a char) y son útiles cuando no se sabe el número de argumentos en tiempo de compilación.
• path es la ruta (completa o relativa al directorio de trabajo) de acceso al archivo ejecutable.
• argi es el argumento i–´esimo (sólo en llamadas execl).
• argv es una cadena con todos los argumentos (sólo en llamadas execv).
• envp es una cadena con el entorno que se le quiere pasar al nuevo proceso.

Ejemplo 5 El siguiente programa utiliza la función execl para listar los procesos activos en el momento de la
ejecución. Ejecútelo y vea los errores del mismo.

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
int main()
{
int status;
printf ("Lista de procesos\n");
if (execl ("ps", "ps", "-f", 0) < 0)
{
fprintf(stderr, "Error en exec %d\n", errno);
exit(1);
}
printf ("Fin de la lista de procesos\n");
exit(0);
}
Ejemplo 6 El siguiente programa utiliza un esquema fork–exec para listar los procesos del usuario propietario
del proceso.
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

int main()
{
pid_t childpid, waitreturn;
int status;
if ((childpid = fork()) == -1)
{
fprintf(stderr, "Error en fork %d\n", errno);
exit(1);
}
else if (childpid == 0)

{ /* código del proceso hijo */

if ( execl ("/bin/ps", "ps", "-fu", getenv ("USER"), 0) < 0)


{
fprintf(stderr, "Error en exec %d\n", errno);
exit(1);

6 MGB
Procesos

}
}
else /* c´odigo del proceso padre */
while(childpid != (waitreturn = wait(&status)))
if ((waitreturn == -1) && (errno != EINTR))
break;
exit(0);

Ejemplo 7 En el siguiente ejemplo, el proceso hijo ejecutará el código del ejecutable esclavo. Este programa recibe
dos parámetros de entrada, primero, el nombre bajo el que se ejecutará el proceso hijo (``nombre'') y el segundo es
el parámetro ``-a''. Para que la lista se de por terminada habrá que especificar el parámetro NULL.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{

int pid = 0, status = 0;


pid = fork() ;
switch( pid)
{
case -1:
printf(“Error al crear proceso hijo\n”);
exit(1);
break ;
case 0:

/* Proceso Hijo */
if (execl(``esclavo'', ``nombre'', ``-a'', NULL) == -1)
{
printf(``Error al ejecutar execl\n'');
exit(1);
}
break ;
default:
/* Proceso Padre */
wait( &status);
printf(“\nEl proceso hijo finalizo con el estado %d\n”, status);
exit(0);
}
}

A continuación se muestra un posible código del proceso esclavo, que simplemente imprime en pantalla la lista
de argumentos recibidos:
#include <stdio.h>

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


{

int i = 0;

for (i = 0; i < argc; i++)


printf(``\nArgumento [%d]: %s'', i, argv[i]);

exit(0);
}

MGB 7
Procesos

10. La llamada wait()


La función int wait() (int *status) forzará a un proceso padre para que espere a un proceso hijo que
se detenga o termine. La función regresa el PID del hijo o -1 en caso de errror. El estado de la salida del hijo es
regresado en status.

11. La llamada exit()


La función void exit(int status) termina el proceso que llama a esta función y regresa en la salida el
valor de status. Tanto UNIX y los programas bifurcados de C pueden leer el valor de status.
Por convención, un estado de 0 significa terminación normal y cualquier otro indica un error o un evento no
usual. Muchas llamadas de la biblioteca estándar tienen errores definidos en la cabecera de archivo sys/stat.h.
Se puede fácilmente derivar su propia convención.

12. La terminación de procesos


Cuando termina un proceso (normal o anormalmente), el SO recupera los recursos asignados al proceso
terminado, actualiza las estadísticas apropiadas y notifica a los demás procesos la terminación.
Las actividades realizadas durante la terminación de un proceso incluyen la cancelación de temporizadores y
señales pendientes, la liberación de los recursos de memoria virtual así como la de otros recursos del sistema
ocupados por el proceso.
Cuando un proceso termina, sus hijos huérfanos son adoptados por el proceso init, cuyo ID el 1. Si un proceso
padre no espera a que sus hijos terminen la ejecución, entonces éstos se convierten en procesos zombies y tiene que
ser el proceso init el que los libere (lo hace de manera periódica).
1
Las llamadas a exit y exit se utilizan para terminar de forma normal un proceso. La función exit lo que hace es
llamar a los manejadores de terminación del usuario (si existen) y después llamar a exit.

13. Ejercicios
1. Realice un programa que cree cuatro procesos, A, B, C Y D, de forma que A sea padre de B, B sea padre de C, y C
sea padre de D.

2. Realice un programa copiaConc al que se le pase una lista de nombres de archivos y para cada archivo f cree un
nuevo proceso que se encargue de copiar dicho archivo a f.bak.

3. Realice un programa ejecuta que lea de la entrada estándar el nombre de un programa y cree un proceso hijo para
ejecutar dicho programa.

4. ¿Cuál es el efecto del siguiente programa?


main()
{
int e, i;
for (i=1;i<=3;i++)
{
if (fork()!=0) wait(&e);
printf("%d\n",i);
}
}
Indicar las salidas a pantalla y el árbol de procesos creados.
5. Dado el siguiente trozo de programa:

for(i=0;i<2;i++)
if(fork()==getpid()) printf("UCLM");

¿Cuál es el resultado impreso en UNIX?


a) Imprime “UCLM” dos veces.
b) Imprime “UCLM” cuatro veces.

1
Fíjese que return no es una llamada al sistema sino que se trata de una instrucción del lenguaje C.

8 MGB
Procesos

c) No imprime nada.

6. Dado el siguiente código:


void main()
{
if (fork() != 0)
{
if (fork() != 0)
printf("Proceso Padre\n");
else
printf("Proceso Hijo 2\n");
}
else
printf("Proceso Hijo 1\n");
}
a) Dibujar el árbol de procesos.
b) Indicar el orden en que se imprimen los mensajes.

7. Dado el siguiente código:


void main()
{
int i;
int j=0;

for (i=1; i<4; i++)


{
if (fork() == 0)
{
j++;
}
else
{
exit(1);
}
j++;
}
}
a) Dibujar el árbol de procesos que se obtiene.
b) Indicar el valor final de la variable j en cada uno de ellos.

8. Dado el siguiente código:


void main()
{
int j=1;
for (i=1; i<3; i++)
{
fork();
printf("%d, %d", i, j);
j++;
}
}
a) Dibujar el árbol de procesos que se obtiene.
b) ¿Qué mensaje imprime cada proceso.

MGB 9

You might also like