Cron v Linuxu: zgodovina, uporaba in naprava

Cron v Linuxu: zgodovina, uporaba in naprava

Klasik je zapisal, da vesele ure ne gledajo. V tistih divjih časih ni bilo ne programerjev ne Unixa, danes pa programerji zagotovo vedo: namesto njih bo čas spremljal cron.

Pripomočki ukazne vrstice so zame hkrati slabost in težava. sed, awk, wc, cut in drugi stari programi se vsak dan izvajajo s skripti na naših strežnikih. Mnogi od njih so zasnovani kot naloge za cron, razporejevalnik iz 70-ih.

Dolgo časa sem uporabljal cron površno, ne da bi se spuščal v podrobnosti, a nekega dne, ko sem naletel na napako pri izvajanju skripta, sem se odločil, da jo temeljito preučim. Tako je nastal ta članek, med pisanjem sem se seznanil s POSIX crontabom, glavnimi možnostmi cron v priljubljenih distribucijah Linuxa in strukturo nekaterih od njih.

Ali uporabljate Linux in izvajate naloge cron? Vas zanima arhitektura sistemskih aplikacij v Unixu? Potem smo na poti!

Vsebina

Izvor vrste

Periodično izvajanje uporabniških ali sistemskih programov je očitna nuja v vseh operacijskih sistemih. Zato so programerji že zdavnaj spoznali potrebo po storitvah, ki jim omogočajo centralno načrtovanje in izvajanje nalog.

Operacijski sistemi, podobni Unixu, izvirajo iz različice 7 Unixa, ki jo je v 70. letih prejšnjega stoletja razvil Bell Labs, med njimi tudi slavni Ken Thompson. Unix različice 7 je vključeval tudi cron, storitev za redno izvajanje nalog superuporabnika.

Tipičen sodobni cron je preprost program, vendar je bil algoritem delovanja prvotne različice še preprostejši: storitev se je zbudila enkrat na minuto, prebrala tabelo z opravili iz ene same datoteke (/etc/lib/crontab) in opravila za superuporabnik tista opravila, ki bi morala biti opravljena v tem trenutku.

Kasneje so bile izboljšane različice preproste in uporabne storitve dobavljene z vsemi operacijskimi sistemi, podobnimi Unixu.

Posplošeni opisi formata crontab in osnovnih principov delovanja pripomočka so bili leta 1992 vključeni v glavni standard Unix podobnih operacijskih sistemov - POSIX - in tako je cron iz standarda de facto postal standard de jure.

Leta 1987 je Paul Vixie po raziskavi uporabnikov Unixa o njihovih željah glede crona izdal drugo različico demona, ki je popravil nekatere težave tradicionalnega crona in razširil sintakso tabelnih datotek.

Do tretje različice Vixie cron je začel izpolnjevati zahteve POSIX, poleg tega je program imel liberalno licenco, oziroma licence sploh ni bilo, razen želja v README: avtor ne daje garancij, ime avtorja ni mogoče izbrisati, program pa se lahko prodaja le skupaj z izvorno kodo. Izkazalo se je, da so te zahteve združljive z načeli brezplačnega programja, ki je v tistih letih postajalo priljubljeno, zato so nekatere ključne distribucije Linuxa, ki so se pojavile v zgodnjih 90. letih, vzele Vixie cron za svoj sistem in ga razvijajo še danes.

Zlasti Red Hat in SUSE razvijata fork Vixie cron - cronie, Debian in Ubuntu pa uporabljata izvirno izdajo Vixie cron s številnimi popravki.

Najprej se seznanimo z uporabniškim pripomočkom crontab, opisanim v POSIX-u, nato pa si bomo ogledali sintaksne razširitve, ki jih ponuja Vixie cron, in uporabo različic Vixie cron v priljubljenih distribucijah Linuxa. In končno, češnja na torti je analiza naprave cron daemon.

POSIX crontab

Če je prvotni cron vedno deloval za superuporabnika, se sodobni razporejevalniki pogosto ukvarjajo z nalogami običajnih uporabnikov, kar je bolj varno in priročno.

Crons so na voljo kot komplet dveh programov: neprestano delujočega demona cron in pripomočka crontab, ki je na voljo uporabnikom. Slednji vam omogoča urejanje tabel opravil, specifičnih za vsakega uporabnika v sistemu, medtem ko demon zažene naloge iz uporabniških in sistemskih tabel.

В standard POSIX obnašanje demona ni na noben način opisano in samo uporabniški program je formaliziran crontab. Obstoj mehanizmov za zagon uporabniških nalog je seveda impliciran, vendar ni podrobneje opisan.

Če pokličete pripomoček crontab, lahko storite štiri stvari: uredite tabelo opravil uporabnika v urejevalniku, naložite tabelo iz datoteke, prikažete trenutno tabelo opravil in počistite tabelo opravil. Primeri delovanja pripomočka crontab:

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

Ob klicu crontab -e uporabljen bo urejevalnik, določen v standardni spremenljivki okolja EDITOR.

Sama opravila so opisana v naslednji obliki:

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

Prvih pet polj zapisa: minute [1..60], ure [0..23], dnevi v mesecu [1..31], meseci [1..12], dnevi v tednu [0. .6], kjer je 0 nedelja. Zadnje, šesto, polje je vrstica, ki jo bo izvedel standardni tolmač ukazov.

V prvih petih poljih so vrednosti lahko navedene ločene z vejicami:

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

Ali z vezajem:

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

Uporabniški dostop do razporejanja opravil je v POSIX reguliran z datotekama cron.allow in cron.deny, ki navajata uporabnike z dostopom do crontab oziroma uporabnike brez dostopa do programa. Standard na noben način ne ureja lokacije teh datotek.

V skladu s standardom morajo biti zagnanim programom posredovane vsaj štiri spremenljivke okolja:

  1. HOME - domači imenik uporabnika.
  2. LOGNAME — prijava uporabnika.
  3. PATH je pot, kjer najdete standardne sistemske pripomočke.
  4. SHELL — pot do uporabljenega tolmača ukazov.

Predvsem POSIX ne pove ničesar o tem, od kod prihajajo vrednosti za te spremenljivke.

Najbolje prodajano - Vixie cron 3.0pl1

Skupni prednik priljubljenih variant cron je Vixie cron 3.0pl1, predstavljen na poštnem seznamu comp.sources.unix leta 1992. Podrobneje bomo preučili glavne značilnosti te različice.

Vixie cron je na voljo v dveh programih (cron in crontab). Kot običajno je demon odgovoren za branje in izvajanje nalog iz sistemske tabele opravil in tabel posameznih uporabniških opravil, pripomoček crontab pa je odgovoren za urejanje uporabniških tabel.

Tabela opravil in konfiguracijske datoteke

Tabela nalog superuporabnika se nahaja v /etc/crontab. Sintaksa sistemske tabele ustreza sintaksi Vixie cron, z izjemo, da šesti stolpec v njej označuje ime uporabnika, v imenu katerega se zažene naloga:

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

Običajne tabele uporabniških opravil se nahajajo v /var/cron/tabs/username in uporabljajo isto sintakso. Ko kot uporabnik zaženete pripomoček crontab, so te datoteke urejene.

Seznami uporabnikov z dostopom do crontaba se upravljajo v datotekah /var/cron/allow in /var/cron/deny, kjer morate le vnesti uporabniško ime v ločeno vrstico.

Razširjena sintaksa

V primerjavi s POSIX crontab vsebuje rešitev Paula Vixeya več zelo uporabnih sprememb sintakse tabel opravil pripomočka.

Na voljo je nova sintaksa tabele: na primer, lahko določite dneve v tednu ali mesece po imenu (pon, tor itd.):

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

Določite lahko korak, skozi katerega se zaženejo naloge:

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

Korake in intervale je mogoče mešati:

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

Podprte so intuitivne alternative običajni sintaksi (ponovni zagon, letno, letno, mesečno, tedensko, dnevno, polnoč, vsako uro):

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

Okolje za izvajanje nalog

Vixie cron vam omogoča spreminjanje okolja zagnanih aplikacij.

Okoljskih spremenljivk USER, LOGNAME in HOME ne zagotovi preprosto demon, ampak so vzete iz datoteke passwd. Spremenljivka PATH je nastavljena na "/usr/bin:/bin" in spremenljivka SHELL je nastavljena na "/bin/sh". Vrednosti vseh spremenljivk razen LOGNAME je mogoče spremeniti v uporabniških tabelah.

Nekatere spremenljivke okolja (predvsem SHELL in HOME) uporablja sam cron za izvajanje naloge. Evo, kako bi lahko izgledala uporaba bash namesto standardnega sh za izvajanje opravil po meri:

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

Končno bodo vse spremenljivke okolja, definirane v tabeli (ki jih uporablja cron ali jih potrebuje proces), posredovane opravilu, ki se izvaja.

Za urejanje datotek crontab uporablja urejevalnik, določen v spremenljivki okolja VISUAL ali EDITOR. Če okolje, v katerem se je izvajal crontab, nima definiranih teh spremenljivk, se uporabi "/usr/ucb/vi" (ucb je verjetno Univerza Kalifornije, Berkeley).

cron na Debianu in Ubuntuju

Razvijalci Debiana in izpeljanih distribucij so izdali zelo modificirana različica Vixie cron različica 3.0pl1. V sintaksi datotek tabel ni razlik, za uporabnike je enak Vixie cron. Največja nova funkcija: podpora syslog, SELinux и PAM.

Manj opazne, a oprijemljive spremembe vključujejo lokacijo konfiguracijskih datotek in tabel opravil.

Uporabniške tabele v Debianu se nahajajo v imeniku /var/spool/cron/crontabs, sistemska tabela je še vedno tam - v /etc/crontab. Tabele opravil, specifične za paket Debian, so postavljene v /etc/cron.d, od koder jih demon cron samodejno prebere. Nadzor uporabniškega dostopa nadzirata datoteki /etc/cron.allow in /etc/cron.deny.

Privzeta lupina je še vedno /bin/sh, kar je v Debianu majhna lupina, združljiva s POSIX-om dash, ki se zažene brez branja kakršne koli konfiguracije (v neinteraktivnem načinu).

Sam Cron v najnovejših različicah Debiana se zažene prek systemd, konfiguracijo zagona pa si lahko ogledate v /lib/systemd/system/cron.service. V konfiguraciji storitve ni nič posebnega; vsako bolj subtilno upravljanje opravil je mogoče izvesti s spremenljivkami okolja, deklariranimi neposredno v crontabu vsakega uporabnika.

cronie na RedHat, Fedora in CentOS

cronie — fork Vixie cron različice 4.1. Tako kot v Debianu sintaksa ni bila spremenjena, vendar je bila dodana podpora za PAM in SELinux, delo v gruči, sledenje datotekam z uporabo inotify in druge funkcije.

Privzeta konfiguracija je na običajnih mestih: sistemska tabela je v /etc/crontab, paketi postavijo svoje tabele v /etc/cron.d, uporabniške tabele v /var/spool/cron/crontabs.

Demon deluje pod nadzorom systemd, konfiguracija storitve je /lib/systemd/system/crond.service.

V distribucijah, podobnih Red Hat, se ob zagonu privzeto uporablja /bin/sh, kar je standardni bash. Upoštevati je treba, da se lupina bash pri izvajanju opravil cron prek /bin/sh zažene v načinu, združljivem s POSIX, in ne bere nobene dodatne konfiguracije, teče v neinteraktivnem načinu.

cronie v SLES in openSUSE

Nemška distribucija SLES in njena izpeljanka openSUSE uporabljata istega prijatelja. Tudi tukaj se demon zažene pod systemd, konfiguracija storitve se nahaja v /usr/lib/systemd/system/cron.service. Konfiguracija: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh je isti bash, ki se izvaja v neinteraktivnem načinu, skladnem s POSIX.

Naprava Vixie cron

Sodobni potomci crona se v primerjavi z Vixie cron niso radikalno spremenili, vendar so še vedno pridobili nove funkcije, ki niso potrebne za razumevanje načel programa. Mnoge od teh razširitev so slabo zasnovane in povzročajo zmedo kodi. Izvirno izvorno kodo cron Paula Vixeya je užitek brati.

Zato sem se odločil analizirati napravo cron na primeru programa cron, skupnega obema vejama razvoja - Vixie cron 3.0pl1. Primere bom poenostavil tako, da bom odstranil ifdef, ki otežujejo branje, in izpustil manjše podrobnosti.

Delo demona lahko razdelimo na več stopenj:

  1. Inicializacija programa.
  2. Zbiranje in posodabljanje seznama opravil za izvajanje.
  3. Glavna cron zanka se izvaja.
  4. Začni nalogo.

Poglejmo jih po vrsti.

Inicializacija

Ob zagonu cron po preverjanju argumentov procesa namesti upravljalnika signalov SIGCHLD in SIGHUP. Prvi naredi vnos v dnevnik o prekinitvi podrejenega procesa, drugi pa zapre deskriptor datoteke dnevnika:

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

Demon cron vedno deluje sam v sistemu, samo kot superuporabnik in iz glavnega imenika cron. Naslednji klici ustvarijo zaklenjeno datoteko s PID procesa demona, preverijo, ali je uporabnik pravilen, in spremenijo trenutni imenik v glavnega:

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

Nastavljena je privzeta pot, ki bo uporabljena pri zagonu procesov:

setenv("PATH", _PATH_DEFPATH, 1);

Nato je proces "demoniziran": ustvari podrejeno kopijo procesa s klicem fork in novo sejo v podrejenem procesu (klicanje setsid). Nadrejeni proces ni več potreben in zapusti:

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

Prekinitev nadrejenega procesa sprosti zaklepanje datoteke zaklepanja. Poleg tega je treba posodobiti PID v datoteki za otroka. Po tem se baza nalog izpolni:

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

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

Nato se cron premakne na glavni delovni cikel. Pred tem pa si velja ogledati nalaganje seznama opravil.

Zbiranje in posodabljanje seznama opravil

Za nalaganje seznama nalog je odgovorna funkcija load_database. Preveri glavni sistemski crontab in imenik z uporabniškimi datotekami. Če se datoteke in imenik niso spremenili, se seznam opravil ne prebere znova. V nasprotnem primeru se začne oblikovati nov seznam opravil.

Nalaganje sistemske datoteke s posebnimi imeni datotek in tabel:

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

Nalaganje uporabniških tabel v zanki:

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

Po tem se stara zbirka podatkov nadomesti z novo.

V zgornjih primerih klic funkcije process_crontab preveri, ali obstaja uporabnik, ki se ujema z imenom datoteke tabele (razen če je superuporabnik), nato pa pokliče load_user. Slednji že prebere samo datoteko vrstico za vrstico:

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 je spremenljivka okolja nastavljena (vrstice v obliki VAR=vrednost) s funkcijama load_env / env_set ali pa se prebere opis opravila (* * * * * /path/to/exec) s funkcijo load_entry.

Vnosna entiteta, ki jo vrne load_entry, je naša naloga, ki je uvrščena na splošni seznam nalog. Funkcija sama izvaja podrobno razčlenjevanje časovnega formata, vendar nas bolj zanima oblikovanje spremenljivk okolja in parametrov zagona naloge:

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

Glavna zanka deluje s trenutnim seznamom opravil.

Glavna zanka

Prvotni cron iz različice 7 Unixa je deloval precej preprosto: znova je prebral konfiguracijo v zanki, zagnal naloge trenutne minute kot superuporabnik in spal do začetka naslednje minute. Ta preprost pristop na starejših strojih je zahteval preveč virov.

V SysV je bila predlagana alternativna različica, v kateri je demon prešel v stanje spanja do najbližje minute, za katero je bila naloga definirana, ali za 30 minut. Za ponovno branje konfiguracije in preverjanje opravil v tem načinu je bilo porabljenih manj virov, vendar je hitro posodabljanje seznama opravil postalo neprijetno.

Vixie cron se je vrnil k preverjanju seznamov opravil enkrat na minuto, na srečo je bilo do konca 80. let na standardnih strojih Unix bistveno več virov:

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

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

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

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

Funkcija cron_sleep je neposredno vključena v izvajanje nalog, kliče funkciji job_runqueue (naštej in zaženi naloge) in do_command (zaženi vsako posamezno nalogo). Zadnjo funkcijo je vredno podrobneje preučiti.

Izvajanje naloge

Funkcija do_command se izvaja v dobrem slogu Unixa, kar pomeni, da izvede razcep za asinhrono izvedbo naloge. Nadrejeni proces nadaljuje z zagonom opravil, podrejeni proces pripravi postopek opravila:

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

V child_processu je precej logike: sprejme standardne izhode in tokove napak vase, da jih nato pošlje na pošto (če je spremenljivka okolja MAILTO določena v tabeli opravil), in na koncu počaka na glavno postopek naloge, ki jo je treba dokončati.

Postopek naloge tvorijo druge vilice:

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 bistvu vse, kar je cron. Izpustil sem nekaj zanimivih podrobnosti, na primer računovodstvo za oddaljene uporabnike, vendar sem orisal glavno.

spremna beseda

Cron je presenetljivo preprost in uporaben program, narejen v najboljših tradicijah sveta Unix. Ne dela nič dodatnega, a svoje delo že nekaj desetletij odlično opravlja. Preučevanje kode za različico, ki je priložena Ubuntuju, ni trajalo več kot eno uro in zelo sem se zabaval! Upam, da sem ga lahko delil z vami.

Ne vem za vas, a jaz sem nekoliko žalosten, ko ugotavljam, da sodobno programiranje s svojo nagnjenostjo k prekomernemu kompliciranju in abstrahiranju že dolgo ni bilo naklonjeno takšni preprostosti.

Obstaja veliko sodobnih alternativ cronu: sistemski časovniki vam omogočajo organiziranje kompleksnih sistemov z odvisnostmi, fcron vam omogoča bolj prožno uravnavanje porabe virov po nalogah. Ampak osebno mi je vedno zadostoval najpreprostejši crontab.

Skratka, ljubite Unix, uporabljajte preproste programe in ne pozabite prebrati mane za svojo platformo!

Vir: www.habr.com

Dodaj komentar