
El clásico escribió que las horas felices no se observan. En esos tiempos salvajes, no había programadores ni Unix, pero hoy en día los programadores lo saben muy bien: en lugar de ellos, cron seguirá el tiempo.
Las utilidades de línea de comando para mí son tanto debilidad como de rutina. sed, awk, wc, cut y otros programas antiguos se ejecutan diariamente mediante scripts en nuestros servidores. Muchos de ellos están diseñados como tareas para cron, un planificador de los años 70.
Durante mucho tiempo usé cron superficialmente, sin entrar en detalles, pero una vez, al encontrar un error al ejecutar el script, decidí resolverlo a fondo. Así que este artículo apareció, al escribir el que conocí con POSIX crontab, las principales variantes cron en las distribuciones populares de Linux y el dispositivo de algunas de ellas.
¿Usando Linux y ejecutando tareas en cron? ¿Interesado en la arquitectura de aplicaciones del sistema Unix? Entonces estamos en camino!
Contenido
Origen de las especies
La ejecución periódica de programas de usuario o sistema es una necesidad obvia para todos los sistemas operativos. Por lo tanto, la necesidad de servicios que permitan la planificación centralizada y la ejecución de tareas, los programadores se han dado cuenta desde hace mucho tiempo.
Los sistemas operativos tipo Unix derivan su pedigrí de la Versión 7 Unix, desarrollada en la década de 1970 por Bell Labs, incluido el famoso Ken Thompson. Junto con la Versión 7 de Unix, también se suministró cron, un servicio para la ejecución regular de tareas de superusuario.
Un cron moderno típico es un programa simple, pero el algoritmo de la versión original era aún más simple: el servicio se despertaba una vez por minuto, leía la placa de tareas de un solo archivo (/ etc / lib / crontab) y realizaba para el superusuario las tareas que deberían realizarse en el minuto actual .
Posteriormente, las opciones avanzadas para un servicio simple y útil llegaron con todos los sistemas operativos tipo Unix.
Las descripciones generalizadas del formato crontab y los principios básicos de la utilidad en 1992 se incluyeron en el estándar principal de los sistemas operativos tipo Unix, POSIX, y, por lo tanto, cron del estándar de facto se convirtió en el estándar de jure.
En 1987, Paul Vixie, después de entrevistar a los usuarios de Unix para obtener sugerencias para cron, lanzó otra versión del demonio que soluciona algunos de los problemas del cron tradicional y extiende la sintaxis de los archivos de tabla.
En la tercera versión, Vixie cron comenzó a cumplir con los requisitos de POSIX, además, el programa tenía una licencia liberal, o mejor dicho, no había ninguna licencia, excepto por los deseos en README: el autor no da garantías, no puede eliminar el nombre del autor y solo puede vender el programa con código fuente Estos requisitos resultaron ser compatibles con los principios del software libre, que estaba ganando popularidad en esos años, por lo que algunas de las distribuciones clave de Linux que aparecieron a principios de los 90 tomaron Vixie cron como una distribución del sistema y todavía lo están desarrollando.
En particular, Red Hat y SUSE están desarrollando Vixie cron - cronie fork, mientras que Debian y Ubuntu están usando el cron original de Vixie con muchos parches.
Primero, conozcamos la utilidad crontab definida por el usuario descrita en POSIX, después de lo cual analizaremos las extensiones de sintaxis introducidas en Vixie cron y el uso de variaciones de Vixie cron en distribuciones populares de Linux. Y finalmente, la guinda del pastel es un análisis del dispositivo cron daemon.
Posix crontab
Si el cron original siempre funcionó para el superusuario, los programadores modernos a menudo se ocupan de las tareas de los usuarios comunes, lo que es más seguro y conveniente.
Los Cron se envían con un conjunto de dos programas: el demonio cron que se ejecuta constantemente y la utilidad crontab disponible para los usuarios. Este último le permite editar tablas de tareas específicas para cada usuario en el sistema, mientras que el demonio inicia tareas desde las tablas de usuario y del sistema.
El estándar POSIX no describe el comportamiento del demonio, y solo se formaliza el programa de usuario crontab . La existencia de mecanismos para iniciar las tareas del usuario, por supuesto, está implícita, pero no se describe en detalle.
Hay cuatro cosas que puede hacer con la utilidad crontab: editar la tabla de tareas del usuario en el editor, cargar la tabla desde el archivo, mostrar la tabla de tareas actual y borrar la tabla de tareas. Ejemplos de la utilidad crontab:
crontab -e # crontab -l # crontab -r # crontab path/to/file.crontab #
Al llamar a crontab -e
, se utilizará el editor especificado en la EDITOR
entorno EDITOR
estándar.
Las tareas mismas se describen en el siguiente formato:
# - # # , * * * * * /path/to/exec -a -b -c # , 10- 10 * * * * /path/to/exec -a -b -c # , 10- 10 2 * * * /path/to/exec -a -b -c > /tmp/cron-job-output.log
Los primeros cinco campos de registro: minutos [1..60], horas [0..23], días del mes [1..31], meses [1..12], días de la semana [0..6], donde 0 - domingo El último, sexto campo, es una cadena que será ejecutada por el intérprete de comandos estándar.
En los primeros cinco campos, los valores se pueden enumerar con una coma:
# , 1,10 * * * * /path/to/exec -a -b -c
O a través de un guión:
# , 0-9 * * * * /path/to/exec -a -b -c
El acceso de los usuarios a la programación de tareas está regulado en los archivos POSIX cron.allow y cron.deny que enumeran, respectivamente, los usuarios con acceso a crontab y los usuarios sin acceso al programa. El estándar no regula la ubicación de estos archivos.
Los programas en ejecución, de acuerdo con el estándar, deben pasar al menos cuatro variables de entorno:
- HOME es el directorio de inicio del usuario.
- LOGNAME: inicio de sesión del usuario.
- RUTA es el camino por el cual encontrar las utilidades del sistema estándar.
- SHELL es el camino hacia el shell utilizado.
Es de destacar que POSIX no dice nada acerca de dónde provienen los valores para estas variables.
Bestseller - Vixie cron 3.0pl1
El antecesor común de las variantes cron populares es Vixie cron 3.0pl1, presentado en la lista de correo comp.sources.unix de 1992. Las características principales de esta versión las consideraremos con más detalle.
Vixie cron viene en dos programas (cron y crontab). Como de costumbre, el daemon es responsable de leer e iniciar tareas desde la tabla de tareas del sistema y las tablas de tareas de usuarios individuales, y la utilidad crontab es responsable de editar las tablas de usuarios.
Tabla de tareas y archivos de configuración
La tabla de tareas del superusuario se encuentra en / etc / crontab. La sintaxis de la tabla del sistema corresponde a la sintaxis de Vixie cron, ajustada por el hecho de que la sexta columna indica el nombre del usuario en nombre de quién se inicia la tarea:
# vlad * * * * * vlad /path/to/exec
Las tablas de tareas de usuario comunes se encuentran en / var / cron / tabs / username y usan una sintaxis común. Cuando se inicia la utilidad crontab, estos archivos se editan en nombre del usuario.
Las listas de usuarios con acceso a crontab se administran en los archivos / var / cron / allow y / var / cron / deny, donde es suficiente agregar el nombre de usuario como una línea separada.
Sintaxis Extendida
En comparación con el crontab POSIX, la solución de Paul Vixie contiene varias modificaciones muy útiles a la sintaxis de la tabla de tareas de la utilidad.
Se ha puesto a disposición una nueva sintaxis de tabla: por ejemplo, puede especificar los días de la semana o los meses por nombre (lunes, martes, etc.):
# * * * Jan Mon,Tue /path/to/exec
Puede especificar el paso a través del cual se inician las tareas:
# */2 * * * Mon,Tue /path/to/exec
Los pasos e intervalos se pueden mezclar:
# 0-10/2 * * * * /path/to/exec
Se admiten alternativas intuitivas a la sintaxis regular (reinicio, anual, anual, mensual, semanal, diario, medianoche, por hora):
# @reboot /exec/on/reboot # @daily /exec/daily # @hourly /exec/daily
Entorno de ejecución de tareas
Vixie cron le permite cambiar el entorno de las aplicaciones en ejecución.
Las variables de entorno USER, LOGNAME y HOME no solo las proporciona el daemon, sino que se toman del archivo passwd . La variable PATH obtiene el valor "/ usr / bin: / bin", y SHELL obtiene el valor "/ bin / sh". Los valores de todas las variables excepto LOGNAME se pueden cambiar en las tablas de usuario.
El mismo cron utiliza algunas variables de entorno (principalmente SHELL y HOME) para ejecutar la tarea. Así es como se vería usar bash en lugar de sh estándar para ejecutar tareas personalizadas:
SHELL=/bin/bash HOME=/tmp/ # exec bash- /tmp/ * * * * * /path/to/exec
En última instancia, todas las variables de entorno definidas en la tabla (utilizadas por cron o necesarias para el proceso) se transferirán a la tarea en ejecución.
La utilidad crontab utiliza el editor especificado en la variable de entorno VISUAL o EDITOR para editar archivos. Si estas variables no están definidas en el entorno donde se lanzó crontab, entonces se usa "/ usr / ucb / vi" (ucb es probablemente la Universidad de California, Berkeley).
cron en Debian y Ubuntu
Los desarrolladores de Debian y derivados han lanzado una versión altamente modificada de Vixie cron versión 3.0pl1. No hay diferencias en la sintaxis de los archivos de tabla; para los usuarios, este es el mismo cron de Vixie. Nuevas características más importantes: syslog , SELinux y soporte PAM .
De los cambios menos notables, pero tangibles: la ubicación de los archivos de configuración y las tablas de tareas.
Las tablas de usuarios en Debian se encuentran en el directorio / var / spool / cron / crontabs, la tabla del sistema todavía está en / etc / crontab. Las tablas de tareas específicas de Debian se colocan en /etc/cron.d, desde donde el cron daemon las lee automáticamente. El control de acceso del usuario está regulado por los archivos /etc/cron.allow y /etc/cron.deny.
El shell / bin / sh predeterminado todavía se usa como el shell predeterminado. Debian reproduce un pequeño shell de tablero compatible con POSIX que se ejecuta sin leer ninguna configuración (en modo no interactivo).
Cron en las últimas versiones de Debian se inicia a través de systemd, y la configuración de inicio se puede ver en /lib/systemd/system/cron.service. No hay nada especial en la configuración del servicio; cualquier gestión de tareas más fina se puede hacer a través de variables de entorno declaradas directamente en el crontab de cada usuario.
cronie en RedHat, Fedora y CentOS
cronie - fork de Vixie cron versión 4.1. Al igual que en Debian, la sintaxis no cambió, pero se agregó soporte para PAM y SELinux, trabajando en un clúster, rastreando archivos usando inotify y otras características.
La configuración predeterminada está en los lugares habituales: la tabla del sistema está en / etc / crontab, los paquetes colocan sus tablas en /etc/cron.d, las tablas de usuario se encuentran en / var / spool / cron / crontabs.
El daemon se ejecuta en systemd, la configuración del servicio es /lib/systemd/system/crond.service.
En el inicio, las distribuciones similares a Red Hat usan / bin / sh de manera predeterminada, cuya función es bash estándar. Cabe señalar que cuando se ejecutan tareas cron a través de / bin / sh, el shell bash se inicia en modo compatible con POSIX y no lee ninguna configuración adicional cuando se opera en modo no interactivo.
compinche en SLES y openSUSE
La distribución alemana SLES y su derivada openSUSE usan el mismo compinche. El demonio aquí también se ejecuta en systemd, la configuración del servicio está en /usr/lib/systemd/system/cron.service. Configuración: / etc / crontab, /etc/cron.d, / var / spool / cron / tabs. Como / bin / sh actúa el mismo bash, lanzado en modo no interactivo compatible con POSIX.
Dispositivo cron Vixie
Los descendientes modernos de cron no han cambiado radicalmente en comparación con Vixie cron, pero sin embargo, han adquirido nuevas capacidades que no se requieren para comprender los principios del programa. Muchas de estas extensiones son desordenadas y confunden el código. Es un placer leer el código fuente original de cron de Paul Vixie.
Por lo tanto, decidí analizar el dispositivo cron utilizando el ejemplo de un programa común para ambas ramas del desarrollo cron: Vixie cron 3.0pl1. Simplificaré los ejemplos eliminando ifdefs que complican la lectura y omitiendo los detalles secundarios.
El trabajo del demonio se puede dividir en varias etapas:
- Inicialización del programa.
- Recopile y actualice la lista de tareas para ejecutar.
- La operación principal del ciclo cron.
- Lanzamiento de tarea.
Vamos a ordenarlos en orden.
Inicialización
Cuando se inicia, después de verificar los argumentos del proceso, cron instala los manejadores de señal SIGCHLD y SIGHUP. El primero registra la finalización del proceso secundario, el segundo cierra el descriptor de archivo del archivo de registro:
signal(SIGCHLD, sigchld_handler); signal(SIGHUP, sighup_handler);
El demonio cron en el sistema siempre funciona solo, solo como superusuario y desde el directorio principal cron. Las siguientes llamadas crean un bloqueo de archivo con el PID del proceso del daemon, asegúrese de que el usuario sea correcto y cambie el directorio actual al directorio principal:
acquire_daemonlock(0); set_cron_uid(); set_cron_cwd();
Se establece la ruta predeterminada, que se utilizará al iniciar los procesos:
setenv("PATH", _PATH_DEFPATH, 1);
Luego, el proceso se "demoniza": crea una copia secundaria del proceso llamando a fork y una nueva sesión en el proceso secundario (llamando a setsid). Ya no hay necesidad del proceso padre, y completa el trabajo:
switch (fork()) { case -1: exit(0); break; case 0: (void) setsid(); break; default: _exit(0); }
La finalización del proceso principal libera el bloqueo en el archivo de bloqueo. Además, debe actualizar el PID en el archivo para el niño. Después de eso, se llena la base de datos de tareas:
acquire_daemonlock(0); database.head = NULL; database.tail = NULL; database.mtime = (time_t) 0; load_database(&database);
Más cron procede al ciclo de trabajo principal. Pero antes de eso, eche un vistazo a cargar la lista de tareas.
Recopilar y actualizar la lista de tareas
La función load_database es responsable de cargar la lista de tareas. Comprueba el crontab del sistema principal y el directorio con archivos de usuario. Si los archivos y el directorio no han cambiado, la lista de tareas no se vuelve a leer. De lo contrario, comienza a formarse una nueva lista de tareas.
Descargar un archivo del sistema con nombres especiales de archivos y tablas:
if (syscron_stat.st_mtime) { process_crontab("root", "*system*", SYSCRONTAB, &syscron_stat, &new_db, old_db); }
Carga de tablas de usuario en un bucle:
while (NULL != (dp = readdir(dir))) { char fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1]; if (dp->d_name[0] == '.') continue; (void) strcpy(fname, dp->d_name); sprintf(tabname, CRON_TAB(fname)); process_crontab(fname, fname, tabname, &statbuf, &new_db, old_db); }
Luego, la base de datos anterior se reemplaza por una nueva.
En los ejemplos anteriores, llamar a la función process_crontab asegura que exista el usuario que coincida con el nombre del archivo de la tabla (a menos que sea el superusuario) y luego llama a load_user. Este último ya lee el archivo en sí línea por línea:
while ((status = load_env(envstr, file)) >= OK) { switch (status) { case ERR: free_user(u); u = NULL; goto done; case FALSE: e = load_entry(file, NULL, pw, envp); if (e) { e->next = u->crontab; u->crontab = e; } break; case TRUE: envp = env_set(envp, envstr); break; } }
Aquí, las funciones load_env / env_set establecen la variable de entorno (líneas de la forma VAR = valor), o la función load_entry lee la descripción de la tarea (* * * * * / path / to / exec).
La entidad de entrada devuelta por load_entry es nuestra tarea ubicada en la lista general de tareas. En la función en sí, se realiza un largo análisis del formato de tiempo, pero estamos más interesados en la formación de variables de entorno y parámetros de inicio de tareas:
e->uid = pw->pw_uid; e->gid = pw->pw_gid; e->envp = env_copy(envp); if (!env_get("SHELL", e->envp)) { sprintf(envstr, "SHELL=%s", _PATH_BSHELL); e->envp = env_set(e->envp, envstr); } if (!env_get("HOME", e->envp)) { sprintf(envstr, "HOME=%s", pw->pw_dir); e->envp = env_set(e->envp, envstr); } if (!env_get("PATH", e->envp)) { sprintf(envstr, "PATH=%s", _PATH_DEFPATH); e->envp = env_set(e->envp, envstr); } sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name); e->envp = env_set(e->envp, envstr);
El ciclo principal también funciona con la lista actual de tareas.
Ciclo principal
El cron original de la versión 7 de Unix funcionó de manera bastante simple: en un ciclo volví a leer la configuración, ejecuté las tareas del minuto actual como superusuario y dormí hasta el comienzo del siguiente minuto. Este enfoque simple en máquinas antiguas requería demasiados recursos.
Se propuso una versión alternativa en SysV, en la que el demonio se durmió hasta el siguiente minuto, para el cual se definió la tarea, o durante 30 minutos. Los recursos para releer la configuración y verificar las tareas en este modo se consumieron menos, pero se volvió inconveniente actualizar rápidamente la lista de tareas.
Vixie cron volvió a revisar las listas de tareas una vez por minuto, ya que a fines de los años 80 los recursos en máquinas Unix estándar se habían vuelto mucho más grandes:
load_database(&database); run_reboot_jobs(&database); cron_sync(); while (TRUE) { cron_sleep(); load_database(&database); cron_tick(&database); TargetTime += 60; }
La función cron_sleep, que llama a las funciones job_runqueue (enumeración e inicio de tareas) y do_command (inicio de cada tarea individual), participa directamente en la realización de tareas. La última función debe considerarse con más detalle.
Lanzamiento de tarea
La función do_command se ejecuta en un buen estilo Unix, es decir, se bifurca para la ejecución asincrónica de tareas. El proceso padre continúa lanzando tareas, el proceso hijo está preparando el proceso de la tarea:
switch (fork()) { case -1: break; case 0: acquire_daemonlock(1); child_process(e, u); _exit(OK_EXIT); break; default: break; }
Hay mucha lógica en child_process: toma la salida estándar y el error fluye sobre sí mismo, para que luego se pueda enviar por correo (si la variable de entorno MAILTO se especifica en la tabla de tareas) y, finalmente, espera a que se complete el proceso de la tarea principal.
El proceso de la tarea está formado por otra bifurcación:
switch (vfork()) { case -1: exit(ERROR_EXIT); case 0: (void) setsid(); setgid(e->gid); setuid(e->uid); chdir(env_get("HOME", e->envp)); { char *shell = env_get("SHELL", e->envp); execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); perror("execl"); _exit(ERROR_EXIT); } break; default: break; }
Aquí, en general, y todo el cron. Omití algunos detalles interesantes, por ejemplo, la contabilidad de usuarios remotos, pero describí lo principal.
Epílogo
Cron es un programa sorprendentemente simple y útil, hecho en las mejores tradiciones del mundo Unix. Ella no hace nada superfluo, pero ha estado haciendo su trabajo notablemente durante las últimas décadas. Conocer el código de la versión que viene con Ubuntu no tardó más de una hora, ¡y me divertí mucho! Espero poder compartirlo contigo.
No sé sobre ti, pero es un poco triste para mí darme cuenta de que la programación moderna, con su tendencia a complicarse y abstraerse, ha dejado de tener tanta simplicidad.
Hay muchas alternativas modernas para cron: systemd-timers le permite organizar sistemas complejos con dependencias, en fcron puede controlar de manera más flexible el consumo de recursos por tareas. Pero personalmente, siempre he tenido el crontab más simple.
En una palabra, ¡ama Unix, usa programas simples y no te olvides de leer maná para tu plataforma!