Cron v Linuxe: história, použitie a zariadenie

Cron v Linuxe: história, použitie a zariadenie

Klasik pisal, ze happy hours nesleduju. V tých divokých časoch neexistovali ani programátori, ani Unix, ale dnes programátori vedia s istotou: cron bude sledovať čas namiesto nich.

Obslužné programy príkazového riadku sú pre mňa slabosťou aj trápením. sed, awk, wc, cut a iné staré programy spúšťajú skripty na našich serveroch každý deň. Mnohé z nich sú navrhnuté ako úlohy pre cron, plánovač pôvodne zo 70. rokov.

Dlho som cron používal povrchne, bez toho, aby som zachádzal do detailov, ale jedného dňa, keď som narazil na chybu pri spúšťaní skriptu, rozhodol som sa na to dôkladne pozrieť. Takto vyzeral tento článok, pri jeho písaní som sa oboznámil s POSIX crontab, hlavnými možnosťami cronu v populárnych linuxových distribúciách a štruktúrou niektorých z nich.

Používate Linux a spúšťate úlohy cronu? Zaujíma vás architektúra systémových aplikácií v Unixe? Potom sme na ceste!

Obsah

Pôvod druhov

Pravidelné spúšťanie užívateľských alebo systémových programov je samozrejmou nevyhnutnosťou vo všetkých operačných systémoch. Preto si programátori už dávno uvedomili potrebu služieb, ktoré im umožnia centrálne plánovať a vykonávať úlohy.

Operačné systémy podobné Unixu majú svoj pôvod v Unixe verzie 7, ktorý vyvinul v 70. rokoch minulého storočia v Bell Labs, vrátane slávneho Kena Thompsona. Verzia 7 Unix tiež obsahovala cron, službu pre pravidelne spustené úlohy superužívateľa.

Typický moderný cron je jednoduchý program, ale operačný algoritmus pôvodnej verzie bol ešte jednoduchší: služba sa prebudila raz za minútu, prečítala tabuľku s úlohami z jedného súboru (/etc/lib/crontab) a vykonala pre superuser tie úlohy, ktoré mali byť v danom momente vykonané.

Následne boli vylepšené verzie jednoduchej a užitočnej služby dodávané so všetkými operačnými systémami podobnými Unixu.

Zovšeobecnené popisy formátu crontab a základné princípy fungovania utility boli zahrnuté do hlavného štandardu unixových operačných systémov – POSIX – v roku 1992, a tak sa cron z de facto štandardu stal štandardom de jure.

V roku 1987 Paul Vixie po prieskume používateľov Unixu o ich želaniach pre cron vydal ďalšiu verziu démona, ktorá opravila niektoré problémy tradičného cronu a rozšírila syntax tabuľkových súborov.

Tretia verzia Vixie cron začala spĺňať požiadavky POSIX, navyše mal program liberálnu licenciu, respektíve neexistovala žiadna licencia, okrem želaní v README: autor nedáva záruky, meno autora nie je možné odstrániť a program je možné predávať iba spolu so zdrojovým kódom. Ukázalo sa, že tieto požiadavky sú kompatibilné s princípmi slobodného softvéru, ktorý si v tých rokoch získaval na popularite, takže niektoré kľúčové linuxové distribúcie, ktoré sa objavili na začiatku 90. rokov, si vzali Vixie cron za svoj systém a vyvíjajú ho dodnes.

Najmä Red Hat a SUSE vyvíjajú fork Vixie cron - cronie a Debian a Ubuntu používajú pôvodné vydanie Vixie cron s mnohými záplatami.

Najprv sa zoznámime s užívateľským nástrojom crontab opísaným v POSIX, potom sa pozrieme na rozšírenia syntaxe poskytované vo Vixie cron a na použitie variácií Vixie cron v populárnych linuxových distribúciách. A nakoniec čerešničkou na torte je analýza zariadenia cron démon.

POSIX crontab

Ak pôvodný cron vždy fungoval pre superužívateľa, moderné plánovače sa často zaoberajú úlohami bežných používateľov, čo je bezpečnejšie a pohodlnejšie.

Crony sa dodávajú ako sada dvoch programov: neustále spustený démon cron a nástroj crontab dostupný používateľom. Ten vám umožňuje upravovať tabuľky úloh špecifické pre každého užívateľa v systéme, zatiaľ čo démon spúšťa úlohy z užívateľských a systémových tabuliek.

В štandard POSIX správanie démona nie je nijako popísané a formalizovaný je len užívateľský program crontab. Existencia mechanizmov na spúšťanie užívateľských úloh je samozrejme implicitná, ale nie je podrobne popísaná.

Zavolaním pomôcky crontab môžete urobiť štyri veci: upraviť tabuľku úloh používateľa v editore, načítať tabuľku zo súboru, zobraziť aktuálnu tabuľku úloh a vymazať tabuľku úloh. Príklady fungovania nástroja crontab:

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

Pri telefonovaní crontab -e použije sa editor špecifikovaný v štandardnej premennej prostredia EDITOR.

Samotné úlohy sú opísané v nasledujúcom formáte:

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

Prvých päť polí záznamov: minúty [1..60], hodiny [0..23], dni v mesiaci [1..31], mesiace [1..12], dni v týždni [0. .6], kde 0 je nedeľa. Posledné, šieste, pole je riadok, ktorý vykoná štandardný interpret príkazov.

V prvých piatich poliach môžu byť hodnoty uvedené oddelené čiarkami:

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

Alebo s pomlčkou:

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

Prístup používateľov k plánovaniu úloh je v POSIX regulovaný súbormi cron.allow a cron.deny, ktoré uvádzajú používateľov s prístupom k crontab a používateľov bez prístupu k programu. Umiestnenie týchto súborov norma nijako neupravuje.

Podľa štandardu musia byť spusteným programom odovzdané aspoň štyri premenné prostredia:

  1. HOME - domovský adresár používateľa.
  2. LOGNAME — prihlásenie užívateľa.
  3. PATH je cesta, kde môžete nájsť štandardné systémové nástroje.
  4. SHELL — cesta k použitému interpreteru príkazov.

Najmä POSIX nehovorí nič o tom, odkiaľ pochádzajú hodnoty týchto premenných.

Najpredávanejší - Vixie cron 3.0pl1

Spoločným predchodcom populárnych variantov cron je Vixie cron 3.0pl1, ktorý bol predstavený v mailing listu comp.sources.unix v roku 1992. Hlavné vlastnosti tejto verzie zvážime podrobnejšie.

Vixie cron prichádza v dvoch programoch (cron a crontab). Démon je ako zvyčajne zodpovedný za čítanie a spúšťanie úloh zo systémovej tabuľky úloh a jednotlivých tabuliek užívateľských úloh a pomocný program crontab je zodpovedný za úpravu užívateľských tabuliek.

Tabuľka úloh a konfiguračné súbory

Tabuľka úloh superužívateľa sa nachádza v /etc/crontab. Syntax systémovej tabuľky zodpovedá syntaxi Vixie cron s výnimkou, že šiesty stĺpec v nej označuje meno používateľa, v mene ktorého sa úloha spúšťa:

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

Bežné tabuľky úloh používateľa sa nachádzajú v /var/cron/tabs/username a používajú rovnakú syntax. Keď spustíte pomôcku crontab ako používateľ, toto sú súbory, ktoré sa upravujú.

Zoznamy používateľov s prístupom k crontab sú spravované v súboroch /var/cron/allow a /var/cron/deny, kde stačí zadať meno používateľa do samostatného riadku.

Rozšírená syntax

V porovnaní s POSIX crontab obsahuje riešenie Paula Vixeyho niekoľko veľmi užitočných úprav syntaxe tabuliek úloh pomôcky.

Sprístupnila sa nová syntax tabuľky: napríklad môžete zadať dni v týždni alebo mesiace podľa názvu (pondelok, utorok atď.):

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

Môžete určiť krok, prostredníctvom ktorého sa úlohy spúšťajú:

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

Kroky a intervaly je možné kombinovať:

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

Podporované sú intuitívne alternatívy k bežnej syntaxi (reštartovanie, ročné, ročné, mesačné, týždenné, denné, polnočné, hodinové):

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

Prostredie vykonávania úloh

Vixie cron umožňuje meniť prostredie spustených aplikácií.

Premenné prostredia USER, LOGNAME a HOME nie sú jednoducho poskytované démonom, ale sú prevzaté zo súboru passwd. Premenná PATH je nastavená na "/usr/bin:/bin" a premenná SHELL je nastavená na "/bin/sh". Hodnoty všetkých premenných okrem LOGNAME je možné zmeniť v používateľských tabuľkách.

Niektoré premenné prostredia (najmä SHELL a HOME) používa samotný cron na spustenie úlohy. Takto môže vyzerať použitie bash namiesto štandardného sh na spustenie vlastných úloh:

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

V konečnom dôsledku budú všetky premenné prostredia definované v tabuľke (používané cronom alebo potrebné v procese) odovzdané spustenej úlohe.

Na úpravu súborov používa crontab editor špecifikovaný v premennej prostredia VISUAL alebo EDITOR. Ak prostredie, v ktorom bol spustený crontab, nemá definované tieto premenné, potom sa použije "/usr/ucb/vi" (ucb je pravdepodobne University of California, Berkeley).

cron na Debiane a Ubuntu

Vývojári Debianu a odvodených distribúcií vydali vysoko upravená verzia Vixie cron verzia 3.0pl1. Neexistujú žiadne rozdiely v syntaxi tabuľkových súborov, pre používateľov je to rovnaký cron Vixie. Najväčšia nová funkcia: Podpora syslog, SELinux и PAM.

Menej viditeľné, ale hmatateľné zmeny zahŕňajú umiestnenie konfiguračných súborov a tabuliek úloh.

Užívateľské tabuľky v Debiane sa nachádzajú v adresári /var/spool/cron/crontabs, systémová tabuľka je tam stále - v /etc/crontab. Tabuľky úloh špecifických pre balík Debianu sú umiestnené v /etc/cron.d, odkiaľ ich démon cron automaticky číta. Riadenie prístupu používateľov je riadené súbormi /etc/cron.allow a /etc/cron.deny.

Predvolený shell je stále /bin/sh, čo je v Debiane malý shell kompatibilný s POSIX pomlčka, spustený bez načítania akejkoľvek konfigurácie (v neinteraktívnom režime).

Samotný Cron v najnovších verziách Debianu sa spúšťa cez systemd a konfiguráciu spustenia si môžete pozrieť v /lib/systemd/system/cron.service. V konfigurácii služby nie je nič zvláštne, akúkoľvek jemnejšiu správu úloh možno vykonať pomocou premenných prostredia deklarovaných priamo v crontab každého používateľa.

kamarát na RedHat, Fedora a CentOS

cronie — vidlica Vixie cron verzie 4.1. Podobne ako v Debiane sa syntax nezmenila, no pribudla podpora PAM a SELinux, práca v klastri, sledovanie súborov pomocou inotify a ďalšie funkcie.

Predvolená konfigurácia je na zvyčajných miestach: systémová tabuľka je v /etc/crontab, balíky ukladajú svoje tabuľky do /etc/cron.d, používateľské tabuľky sú v /var/spool/cron/crontabs.

Démon beží pod kontrolou systemd, konfigurácia služby je /lib/systemd/system/crond.service.

V distribúciách podobných Red Hat sa pri spustení štandardne používa /bin/sh, čo je štandardný bash. Treba poznamenať, že pri spúšťaní úloh cron cez /bin/sh sa bash shell spustí v režime kompatibilnom s POSIX a nečíta žiadnu dodatočnú konfiguráciu, beží v neinteraktívnom režime.

kamarát v SLES a openSUSE

Nemecká distribúcia SLES a jej derivát openSUSE používajú rovnakého kamaráta. Démon sa tu spúšťa aj pod systemd, konfigurácia služby sa nachádza v /usr/lib/systemd/system/cron.service. Konfigurácia: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh je rovnaký bash, ktorý beží v neinteraktívnom režime kompatibilnom s POSIX.

Zariadenie Vixie cron

Moderní potomkovia cronu sa v porovnaní s Vixie cron radikálne nezmenili, ale stále získali nové funkcie, ktoré nie sú potrebné na pochopenie princípov programu. Mnohé z týchto rozšírení sú zle navrhnuté a mätú kód. Pôvodný zdrojový kód cronu od Paula Vixeyho je radosť čítať.

Preto som sa rozhodol analyzovať cron zariadenie na príklade programu cron spoločného pre obe vetvy vývoja - Vixie cron 3.0pl1. Zjednoduším príklady odstránením ifdefov, ktoré komplikujú čítanie a vynechaním menších detailov.

Práca démona sa dá rozdeliť do niekoľkých etáp:

  1. Inicializácia programu.
  2. Zhromažďovanie a aktualizácia zoznamu úloh, ktoré sa majú spustiť.
  3. Hlavná cron slučka beží.
  4. Začnite úlohu.

Pozrime sa na ne v poradí.

Inicializácia

Po spustení, po skontrolovaní argumentov procesu, cron nainštaluje obsluhu signálu SIGCHLD a SIGHUP. Prvý urobí záznam o ukončení podriadeného procesu, druhý zatvorí deskriptor súboru denníka:

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

Démon cron vždy beží v systéme sám, iba ako superužívateľ a z hlavného adresára cron. Nasledujúce volania vytvoria súbor zámku s PID procesu démona, uistite sa, že používateľ je správny a zmeňte aktuálny adresár na hlavný:

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

Je nastavená predvolená cesta, ktorá sa použije pri spúšťaní procesov:

setenv("PATH", _PATH_DEFPATH, 1);

Potom je proces „daemonizovaný“: vytvorí podradenú kópiu procesu volaním fork a novej relácie v podradenom procese (volaním setsid). Nadradený proces už nie je potrebný a ukončí sa:

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

Ukončenie nadradeného procesu uvoľní zámok na súbore zámku. Okrem toho je potrebné aktualizovať PID v súbore dieťaťu. Potom sa vyplní databáza úloh:

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

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

Potom cron prejde na hlavný pracovný cyklus. Predtým sa však oplatí pozrieť sa na načítanie zoznamu úloh.

Zhromažďovanie a aktualizácia zoznamu úloh

Funkcia load_database je zodpovedná za načítanie zoznamu úloh. Skontroluje hlavný systémový crontab a adresár s používateľskými súbormi. Ak sa súbory a adresár nezmenili, zoznam úloh sa znova neprečíta. V opačnom prípade sa začne vytvárať nový zoznam úloh.

Načítanie systémového súboru so špeciálnymi názvami súborov a tabuliek:

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

Načítanie užívateľských tabuliek v slučke:

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

Potom sa stará databáza nahradí novou.

Vo vyššie uvedených príkladoch volanie funkcie process_crontab overí, či existuje používateľ zodpovedajúci názvu súboru tabuľky (pokiaľ nejde o superužívateľa), a potom zavolá load_user. Ten už číta samotný súbor riadok po riadku:

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

Tu sa buď nastaví premenná prostredia (riadky v tvare VAR=hodnota) pomocou funkcií load_env / env_set, alebo sa načíta popis úlohy (* * * * * /cesta/k/exec) pomocou funkcie load_entry.

Vstupná entita, ktorá vracia load_entry, je naša úloha, ktorá je umiestnená vo všeobecnom zozname úloh. Samotná funkcia vykonáva podrobnú analýzu formátu času, ale viac nás zaujíma tvorba premenných prostredia a parametre spúšťania úlohy:

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

Hlavná slučka pracuje s aktuálnym zoznamom úloh.

Hlavná slučka

Pôvodný cron z verzie 7 Unix fungoval celkom jednoducho: znovu načítal konfiguráciu v slučke, spúšťal úlohy aktuálnej minúty ako superužívateľ a spal až do začiatku nasledujúcej minúty. Tento jednoduchý prístup na starších počítačoch vyžadoval príliš veľa zdrojov.

V SysV bola navrhnutá alternatívna verzia, v ktorej démon prešiel do režimu spánku buď do najbližšej minúty, na ktorú bola úloha definovaná, alebo na 30 minút. Na opätovné načítanie konfigurácie a kontrolu úloh v tomto režime sa spotrebovalo menej zdrojov, ale rýchla aktualizácia zoznamu úloh sa stala nepohodlnou.

Vixie cron sa vrátil ku kontrole zoznamov úloh raz za minútu, našťastie koncom 80. rokov bolo na štandardných unixových strojoch podstatne viac zdrojov:

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

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

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

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

Funkcia cron_sleep sa priamo podieľa na vykonávaní úloh, pričom volá funkcie job_runqueue (vyčíslenie a spustenie úloh) a do_command (spustenie každej jednotlivej úlohy). Posledná funkcia stojí za podrobnejšie preskúmanie.

Spustenie úlohy

Funkcia do_command sa vykonáva v dobrom unixovom štýle, to znamená, že vykonáva rozvetvenie na asynchrónne vykonávanie úlohy. Rodičovský proces pokračuje v spúšťaní úloh, podriadený proces pripravuje proces úlohy:

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

V child_process je pomerne veľa logiky: berie štandardný výstup a chybové prúdy na seba, aby ich potom poslal na poštu (ak je v tabuľke úloh špecifikovaná premenná prostredia MAILTO), a nakoniec čaká na hlavný proces dokončenia úlohy.

Proces úlohy je tvorený ďalšou vidlicou:

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

To je v podstate všetko, čo cron je. Vynechal som niektoré zaujímavé detaily, napríklad účtovanie pre vzdialených používateľov, ale načrtol som to hlavné.

Doslov

Cron je prekvapivo jednoduchý a užitočný program vytvorený podľa najlepších tradícií sveta Unixu. Nerobí nič extra, ale už niekoľko desaťročí robí svoju prácu úžasne. Získanie kódu pre verziu dodávanú s Ubuntu netrvalo dlhšie ako hodinu a užil som si veľa zábavy! Dúfam, že sa mi to podarilo s vami podeliť.

Neviem ako vy, ale mne je trochu smutno, keď si uvedomím, že moderné programovanie so svojou tendenciou k prílišnej komplikovanosti a prílišnej abstraktnosti už dlho neprispieva k takejto jednoduchosti.

Existuje mnoho moderných alternatív ku cronu: systemd-timery vám umožňujú organizovať zložité systémy so závislosťami, fcron vám umožňuje flexibilnejšie regulovať spotrebu zdrojov podľa úloh. Osobne mi ale vždy stačil ten najjednoduchší crontab.

Skrátka, milujte Unix, používajte jednoduché programy a nezabudnite si prečítať manu pre vašu platformu!

Zdroj: hab.com

Pridať komentár