Cron a Linux: historial, ús i dispositiu

Cron a Linux: historial, ús i dispositiu

El clàssic va escriure que no s'observen les hores feliços. En aquells temps salvatges, encara no hi havia programadors ni Unix, però aquests dies, els programadors ho saben del cert: en comptes d'ells, cron farà un seguiment del temps.

Les utilitats de la línia d'ordres són tant febles com rutinàries per a mi. sed, awk, wc, cut i altres programes antics s'executen diàriament mitjançant scripts als nostres servidors. Molts d'ells estan dissenyats com a tasques per a cron, un planificador dels anys 70.

Vaig utilitzar cron de manera superficial durant molt de temps, sense aprofundir en els detalls, però un dia, davant d'un error en executar el guió, vaig decidir mirar-ho a fons. Així va sorgir aquest article, durant el qual em vaig familiaritzar amb el crontab POSIX, les principals opcions de cron de les distribucions populars de Linux i el disseny d'algunes d'elles.

Esteu utilitzant Linux i executant tasques a cron? T'interessa l'arquitectura d'aplicacions del sistema a Unix? Llavors estem en el nostre camí!

Contingut

Origen de les espècies

L'execució periòdica de programes d'usuari o sistema és una necessitat òbvia en tots els sistemes operatius. Per tant, els programadors han reconegut la necessitat de serveis que permetin una planificació i execució centralitzada de les tasques durant molt de temps.

Els sistemes operatius semblants a Unix remunten el seu llinatge a la versió 7 Unix, desenvolupada als anys 70 del segle passat als Bell Labs, inclòs el famós Ken Thompson. La versió 7 Unix també s'envia amb cron, un servei per executar regularment tasques de superusuari.

Un cron modern típic és un programa senzill, però l'algoritme de la versió original era encara més senzill: el servei es despertava un cop per minut, llegia una taula amb tasques d'un sol fitxer (/etc/lib/crontab) i es realitzava per al superusuari. aquelles tasques que s'haurien d'haver realitzat en el minut actual.

Posteriorment, es van enviar versions millorades del servei senzill i útil amb tots els sistemes operatius semblants a Unix.

Les descripcions generalitzades del format crontab i els principis bàsics de la utilitat l'any 1992 es van incloure a l'estàndard principal per a sistemes operatius semblants a Unix - POSIX - i, per tant, cron de l'estàndard de facto es va convertir en l'estàndard de jure.

El 1987, Paul Vixie, després d'enquestar els usuaris d'Unix sobre els desitjos de cron, va llançar una altra versió del dimoni que va solucionar alguns dels problemes del cron tradicional i va ampliar la sintaxi dels fitxers de taula.

A la tercera versió de Vixie cron va començar a complir els requisits de POSIX, a més, el programa tenia una llicència liberal, o millor dit, no hi havia cap llicència, excepte els desitjos del README: l'autor no dóna garanties, el nom de l'autor no es pot esborrar i el programa només es pot vendre juntament amb el codi font. Aquests requisits van resultar ser compatibles amb els principis del programari lliure, que anava guanyant popularitat en aquells anys, de manera que algunes de les distribucions clau de Linux que van aparèixer a principis dels 90 van prendre Vixie cron com a sistema i encara l'estan desenvolupant.

En particular, Red Hat i SUSE estan desenvolupant un fork de Vixie cron - cronie, mentre que Debian i Ubuntu utilitzen l'edició original de Vixie cron amb molts pedaços.

Vegem primer la utilitat crontab definida per l'usuari POSIX i, després, les extensions de sintaxi introduïdes a Vixie cron i l'ús de les variacions de Vixie cron en distribucions populars de Linux. I, finalment, la cirereta del pastís és desmuntar el dispositiu del dimoni cron.

POSIX crontab

Tot i que el cron original sempre s'executava per al superusuari, és més probable que els planificadors moderns s'ocupin de les tasques habituals dels usuaris, cosa que és més segura i convenient.

Crons inclou un conjunt de dos programes: un dimoni cron en execució constant i una utilitat crontab accessible per l'usuari. Aquest últim us permet editar taules de tasques específiques per a cada usuari del sistema, mentre que el dimoni executa tasques des de les taules d'usuari i del sistema.

В Estàndard POSIX el comportament del dimoni no es descriu de cap manera i només es formalitza el programa d'usuari crontab. L'existència de mecanismes per llançar tasques d'usuari està, per descomptat, implícita, però no descrita en detall.

Hi ha quatre coses que es poden fer trucant a la utilitat crontab: editar la taula de tasques de l'usuari a l'editor, carregar la taula des d'un fitxer, mostrar la taula de tasques actual i esborrar la taula de tasques. Exemples de la utilitat crontab:

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

En trucar crontab -e s'utilitzarà l'editor especificat a la variable d'entorn estàndard EDITOR.

Les tasques en si es descriuen en el format següent:

# строки-комментарии игнорируются
#
# задача, выполняемая ежеминутно
* * * * * /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

Els cinc primers camps de registres: minuts [1..60], hores [0..23], dies del mes [1..31], mesos [1..12], dies de la setmana [0.. 6], on 0 — diumenge. L'últim, sisè, camp és una línia que s'executarà per l'intèrpret d'ordres estàndard.

Als cinc primers camps, els valors es poden llistar separats per comes:

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

O amb un guionet:

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

L'accés dels usuaris a la programació de tasques està regulat a POSIX pels fitxers cron.allow i cron.deny, que enumeren, respectivament, els usuaris amb accés crontab i els usuaris sense accés al programa. L'estàndard no regula la ubicació d'aquests fitxers de cap manera.

Els programes iniciats, segons la norma, han de passar almenys quatre variables d'entorn:

  1. HOME és el directori inicial de l'usuari.
  2. LOGNAME - inici de sessió d'usuari.
  3. PATH: el camí on podeu trobar les utilitats estàndard del sistema.
  4. SHELL és el camí a l'intèrpret d'ordres utilitzat.

En particular, POSIX no diu res sobre d'on provenen els valors d'aquestes variables.

El més venut - Vixie cron 3.0pl1

L'avantpassat comú de les variants cron populars és Vixie cron 3.0pl1, introduït a la llista de correu comp.sources.unix el 1992. Considerarem les característiques principals d'aquesta versió amb més detall.

Vixie cron ve en dos programes (cron i crontab). Com és habitual, el dimoni s'encarrega de llegir i executar les tasques de la taula de tasques del sistema i de les taules de tasques d'usuari individuals, mentre que la utilitat crontab s'encarrega d'editar les taules d'usuari.

Taula de tasques i fitxers de configuració

La taula de tasques de superusuari es troba a /etc/crontab. La sintaxi de la taula del sistema correspon a la sintaxi de Vixie cron, amb l'excepció que la sisena columna indica el nom de l'usuari en nom del qual s'inicia la tasca:

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

Les taules de tasques d'usuari habituals resideixen a /var/cron/tabs/username i utilitzen una sintaxi comuna. Quan executeu la utilitat crontab com a usuari, aquests fitxers s'editen.

Les llistes d'usuaris que tenen accés a crontab es gestionen als fitxers /var/cron/allow i /var/cron/deny, on n'hi ha prou amb introduir el nom d'usuari en una línia separada.

Sintaxi ampliada

En comparació amb el crontab POSIX, la solució de Paul Wixey conté algunes modificacions molt útils a la sintaxi de les taules de tasques de la utilitat.

S'ha disponible una nova sintaxi de taula: per exemple, podeu especificar els dies de la setmana o els mesos pel nom (dl, dt, etc.):

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

Podeu especificar el pas a través del qual s'inicien les tasques:

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

Els passos i els intervals es poden barrejar:

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

S'admeten alternatives intuïtives a la sintaxi habitual (reinici, anualment, anualment, mensual, setmanal, diari, mitjanit, cada hora):

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

Entorn d'execució de tasques

Vixie cron us permet canviar l'entorn de les aplicacions llançades.

Les variables d'entorn USER, LOGNAME i HOME no només les proporciona el dimoni, sinó que es prenen del fitxer passwd. La variable PATH s'estableix a "/usr/bin:/bin" i la variable SHELL s'estableix a "/bin/sh". Els valors de totes les variables excepte LOGNAME es poden canviar a les taules d'usuari.

Algunes variables d'entorn (principalment SHELL i HOME) són utilitzades pel mateix cron per iniciar una tasca. A continuació es mostra com pot semblar utilitzar bash en comptes del sh predeterminat per executar tasques d'usuari:

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

Finalment, totes les variables d'entorn definides a la taula (ja siguin utilitzades per cron o necessàries pel procés) es passaran a la tasca en execució.

Per editar fitxers, crontab utilitza l'editor especificat a la variable d'entorn VISUAL o EDITOR. Si l'entorn on es va executar crontab no té aquestes variables definides, s'utilitza "/usr/ucb/vi" (probablement ucb és la Universitat de Califòrnia, Berkeley).

cron a Debian i Ubuntu

Debian i desenvolupadors derivats han publicat versió molt modificada versió de Vixie cron 3.0pl1. No hi ha diferències en la sintaxi dels fitxers de taula; per als usuaris, aquest és el mateix cron de Vixie. Noves característiques més importants: suport syslog, SELinux и PAM.

Dels canvis menys notables, però tangibles, són la ubicació dels fitxers de configuració i les taules de tasques.

Les taules d'usuari a Debian es troben al directori /var/spool/cron/crontabs, la taula del sistema encara hi és - a /etc/crontab. Les taules de tasques específiques del paquet de Debian es col·loquen a /etc/cron.d, des del qual el dimoni cron les llegeix automàticament. El control d'accés dels usuaris es regeix pels fitxers /etc/cron.allow i /etc/cron.deny.

L'intèrpret d'ordres per defecte segueix sent /bin/sh, que a Debian és un petit intèrpret d'ordres compatible amb POSIX guió, llançat sense llegir cap configuració (en mode no interactiu).

El mateix Cron en les versions recents de Debian s'executa mitjançant systemd, i la configuració d'inici es pot trobar a /lib/systemd/system/cron.service. No hi ha res especial en la configuració del servei, qualsevol gestió de tasques més fina es pot fer mitjançant variables d'entorn declarades directament a la crontab de cada usuari.

cronie a RedHat, Fedora i CentOS

amigues - bifurcació de Vixie cron versió 4.1. Igual que a Debian, la sintaxi no ha canviat, però s'ha afegit suport per a PAM i SELinux, treballant en un clúster, supervisant fitxers amb inotify i altres funcions.

La configuració per defecte es troba als llocs habituals: la taula del sistema es troba a /etc/crontab, els paquets posen les seves taules a /etc/cron.d, les taules d'usuari van a /var/spool/cron/crontabs.

El dimoni s'executa sota systemd, la configuració del servei és /lib/systemd/system/crond.service.

A les distribucions semblants a Red Hat, l'inici predeterminat és /bin/sh, que és el bash estàndard. Tingueu en compte que quan s'executen treballs cron mitjançant /bin/sh, l'intèrpret d'ordres bash s'inicia en mode compatible amb POSIX i no llegeix cap configuració addicional, s'executa en mode no interactiu.

cronie a SLES i openSUSE

La distribució alemanya SLES i el seu derivat d'openSUSE utilitzen el mateix client. El dimoni aquí també s'executa sota systemd, la configuració del servei es troba a /usr/lib/systemd/system/cron.service. Configuració: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh és el mateix bash que s'executa en mode no interactiu compatible amb POSIX.

Dispositiu Vixie cron

Els descendents moderns de cron no han canviat radicalment en comparació amb Vixie cron, però encara han adquirit noves característiques que no són necessàries per entendre els principis del programa. Moltes d'aquestes extensions són descuidades i confonen el codi. El codi font cron original de Paul Wixie és un plaer llegir-lo.

Per tant, vaig decidir analitzar el dispositiu cron utilitzant l'exemple d'un programa comú a les dues branques del desenvolupament cron: Vixie cron 3.0pl1. Simplificaré els exemples eliminant els ifdefs que dificulten la lectura i ometent detalls secundaris.

El treball del dimoni es pot dividir en diverses etapes:

  1. Inicialització del programa.
  2. Recolliu i actualitzeu la llista de tasques a executar.
  3. El treball del bucle cron principal.
  4. Començar una tasca.

Posem-los en ordre.

Inicialització

A l'inici, després de comprovar els arguments del procés, cron instal·la els controladors de senyal SIGCHLD i SIGHUP. El primer registra una entrada sobre la finalització del procés fill, el segon tanca el descriptor del fitxer de registre:

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

El dimoni cron del sistema sempre s'executa sol, només en el paper de superusuari i des del directori cron principal. Les trucades següents creen un fitxer de bloqueig amb el PID del procés del dimoni, assegureu-vos que l'usuari sigui correcte i canvieu el directori actual al directori principal:

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

S'estableix la ruta per defecte, que s'utilitzarà quan s'inicien els processos:

setenv("PATH", _PATH_DEFPATH, 1);

A continuació, el procés està "demonitzat": crea una còpia secundària del procés cridant a fork i una nova sessió al procés fill (cridant setsid). El procés principal ja no és necessari i surt:

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

En finalitzar el procés principal, s'allibera el bloqueig del fitxer de bloqueig. A més, cal actualitzar el PID del fitxer a un nen. Després d'això, s'omple la base de dades de tasques:

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

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

A continuació, cron passa al bucle de treball principal. Però abans d'això, val la pena fer una ullada a la càrrega de la llista de tasques.

Recollida i actualització de la llista de tasques

La funció load_database s'encarrega de carregar la llista de tasques. Comprova el crontab del sistema principal i el directori de fitxers d'usuari. Si els fitxers i el directori no han canviat, la llista de tasques no es rellegeix. En cas contrari, es començarà a formar una nova llista de tasques.

Carregant un fitxer del sistema amb noms especials de fitxers i taules:

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

Carregant taules personalitzades 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);
}

Després d'això, la base de dades antiga es substitueix per una de nova.

En els exemples anteriors, la crida a process_crontab comprova l'existència d'un usuari corresponent al nom de fitxer de la taula (tret que sigui el superusuari) i després crida a load_user. Aquest últim ja llegeix el propi fitxer línia per línia:

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í la variable d'entorn s'estableix (cadenes com VAR=valor) per les funcions load_env / env_set, o la descripció de la tasca (* * * * * /path/to/exec) es llegeix per la funció load_entry.

L'entitat d'entrada que retorna load_entry és la nostra tasca, que es col·loca a la llista general de tasques. A la funció en si, es realitza una anàlisi detallada del format de temps, però ens interessa més la formació de variables d'entorn i paràmetres de llançament de tasques:

/* пользователь и группа для запуска задачи берутся из 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);

Amb la llista real de tasques, el bucle principal funciona.

Bucle principal

El cron original de la versió 7 Unix funcionava de manera senzilla: rellegia la configuració en un bucle, iniciava les tasques del minut actual com a superusuari i dormia fins al començament del minut següent. Aquest senzill enfocament en màquines antigues requeria massa recursos.

A SysV, s'ha proposat una versió alternativa on el dimoni dorm fins al minut més proper per al qual es defineix la tasca o durant 30 minuts. Els recursos per rellegir la configuració i comprovar les tasques en aquest mode consumien menys, però es va convertir en inconvenient actualitzar ràpidament la llista de tasques.

Vixie cron va tornar a comprovar les llistes de tasques una vegada per minut, ja que a finals dels anys 80 hi havia molts més recursos a les màquines Unix estàndard:

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

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

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

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

La funció cron_sleep està directament implicada en l'execució de tasques, cridant a les funcions job_runqueue (enumeració i inici de tasques) i do_command (inici de cada tasca individual). L'última funció val la pena analitzar-la amb més detall.

Començar una tasca

La funció do_command es fa amb un bon estil Unix, és a dir, fa una bifurcació per executar una tasca de manera asíncrona. El procés principal continua executant tasques, mentre que el procés secundari prepara el procés de tasques:

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

Hi ha molta lògica a child_process: pren la sortida estàndard i els fluxos d'error sobre si mateix, de manera que després es pot enviar al correu (si la variable d'entorn MAILTO s'especifica a la taula de tasques) i, finalment, espera perquè es completi el procés principal de la tasca.

El procés de tasca està format per una altra bifurcació:

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;
}

Això, en general, és tot el cron. Vaig ometre alguns detalls interessants, per exemple, la comptabilització dels usuaris remots, però vaig descriure el més important.

Paraula posterior

Cron és un programa sorprenentment senzill i útil, fet amb les millors tradicions del món Unix. No fa res més, però fa dècades que fa la seva feina de manera notable. Va trigar menys d'una hora a revisar el codi de la versió que s'envia amb Ubuntu, i em vaig divertir molt! Espero haver pogut compartir-ho amb vosaltres.

No sé vosaltres, però em fa una mica de pena adonar-me que la programació moderna, amb la seva tendència a la complexitat excessiva i l'abstracció excessiva, fa temps que ha deixat de ser propicia per a tanta simplicitat.

Hi ha moltes alternatives modernes a cron: systemd-timers us permeten organitzar sistemes complexos amb dependències, fcron us permet regular de manera més flexible el consum de recursos per tasques. Però personalment, el crontab més senzill sempre va ser suficient per a mi.

En resum, estima Unix, utilitza programes senzills i no t'oblidis de llegir el mana de la teva plataforma!

Font: www.habr.com

Afegeix comentari