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:Algunos procesos importantes:
#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.
Scheduler -> Identificador 0Funciones para obtener el identificador de un proceso desde código C:
/sbin/init (caso aragorn) /bin/systemd (caso Ubuntu 16.04 LTS) -> 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 y algún linux recientemente instalado.
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:
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)
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. |
[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.
#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
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 é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[]);
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)
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)).