Llamadas al sistema

Identificador de Procesos
Cada proceso tiene un identificador único (PID). Este 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
char *tmpnam(char *ptr);
la cual retorna un pathname único.
y
FILE *tmpfile(void);
Este archivo es automáticamente borrado o removido cuando es cerrado.
Algunos procesos importantes:
Scheduler -> Identificador 0
/sbin/init -> Identificador de proceso 1, Es el padre de todos los procesos. Su rol primario es crear procesos desde un script (archivo de comandos) ubicado en /etc/inittab.
Funciones para obtener el identificador de un proceso desde un código en C:
#include
#include
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 el bit setuid fijado, el programa puede correr con los permisos del "owner" independientemente de quien lo ejecute. Ver programa mail o ping (por ejemplo haga "which ping" y luego vaya a ese directorio y vea ls -l).

gid_t getgid(void); retorna el identificador de grupo real
gid_t getegid(void); retorna el identificador de grupo efectivo.

Función fork
Esta es la "única" manera de crear un nuevo proceso en UNIX. (excepciones PID 0, PID 1)
Uso:
#include
#include
pid_t fork(void);
Retorna: 0 en hijo y el identificador de 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 efectiva, group id real y efectiva.
Los flag set-user-id y set-group-id
Efecto en archivos: todos los descriptores de archivo son "duplicados". Padre e hijo comparten la tabla de entrada de archivos de cada descriptor abierto. Es decir, ambos ven el mismo puntero de escritura y lectura.

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 pendientes en el padre no son pasadas al hijo. El hijo parte sin alarmas pendientes. Los locks del padre tampoco son pasados.

Usamos fork por dos razones: Duplicar un proceso y para cuando deseamos ejecutar un programa diferente.

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. Ver exec más adelante y man vfork en aragorn.

Funciones de terminación
Hay 5 formas en que un proceso puede terminar:
Terminación normal:
retornar de la función main (equivale a llamar exit)
llamar exit
llamar _exit
Terminación anormal:
llamar abort
terminar por una señal (más adelante)
La función exit retorna 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
void exit( int status);

#include
void _exit(init status);

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

Terminación anormal:
Llamar a abort: esta función genera la señal SIGABRT, por lo tanto es un caso particular del siguiente caso.
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 su estatus de término. El kernel mantiene esta información hasta que el padre la solicita. Si no lo hace el proceso se convierte en un proceso " zombie ".

Funciones wait y waitpid
#include
#include
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 6 formas de la función exec.
Cuando una de ellas es llamada, el proceso completo es reemplazado por un nuevo programa.
El nuevo programa parte en su función main.
El nuevo programa no cambia el identificador de proceso.
#include
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[]);

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?

Ejemplo de función exec.
Programa echoall

Función system
#include
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 para lograr lo mismo, en este caso función time(2).