Programación de Sistemas
	Tema III
	Procesos y Programas.


+ Programas y procesos.

UNIX es un sistema multitarea, por lo cual, con el fin de poder ejecutar
múltiples programas de forma simultanea, utiliza el concepto de un proceso.

Un programa para UNIX, es un conjunto de instrucciones en código máquina que
se debe ejecutar en la Unidad de Procesamiento Central (CPU).

UNIX debe proporcionar al usuario la idea de que su programa está ejecutándose
de forma simultanea con otros programas.

Para ésto, UNIX define el concepto de proceso, que es un programa ejecutable
que es manipulado por el sistema operativo. Esto es, dicho programa ejecutable
es cargado a memoria y sus instrucciones son ejecutadas con ayuda del núcleo
del sistema operativo. El usuario nunca se preocupa por dialogar directamente
con el sistema operativo con el fin de ejecutar su programas, sino que
crea procesos para que estos queden bajo el control del núcleo.

+ Estructura de un programa ejecutable.

Cualquier programa para ejecutarse, necesita de memoria de la computadora.

Sin un sistema operativo, un programa puede utilizar la memoria para
cargar sus instrucciones y alojar datos de una forma indistinta. Pero el
programador debe incluir las instrucciones necesarias para manipular la
 memoria de la computadora.

Con el fin de que el programador no interactue con el sistema operativo, éste
organiza cada programa ejecutable para que al momento de ejecución tenga
los recursos de memoria que necesite.

Para UNIX, la memoria de un programa consiste en: (ordenado de direcciones
bajas a direcciones altas).

	+ Texto del programa. 
		Código máquina.
	+ Variables inicializadas. 
		Variables que son inicializadas desde el programa 
		y que son de sólo lectura.
	+ Variables no inicializadas.
		Variables que no se inicializan hasta el momento de
		ejecución. Son variables de lectura y escritura.
	+ Area de memoria dinámica o heap.
		Cuando se necesita utilizar memoria de forma dinámica 
		se proporciona una zona de memoria de la que se puede tomar
		o liberar dicha memoria.
	+ Pila para manejo de funciones.
		Aquellas variables locales, parámetros y valores de
		retorno que se utilizan dentro de una función. 
	+ Argumentos de comandos y variables de ambiente.

Toda la memoria asignada a un proceso es tomada de la memoria virtual, la cual
está dividida en páginas. Estas páginas pueden estar cargadas en RAM o 
en disco. 

+ Estructura de un proceso.

Para lograr la imágen de un sistema multitarea, a cada proceso se le asigna un
tiempo de ejecución en el CPU. El sistema operativo es el encargado de planear
como se realiza dicha asignación de tiempo.

Además, cada proceso tiene un conjunto de referencias a los recursos que 
necesita un programa.

Para lograr esto, el sistema operativo define un proceso como una estructura
de datos que contiene:
	+ Identificador del proceso.
	+ Tabla de descriptores de archivos.
	+ Estructuras para almacenar el contexto del programa
	(contador del programa, valores de los registros del CPU)
	+ Estructuras para apuntar a las diferentes zonas de memoria.
	+ Tiempo de inicio y duración del proceso.
	+ Identificador del usuario y grupo al que pertenece este
	proceso.

Afortunadamente, el usuario sólo debe preocuparse por el identificador del
proceso, que es lo que permite manipularlo.

Todos los procesos son almacenados en otra estructura de datos, denominada
la tabla de procesos. Es decir, cada proceso creado, es insertado en dicha
tabla. 


+ Organización de procesos en UNIX.

UNIX tiene un modelo en el cual un proceso puede crear a otros procesos y
así sucesivamente.

En UNIX, después de cargar el kernel, se lanza un proceso denominado init.
Este proceso es el que se inicia al cargar el sistema operativo y da origen
a otros procesos. UNIX le asigna el identificador 1. Este proceso le 
pertenece al usuario root.

Para poder investigar el estado de un proceso se puede utilizar el comando 
ps.

Por ejemplo, para buscar el proceso init, teclear el comando:

ps -fea | more

Esto muestra todos los procesos en el sistema, y la información de los
mismos, tales como identificador del usuario que lo creo, el identificador
del procesos, proceso padre del proceso y nombre del proceso.

Ejercicios:


1. Investigar cual es el identificador de proceso de la sesión de trabajo y
además quien fue su proceso padre.
2. Seguir todo el árbol de procesos de la sesión actual. ¿ Qué proceso
origino a todo el árbol ?
3. Se puede destruir al proceso 1 ? (probar kill -9 1)

+ Ciclo de vida de un proceso.

En UNIX un proceso puede tener los siguientes estados:

+ Creado. Este estado es cuando se origina un nuevo proceso y consiste 
en insertar en la tabla de procesos al nuevo proceso y asignar todos
los recursos necesarios.
+ Listo para ejecución. Todo proceso se pone en la cola de procesos para
esperar su turno en el CPU.
+ Ejecutado. Cuando está realizando instrucciones en el CPU.
+ Bloqueado. El proceso ejecutó una instrucción que lo lleva a esperar por
un recurso.
+ Finalizado. El proceso concluye su ejecución y libera recursos.
+ Zombie. Es cuando el proceso no puede morirse del todo, ya que está esperando
que sus hijos o padre le retone un valor. Normalmente un proceso zombie ya
no ocupa espacio en memoria, pero muchos procesos zombie pueden llevar a
saturación del sistema.

En base a este ciclo de vida, UNIX define un conjunto de llamadas al sistema.

+ Obtención del identificador del proceso y de su padre.

Para que un proceso determine cuál es su identificador, se puede utilizar
la llamada al kernel getpid(2).

Se debe incluir :
#include 

su formato es:
pid_t getpid(void).

No recibe parámetros y retorna un número positivo, que es el 
identificador del proceso o el Process Id (PID).

Un proceso tambien puede saber quien lo origino, esto se hace con la llamada
getppid(2).

Se debe incluir :
#include 

su formato es:
pid_t getppid(void).

Esta llamada retorna el identificador del proceso padre o Parent Process Id.
(PPID).

/*Ejemplo 1
 * Imprimir el identificador de proceso de este programa y el
 * identificador de su proceso padre
 */

#include 
#include 

main(){

	printf("\tPID\tPPID\n");
        /*Se obtiene el identificador del procesos y el de su padre
         *utilizando las llamadas getpid y getppid
         */
	printf("\t%d\t%d\n",getpid(),getppid());
	
}

Este programa imprime el id del proceso y el de su padre.

Por ejemplo:

Ejemplo1
	PID	PPID
	475	447

Si se corre varias veces, en la misma sesión, se puede observar que el PID
varia, sin embargo el PPID no. Esto es debido a que el PPID es el identificador
del shell que ejecuta el programa.

Para investigar el proceso que es el PPID de Ejemplo1:

ps -ef | grep 447

Aquí se debe varia el PPID dependiendo del resultado.


+ Creacion de proceso. Llamada fork.

La llamada fork(2), permite crear un proceso.

Para utilizarla, se deben incluir los siguientes archivos:
#include 
#include 

y su formato es:

pid_t fork(void)

Es decir, fork no necesita de parámetros, pero en cambio, si tiene éxito, retorna el identificador del proceso creado o el Process Id (PID), que es un número entero positivo. 

Como su nombre lo indica, fork divide en dos flujos al programa, uno de
ellos es el flujo que sigue el proceso padre y el otro el flujo que sigue
el proceso hijo. Por tanto, fork retorna dos valores. Al padre le retorna
el identificador del proceso creado, pero al hijo le retorna 0. Con
ésto es posible diferenciar entre el proceso padre e hijo.

UNIX, al momento de ejecutar un fork, copia el área de datos al nuevo
proceso, y lo único que comparten el padre y el hijo es el texto del código
máquina.

El siguiente ejemplo ilustra esto:



/*Ejemplo 2
 * Crear un proceso hijo y mostrar que es diferente.
 */

#include 
#include 

main(){

	int chPid;
	int variable;

	/*Crear un nuevo proceso*/
	chPid=fork(); /*Aqui empieza la división*/

	if (chPid ==0) { /* Este es el hijo*/
		printf("Soy el hijo!! \n");
		for (variable=0;variable<30;variable++);
		printf("El hijo deja variable con valor %d",variable);
	} else {
		printf("Soy el padre del proceso %d\n",chPid);
		for (variable=0;variable>-30;variable--);
		printf("El padre deja variable con valor %d",variable);

	}
	/*Padre e hijo imprimen identificadores*/
	printf("\tPID\tPPID\n");
        /*Se obtiene el identificador del procesos y el de su padre
       	*utilizando las llamadas getpid y getppid
        */
	printf("\t%d\t%d\n",getpid(),getppid());
	
}

Al corre este programa:

Soy el hijo!! 
El hijo deja variable con valor 30
	PID	PPID
	3757	3756
Soy el padre del proceso 3757
El padre deja variable con valor -30
	PID	PPID
	3756	3729

Las salidas de los PID y PPID puede variar. Pero es necesario observar
que ambos procesos están corriendo de forma simultanea y los caminos que
siguen alsepararse son distintos.  Uno pone la variable a 30 y otro a -30.
De hecho, el dato variable que están manipulando son diferentes.

Existen varias formas de organizar los procesos. Una de ellas es una cadena
de procesos.


/*Ejemplo 3
 * Crear una cadena de procesos 
 */

#include 
#include 
#include 

/*Recibe desde la linea de comandos cuantos procesos crear en la cadena*/
main(int argc,char *argv[]){

        int chPid;
        int numProcesos,i;

        if (argc==1)
                numProcesos=1;
        else
                numProcesos=atoi(argv[1]);

        for (i=1;i<=numProcesos;i++) {
                /*Crear nuevo proceso*/
                chPid=fork();
                /*Aqui ya se bifurco*/
                if (chPid!=0) { /*este es el papa*/
                        printf("PID %d  papa de %d\n",getpid(),chPid);
                        /*Concluir el ciclo, ya no puede tener mas hijos*/
                        break;
                }else {
                        printf("\tNivel:%d \n",i);
                        printf("\tPID\tPPID\n");
                        /*Se obtiene el identificador del procesos
                         * y el de su padre
                        *utilizando las llamadas getpid y getppid
                        */
                        printf("\t%d\t%d\n",getpid(),getppid());
                        /*Continuar y crear un hijo*/
               }/*if*/

        }/*for*/

}

Ejecutar:
Ejemplo3 5
	Nivel:1 
	PID	PPID
	3891	3890
	Nivel:2 
	PID	PPID
	3892	3891
	Nivel:3 
	PID	PPID
	3893	3892
	Nivel:4 
	PID	PPID
	3894	3893
	Nivel:5 
	PID	PPID
	3895	3894
PID 3894  papa de 3895
PID 3893  papa de 3894
PID 3892  papa de 3893
PID 3891  papa de 3892
PID 3890  papa de 3891

Se puede observar que cada hijo lo que hace es imprimir el Nivel en el que
fue creado y continua el ciclo. Cuando llega al fork, el hijo se convierte
en padre y termina su ciclo de vida. Con esta organización, un proceso tiene
derecho a un solo hijo.

Otra forma de organizar a los procesos es en forma de abanico, en la cual
sólo un padre tiene varios hijos. Este es el caso contrario al de cadena
de procesos, cuando se crea un hijo, este concluye y quien continua el
ciclo es el padre.


/*Ejemplo 4
 * Crear un abanico de procesos 
 */

#include 
#include 
#include 

/*Recibe desde la linea de comandos cuantos procesos crear en el abanico*/
main(int argc,char *argv[]){

        int chPid;
        int numProcesos,i;

        if (argc==1)
                numProcesos=1;
        else
                numProcesos=atoi(argv[1]);

        for (i=1;i<=numProcesos;i++) {
                /*Crear nuevo proceso*/
                chPid=fork();
                /*Aqui ya se bifurco*/
                if (chPid==0) { /*este es el papa*/
                        printf("PID %d  papa de %d\n",getpid(),chPid);
                        /*Continuar el ciclo,  y crear un hijo*/
                }else {
                        printf("\tNodo:%d \n",i);
                        printf("\tPID\tPPID\n");
                        /*Se obtiene el identificador del procesos
                         * y el de su padre
                        *utilizando las llamadas getpid y getppid
                        */
                        printf("\t%d\t%d\n",getpid(),getppid());
			 break;

                        /*salir del ciclo*/
               }/*if*/

        }/*for*/

}

Al correr

 Ejemplo4 5
        Nodo:1 
        PID     PPID
        3935    3934
PID 3934  papa de 3935
        Nodo:2 
        PID     PPID
        3936    3934
PID 3934  papa de 3936
        Nodo:3 
        PID     PPID
        3937    3934
PID 3934  papa de 3937
        Nodo:4 
        PID     PPID
        3938    3934
PID 3934  papa de 3938
        Nodo:5 
        PID     PPID
        3939    3934
PID 3934  papa de 3939


Se puede observar que el PPID de todos los procesos es 3934. El proceso
padre es quien se encarga de concluir el ciclo.

Se puede combinar la estructura de una cadena de procesos y de un abanico,
resultando un árbol de procesos. Este consiste en un ciclo en el cual
tanto el padre y el hijo continuan la bifurcación de procesos.

/*Ejemplo 5
 * Crear un arbol de procesos 
 */

#include 
#include 
#include 

/*Recibe desde la linea de comandos cuantos procesos crear en el abanico*/
main(int argc,char *argv[]){

        int chPid;
        int numProcesos,i;

        if (argc==1)
                numProcesos=1;
        else
                numProcesos=atoi(argv[1]);

        for (i=1;i<=numProcesos;i++) {
                /*Crear nuevo proceso*/
                chPid=fork();
                /*Aqui ya se bifurco*/
                if (chPid==0) { /*este es el papa*/
                        printf("PID %d  papa de %d\n",getpid(),chPid);
                        /*Continuar el ciclo,  y crear un hijo*/
                }else {
                        printf("\tPID\tPPID\n");
                        /*Se obtiene el identificador del procesos
                         * y el de su padre
                        *utilizando las llamadas getpid y getppid
                        */
                        printf("\t%d\t%d\n",getpid(),getppid());
		 
                         /*Continuar el ciclo,  y crear un hijo*/
               }/*if*/

        }/*for*/

}
Ejemplo5 3
        PID     PPID
        3977    3976
        PID     PPID
        3978    3977
        PID     PPID
        3979    3978
PID 3978  papa de 3979
PID 3977  papa de 3978
        PID     PPID
        3980    3977
PID 3976  papa de 3977
        PID     PPID
        3981    3976
        PID     PPID
        3982    3981
PID 3981  papa de 3982
PID 3976  papa de 3981
        PID     PPID
        3983    3976
PID 3977  papa de 3980
PID 3976  papa de 3983

Aquí la salida puede resultar confusa, pero se debe a que:

El proceso 3976 tiene tres procesos 3977,3981,3983, debido a que 
tenia que crear 3 ciclos.
El proceso 3977 crea dos procesos 3978 y 3980 (con contador 2)
El proceso 3981 crea un solo proceso 3982     (con contador 1)
El proceso 3983 no crea ningun proceos		(con contador 0)
El proceso 3978 crea un solo proceso 3979
El proceso 3980 no crea proceso (contador 0).
El proceso 3982 y 3979 tampoco crean hijos, debido a que tienen el contador 0.

Ejercicios:
1. Dibujar en forma de un grafo los resultados del Ejemplo3, Ejemplo4 y
Ejemplo5.
2. Crear un programa en el cual, si el id del proceso es par, continue
con el ciclo, de lo contrario, que ya no cree mas hijos.
3. Crear un programa en el cual se indique el nivel del árbol y el número
de nodos por cada nodo del árbol. Dibujar el grafo para un árbol de nivel
2 y con 3 nodos.


+ En espera de otro proceso. wait.

Cuando un proceso se bifurca, es posible que el proceso padre se quede
en espera de la terminación de su hijo.

Para realizar esto, se debe utilizar la llamada wait(2).

Esta llamada suspende al proceso que la invoca hasta que uno de sus hijos
termina la ejecución.

Su formato es:
     #include 
     #include 

     pid_t wait(int *stat_loc);

El argumento stat_loc es un apuntador a un entero, ya que el entero es
 modificado con un valor que indica el estado del proceso hijo al momento
de concluir su actividad. wait retorna el PID del proceso hijo.

/*Ejemplo 8
 * Muestra el uso de la llamada wait
 */
#include 
#include 
#include 

main() {

	int pid;

	pid=fork(); /*bifurcarse*/

	if (pid == 0) {
		int i;
		printf("Soy el hijo \n");
		/*ejecutar un ciclo ocioso*/
		for (i=0;i<10000;i++);
		printf("Concluye el hijo  \n");
			
	} else {
		int status; /*valor que contendra el estado del proceso hijo*/
		int rpid;
		printf("Tuve un hijo con PID %d, esperando ...",pid);
		rpid=wait(&status);

		/*si el pid de retorno de wait es identico al del hijo*/
		if (rpid==pid)	{
			printf("Concluye el hijo con PID %d y estado %d\n",pid,status);
		} else {
			perror("El hijo tuvo problemas");
		}
		
	}

	
}

El ejemplo 9 muestra un proceso hijo que tiene un error

/*Ejemplo 9
 * Muestra el uso de la llamada wait
 */
#include 
#include 
#include 

main() {

	int pid;

	pid=fork(); /*bifurcarse*/

	if (pid == 0) {
		int i;
		char *ch=0;
		printf("Soy el hijo \n");
		/*instruccion con error!!*/
		*ch='a';
		printf("Concluye el hijo  \n");
			
	} else {
		int status; /*valor que contendra el estado del proceso hijo*/
		int rpid;
		printf("Tuve un hijo con PID %d, esperando ...",pid);
		rpid=wait(&status);

		/*si el pid de retorno de wait es identico al del hijo*/
		if (rpid==pid)	{
			printf("Concluye el hijo con PID %d y estado %d\n",pid,status);
		} else {
			perror("El hijo tuvo problemas");
		}
		
	}

	
}

El estado del hijo es diferente cuando trata de accesar un apuntador nulo.
De hecho genera un archivo core.

Tambien se puede poner un proceso que cree a varios hijos y los espere.


/*Ejemplo 10
 * Muestra el uso de la llamada wait con multiples procesos
 */
#include 
#include 
#include 


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

	int pid,nproc,i;

	int status; /*valor que contendra el estado del proceso hijo*/
	int tablaProcesos[128]; /*arreglo con identificadores de procesos*/
	
	if (argc==1)
		nproc=1;
	else {
		nproc=atoi(argv[1]);

		/*guardar solo a 128 PID*/
		if (nproc>128)
			nproc=128;
	}

	/*crear un abanico de procesos*/

	for (i=0; i< nproc;i++) {
		pid=fork(); /*bifurcarse*/

		
		if (pid == 0) {
			int i;
			printf("Soy el hijo \n");
			/*ejecutar un ciclo ocioso*/
			for (i=0;i<10000;i++);
			printf("Concluye el hijo  \n");
			break;		
		} else {
			printf("Tuve un hijo con PID %d",pid);
			tablaProcesos[i]=pid;
			continue;
		}/*if*/

	}/*for*/

	/*este es el proceso padre*/
	if (pid!=0) {
		/*Imprimir toda la tabla de procesos */
		printf("\n******************************************\n");
		for (i=0; i< nproc;i++) {
			printf("%d ",tablaProcesos[i]);
		}/*for*/
		printf("\n******************************************\n");

		/*esperar a cada hijo*/
		for (i=0; i< nproc;i++) {


			int rpid;
			int status;
			rpid=wait(&status);

			if (rpid!=-1) {
				printf("Concluye el hijo con PID %d y estado %d\n",rpid,status);
				tablaProcesos[i]=rpid;
			} else {
				perror("El hijo tuvo problemas");
			}/*if*/

		}/*for*/

 		/*Imprimir toda la tabla de procesos */

		printf("\n******************************************\n");
		for (i=0; i< nproc;i++) {
			printf("%d ",tablaProcesos[i]);
		}/*for*/
		printf("\n******************************************\n");
	}/*if*/
}

 Ejemplo10 3
Soy el hijo 
Concluye el hijo  
Tuve un hijo con PID 941Soy el hijo 
Concluye el hijo  
Tuve un hijo con PID 941Tuve un hijo con PID 942Soy el hijo 
Concluye el hijo  
Tuve un hijo con PID 941Tuve un hijo con PID 942Tuve un hijo con PID 943
******************************************
941 942 943 
******************************************
Concluye el hijo con PID 943 y estado 512
Concluye el hijo con PID 942 y estado 512
Concluye el hijo con PID 941 y estado 512

******************************************
943 942 941 
******************************************

Es necesario observar que el orden de la tabla de procesos es diferente
la dos veces que se imprime. La primera muestra como se fueron insertando
los procesos y la segunda muestra como fueron terminando.

Esto implica que el orden de conclusión de un proceso no está en función
de su orden de creación, sin en el tiempo de ejecución dentro del kernel y
que esta relacionado con el planificador de procesos de UNIX.

Se puede obligar que se espere un proceso especifico, con la llamada
waitpid.

Esta llamada tiene el formato:
     #include 
     #include 

     pid_t waitpid(pid_t pid, int *stat_loc, int options);

donde recibe tres argumentos, el identificador de proceso a esperar, 
el apuntador a la variable de estado, y algunas opciones que no se detallan
en este tema. Retorna el identificador del proceso que se esta esperando
o error (-1), que se puede determinar con la funcion perror.

  
/*Ejemplo 11
 * Muestra el uso de la llamada waitpid con multiples procesos
 */
#include 
#include 
#include 


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

	int pid,nproc,i;

	int status; /*valor que contendra el estado del proceso hijo*/
	int tablaProcesos[128]; /*arreglo con identificadores de procesos*/
	
	if (argc==1)
		nproc=1;
	else {
		nproc=atoi(argv[1]);

		/*guardar solo a 128 PID*/
		if (nproc>128)
			nproc=128;
	}

	/*crear un abanico de procesos*/

	for (i=0; i< nproc;i++) {
		pid=fork(); /*bifurcarse*/

		
		if (pid == 0) {
			int i;
			printf("Soy el hijo \n");
			/*ejecutar un ciclo ocioso*/
			for (i=0;i<10000;i++);
			printf("Concluye el hijo  \n");
			break;		
		} else {
			printf("Tuve un hijo con PID %d",pid);
			tablaProcesos[i]=pid;
			continue;
		}/*if*/

	}/*for*/

	/*este es el proceso padre*/
	if (pid!=0) {
		/*Imprimir toda la tabla de procesos */
		printf("\n******************************************\n");
		for (i=0; i< nproc;i++) {
			printf("%d ",tablaProcesos[i]);
		}/*for*/
		printf("\n******************************************\n");

		/*esperar a cada hijo*/
		for (i=0; i< nproc;i++) {


			int rpid;
			int status;
			rpid=waitpid(tablaProcesos[i],&status,0);

			if (rpid!=tablaProcesos[i]) {
				printf("Concluye el hijo con PID %d y estado %d\n",rpid,status);
				tablaProcesos[i]=rpid;
			} else {
				perror("El hijo tuvo problemas");
			}/*if*/

		}/*for*/

 		/*Imprimir toda la tabla de procesos */

		printf("\n******************************************\n");
		for (i=0; i< nproc;i++) {
			printf("%d ",tablaProcesos[i]);
		}/*for*/
		printf("\n******************************************\n");
	}/*if*/
}

Al ejecutar

 

En este ejemplo, la tabla de procesos se imprime en el mismo orden
de creación de procesos.

 Ejemplo11 3
Soy el hijo 
Concluye el hijo  
Tuve un hijo con PID 945Soy el hijo 
Concluye el hijo  
Tuve un hijo con PID 945Tuve un hijo con PID 946Soy el hijo 
Concluye el hijo  
Tuve un hijo con PID 945Tuve un hijo con PID 946Tuve un hijo con PID 947
******************************************
945 946 947 
******************************************
Concluye el hijo con PID 945 y estado 512
Concluye el hijo con PID 946 y estado 512
Concluye el hijo con PID 947 y estado 512

******************************************
945 946 947 
******************************************


Ejercicios.

1. Para interpretar el valor de status que se obtiene con wait, existen
un conjunto de Macros de lenguaje C, denominadas WIFEXITED, WEXITSTUS,WTERMSIG.
Explicarlas.

2. Explicar la diferencia entre wait y waitpid.

3. Generar un deadlock o bloqueo de procesos. Crear un programa, donde
el proceso padre espere a su hijo, y el proceso hijo este dentro de un
ciclo infinito.  

4. En el ejercicio 3, el padre se queda bloqueado. Como el sistema operativo
no detecta bloqueos, es necesario desbloquear, utilizando el comando kill.
Buscar en la tabla de procesos 

ps -ef | grep Ejemplo12
     gus  1040   297  0 23:14:03 pts/2    0:00 Ejemplo12
     gus  1041  1040 18 23:14:03 pts/2    0:04 Ejemplo12

se puede observer que hay dos procesos Ejemplo2, el padre es el 1040 y
el hijo es 1041.
Ejecutar 
kill -9 1040

y 

ps -ef | grep Ejemplo12
     gus  1041     1 93 23:14:03 pts/2    0:57 Ejemplo12

se puede notar que el proceso 1041 tiene ahora como padre al proceso 1 o
init.

Explicar que paso en base al concepto de procesos zombie o huérfano.

5. Repetir el mismo experimento que en el ejercicio 4, pero ahora matando
al hijo. Qué comportamiento genera ?

6. Checar los manuales de UNIX para una explicación detallada de wait
y waitpid.

+ Ejecución de programas. exec.

Como se pudo apreciar, fork crea una copia del proceso y dicha copia
se convierte en el proceso hijo. 

Normalmente un proceso hijo puede ejecutar un programa diferente al
proceso padre, es decir código máquina diferente al del padre. Esto implica
que debe invocar a otros programas ejecutable.

Para poder ejecutar un programa desde un proceso, se puede utilizar el conjunto
de llamadas exec(2). En total son 6.

     #include 

     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,char *const 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*/);

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


Todas las llamadas reciben como parámetro el nombre del programa ejecutable.

Cada llamada tiene diferencias.

1. Todas las llamadas, deben proporcionar el ejecutable con la ruta completa o absoluta. Las excepciones , execlp y execvp, pueden proporcionar el nombre
del ejecutable, y el kernel se encargar de buscarlo según el valor de la
variable PATH.

2. Las llamadas de tipo execl (execl,execle,execlp) reciben argumentos
de tamaño variable, donde el último argumento es un apuntador a nulo.

3. Las llamadas de tipo execv ( execv, execve, execvp) reciben un arreglo
de apuntadores, denominado argv, cuya ultima celda debe tener un valor nulo.
Estos son los argumentos de un programa, o conocido como lineas de comando.
(ver tema II).

4. Las llamadas execle y execve, pasan como último argumento, las variables
de ambiente del proceso padre (ver tema II). En ambas es un arreglo de apuntadores acadena de caracteres, donde la última celda apunta a nulo.

El ejemplo número 13 ilustra este concepto.

/*Ejemplo 13, uso de la familia de llamadas exec.
 * se realiza un ls -l 
 */
/*#include 
#include 
*/
main(int argc, char *argv[], char *envp[]) {

	char comandoconruta[]="/bin/ls";
	char comandosinruta[]="ls";
	char *argumentos[]={"/bin/ls","-l",(char *)0};
	int pid;
	int rtn; /*valor de retorno*/

	pid=fork();
	/*se debe bifurcar, ya que la llamada exec no es reentrante*/

	if (pid==0) {
		printf("Ejecutando ls -l con execl\n");
		rtn=execl(comandoconruta,"/bin/ls","-l",(char *)0);

		if (rtn==-1)
			perror("Error en execl");
	}
	/*Esperar al hijo*/
	waitpid(pid,(int *)0,0);

	pid=fork(); 
	/*se debe bifurcar, ya que la llamada exec no es reentrante*/

	if (pid==0) {
		printf("Ejecutando ls -l con execv\n");
		rtn=execv(comandoconruta,argumentos);

		if (rtn==-1)
			perror("Error en execv");
	}
	/*Esperar al hijo*/
	waitpid(pid,(int *)0,0);

	pid=fork(); 
	/*se debe bifurcar, ya que la llamada exec no es reentrante*/

	if (pid==0) {
		printf("Ejecutando ls -l con execle\n");
		rtn=execle(comandoconruta,"/bin/ls","-l",(char *)0,envp);

		if (rtn==-1)
			perror("Error en execle");
	}
	/*Esperar al hijo*/
	waitpid(pid,(int *)0,0);

        pid=fork();
        /*se debe bifurcar, ya que la llamada exec no es reentrante*/

        if (pid==0) {
                printf("Ejecutando ls -l con execve\n");
                rtn=execve(comandoconruta,argumentos,envp);

                if (rtn==-1)
                        perror("Error en execve");
        }
        /*Esperar al hijo*/
        waitpid(pid,(int *)0,0);


        pid=fork();
        /*se debe bifurcar, ya que la llamada exec no es reentrante*/

        if (pid==0) {
                printf("Ejecutando ls -l con execlp\n");
                rtn=execlp(comandosinruta,"ls","-l",(char *)0);

                if (rtn==-1)
                        perror("Error en execlp");
        }
        /*Esperar al hijo*/
        waitpid(pid,(int *)0,0);


         pid=fork();
        /*se debe bifurcar, ya que la llamada exec no es reentrante*/

        if (pid==0) {
                printf("Ejecutando ls -l con execvp\n");
                rtn=execvp(comandosinruta,argumentos);

                if (rtn==-1)
                        perror("Error en execvp");
        }
        /*Esperar al hijo*/
        waitpid(pid,(int *)0,0);


	printf("Fin de pruebas con familia exec\n");
	

}



En los comentarios se habla del concepto de que una llamadas es no reentrante.
La familia exec, si ejecutan el comando, no retornan al proceso que
las creo. Si en el programa no se hubiera utilizado una llamada fork, entonces
sólo se hubiera ejecutado una sola llamada exec y las otras no se hubieran
ejecutado, ya que no existe retorno despues de la ejecución de exec, a menos
de que surgiera un error.

Ya sea como vector o lista de argumentos, el primer argumento debe ser
el nombre del ejecutable.

El siguiente programa muestra algunos errores.


 
/*Ejemplo14. El usuario puede indicar el programa a ejecutar y
 * por tanto pueden existir errores
 */


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

	int pid, rtn;


	if (argc>1) {
		/*Bifurcar el programa*/

		printf("Ejecutando %s\n",argv[1]);	
		pid= fork();

		if (pid==0) {
		
			rtn= execv(argv[1],&argv[1]);

			if (rtn==-1) {
				perror("Algo esta mal, intento execvp");
				rtn= execvp(argv[1],&argv[1]);
				if (rtn==-1) {
					perror("Algo esta mal");
				}
				

			}/*if*/
		}/*if*/ 

		waitpid(pid,(int *)0,0);

	}/*if*/
}


Este ejemplo es un shell muy sencillo, que si el comando no es proporcionado
con una ruta absoluta, entonces trata de buscar en la ruta.

Ejercicios.

1. Crear un shell muy sencillo, que lea de la entrada estandar los comandos
a ejecutar, incluyendo sus argumentos (separados por espacios).

+ Terminacion de procesos. exit

Para que un proceso concluya correctamente una tarea, debe indicar
el valor con el que retorna.

La llamada exit(2) permite indicar el estado de terminacion.

El formato es :

void exit(int status);

donde el argumento status, puede tomar valores negativos o positivos.
Normalmente, un proceso que concluye de forma exitosa, debe retornar 0,
de lo contrario debe indicar 1.

/* Ejemplo16 Muestra uso de exit*/

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

	int estadofinal;
	if (argc==1) {
		estadofinal=0;	
	}else 
		estadofinal= atoi(argv[1]);

	pid=fork(); /*bifurcar al proceso*/

	
	if (pid ==0) {
		printf("Hijo con pid %d\n",getpid());
		exit(estadofinal);
	} else {
		int status,valorRetorno;
		waitpid(pid,&status,0);
		printf("Estado del PID %d es %x\n",pid,status);

		printf("Valor retorno %x\n",(status&0xFF00)>>8);
		printf("Valor retorno %x\n",status&0x00FF);
		valorRetorno=(status&0xFF00)>>8;
		if (valorRetorno==estadofinal)
			printf("El proceso HIJO concluyo en el estado esperado");
		else
			printf("El proceso HIJO no  concluyo en el estado esperado");
			
	}

	
}

Estado del PID 1278 es 1a00
Valor retorno 1a
Valor retorno 0
El proceso HIJO concluyo en el estado esperado

La bandera status de la llamada waitid, contiene el valor de retorno de
la llamada exit.

En los primeros 8 bits está almacenado el valor de exit, en los últimos 8
bits están a 0.

Debido a esto, se realizo una operacion AND de bits sobre el estado regresado,
y luego un corrimiento a la derecha de 8 bits.

Se pueden realizar algunas actividades antes de que un proceso concluya su
vida. Por ejemplo limpiar memoria o cerrar conexiones.

Con la funcióp atexit(3) es posible realizar esto.

El formato es:

 #include 

 int atexit(void (*func)(void));

El argumento es un apuntador a una función que no tiene argumento de entrada
y no retorna nada.

/*Ejemplo17 Muestra la invocación de una función al concluir un proceso*/

void mensajesalida(void) {

	printf("El PID %d con PPID %d ha muerto!!",getpid(),getppid());
}

main() {

	int pid,i;

	atexit(mensajesalida);

	/*crear un abanico de procesos*/
	for (i=0;i<10;i++) {
		pid=fork();
		if (pid==0) {
			printf("Hijo con pid %d\n",getpid());
			exit(i);
		} else {
			int status;
			waitpid(pid,&status,0);
			printf("Valor retorno %x\n",(status&0xFF00)>>8);
		}	

	}

}

La salida de este proceso es:

Hijo con pid 1266
El PID 1266 con PPID 1265 ha muerto!!Valor retorno 0
Hijo con pid 1267
El PID 1267 con PPID 1265 ha muerto!!Valor retorno 1
Hijo con pid 1268
El PID 1268 con PPID 1265 ha muerto!!Valor retorno 2
Hijo con pid 1269
El PID 1269 con PPID 1265 ha muerto!!Valor retorno 3
Hijo con pid 1270
El PID 1270 con PPID 1265 ha muerto!!Valor retorno 4
Hijo con pid 1271
El PID 1271 con PPID 1265 ha muerto!!Valor retorno 5
Hijo con pid 1272
El PID 1272 con PPID 1265 ha muerto!!Valor retorno 6
Hijo con pid 1273
El PID 1273 con PPID 1265 ha muerto!!Valor retorno 7
Hijo con pid 1274
El PID 1274 con PPID 1265 ha muerto!!Valor retorno 8
Hijo con pid 1275
El PID 1275 con PPID 1265 ha muerto!!Valor retorno 9
El PID 1265 con PPID 405 ha muerto!

Observar que el ultimo proceso en imprimir el mensaje de fin es el
proceso padre del abanico.

Como regla general, es cuando se concluya un proceso, se debe invocar
la llamada exit con su valor que indique el estado del proceso. 

Ejercicio.
1. Modificar el Ejemplo16 y terminar el proceso con un valor negativo al
proporcionado en la linea de comando.


2. Indicar la diferencia entre la llamada exit(2) y _exit(2)

3. Codificar un programa que dispare un proceso y que mida el tiempo
de ejecución cuando este finalize.


+ Destruccion de procesos. kill

Un proceso puede destruir a otro procesos, siempre y cuando el proceso
destructor tiene permisos sobre los otros procesos.

La llamada para realizar esto es kill(2).

#include 
#include 

     int kill(pid_t pid, int sig);

Esta debe conocer como argumentos el PID del proceso a eliminar y un número
que indica como debe ser destruido el proceso.

El siguiente ejemplo construye un abanico de procesos y cuando el
proceso padre decide terminar, manda a destruir todos los nodos del 
abanico.

 /*Ejemplo 20*/
#define MAXPROC 128
int tablaprocesos[MAXPROC];
int contador=0;

void finprocesohijo(void) {

	printf("El PID %d con PPID %d ha muerto!!\n",getpid(),getppid());

}

void finprocesopadre(void) {
	int i;
	for (i=0;iMAXPROC)
			contador=MAXPROC;
	}

	atexit(finprocesopadre);

	for ( i=0;i 1) {
		int pid= fork();

		if (pid < 0)
			perror("Error en fork");

		else if (pid==0) { /*El hijo manda a ejecutar el comando*/
			int rtn;
			printf("Ejecutando proceso en segundo plano");
			rtn=execvp(argv[1],&argv[1]):
			printf("este mensaje no sucede ante correcta ejecucion");

			if (rtn<0)
			 	perror("Error en ejecucion");
			exit(1);
		}else
			exit(0);
	}
}

Aqui se puede observar que el proceso padre genera un exit, sin esperar
que el proceso hijo concluya. Cuando un proceso es ejecutado y su proceso
padre no lo espera, se dice que el proceso está en segundo plano o background.

Un proceso demonio es aquel que sigue activo en UNIX aún despues de que
el proceso que originó la sesión UNIX ha sido destruído. Un proceso demonio
debe estar en plano secundario. Para fines de este capitulo, el programa anterior puede utilizarse para crear procesos demonios. En temas posteriores
se explicará a mas detalle como crear un proceso demonio. De hecho, un
proceso demonio persiste aún después de que el usuario que lo creo ha salido
de sesión.

Para probar este programa, escribir el siguiente SCRIPT de UNIX.


#!/bin/sh

while [ 1 ]
do  
        sleep 2
        echo "Molestando " > /dev/console
done

y ejecutar
chmod 755 molestando.sh
Ejemplo22 molestando.sh

este programa duerme 2 segundos y envia a la consola del sistema el
mensaje Molestando.

Checar con el comando ps la ejecucion de este programa.

Ejercicios.

1. Investigar como se puede poner un programa en segundo plano utilizando
el shell de UNIX.
2. Investigar el comando nohup


+ Multihilos.

Cuando un programa se ejecuta, el CPU utiliza el valor de los registros de
la computadora; por ejemplo el contador del programa, para determinar cual 
instruccion debe ejecutar a continuación. El flujo de instrucciones resultantes
se denomina hilo de ejecución del programa y es el flujo de control para el 
proceso representado por la sencuencia de instrucciones que se aplica durante
la ejecución del código máquina.

Cuando un proceso utiliza un solo flujo se conoce como de un solo hilo.

Los sistemas operativos que se llaman multihilados es por que permiten que un
proceso contenga multiples hilos de ejecución y que además si existen multiples
CPUs en la computadora o arquitectura de multiprocesador simétrico, se puede 
asignar un hilo por CPU y asi aprovechar el paralelismo del hardware

Tipicamente el hilo de ejecución es el que el sistema operativo administra. Si
la maquina es mono o multi procesador, debe ser transparente para el usuario el
escalamiento. 

Un programador de aplicaciones interactua con un tipo de dato denominado hilo 
(thread en ingles) al cual puede asignarle un flujo.

Existe una libreria, definida por POSIX, para soportar multihilos, conocidos como
POSIX threads.

+ Diferencia entre un proceso y un hilo.

Cuando un proceso es creado, con la llamada fork(), duplica su area de datos y
genera un nuevo espacio de datos, que es independiente del proceso padre y puede
iniciar una ejecución "paralela" al proceso padre.

En el caso de un hilo, cuando es creado, comparte el mismo espacio de datos que el flujo que lo origino y empieza su ejecución "paralela" al flujo padre. 
Pero se debe considerar que los datos son idénticos y si un flujo afecta un dato,
el otro puede ver la afectacion.

Surge la pregunta, cuando usar multitarea en lugar de multihilo. 
Siempre que se necesite una manera de procesamiento donde los datos son independientes al flujo, se aconseja multitarea.
Sin embargo, si se necesita una manera de procesamiento donde se deben compartir
los datos, se aconseja multihilado.

El problema al tener multiples hilos llega al momento de tener que coordinarlos para evitar problemas de concurrencia.

+ Ciclo de vida de un hilo.

Un hilo se crea, se pone en ejecución, puede preguntar por su propia identidad,concluye su ejecución, puede esperar por la conclusión de un hilo "hijo" o puede ser eliminado por otro thread.

El ciclo de vida de un hilo tiene puntos análogos a un ciclo de vida de un proceso, pero difieren en que no existe una tabla de hilos en el sistema operativo.

+ Creación de un hilo.

Para que un hilo sea creado, se debe indicar el flujo que debe ejecutar, esto
se hace via apuntadores a funciones.

La llamada se denomina pthread_create

#include 

int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_rou-
       tine)(void *), void * arg);

El primer argumento es una dirección a un tipo de dato pthread_t y es inicializado por la llamada con el fin de que se use como argumentos a otras llamadas.
El segundo argumento es una lista de atributos que se indican para la ejecución del hilo, si no se quiere indicar nada, se pone como nulo
El tercer parametro es un apuntador a una función que recibe cualuqier tipo de dato (void *) y retorna cualquier tipo de dato (void *)
El cuarto parametro es un apuntador a cualquier tipo de datos e indica la direccion del parametro que se pasa a la rutina de ejecución

La llamada retorna un cero y el identificador del hilo (unico por proceso) es 
almacenado en la estructura pthread_t y en caso de error un valor distinto de cero.


El siguiente ejemplo crea 3 hilos e invoca 3 diferentes funciones, usando distintos
argumentos.

/*Ejemplo1MT.c*/
#include 
#include 
void * fth1(void * arg) {
  int x = *((int *)arg);
  printf("fth1:%d\n", x);
}
void * fth2(void * arg) {
  char ch = *((char *)arg);
  printf("fth2:%c\n", ch);
}
void * fth3(void * arg) {
  int * arr = (int *) arg;
  int x;
 x = *arr;
  while ( x!=0 ) {
     printf("fth3:%d\t",*arr);
     arr++;
     x = *arr;
  }
  printf("\n");
}
main() {
 pthread_t th1,th2,th3;
 int x={1};
 char ch = 'a';
 int arr[] = {1,2,3,0};
 void *p;
 /*crear 3 hilos*/
 p = (void *)&x;
 if ( pthread_create(&th1,(pthread_attr_t *)0,fth1,p ) )
    perror("Error en creacion de thread1");
 p = (void *)&ch;
 if (pthread_create(&th2,(pthread_attr_t *) 0,fth2, p))
    perror("Error en creacion de thread2");
 p = (void *)arr;
 if (pthread_create(&th3,(pthread_attr_t *) 0,fth3,(void *)p ))
    perror("Error en creacion de thread3");

 pthread_exit(0);
}

La llamada pthread_exit(0) se aplica para que aunque el flujo del main concluya, deje que los hilos creados se puedan ejecutar.

Para generar el ejecutable de este programa, se debe enlazar con la libreria
de multithreading de Linux

gcc -o Ejemplo1MT Ejemplo1MT.c -lpthread

+ Conclusión de un hilo

Cuando un hilo termine su procesamiento, debe invocar a la función pthread_exit, 
en el cual se indica un parámetro que se retorna a otro hilo que esperaba por él.

#include 
 void pthread_exit(void *retval);

El argumento retval es un apuntador a cualquier tipo de dato.

+ Esperando a un hilo .

Un flujo o hilo puede esperar por la conclusión de otro hilo usando la llamada
pthread_join, dejando suspendido al hilo que lo invoque

#include 

int pthread_join(pthread_t th, void **thread_return);

El primer argumento indica la estructura del hilo que se desea esperar.
El segundo argumento recibe el valor del apuntador que retorna la llamada pthread_exit. Obteniendo el contenido del apuntador (previa modificacion de tipo), se 
obtiene el valor retornado

En exito retorna 0  en caso contrario retorna un valor distinto de 0 , indicando error.

+ Preguntado el identificador de un hilo

Un hilo puede preguntar cual es su identificador de hilo, con la llamada
pthread_self

       #include 

       pthread_t pthread_self(void);

Esta llamada retorna el identificador del hilo que la invoca.

+ Ejemplo final

El siguiente ejemplo muestra un abanico de hilos, donde cada nodo del abanico retorna un valor al hilo que lo espera.
Se usan las funciones pthread_create, pthread_join,pthread_exit, pthread_self


#include 
#include 
void * fth1(void * arg) {
  int x = *((int *)arg);
  pthread_t self= pthread_self();
  printf("thid %d fth1:%d\n",self ,x);
  x++;
  pthread_exit((void *) &x);
}
void * fth2(void * arg) {
  char ch = *((char *)arg);
  pthread_t self= pthread_self();
  printf("thid %d ",self );
  printf("fth2:%c\n", ch);
  ch = ch +1;
  pthread_exit((void *) &ch);
}
void * fth3(void * arg) {
  int retorno[4]={0,0,0,0};
  int * arr = (int *) arg;
  pthread_t self= pthread_self();
  printf("thid %d ",self );
  int x,i=0;
  x = *arr;
  while ( x!=0 ) {
     retorno[i] = x-1;
     printf("fth3:%d\t",*arr);
     arr++;
     x = *arr;
     i++;
  }
  printf("\n");
  pthread_exit((void *) retorno);
}
main() {
 pthread_t abanico[3];
 int x={1};
 char ch = 'a';
 int arr[] = {1,2,3,0};
 void *p;
 /*crear 3 hilos*/
 p = (void *)&x;
 if ( pthread_create(&abanico[0],(pthread_attr_t *)0,fth1,p ) )
    perror("Error en creacion de thread1");
 p = (void *)&ch;
 if (pthread_create(&abanico[1],(pthread_attr_t *) 0,fth2, p))
    perror("Error en creacion de thread2");
 p = (void *)arr;
 if (pthread_create(&abanico[2],(pthread_attr_t *) 0,fth3,(void *)p ))
    perror("Error en creacion de thread3");
/*esperar a los 3 hilos*/
 if ( pthread_join(abanico[0],&p) ) { perror("error en pthread_join"); } else { printf("Retorno th1 %d\n",*((int*)p));}
 if ( pthread_join(abanico[1],&p) ) { perror("error en pthread_join"); } else {
        printf("Retorno th2 %c\n",*((char*)p));}
 if ( pthread_join(abanico[2],&p) ) { perror("error en pthread_join"); } else {
      int * pi = (int *)p;
      printf("Retorno th3 %d\n",*pi);pi++;
      printf("Retorno th3 %d\n",*pi);pi++;
      printf("Retorno th3 %d\n",*pi);
  }
}


+ Hilos de usuarios e hilos del nucleo (kernel threads)

Los hilos POSIX aqui visto son hilos de un usuario y se ejecutan con ayuda de
un sistema operativo. Dichos hilos no son administrados por el nucleo, sino son
parte de la implantacion de la libreria.

En el caso de Linux, los hilos acaban siendo procesos de Linux.

En cambio, los hilos del kernel son aquellos que realmente son administrados
por el sistema operativo y estan compitiendo por los recursos. La ventaja es
que aprovechan la existencia de hardware paralelo, sin embargo son mas costosos
de programar y por tanto, se debe cuidar de los esquemas de concurrencia. Si
un nucleo del sistema operativo es multihilo, debe cuidar que las secciones
criticas del nucleo no tengan problemas al momento de ser paralelos.

Sistemas operativos como Solaris son orientados a hardware paralelo, fue de
los pioneros, pero en la actualidad Linux esta incluyendo hilos en su diseño.



EJEMPLO FINAL

Actualmente, los sistemas UNIX utilizan procesos demonios para implantar
servicios de Internet.

Uno de los mas conocidos es el servicio de HTTP o WWW. 

Este servicio, en su forma mas sencilla, proporciona dos tipos de instrucciones:
GET, que consiste en desplegar el contenido de un archivo.
PUT, que consiste en ejecutar un programa (conocido como un CGI).

El siguiente código muestra un ejemplo de como implantar un servicio
de HTTP con procesos. 

/*Ejemplo23 -- Servicio Sencillo de HTTP 
 * Se deja la documentación al alumno
*/

#define MAXCMD 128
#define CMDGET "GET"
#define CMDPOST "POST"
#define CMDFORGET "/bin/cat"

void procesoDemonio(void) {

	int pid;

	pid=fork();
	if (pid ==0) {
		printf("Proceso demonio con PID %d",getpid());
		return;
	} else {
		exit(0);
	}
}

void finHijo(void) {
	printf("Proceso Hijo %d concluye\n",getpid());
}

void procesaComando(char *cmd ) {

	int pid=fork();
	extern char * strstr(const char *,const char *);

	if (pid < 0) {

	} else if (pid == 0) {
		char* archivo=(char *)0;
		int rtn;
		atexit(finHijo);
		printf("PID %d procesando %s\n",getpid(),cmd);

		if ( archivo=strstr(cmd,CMDGET) ) {
			printf("GET %s",archivo+4);	
			rtn=execlp(CMDFORGET,"GET",archivo+4,(char *)0);
			if (rtn==-1) {
				perror("Error en GET");
				exit(2);
			}
		}else if ( archivo=strstr(cmd,CMDPOST) ) {
			printf("POST %s ",archivo+5);	
			rtn-execlp(archivo+5,"POST",(char *)0);
			if (rtn==-1) {
				perror("Error en POST");
				exit(3);
			}

		} else {
			printf("Directiva desconocida ");
			exit(1);
		}	

		exit(0);
	} 

	return;
}

void esperaTransaccion() {
	char cmd[MAXCMD];
	extern char *gets(char *);
	while (1) {
		char *ptr;
		printf("Esperando transaccion ...\n");
		ptr=gets(cmd);

		if (ptr) {
			procesaComando(cmd);
		}
	}
}

main() {

	procesoDemonio();
	esperaTransaccion();
}


Para ejecutarlo, realiza lo siguiente:
tail -f transacciones.txt | Ejemplo23 

Y en otra sesion edita el archivo transacciones.txt y añadir la siguiente
linea (entre el comando GET y el nombre del archivo SOLO va un espacio)
GET /etc/passwd

y guardar el archivo.

Añadir (entre el comando GET y el nombre del ejecutable SOLO va un espacio)

POST /bin/ls

Observar que al guardar la linea 1, despliega el contenido del archivo
/etc/passwd y al guardar la linea 2, ejecuta el comando ls.

Probar con diferentes archivos y comandos.



EJERCICIOS ADICIONALES

+ Documentar y explicar el Ejemplo final. Criticar algunos aspectos
de la programación y encontrar algunos errores (bugs) del programa.

+ Del libro de Texto, UNIX Programación práctica, resolver los ejercicios:
2.12, 2.13, 2.14

+ El servicio de FTP consiste en dos directivas: GET, PUT,DIR. La primera
"despliega" o envia el contenido de un archivo al cliente,la segunda copia el 
archivo en el servidor y la tercera lista un directorio. 
Siguiendo el ejemplo final, implantar un proceso demonio de FTP.

TIP: Utilizar el comando cat para la directiva GET y el comando cp para
el comando PUT, y el comando ls para DIR.

Con mas confianza, implantar la directiva delete con el comando rm (OJO, ten
cuidado aqui, no vayas a borrar archivos de mas).

+ ** El servicio de Sendmail o correo electrónico consiste en las siguientes
directivas:
HELO -- el cliente remoto proporciona el nombre de la maquina cliente.
MAIL FROM -- el cliente remoto indica de quien viene el correo
RCPT TO -- el cliente remoto indica a quien va el correo
DATA -- se proporciona el texto del correo
QUIT -- se cierra la sesion

Crear un proceso demonio que implante estas directivas. Utilizar el comando
mail de UNIX para implantar este servicio.

** este ejercicio es opcional y si es resuelto, te da el derecho a obtener
todos los puntos del tema II del exámen I como correctos. (25% del examen).

+ Un monitor de transacciones normalmente es un conjunto de procesos del
mismo tipo prearrancados (en forma de abanico de procesos). Plantea el 
pseudocodigo de un monitor de transacciones, utilizando los conceptos de 
UNIX.

+ Realizar una comparación del modelo UNIX contra NT a nivel procesos.

RELACION DE LLAMADAS AL SISTEMA EXPUESTAS EN ESTE TEMA.
getpid(2) -- obtiene el PID del proceso que invoca esta llamada
getppid(2) --  obtiene el PPID del proceso que invoca esta llamada
fork(2) -- crea una nueva copia del proceso que invoca la llamada
wait(2) -- espera que algún proceso hijo concluya su procesamiento
waitpid(2) -- espera la conclusion de un proceso hijo dado.
exec(2) -- familia de llamadas exec para ejecutar programas
exit(2) -- concluye la ejecucion de un proceso y retrona un estado al kernel
atexit(3C) -- llama a una funcion al finalizar el proceso
kill(2) -- Destruye a un proceso dado.

NOTA.

Los ejercicios entregarlos en codigo fuente e instrucciones necesarias
para compilar (puedes usar un Makefile).

Entregarlo en formato tar.Z.

El comando para poner en este formato es:

tar cvf - src | compress > practica2.taz

en el caso de que el directorio src contenga todos los ejercicios

    Source: geocities.com/gusdelact/progsis

               ( geocities.com/gusdelact)