Cron en Linux: historial, uso e dispositivo

Cron en Linux: historial, uso e dispositivo

O clásico escribiu que as horas felices non miran. Naqueles tempos salvaxes non había nin programadores nin Unix, pero hoxe os programadores saben con certeza: cron fará un seguimento do tempo no canto deles.

As utilidades da liña de comandos son tanto unha debilidade como unha tarefa para min. sed, awk, wc, cut e outros programas antigos son executados por scripts nos nosos servidores todos os días. Moitos deles están deseñados como tarefas para cron, un planificador orixinario dos anos 70.

Durante moito tempo usei cron superficialmente, sen entrar en detalles, pero un día, cando atopei un erro ao executar un script, decidín investigalo a fondo. Así apareceu este artigo, mentres o escribía familiarizándome co crontab de POSIX, as principais opcións de cron das distribucións populares de Linux e a estrutura dalgunhas delas.

Estás usando Linux e executando tarefas cron? Estás interesado na arquitectura de aplicacións do sistema en Unix? Entón imos camiño!

Contido

Orixe das especies

A execución periódica de programas de usuario ou do sistema é unha necesidade obvia en todos os sistemas operativos. Polo tanto, hai moito tempo os programadores decatáronse da necesidade de servizos que lles permitan planificar e executar tarefas de forma centralizada.

Os sistemas operativos similares a Unix remóntanse ás súas orixes á Versión 7 de Unix, desenvolvida nos anos 70 do século pasado nos Laboratorios Bell, incluído o famoso Ken Thompson. A versión 7 de Unix tamén incluía cron, un servizo para executar tarefas de superusuario regularmente.

Un cron moderno típico é un programa sinxelo, pero o algoritmo operativo da versión orixinal era aínda máis sinxelo: o servizo espertaba unha vez por minuto, lía unha táboa con tarefas dun único ficheiro (/etc/lib/crontab) e realizábase para o superusuario aquelas tarefas que deberían ter sido realizadas no momento actual .

Posteriormente, proporcionáronse versións melloradas do servizo sinxelo e útil con todos os sistemas operativos similares a Unix.

As descricións xeneralizadas do formato crontab e os principios básicos do funcionamento da utilidade foron incluídas no estándar principal dos sistemas operativos tipo Unix - POSIX - en 1992, e así o cron dun estándar de facto converteuse nun estándar de iure.

En 1987, Paul Vixie, despois de enquisar aos usuarios de Unix sobre os seus desexos de cron, lanzou outra versión do daemon que corrixiu algúns dos problemas do cron tradicional e ampliou a sintaxe dos ficheiros de táboa.

Na terceira versión de Vixie cron comezou a cumprir os requisitos POSIX, ademais, o programa tiña unha licenza liberal, ou mellor dito, non había ningunha licenza, agás os desexos no README: o autor non dá garantías, o nome do autor. non se pode eliminar e o programa só se pode vender xunto co código fonte. Estes requisitos resultaron ser compatibles cos principios do software libre que gañaba popularidade naqueles anos, polo que algunhas das distribucións clave de Linux que apareceron a principios dos 90 tomaron Vixie cron como o seu sistema e aínda o están desenvolvendo na actualidade.

En particular, Red Hat e SUSE desenvolven un fork de Vixie cron - cronie, e Debian e Ubuntu usan a edición orixinal de Vixie cron con moitos parches.

Imos primeiro familiarizarnos coa utilidade crontab de usuario descrita en POSIX, despois de que veremos as extensións de sintaxe proporcionadas en Vixie cron e o uso de variacións de Vixie cron nas distribucións populares de Linux. E, finalmente, a cereixa do bolo é a análise do dispositivo cron daemon.

POSIX crontab

Se o cron orixinal sempre funcionou para o superusuario, os programadores modernos adoitan tratar tarefas dos usuarios comúns, o que é máis seguro e cómodo.

Crons ofrécense como un conxunto de dous programas: o daemon cron en execución constante e a utilidade crontab dispoñible para os usuarios. Este último permítelle editar táboas de tarefas específicas para cada usuario do sistema, mentres que o daemon lanza tarefas desde as táboas do usuario e do sistema.

В Estándar POSIX o comportamento do daemon non se describe de ningún xeito e só se formaliza o programa de usuario crontab. A existencia de mecanismos para lanzar tarefas de usuario está, por suposto, implícita, pero non se describe en detalle.

Ao chamar á utilidade crontab, podes facer catro cousas: editar a táboa de tarefas do usuario no editor, cargar a táboa desde un ficheiro, mostrar a táboa de tarefas actual e borrar a táboa de tarefas. Exemplos de como funciona a utilidade crontab:

crontab -e # редактировать таблицу задач
crontab -l # показать таблицу задач
crontab -r # удалить таблицу задач
crontab path/to/file.crontab # загрузить таблицу задач из файла

Cando se chama crontab -e utilizarase o editor especificado na variable de ambiente estándar EDITOR.

As tarefas en si descríbense no seguinte 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

Os cinco primeiros campos dos rexistros: minutos [1..60], horas [0..23], días do mes [1..31], meses [1..12], días da semana [0. .6], onde 0 é domingo. O último, sexto, campo é unha liña que será executada polo intérprete de comandos estándar.

Nos primeiros cinco campos, pódense enumerar os valores separados por comas:

# задача, выполняемая в первую и десятую минуты каждого часа
1,10 * * * * /path/to/exec -a -b -c

Ou cun guión:

# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -c

O acceso dos usuarios á programación de tarefas está regulado en POSIX polos ficheiros cron.allow e cron.deny, que enumeran os usuarios con acceso a crontab e os usuarios sen acceso ao programa, respectivamente. A norma non regula a localización destes ficheiros de ningún xeito.

Segundo o estándar, deben pasarse polo menos catro variables de ambiente aos programas iniciados:

  1. HOME - directorio persoal do usuario.
  2. LOGNAME — inicio de sesión de usuario.
  3. PATH é o camiño onde podes atopar as utilidades estándar do sistema.
  4. SHELL — camiño ao intérprete de comandos usado.

En particular, POSIX non di nada sobre a orixe dos valores destas variables.

O máis vendido - Vixie cron 3.0pl1

O antepasado común das variantes cron populares é Vixie cron 3.0pl1, introducido na lista de correo comp.sources.unix en 1992. Consideraremos as principais características desta versión con máis detalle.

Vixie cron vén en dous programas (cron e crontab). Como é habitual, o daemon encárgase de ler e executar as tarefas da táboa de tarefas do sistema e das táboas de tarefas de usuario individuais, e a utilidade crontab encárgase de editar as táboas de usuarios.

Táboa de tarefas e ficheiros de configuración

A táboa de tarefas de superusuario está situada en /etc/crontab. A sintaxe da táboa do sistema corresponde á sintaxe de Vixie cron, coa excepción de que a sexta columna indica o nome do usuario baixo cuxo nome se inicia a tarefa:

# Запускается ежеминутно от пользователя vlad
* * * * * vlad /path/to/exec

As táboas de tarefas de usuario habituais están situadas en /var/cron/tabs/username e usan a mesma sintaxe. Cando executas a utilidade crontab como usuario, estes son os ficheiros que se editan.

As listas de usuarios con acceso a crontab xestionanse nos ficheiros /var/cron/allow e /var/cron/deny, onde só precisa introducir o nome de usuario nunha liña separada.

Sintaxe estendida

En comparación co crontab POSIX, a solución de Paul Vixey contén varias modificacións moi útiles na sintaxe das táboas de tarefas da utilidade.

Dispoñible unha nova sintaxe de táboa: por exemplo, pode especificar os días da semana ou os meses polo nome (Lun, Mar, etc.):

# Запускается ежеминутно по понедельникам и вторникам в январе
* * * Jan Mon,Tue /path/to/exec

Podes especificar o paso a través do cal se inician as tarefas:

# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/exec

Os pasos e os intervalos pódense mesturar:

# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/exec

Admítense alternativas intuitivas á sintaxe habitual (reinicio, anual, anual, mensual, semanal, diario, medianoite, cada hora):

# Запускается после перезагрузки системы
@reboot /exec/on/reboot
# Запускается раз в день
@daily /exec/daily
# Запускается раз в час
@hourly /exec/daily

Entorno de execución de tarefas

Vixie cron permítelle cambiar o ambiente das aplicacións en execución.

As variables de ambiente USER, LOGNAME e HOME non son proporcionadas simplemente polo daemon, senón que son tomadas dun ficheiro passwd. A variable PATH está configurada en "/usr/bin:/bin" e a variable SHELL está configurada en "/bin/sh". Os valores de todas as variables, excepto LOGNAME, pódense cambiar nas táboas de usuarios.

Algunhas variables de ambiente (sobre todo SHELL e HOME) son usadas polo propio cron para executar a tarefa. Isto é o que pode parecer usar bash en lugar de sh estándar para executar tarefas personalizadas:

SHELL=/bin/bash
HOME=/tmp/
# exec будет запущен bash-ем в /tmp/
* * * * * /path/to/exec

En definitiva, todas as variables de ambiente definidas na táboa (utilizadas por cron ou necesarias polo proceso) pasaranse á tarefa en execución.

Para editar ficheiros, crontab usa o editor especificado na variable de ambiente VISUAL ou EDITOR. Se o ambiente onde se executou crontab non ten definidas estas variables, úsase "/usr/ucb/vi" (probablemente ucb sexa a Universidade de California, Berkeley).

cron en Debian e Ubuntu

Os desenvolvedores de Debian e distribucións derivadas lanzaron versión altamente modificada Vixie cron versión 3.0pl1. Non hai diferenzas na sintaxe dos ficheiros da táboa; para os usuarios é o mesmo cron de Vixie. Nova característica máis grande: soporte syslog, SELinux и PAM.

Os cambios menos notables, pero tanxibles inclúen a localización dos ficheiros de configuración e as táboas de tarefas.

As táboas de usuarios en Debian están situadas no directorio /var/spool/cron/crontabs, a táboa do sistema aínda está alí - en /etc/crontab. As táboas de tarefas específicas do paquete de Debian colócanse en /etc/cron.d, desde onde o daemon cron as le automaticamente. O control de acceso dos usuarios está controlado polos ficheiros /etc/cron.allow e /etc/cron.deny.

O shell predeterminado aínda é /bin/sh, que en Debian é un pequeno shell compatible con POSIX trazo, lanzado sen ler ningunha configuración (en modo non interactivo).

O propio Cron nas últimas versións de Debian lánzase a través de systemd, e a configuración de inicio pódese ver en /lib/systemd/system/cron.service. Non hai nada especial na configuración do servizo; calquera xestión de tarefas máis sutil pódese facer a través de variables de ambiente declaradas directamente na crontab de cada usuario.

amigo en RedHat, Fedora e CentOS

compañeiro — fork de Vixie cron versión 4.1. Como en Debian, a sintaxe non cambiou, pero engadiuse soporte para PAM e SELinux, traballar nun clúster, rastrexar ficheiros mediante inotify e outras funcións.

A configuración predeterminada está nos lugares habituais: a táboa do sistema está en /etc/crontab, os paquetes colocan as súas táboas en /etc/cron.d, as táboas de usuarios van en /var/spool/cron/crontabs.

O daemon execútase baixo o control systemd, a configuración do servizo é /lib/systemd/system/crond.service.

Nas distribucións tipo Red Hat, /bin/sh úsase por defecto ao inicio, que é o bash estándar. Nótese que cando se executan traballos cron a través de /bin/sh, o shell bash comeza en modo compatible con POSIX e non le ningunha configuración adicional, executándose en modo non interactivo.

amigo en SLES e openSUSE

A distribución alemá SLES e o seu derivado openSUSE usan o mesmo cliente. O daemon aquí tamén se inicia baixo systemd, a configuración do servizo está situada en /usr/lib/systemd/system/cron.service. Configuración: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh é o mesmo bash que se executa no modo non interactivo compatible con POSIX.

Dispositivo cron vixie

Os descendentes modernos de cron non cambiaron radicalmente en comparación con Vixie cron, pero aínda adquiriron novas funcións que non son necesarias para comprender os principios do programa. Moitas destas extensións están mal deseñadas e confunden o código. O código fonte cron orixinal de Paul Vixey é un pracer ler.

Polo tanto, decidín analizar o dispositivo cron usando o exemplo dun programa cron común a ambas ramas de desenvolvemento: Vixie cron 3.0pl1. Simplificarei os exemplos eliminando os ifdefs que complican a lectura e omitindo detalles menores.

O traballo do demo pódese dividir en varias etapas:

  1. Inicialización do programa.
  2. Recopilación e actualización da lista de tarefas a executar.
  3. Loop cron principal en execución.
  4. Comezar unha tarefa.

Mirámolos por orde.

Inicialización

Cando se inicia, despois de comprobar os argumentos do proceso, cron instala os controladores de sinais SIGCHLD e SIGHUP. O primeiro fai unha entrada de rexistro sobre a terminación do proceso fillo, o segundo pecha o descritor do ficheiro de rexistro:

signal(SIGCHLD, sigchld_handler);
signal(SIGHUP, sighup_handler);

O daemon cron sempre se executa só no sistema, só como superusuario e desde o directorio cron principal. As seguintes chamadas crean un ficheiro de bloqueo co PID do proceso daemon, asegúrate de que o usuario é correcto e cambia o directorio actual ao principal:

acquire_daemonlock(0);
set_cron_uid();
set_cron_cwd();

Establécese o camiño predeterminado, que se utilizará ao iniciar os procesos:

setenv("PATH", _PATH_DEFPATH, 1);

Entón o proceso é "demonizado": crea unha copia filla do proceso chamando a fork e unha nova sesión no proceso fillo (chamando setsid). O proceso principal xa non é necesario e sae:

switch (fork()) {
case -1:
    /* критическая ошибка и завершение работы */
    exit(0);
break;
case 0:
    /* дочерний процесс */
    (void) setsid();
break;
default:
    /* родительский процесс завершает работу */
    _exit(0);
}

A terminación do proceso principal libera o bloqueo do ficheiro de bloqueo. Ademais, é necesario actualizar o PID no ficheiro para o neno. Despois diso, énchese a base de datos de tarefas:

/* повторный захват лока */
acquire_daemonlock(0);

/* Заполнение БД  */
database.head = NULL;
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);

A continuación, cron pasa ao ciclo de traballo principal. Pero antes diso, paga a pena botar unha ollada á carga da lista de tarefas.

Recopilación e actualización da lista de tarefas

A función load_database encárgase de cargar a lista de tarefas. Comproba o crontab do sistema principal e o directorio con ficheiros de usuario. Se os ficheiros e o directorio non cambiaron, a lista de tarefas non se volva ler. En caso contrario, comeza a formarse unha nova lista de tarefas.

Cargando un ficheiro do sistema con nomes especiais de ficheiros e táboas:

/* если файл системной таблицы изменился, перечитываем */
if (syscron_stat.st_mtime) {
    process_crontab("root", "*system*",
    SYSCRONTAB, &syscron_stat,
    &new_db, old_db);
}

Cargando táboas de usuarios nun 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);
}

Despois diso, a antiga base de datos substitúese por unha nova.

Nos exemplos anteriores, a chamada á función process_crontab verifica que existe un usuario que coincida co nome do ficheiro da táboa (a non ser que sexa un superusuario) e logo chama a load_user. Este último xa le o propio ficheiro liña por liña:

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í, ou se define a variable de ambiente (liñas da forma VAR=valor) usando as funcións load_env / env_set, ou se le a descrición da tarefa (* * * * * /path/to/exec) usando a función load_entry.

A entidade de entrada que devolve load_entry é a nosa tarefa, que se sitúa na lista xeral de tarefas. A función en si realiza unha análise detallada do formato de tempo, pero estamos máis interesados ​​na formación de variables de ambiente e parámetros de inicio de tarefas:

/* пользователь и группа для запуска задачи берутся из passwd*/
e->uid = pw->pw_uid;
e->gid = pw->pw_gid;

/* шелл по умолчанию (/bin/sh), если пользователь не указал другое */
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);
}
/* имя пользовтеля всегда из passwd */
sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
e->envp = env_set(e->envp, envstr);

O bucle principal funciona coa lista actual de tarefas.

Bucle principal

O cron orixinal da versión 7 de Unix funcionou de forma sinxela: volveu ler a configuración nun bucle, lanzou as tarefas do minuto actual como superusuario e durmía ata o inicio do minuto seguinte. Este enfoque sinxelo en máquinas antigas requiriu demasiados recursos.

Propúxose unha versión alternativa en SysV, na que o daemon ía durmir ben ata o minuto máis próximo para o que se definiu a tarefa ou durante 30 minutos. Consumíronse menos recursos para reler a configuración e comprobar as tarefas neste modo, pero actualizar rapidamente a lista de tarefas converteuse nun inconveniente.

Vixie cron volveu comprobar as listas de tarefas unha vez por minuto, afortunadamente a finais dos 80 había moito máis recursos nas máquinas estándar Unix:

/* первичная загрузка задач */
load_database(&database);
/* запустить задачи, поставленные к выполнению после перезагрузки системы */
run_reboot_jobs(&database);
/* сделать TargetTime началом ближайшей минуты */
cron_sync();
while (TRUE) {
    /* выполнить задачи, после чего спать до TargetTime с поправкой на время, потраченное на задачи */
    cron_sleep();

    /* перечитать конфигурацию */
    load_database(&database);

    /* собрать задачи для данной минуты */
    cron_tick(&database);

    /* перевести TargetTime на начало следующей минуты */
    TargetTime += 60;
}

A función cron_sleep está directamente implicada na execución de tarefas, chamando ás funcións job_runqueue (enumerar e executar tarefas) e do_command (executar cada tarefa individual). A última función paga a pena examinala con máis detalle.

Execución dunha tarefa

A función do_command execútase nun bo estilo Unix, é dicir, fai unha bifurcación para realizar a tarefa de forma asíncrona. O proceso principal segue iniciando tarefas, o proceso fillo prepara o proceso de tarefas:

switch (fork()) {
case -1:
    /*не смогли выполнить fork */
    break;
case 0:
    /* дочерний процесс: на всякий случай еще раз пробуем захватить главный лок */
    acquire_daemonlock(1);
    /* переходим к формированию процесса задачи */
    child_process(e, u);
    /* по завершению дочерний процесс заканчивает работу */
    _exit(OK_EXIT);
    break;
default:
    /* родительский процесс продолжает работу */
    break;
}

Hai moita lóxica en child_process: toma a saída estándar e fluxos de erro sobre si mesmo, para despois envialo ao correo (se a variable de ambiente MAILTO está especificada na táboa de tarefas) e, finalmente, agarda a que a principal proceso da tarefa a completar.

O proceso de tarefas está formado por outra 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));

    /* запуск самой команды
     */
    {
        /* переменная окружения SHELL указывает на интерпретатор для запуска */
        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;
}

Eso é basicamente todo o que é cron. Omitín algúns detalles interesantes, por exemplo, a contabilización de usuarios remotos, pero delineei o principal.

Posterior

Cron é un programa sorprendentemente sinxelo e útil, feito nas mellores tradicións do mundo Unix. Non fai nada extra, pero leva varias décadas facendo o seu traballo de marabilla. Conseguir o código para a versión que vén con Ubuntu non levou máis dunha hora, e divertín moito! Espero poder compartilo contigo.

Non sei vostedes, pero estou un pouco triste ao darme conta de que a programación moderna, coa súa tendencia a excesivamente complicada e excesivamente abstracta, fai tempo que non propicia tanta sinxeleza.

Hai moitas alternativas modernas para cron: systemd-timers permítenche organizar sistemas complexos con dependencias, fcron permítelle regular de forma máis flexible o consumo de recursos por tarefas. Pero persoalmente, o crontab máis sinxelo sempre foi suficiente para min.

En resumo, ama Unix, usa programas sinxelos e non esquezas ler o mana da túa plataforma.

Fonte: www.habr.com

Engadir un comentario