17 años en Internet

15 enero 2014

Be "nice", asignar prioridades en shells de Unix

     El estándar POSIX permite la definición de prioridades para que ciertos procesos tengan mayor o menor probabilidad de acceso al quantum de la CPU. En esta entrada no voy a entrar en explicar el funcionamiento de dicho estándar, pero a estas alturas queda claro que todos sabemos que si un procesador tiene uno, dos, cuatro o incluso ocho núcleos, pues resulta físicamente imposible que dicho procesador pueda procesar a la vez todos los procesos y aplicaciones que gestiona un sistema operativo.

A groso modo, podemos afirmar que realmente un sistema operativo no es multi proceso, si no que lo emula de forma bastante eficaz. Por un lado, nuestro núcleo realiza una lista con procesos que debe de procesar y durante un periodo de tiempo reducido (conocido como "quantum") ejecuta uno (o varios, si el procesador es multi hilo) dejando durmiendo al resto. Como es de esperar, al igual que en la justicia española no todos los procesos son iguales, siendo unos más importantes que otros y por consiguiente durante un mismo periodo de tiempo ciertos procesos tendrán mayor número de accesos a la CPU que otros.

Esto se puede observar fácilmente a través del comando "top", donde podemos apreciar distintos asignaciones en la columna PR (siendo la columna NI su inversa), con valores que en condiciones normales por lo general van desde el 39 (poco importante) al 0 (muy importante), siendo 20 la prioridad por defecto de todo nuevo proceso. Por lo general, el estándar dicta que un proceso puede tener prioridad del 1 al 32, menos para Linux, el cual puede definirse del 1 al 99 (1). No obstante estos valores pueden variar dependiendo de las distribuciones (el proyecto GNU recomienda hacer uso de las variable sched_get_priority_max si vas a programar algo que requiera modificar prioridades), además de que en la práctica se suele emplear el sistema del 0 (más prioritario) al 39 (menos prioritario).

La cuestión es que a nivel de programación, los lenguajes compatibles con POSIX traen las distintas herramientas necesarias para pelearte con este sistema de prioridades, pero si no queremos tocar código, siempre podemos hacer uso de nice y renice para modificar a placer nuestros propios scripts. El comando nice está pensado para crear una tarea nueva y asignarle una prioridad distinta a la que traiga por defecto, mientras que renice está pensado para cambiar la prioridad de un proceso ya existente a través de su PID, usuario o grupo.

La sintaxis básica de nice es bien simple, puesto que recibe dos únicos argumentos: un número entero que dicta la cantidad de prioridad a restar y la orden a ejecutar. Tengan en cuenta que un proceso normal tiene asignado valor 20. Es decir, que en caso de pasar los valores 5 o 10, estos se sumarán al 20, obteniendo prioridades de 25 y 30 respectivamente... o lo que es lo mismo, con ello hacemos que el proceso a ejecutar pase a ser menos prioritario. En caso de querer dar más prioridad, habrá que poner un valor negativo, o lo que es lo mismo, escribir "--" antes del número entero. Hay que destacar que un usuario puede hacer que su proceso tenga menor prioridad (un valor superior a 20), pero sólo los usuarios con permisos de administrador son capaces de asignar una prioridad inferior a 20. Aquí tenemos un par de ejemplos prácticos:

Comprimir un archivo tgz con 39 de prioridad (la más baja):
~$ nice -19 tar czvf tar.tgz tmp_folder/
Comprimir el mismo archivo tgz utilizando el máximo de CPU:
~$ sudo nice --20 tar czvf tar.tgz tmp_folder/
El primer caso da a entender que la compresión del fichero no es prioritaria, por lo que tendrán prioridad de acceso a CPU el resto de procesos del sistema. La tarea acabará haciéndose porque los núcleos Unix tienen algoritmos para evitar la "inanición", pero se resolverá en un tiempo mayor de lo que tardaría normalmente.

Mientras, en el segundo ejemplo pasa lo contrario, esta compresión pasa a ser uno de los procesos más importantes del sistema, por lo que en la práctica algunos recursos podrían mostrar ralentizaciones hasta que ésta acabe. Está claro que habrán cambios de contexto en el procesador y que se irán abordando y acabando otras tareas mientras ésta se esté realizando... pero ésta tendrá un mayor número de accesos a CPU que el resto durante el mismo periodo de tiempo. No obstante se recomienda no otorgar nunca una prioridad alta a un proceso pesado que vaya a consumir un número elevado de accesos a CPU, puesto que podría causar una sensación de bloqueo o cuelgue del sistema.

Para verificar esta teoría, vamos a proceder a ejecutar un script con tres tipos de prioridades: 20 (por defecto, 39 y 0. Concrétamente, el contenido del script será el siguiente:
#!/bin/bash
for i in `seq 1 10000`; do touch /tmp/lolo.txt; done
Prioridad 20 (proceso normal):
~$ time ./prueba.sh
real 0m20.507s
user 0m3.490s
sys 0m17.750s

Prioridad 39 (baja):
~$ time nice -19 ./prueba.sh
real 0m20.579s
user 0m3.377s
sys 0m18.005s

Prioridad 0 (alta)
~$ time sudo nice --20 ./prueba.sh
real 0m19.945s
user 0m3.894s
sys 0m16.743s

Como era de esperar, a mayor prioridad antes acaba realizándose nuestra tarea, puesto que esto garantiza un mayor número de accesos a CPU. Tras la ejecución de estas pruebas con "nice" queda aclarado que es posible asignar mayor o menor prioridad a nuevas tareas que queramos ejecutar, ¿pero qué pasa con las ya existentes?

Bueno, para ello podemos hacer uso del comando renice, el cual puede asignar cambios de prioridad a procesos a través de su PID (número identificador de proceso), del grupo o incluso del usuario al que pertenence. El funcionamiento es parecido a nice, por un lado indicaremos el incremento de la prioridad a través de su símbolo (esta vez haciendo uso de + y -, en vez de - y -- de nice), mientras que por otro pondremos el PID del proceso a alterar o bien definiremos el grupo o usuario al que pertenece dicho proceso. Hay que aclarar que pese a que el sistema "habla" de prioridades que van del 0 al 39, renice informa en escala que va del -20 (más prioritaria) al +19 (menos prioritaria). Es decir, para renice la prioridad inicial de un proceso no es 20, es 0.

Ejemplo 1, obtenemos el PID de nuestra shell actual y le damos la máxima prioridad:
~$ echo $$
15708 
~$ sudo renice -19 15708
15708 (ID de proceso) prioridad antigua 0, prioridad nueva -19
Ejemplo 2, incrementamos la prioridad para todos los procesos pertenecientes a los usuarios MySQL y Apache:
~$ renice -n -19 -u mysql
107 (user ID) old priority 0, new priority -19 
~$ renice -n -19 -u www-data
107 (user ID) old priority 0, new priority -19

1 comentario:

Si te ha gustado la entrada o consideras que algún dato es erróneo o símplemente deseas dar algún consejo, no dudes en dejar un comentario. Todo feedback es bienvenido siempre que sea respetuoso. También puedes contactarme vía Twitter @Hamster_ruso si lo consideras necesario.