Curso Administración UNIX

Procesos.


Procesos

Se le llama proceso en Unix a un programa en ejecución y al objeto abstracto que crea el sistema operativo para manejar el acceso de ese programa a los recursos del sistema (memoria, CPU, dispositivos de E/S). Pueden coexistir varias instancias de un mismo programa ejecutando en forma simultánea. Cada una de ellas es un proceso diferente.
Unix es un sistema multiproceso por tiempo compartido. A los ojos de un usuario en un momento dado hay múltiples programas en ejecución, cada uno de ellos avanzando en su tarea. Sin embargo en una máquina con un solo procesador hay en cada instante solamente un proceso ejecutando. Es el sistema operativo el que va rotando el uso del procesador a intervalos breves (alguna decena de milisegundos) entre los procesos definidos en el sistema de forma que se crea la ilusión que todos avanzan simultáneamente.
El administrador del sistema dispone de herramientas para supervisar el estado de los procesos y eventualmente tomar acciones para suspender o detener la ejecución de un proceso o simplemente modificar su comportamiento.

Información almacenada por el kernel

Para cada proceso definido en el sistema, el kernel del sistema operativo almacena y mantiene al día varios tipos de información sobre el proceso. Esta información podemos ordenarla de la siguiente forma:
* Información general. Identificadores de proceso, usuario y grupo
* Ambiente (variables, directorio actual, etc.)
* Información de E/S
* Información de Estado
* Espacio de direcciones del proceso. Areas de trabajo (código ("text"), datos, stack)

IDentificadores

Process ID (PID)

Al crearse un nuevo proceso se le asigna un identificador de proceso único. Este número debe utilizarse por el administrador para referirse a un proceso dado al ejecutar un comando.
Los PID son asignados por el sistema a cada nuevo proceso en orden creciente comenzando desde cero. Si antes de un reboot del sistema se llega al nro. máximo, se vuelve a comenzar desde cero, salteando los procesos que aún estén activos.

Parent Process ID (PPID)

La creación de nuevos procesos en Unix se realiza por la vía de duplicar un proceso existente invocando al comando fork(). Al proceso original se le llama "padre" y al nuevo proceso "hijo". El PPID de un proceso es el PID de su proceso padre.
El mecanismo de creación de nuevos procesos en Unix con el comando fork() se ve con más detalle en el apartado "Ciclo de vida de un proceso".

UID y EUID

Normalmente estos dos identificadores coinciden pero hay excepciones.
El User ID (UID) del proceso identifica al creador del proceso, esto es a la persona que lo lanzó a correr. Este usuario y root son los únicos que pueden modificar al proceso. El UID se utiliza con fines de tarificación o accounting. El sistema de accounting carga a la cuenta del usuario identificado por el UID del proceso por los recursos del sistema que el proceso utilice (tiempo de CPU, impresoras, terminales, etc.).
El Effective User ID (EUID) en cambio se utiliza para determinar si el proceso tiene permiso para acceder a archivos y otros recursos del sistema.
La forma más habitual de hacer que el EUID de un proceso sea el de un usuario diferente del que lanza a correr el programa es activando el flag setuid en el archivo del programa. Un ejemplo de esto son los comandos que permiten a un usuario modificar su password, en que se debe modificar el archivo passwd o equivalente del sistema sobre el cual el usuario obviamente no tiene permiso de escritura. Habitualmente ese comando es un archivo de root con setuid y el proceso corre con EUID de root.

GID y EGID

Es totalmente análogo a los identificadores de usuario pero para grupos de usuarios. El GID se hereda del proceso padre. El EGID puede utilizarse igual que el EUID para controlar el acceso del proceso a archivos.
En la mayoría de los sabores actuales de Unix el proceso puede estar en varios grupos y se chequea contra toda la lista de grupos para definir si el proceso puede acceder o no a un recurso.

Información de ambiente

Directorio actual

El proceso mantiene actualizado cual es su directorio de trabajo.

Variables de ambiente globales.

Son heredadas por los procesos hijos.

Variables de ambiente locales.

Solamente existen en el proceso que las define.

Terminal de control.

En general los procesos están asociados a una terminal de control. Esta terminal determina el valor por defecto de los archivos stdin, stdout y stderr del proceso.
Una excepción a esto son los procesos llamados daemons, que una vez lanzados se desvinculan de su terminal de control y siguen ejecutando inclusive después de cerrada la sesión de usuario desde la cual se lanzaron a correr.

Información de E/S

El kernel mantiene descriptores de los archivos abiertos por el proceso.
Están siempre definidos los archivos de entrada, salida y error estándar (stdin, stdout y stderr). Por defecto están asociados con el teclado y la pantalla del terminal de control del proceso pero pueden ser redireccionados a un archivo cualquiera. Todos los shells prevén un mecanismo para hacer este redireccionamiento en el momento de lanzar a correr un programa.

Espacio de direcciones virtual

En la mayoría de los sistemas multiproceso como Unix, cada proceso tiene la ilusión de disponer para si el espacio de direcciones completo del procesador. En realidad el procesador ve un espacio de direcciones virtual. Este espacio está organizado en secciones para el código (text), datos, stack y otras y generalmente está dividido en páginas. En un instante dado una página puede estar residiendo en la memoria física del procesador o puede estar almacenada en disco en un procedimiento llamado "swapping". El sistema operativo, con el auxilio del hardware, mantiene al día una tabla con el estado de cada página de memoria del proceso.

Estado de un proceso

Los estados básicos en los que puede estar un proceso son los siguientes:
* Durmiendo (asleep). En general a la espera de algún recurso compartido.
* Listo para ejecutar (runnable). A la espera que le toque el turno en el uso de la CPU.
* Ejecutando (running). Puede estar ejecutando en modo kernel o en modo usuario.
A su vez el proceso (o partes del espacio de memoria virtual del proceso) puede estar cargado en memoria o "swapped" a disco.
estados
Además de estos estados básicos un proceso puede estar detenido (stopped). En este caso se le prohibe ejecutar al proceso. Hay mecanismos para detener y rearrancar un proceso a través de las señales STOP y CONT que veremos más adelante.
El estado stopped es el estado en que queda un proceso lanzado a correr desde un interprete de comandos (shell) cuando se presiona <Control-Z> o la tecla configurada como "suspend" en el terminal que estemos utilizando.
Finalmente el otro estado en que a menudo un administrador encuentra a un proceso es el estado zombie o exiting. Un proceso en este estado está en proceso de terminación. Este caso se discute más en detalle en el apartado "Ciclo de vida de un proceso".

Ciclo de vida de un proceso

El mecanismo de creación de un proceso en Unix es un poco peculiar. Un proceso se crea invocando a una función del sistema operativo llamada fork(). La función fork() crea una copia idéntica del proceso que la invoca con excepción de:
* El nuevo proceso tiene un PID diferente
* El PPID del nuevo proceso es el PID del proceso original
* Se reinicia la información de tarificación del proceso (uso de CPU, etc.)
Al retorno de fork() se siguen ejecutando las siguientes sentencias del programa en forma concurrente. Para distinguir entre los dos procesos la función fork() devuelve un cero al proceso hijo y el PID del nuevo proceso al proceso padre. Normalmente el proceso hijo lanza luego un nuevo programa ejecutando alguna variante de comando exec(). En el recuadro puede verse un ejemplo del uso de fork.


kidpid = fork()
if (kidpid==0) {
    /* soy el hijo, p. ej. lanzo un nuevo prog. */
       exec(...)
   }
   else{
      /* soy el padre */
        ...
        wait()         /* espero exit() del hijo */
   }

Si este es el mecanismo para crear un proceso, entonces ¿quién lanza a correr el primer proceso? Luego del boot del sistema el kernel instala y deja corriendo un proceso llamado init con PID=1. Una de las funciones principales de init es lanzar mediante fork() intérpretes de comandos que a su vez lanzarán los scripts de inicialización del sistema y los procesos de los usuarios. Además de init el kernel lanza algunos procesos más cuyo nombre y función varía en los diferentes sabores de Unix. A excepción de estos procesos lanzados por el kernel al inicio, todos los demás son descendientes de init.
Normalmente un proceso termina invocando a la función exit() pasando como parámetro un código de salida o exit code. El destinatario de ese código de salida es el proceso padre. El proceso padre puede esperar la terminación de su proceso hijo invocando la función wait(). Esta función manda al padre a dormir hasta que el hijo ejecute su exit() y devuelve el exit code del proceso hijo.
Cuando el proceso hijo termina antes que el padre, el kernel debe conservar el valor del exit code para pasarlo al padre cuando ejecute wait(). En esta situación se dice que el proceso hijo está en el estado zombie. El kernel devuelve todas las áreas de memoria solicitadas por el proceso pero debe mantener alguna información sobre el proceso (al menos su PID y el exit code).
Cuando el proceso padre termina primero el kernel encarga a init la tarea de ejecutar el wait() necesario para terminar todo en forma ordenada. A menudo init falla en esta función y suelen quedar procesos en estado zombie hasta un nuevo reboot. Dado que un proceso zombie no consume recursos fuera de su PID, esto por lo general no provoca problemas.

Threads

Cambio de contexto entre procesos:
* código ("text")
* datos
* stack
* tablas varias
Varias secuencias de ejecución (threads) pueden agruparse en un proceso y compartir algunos segmentos
Ventajas:
* simplifica cambio de contexto y comunicación
* facilita el uso de múltiples procesadores
Soportado por la mayoría de los SO actuales (Digital Unix, DEC OSF/1, Solaris 2, AIX, HP-UX, SGI Irix, Linux)
Posix standard
Bibliotecas provistas por terceros

Señales (signals)

Las señales de Unix son un mecanismo para anunciar a un proceso que ha sucedido cierto evento que debe ser atendido. La lista de posibles señales a comunicar a los procesos está fija, con algunas variaciones de un sabor a otro de Unix.
La recepción de una señal en particular por parte de un proceso provoca que se ejecute una subrutina encargada de atenderla. A esa rutina se le llama el "manejador" de la señal (signal handler). Un proceso puede definir un manejador diferente para sus señales o dejar que el kernel tome las acciones predeterminadas para cada señal.
Cuando un proceso define un manejador para cierta señal se dice que "captura" (catch) esa señal.
Si se desea evitar que determinada señal sea recibida por un proceso se puede solicitar que dicha señal sea ignorada o bloqueada. Una señal ignorada simplemente se descarta sin ningún efecto posterior. Cuando alguien envía a cierto proceso una señal que está bloqueada la solicitud se mantiene encolada hasta que esa señal es explícitamente desbloqueada para ese proceso. Cuando la señal es desbloqueada la rutina de manejo de la señal es invocada una sola vez aunque la señal haya sido recibida más de una vez mientras estaba bloqueada.
Si bien un proceso tiene ciertas libertades para configurar como reacciona frente a una señal (capturando, bloqueando o ignorando la señal), el kernel se reserva ciertos derechos sobre algunas señales. Así, las señales llamadas KILL y STOP no pueden ser capturadas, ni bloqueadas, ni ignoradas y la señal CONT no puede ser bloqueada.
La señal KILL provoca la terminación de un proceso. La señal STOP provoca la detención del proceso que queda en el estado "stopped" hasta que alguien le envíe la señal CONT.
Una señal puede enviarse desde un programa utilizando llamadas al sistema operativo, o desde la línea de comandos de un shell utilizando el comando kill. Al comando kill se le pasa como parámetro el número o nombre de la señal y el PID del proceso. El uso más habitual del comando es para enviar una señal TERM o KILL para terminar un proceso, de ahí su nombre.
Para muchas de las señales la acción predeterminada consiste en terminar el proceso. En algunos casos se genera además un core dump. Un core dump es un archivo con una imagen del estado del sistema que permite al desarrollador de un programa diagnosticar problemas con la ayuda de un debugger. Al usuario final de un programa esta imagen rara vez le sirve de ayuda.
La lista de posibles señales puede obtenerse para cada sistema a través del man del comando kill o de la función kill() del sistema operativo. En la tabla se listan las utilizadas más a menudo por un administrador.

ID

Nombre

Uso habitual

1

SIGHUP

Usualmente para releer configuración

9

SIGKILL

El kernel destruye el proceso

15

SIGTERM

Terminación "elegante", en general termina enviándose un KILLL a sí mismo


SIGSTOP

El kernel pasa el proceso a stopped


SIGCONT



SIGUSR

Definidas por el usuario o más bien por quien programó el proceso

Prioridad de ejecución y valor "nice"

Cuando hay más de un proceso en el estado "listo para ejecutar", el kernel le asigna el uso de la CPU al de mayor prioridad en ese momento. En el caso de Unix esta prioridad varía dinámicamente. Las diferentes versiones y sabores de Unix utilizan diferentes algoritmos de planificación del uso de la CPU (algoritmos de scheduling), pero en todos los casos tienen características similares:
- procuran ser justos con los diferentes procesos
- procuran dar buena respuesta a programas interactivos
Para eso los algoritmos consideran parámetros como cuanto uso de CPU ha hecho el proceso recientemente, si pasa mucho tiempo dormido a la espera de un evento de teclado (sería un proceso interactivo), etc..
El administrador del sistema o el usuario dueño de un proceso pueden influir en el algoritmo de scheduling a través del llamado valor nice. Este es un número que se asigna a cada proceso e indica que tan "nice" es el proceso para con los demás.
Este valor es considerado por el algoritmo de scheduling de manera que un proceso con valor nice alto estará en desventaja frente a otro con valor nice menor a la hora de decidir a quien asignar la CPU. Como ejemplo veamos el algoritmo utilizado por alguna versión de AIX:
P = min + nice + (0.5 x recent)
Donde P indica la prioridad dinámica (a menor P mayor prioridad) y recent es una medida de cuanto ha recibido la CPU el proceso recientemente. Recent se calcula de la siguiente forma:
* Inicialmente vale 0
* Al final de cada time slice (aprox. 10 milisegundos) recent se incrementa en 1 para el proceso que está usando la CPU
* Una vez por segundo se divide por dos el valor recent para todos los procesos
Normalmente el valor nice se hereda del proceso padre. El dueño del proceso o el propio proceso pueden elevar su valor nice (menor prioridad). El superusuario puede modificar el valor nice de todos los procesos a gusto.
En los sistemas "a la BSD" el valor del número nice puede variar entre -20 y +20, siendo por defecto 0.
En System V en cambio los valores posibles van de 0 a 39, siendo 20 el valor por defecto.
Se puede modificar el valor nice por defecto en el momento de lanzar un programa lanzándolo a correr con el comando nice, o posteriormente utilizando el comando renice.

Supervisión de procesos

Averiguando qué pasa

Comando ps (process status)

La herramienta básica para diagnosticar problemas relacionados con procesos es el comando ps. Este comando genera un reporte de un renglón por cada proceso, brindando abundante información sobre cada uno.
El comando ps en los dos sabores básicos de Unix (BSD y System V) difiere en el nombre y función de los parámetros, en la información que brindan sobre cada proceso y en los criterios de ordenación de los procesos.
En ambos casos si ejecuto ps sin parámetros solamente se listará información básica sobre los procesos que pertenecen a mi usuario. A través del uso de parámetros adecuados puedo agregar más columnas (más información sobre cada proceso) o más filas (más procesos) al reporte. Un conjunto de parámetros que permite ver todos los procesos con un grado de detalle que en general es adecuado es:
ps -uax (BSD)
ps -elf (System V)
Los campos de información más importantes desplegados por ps para cada proceso son:
* Usuario (USER)
* Identificadores de proceso (PID, PPID)
* Uso de recursos reciente y acumulado (%CPU, %MEM, TIME)
* Estado del proceso (STAT, S)
* comando invocado (COMMAND)

Comando top

El comando top actualiza un reporte similar al generado por ps a intervalos periódicos. Normalmente top ordena los procesos por uso de CPU en forma decreciente. Permite además de manera amigable invocar los comandos kill y renice sobre el proceso.

Comando pstree

En algunos sistemas está disponible el comando pstree, que lista los procesos y sus descendientes en forma de árbol. Esto permite visualizar rápidamente los procesos que están corriendo en el sistema.

Carga del sistema reportada por w o uptime

Los comandos w (who) y uptime reportan tres números que indican la carga promedio del sistema en un intervalo de 1, 5 y 15 minutos respectivamente. Más precisamente el parámetro reportado es el promedio de la cantidad de procesos listos para correr. Este valor es un indicador rápido de la actividad del sistema y suele vigilarse para detectar sobrecargas en el mismo, o registrarse periódicamente para un análisis posterior en caso de problemas. La relación entre este número y que tan "pesado" se vuelve el sistema para los usuarios varía fuertemente dependiendo de las características del sistema (de la cantidad de procesadores p. ej.) y de los procesos que se están ejecutando.

Comando vmstat (virtual memory stats)

El comando vmstat reporta varias estadísticas que mantiene el kernel sobre los procesos, la memoria y otros recursos del sistema. Se puede tomar una instantánea de esas estadísticas o repetirlo cierta cantidad de veces a intervalos de algunos segundos.
Alguna de la información reportada por vmstat es la siguiente:
* Cantidad de procesos en diferentes estados (listo para correr, bloqueado, "swapeado" a disco.
* Valores totales de memoria asignada a procesos y libre.
* Estadísticas sobre paginado de memoria (page faults, paginas llevadas o traídas a disco, páginas liberadas)
* Operaciones de disco
* Uso de CPU reciente clasificado en inactivo, ejecutando en modo usuario y ejecutando en modo kernel

Tomando acciones

A menudo, por algún bug en el programa o por algún error de operación, los procesos no terminan correctamente y es necesario terminarlos por algún método más violento. El procedimiento usual en estos casos es obtener el PID del proceso con la ayuda de ps y luego terminarlo con el comando kill. Enseñarle a los usuarios a hacer estas operaciones con sus procesos colgados es una muy buena inversión de tiempo para un administrador de sistema.
También suele suceder que algún proceso quede fuera de control y comience a acaparar algún recurso del sistema (memoria, disco o CPU). Esto puede suceder por un error de programación, por un error de configuración, por intentar correr algún proceso que necesita más recursos que los disponibles o directamente por mala intención.
Sea cual sea el caso se hace necesario que el dueño del proceso o el administrador del sistema tomen medidas para frenar o terminar a ese proceso.
En general el primer síntoma es "el sistema está muy pesado" (o el teléfono sonando con reclamos de los usuarios). El primer paso será identificar a el o los procesos problemáticos utilizando las herramientas ya vistas. Si la situación es tan grave que dificulta la operación del administrador, puede ser recomendable lanzar un shell de root con prioridad alta utilizando el comando nice.
Si no está claro por qué el proceso puede estar acaparando recursos se puede intentar detenerlo con la señal STOP hasta ubicar al usuario dueño del proceso. Una vez ubicado al usuario si se considera necesario que el proceso continúe se le puede bajar la prioridad de ejecución con el comando renice.
Las siguientes opciones son o bien pedirle al proceso que tenga a bien terminar con la señal TERM (kill -15 PID) o bien terminarlo por la fuerza con la señal KILL (kill -9 PID)

Ejercicios e investigación

* Use el comando ps y analice la información obtenida. Experimente las diferentes opciones de ps en su sistema.
* Experimente el comando kill. Ejecute comandos en background para tener "materia prima" con qué jugar. Utilice ps junto con grep para averiguar el PID
* Consulte el man para saber como reacciona algún proceso daemon (p. Ej. named) frente a diferentes signals

Bibliografía y Referencias

* man page de:
* ps, top, kill, nice, renice, vmstat
* Nemeth, "UNIX System Administration Handbook", cap 5. El libro rojo/violeta
* Frisch, "Essential Unix Administration", cap. 7. El libro "de la mulita"


Julio Pérez julio@iie.edu.uy
Instituto de Ingeniería Eléctrica - Facultad de Ingeniería - Montevideo, Uruguay.