Control de Procesos
Estudiaremos: La creación de nuevos procesos, su ejecución y terminación en sistema operativo Linux (también válido en otros sistemas operativos que surgen de Unix).

Identificador de Proceso
Cada proceso tiene un identificador único (PID). Éste es un entero no negativo asignado por el sistema. Puede ser usado para garantizar unicidad dentro de una máquina.

Aplicación: este valor es usado por las funciones:
#include <stdio.h>
char *tmpnam(char *ptr);
la cual retorna un pathname único.
y
FILE *tmpfile(void);
Este archivo temporal es automáticamente borrado o removido cuando es cerrado.
Algunos procesos importantes:
Scheduler -> Identificador 0
Que proceso sigue? depende del SO. Vea qué procesos siguen al 0 en aragorn; Identificador de proceso 1 es el padre muchos procesos. Su rol primario es crear el resto de los procesos del sistema.
Todos los procesos restantes conforman un árbol donde el proceso padre de cada proceso es aquel que lo creó.
Probar comandos $ps -eF para listar procesos corriendo en la máquina y $pstree para ver un árbol de los procesos. Correrlos en aragorn o algún linux recientemente instalado.
Funciones para obtener el identificador de un proceso desde código C:
#include <sys/types.h>
#include <unistd.h>
pid_t    getpid(void);    // retorna el identificador del proceso llamador
pid_t    getppid(void);    // retorna el identificador del proceso padre

uid_t    getuid(void);    retorna el identificador de usuario real. Es el correspondiente al  usuario que ejecuta el programa.
uid_t    geteuid(void);    retorna el identificador de usuario efectivo. Si el programa tiene permisos para fijar el ID del usuario al ejecutarse (ver chmod,  4°digito octal o permiso "s"), el programa puede correr con los permisos del "owner" independientemente de quien lo ejecute. Ver programa sudo (por ejemplo haga "which sudo" y luego vaya a ese directorio y haga ls -l, o ejecute ls -l `which sudo` ).

gid_t    getgid(void);    retorna el identificador de grupo real
gid_t    getegid(void);    retorna el identificador de grupo efectivo.  El grupo corresponde al del usuario que corre el programa cuando el permiso del programa indica que se fije el ID del grupo.
Usted puede otorgar los permisos del usuario o del grupo a quien corra sus programas usando el comando chmod con cuatro dígitos octales. El más significativo fija el bit de usuario efectivo. También se puede fijar usando permiso "s" e lugar de x, r, o w.

Función fork
    Esta es la "única" manera de crear un nuevo proceso en sistemas operativos derivados de UNIX. (excepciones PID 0, PID 1, PID 2)
Uso:

#include <sys/types.h>
#include <unistd.h>
pid_t    fork(void);

Retorna: 0 en hijo y el identificador de proceso hijo en el padre. -1 es retornado en caso de error.
La función es llamada una vez, por el proceso padre, pero retorna dos veces !!.
Ambos procesos siguen ejecutando la instrucción que sigue a la llamada fork. Ambos procesos se distinguen por el valor retornado.
El proceso hijo parte como una copia del proceso padre. (espacio de datos, heap, y stack)

Ejemplo de uso de la función fork.
Ejercicios: Cuál es la salida de
a)    % a.out
b)    % a.out > temp.out
% cat temp.out
El hijo hereda del padre:
User ID real y efectivo, group id real y efectivo.
Los bits set-user-id y set-group-id
Efecto en archivos: todos los descriptores de archivo son "duplicados",
luego los archivos abiertos se heredan abiertos hacia el hijo.

Diferencias entre padre e hijo: Valor retornado por folk, ID de proceso.
Los tiempos de usuario, sistema etc del hijo parten en cero (tms_utime, etc..)
El conjunto de alarmas (viene luego) pendientes en el padre no son pasadas al hijo.
El hijo parte sin alarmas pendientes. Los locks del padre tampoco son pasados.


Descriptores de archivo en proceso hijo
[tomado del texto guía: W. Richard S y Stephen A Rago, Advanced Programming in the UNIX Environment, 2° edición]

Usamos fork por dos razones: Para duplicar un proceso y cuando deseamos ejecutar un programa diferente (lo veremos más adelante).

Otra función a considerar es vfork, la cual es invocada en forma similar a fork, pero es más eficiente cuando deseamos ejecutar un programa diferente. Más adelante veremos "exec". Usted puede leer man vfork en aragorn.

Funciones de terminación
    Hay 5 formas en que un proceso puede terminar:
Terminación normal: Terminación anormal: La función exit retorna y efectúa limpieza (llama funciones de término previamente registradas con atexit(), etc) y luego retorna al kernel. La función _exit retorna al kernel inmediatamente.

#include <stdlib.h>
void exit( int status);

#include <unistd.h>
void _exit(init status);

#include <stdlib.h>
int atexit(void (*func) (void));  // retorna 0 si es OK,  distinto de 0 en otro caso.  

Terminación anormal: Esta ocurre  cuando se recibe una señal de término o al llamar  la función abort.
    Llamar a abort: esta función genera la señal SIGABRT, por lo tanto es un caso particular de terminar por una señal.
    Las señales pueden ser generadas por el mismo proceso, otro, o el kernel. Ejemplos: cuando referencias a memoria fuera de su espacio, división por cero, control-C, etc.

Cuando un proceso termina, su estatus de término (exit status) es pasado al proceso padre.
¿Qué pasa si el padre termina primero?
El proceso init se hace cargo de todos los hijos cuyos padres han terminado.

¿Qué pasa si el hijo termina primero?
El proceso padre debe esperar por el estatus de término del hijo. El kernel mantiene esta información hasta que el padre la solicita. Si no lo hace el proceso se convierte en un proceso " zombie ". Zombie es un proceso hijo terminado cuyo padre no ha leído su estatus de término.

Funciones wait y waitpid

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

pid_t wait (int *statloc);

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

¿Cómo podemos crear un hijo, sin esperar por su código de retorno y sin crear un proceso Zombie?
Respuesta

Función exec
    Es usada para ejecutar otro programa luego de haber creado un proceso hijo.
    Hay 7 formas de la función exec.
    Cuando una de éstas es llamada, el código completo es reemplazado por un nuevo programa (se mantiene la tabla de descriptores, etc)
    El nuevo programa parte en su función main.
    El nuevo programa no cambia el identificador de proceso.

    #include <unistd.h>
    int execl (const char *pathname, const char *arg0, ... , (char *) 0);
    int execv (const char *pathname, char * const argv[]);
    int execle (const char *pathname, const char *arg0, ... , (char *) 0, char * const envp[]);
    int execve (const char *pathname, char * const argv[], char * const envp[]);
    int execlp (const char *filename, const char *arg0, ... , (char *) 0);
    int execvp (const char *filename, char * const argv[]);
    int int execvpe(const char *filename, char *const argv[],  char *const envp[]);

l: Argument list,   v: vector de argumentos.
p: Usa path para ubicar ejecutable.
e: las variables de ambiente son pasadas como argumento.

    Valor retornado: -1 en caso de error, ¿Qué retorna en caso de éxito?

Ejemplos de función exec: Mi programa muta a un reloj (requiere acceso gráfico), Mi hijo muta a reloj y padre espera por término, mostrando efecto en variables de entorno.
Programa echoall (se requiere en último ejemplo)

Nota sobre los llamados al sistema en linux, ver $man syscalls. Los llamados al sistema generalmente no son invocados directamente sino a través de una función que los encapsula. Éste es el caso de las las distintas formas de función exec. De las listadas arriba la única que es llamado directo al sistema es execve.

Función system
    #include <stdlib.h>
    int system (const char * cmdstring);

Esta función es internamente implementada llamando a las funciones fork, exec, y waitpid.

Ejemplo de uso: para almacenar el tiempo y fecha en que parte del código fue ejecutado,
    :
    system("date > file");
    : 

No comviene su uso cuando hay llamados al sistema cuando se puede lograr lo mismo vía llamado a función (en este caso time(2)).