Cron na Linuxu: povijest, korištenje i struktura

Cron na Linuxu: povijest, korištenje i struktura

Klasično se kaže da sretni sati ne broje. U ta divlja vremena nije bilo ni programera ni Unixa, ali danas programeri čvrsto znaju: umjesto njih, vrijeme će pratiti cron.

Alati za naredbeni redak su mi istodobno slabost i rutina. sed, awk, wc, cut i drugi stari programi pokreću se skriptama na našim poslužiteljima svakodnevno. Mnogi od njih su organizirani kao zadaci za cron, planer koji potječe iz 70-ih.

Dugo sam koristio cron površno, ne ulazeći u pojedinosti, ali jednom prilikom, suočen s greškom prilikom pokretanja skripte, odlučio sam se temeljito pozabaviti tim pitanjem. Tako je nastao ovaj članak, prilikom čije sam izrade proučio POSIX crontab, osnovne varijante crona u popularnim distribucijama Linuxa i strukturu nekih od njih.

Koristite Linux i pokrećete zadatke u cron-u? Zanimate li se za arhitekturu sistemskih aplikacija u Unixu? Onda smo na istom putu!

Sadržaj

Podrijetlo vrsta

Periodično izvršavanje korisničkih ili sistemskih programa — očita nužnost u svim operacijskim sustavima. Stoga su programeri davno osvijestili potrebu za servisima koji omogućuju centralizirano planiranje i izvršavanje zadataka.

Unix-slični operacijski sustavi vode svoju lozu od Version 7 Unixa, razvijenog 70-ih godina prošlog stoljeća u Bell Labsu, uključujući i poznatog Kena Thompsona. Uz Version 7 Unix dostavljen je i cron, servis za redovito izvršavanje zadataka superkorisnika.

Tipični moderni cron — jednostavan program, ali algoritam rada originalne varijante bio je još jednostavniji: servis se budio svake minute, čitao tablicu s zadacima iz jedne datoteke (/etc/lib/crontab) i izvršavao za superkorisnika one zadatke koje je trebalo izvršiti u trenutnoj minuti.

Kasnije su unaprijeđene verzije ovog jednostavnog i korisnog servisa isporučivane sa svim Unix-sličnim operacijskim sustavima.

Generalizirani opisi formata crontab i osnovnih načela rada alata uključeni su 1992. godine u glavni standard Unix-sličnih operativnih sustava — POSIX — i tako je cron iz de facto standarda postao de jure standard.

Godine 1987. Paul Vixie, anketirajući korisnike Unixa o njihovim željama vezanim za cron, izdao je još jednu verziju demona koja ispravlja određene probleme tradicionalnih crona i proširuje sintaksu datoteka-tablica.

Treća verzija Vixie cron ispunila je zahtjeve POSIX-a, a program je imao liberalnu licencu, zapravo nije bilo nikakve licence osim želja u README: autor ne daje nikakva jamstva, ime autora se ne može ukloniti, a program se može prodavati samo uz izvorni kod. Ovi zahtjevi su se pokazali kompatibilnima s načelima slobodnog softvera koji je u to vrijeme postajao sve popularniji, tako da su neki ključni distribucijski paketi Linuxa iz ranih 90-ih uzeli Vixie cron kao sistemsku opciju i razvijaju ga do danas.

Specifično, Red Hat i SUSE razvijaju fork Vixie cron-a — cronie, dok Debian i Ubuntu koriste originalno izdanje Vixie cron-a sa brojnim ispravcima.

Za početak upoznajmo se s korisničkim alatom crontab opisanom u POSIX-u, nakon čega ćemo razmotriti proširenja sintakse predstavljena u Vixie cron-u i korištenje varijacija Vixie cron-a u popularnim distribucijama Linuxa. I konačno, šlag na tortu — analiza strukture demona cron.

POSIX crontab

Dok je originalni cron uvijek radio za superkorisnika, moderni planer često se bavi zadacima običnih korisnika, što je sigurnije i praktičnije.

Cron se sastoji od dvije komponente: stalno aktivnog demona cron i korisnicima dostupnog alata crontab. Potonji omogućava uređivanje tablica zadataka specifičnih za svakog korisnika u sustavu, dok demon pokreće zadatke iz korisničkih i sistemske tablice.

U POSIX standardu nije opisano ponašanje demona, a samo je formaliziran korisnički program crontab. Postojanje mehanizama za pokretanje korisničkih zadataka svakako se podrazumijeva, ali nije detaljno opisano.

Pozivanjem alata crontab može se učiniti četiri stvari: uređivati korisničku tablicu zadataka u uređivaču, učitati tablicu iz datoteke, prikazati trenutnu tablicu zadataka i očistiti tablicu zadataka. Primjeri rada alata crontab:

crontab -e # uređivanje tablice zadataka
crontab -l # prikaz tablice zadataka
crontab -r # brisanje tablice zadataka
crontab path/to/file.crontab # učitavanje tablice zadataka iz datoteke

Kada pozovete crontab -e će se koristiti uređivač naveden u standardnoj varijabli okruženja UREĐIVAČ.

Zadaci su opisani u sljedećem formatu:

# строки-комментарии игнорируются
#
# задача, выполняемая ежеминутно
* * * * * /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 polja zapisa: minute [1..60], sati [0..23], dani u mjesecu [1..31], mjeseci [1..12], dani u tjednu [0..6], pri čemu 0 označava nedjelju. Posljednje, šesto polje – string koji će izvršiti standardni interpreter komandi.

U prva pet polja vrijednosti se mogu nabrajati zarezom:

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

Ili crticom:

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

Pristup korisnika planiranju zadataka reguliran je POSIX datotekama cron.allow i cron.deny u kojima su navedeni, prema tome, korisnici s pristupom crontab-u i korisnici bez pristupa programu. Lokacija ovih datoteka nije standardizirana.

Pokretanim programima, prema standardu, moraju se proslijediti najmanje četiri varijable okruženja:

  1. HOME — korisnička početna mapa.
  2. LOGNAME — korisničko ime.
  3. PATH — put koji se koristi za pronalaženje standardnih sistemskih alata.
  4. SHELL — put do korištenog komandnog interpretera.

Zanimljivo je da POSIX ne spominje odakle potječu vrijednosti za ove varijable.

Bestseler — Vixie cron 3.0pl1

Zajednički predak popularnih verzija crona je Vixie cron 3.0pl1, predstavljen u novinskoj grupi comp.sources.unix 1992. godine. Osnovne mogućnosti ove verzije ćemo detaljnije razmotriti.

Vixie cron se sastoji od dviju programa (cron i crontab). Kao i obično, demon je odgovoran za čitanje i pokretanje zadataka iz sistemske tablice zadataka i tablica određenih korisnika, dok alat crontab služi za uređivanje korisničkih tablica.

Tablica zadataka i konfiguracijske datoteke

Tablica zadataka superkorisnika nalazi se u /etc/crontab. Sintaksa sistemske tablice odgovara sintaksi Vixie crona s izmjenom da se u njoj šesta kolona navodi ime korisnika pod čijim se imenom pokreće zadatak:

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

Tablice zadataka običnih korisnika su u /var/cron/tabs/ime_korisnika i koriste zajedničku sintaksu. Kada se alat crontab pokrene u ime korisnika, uređuju se upravo ove datoteke.

Upravljanje popisima korisnika s pristupom crontab-u odvija se u datotekama /var/cron/allow i /var/cron/deny, gdje je dovoljno unijeti ime korisnika u zasebnom redu.

Proširena sintaksa

U usporedbi s POSIX crontab-om, rješenje Paula Vixie-a sadrži nekoliko vrlo korisnih izmjena u sintaksi tablica zadataka alata.

Nova sintaksa tablica je postala dostupna: na primjer, možete navesti dane u tjednu ili mjesece po imenu (pon, uto itd.):

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

Možete odrediti korak prema kojem se pokreću zadaci:

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

Koraci i intervalu se mogu kombinirati:

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

Podržavaju se intuitivne alternative uobičajenoj sintaksi (ponovno pokretanje, godišnje, mjesečno, tjedno, dnevno, ponoć, svaka sat):

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

Okruženje izvršavanja zadataka

Vixie cron omogućuje promjenu okruženja pokrenutih aplikacija.

Varijable okruženja USER, LOGNAME i HOME ne samo da se pružaju demonima, već se uzimaju iz datoteke passwd. Varijabla PATH dobiva vrijednost "/usr/bin:/bin", a SHELL – "/bin/sh". Vrijednosti svih varijabli, osim LOGNAME, mogu se promijeniti u korisničkim tablicama.

Neke varijable okruženja (prije svega SHELL i HOME) koristi sam cron za pokretanje zadatka. Evo kako može izgledati korištenje bash umjesto standardnog sh za pokretanje korisničkih zadataka:

SHELL=/bin/bash
HOME=/tmp/
# exec će se pokrenuti putem basha u /tmp/
* * * * * /path/to/exec

Na kraju, sve varijable okruženja definirane u tablici (koje koristi cron ili su potrebne procesu) bit će proslijeđene pokrenutom zadatku.

Za uređivanje datoteka s crontab alatom koristi se uređivač naveden u varijabli okruženja VISUAL ili EDITOR. Ako u okruženju u kojem je pokrenut crontab te varijable nisu definirane, koristi se "/usr/ucb/vi" (ucb vjerojatno označava University of California, Berkeley).

cron u Debiano i Ubuntuu

Razvijači Debiana i sličnih distribucija objavili su značajno modificiranu verziju verziju Vixie cron 3.0pl1. Nema razlika u sintaksi datoteka-tablica, za korisnike je to i dalje isti Vixie cron. Najveće nove mogućnosti: podrška syslog, SELinux i PAM.

Među manje vidljivim, ali opipljivim promjenama su lokacije konfiguracijskih datoteka i tablica zadataka.

Korisničke tablice u Debiánu nalaze se u direktoriju /var/spool/cron/crontabs, sistemska tablica ostaje u /etc/crontab. Specifične za pakete Debian tablice zadataka smještene su u /etc/cron.d, odakle ih cron demon automatski čita. Upravljanje pristupom korisnika regulirano je datotekama /etc/cron.allow i /etc/cron.deny.

Kao komandna ljuska po defaultu i dalje se koristi /bin/sh, dok u Debiánu služi mali POSIX-kompatibilni shell dash, pokrenut bez čitanja bilo kakve konfiguracije (u neinteraktivnom režimu).

Sam cron u najnovijim verzijama Debiana pokreće se putem systemd-a, a konfiguraciju pokretanja možete pogledati u /lib/systemd/system/cron.service. U konfiguraciji servisa nema ništa posebno, bilo kakvo fino upravljanje zadacima može se izvršiti putem varijabli okruženja, koje su izjavljene direktno u crontab-u svakog korisnika.

cronie u RedHat, Fedora i CentOS

cronie — fork Vixie cron verzije 4.1. Kao i u Debiana, sintaksa se nije promenila, ali je dodata podrška za PAM i SELinux, rad u klasteru, praćenje datoteka pomoću inotify i druge mogućnosti.

Zadana konfiguracija se nalazi na uobičajenim mestima: sistemska tabela — u /etc/crontab, paketi smeštaju svoje tabele u /etc/cron.d, korisničke tabele ulaze u /var/spool/cron/crontabs.

Demon se pokreće pod upravom systemd-a, konfiguracija servisa — /lib/systemd/system/crond.service.

U Red Hat-ovim distribucijama po defaultu se koristi /bin/sh, gde se standardni bash koristi kao njegova zastava. Treba napomenuti da prilikom pokretanja cron zadataka putem /bin/sh, komandna ljuska bash se pokreće u POSIX-kompatibilnom režimu i ne čita nikakvu dodatnu konfiguraciju, radeći u neinteraktivnom režimu.

cronie u SLES-u i openSUSE-u

Nemačka distribucija SLES i njen derivat openSUSE koriste isti cronie. Demon se ovde takođe pokreće pod systemd-om, konfiguracija servisa se nalazi u /usr/lib/systemd/system/cron.service. Konfiguracija: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. Kao /bin/sh koristi se isti bash, pokrenut u POSIX-kompatibilnom neinteraktivnom režimu.

Struktura Vixie crona

Suvremeni potomci cron-a u poređenju s Vixie cron-om nisu radikalno promenjeni, ali su ipak dobili nove mogućnosti koje nisu potrebne za razumevanje principa rada programa. Mnoge od tih ekstenzija su loše oblikovane i zbunjuju kod. Originalni izvorni kod crona u izvođenju Pola Vixiea je pravo zadovoljstvo čitati.

Zato sam odlučio da analiziram strukturu crona na primeru zajedničkog za oba ogranka razvoja cron programa — Vixie cron 3.0pl1. Primeri će biti pojednostavljeni, uklanjanjem zbunjujućih ifdef-ova i izostavljanjem sporednih detalja.

Rad demona može se podeliti na nekoliko faza:

  1. Inicijalizacija programa.
  2. Prikupljanje i ažuriranje liste zadataka za pokretanje.
  3. Rad glavnog ciklusa crona.
  4. Pokretanje zadatka.

Razvijamo ih redom.

Inicijalizacija

Kada se pokrene, nakon provjere argumenata, proces cron postavlja obradnike signala SIGCHLD i SIGHUP. Prvi zapisuje u log završetak rada podprocesa, dok drugi zatvara datotečni deskriptor datoteke loga:

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

Demon cron u sustavu uvijek radi kao jedan, samo u ulozi superusera i iz glavnog direktorija crona. Sljedeći pozivi stvaraju datoteku za zaključavanje s PID-om demona, provjeravaju da li je korisnik ispravan i mijenjaju trenutni direktorij na glavni:

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

Postavlja se zadani put koji će se koristiti prilikom pokretanja procesa:

setenv("PATH", _PATH_DEFPATH, 1);

Nakon toga, proces se „demonizira“: stvara se dječja kopija procesa pozivom fork i nova sesija u dječjem procesu (poziv setsid). U roditeljskom procesu više nema potrebe — i on se zatvara:

switch (fork()) {
case -1:
    \/ * kritička greška i završetak rada *\/ 
    exit(0);
break;
case 0:
    \/ * dječji proces *\/ 
    (void) setsid();
break;
default:
    \/ * roditeljski proces završava rad *\/ 
    _exit(0);
}

Zatvaranjem roditeljskog procesa oslobađa se zaključavanje na datoteci za zaključavanje. Također, potrebno je ažurirati PID u datoteci na dječji. Nakon toga se popunjava baza zadataka:

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

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

Nakon toga, cron prelazi u glavni radni ciklus. No prije toga, vrijedi pogledati učitavanje popisa zadataka.

Prikupljanje i ažuriranje popisa zadataka

Za učitavanje popisa zadataka zadužena je funkcija load_database. Ona provjerava glavni sustavni crontab i direktorij s korisničkim datotekama. Ako su datoteke i direktorij ostali nepromijenjeni, popis zadataka se ne ponovno učitava. U suprotnom, počinje se formirati novi popis zadataka.

Učitavanje sustavne datoteke sa posebnim imenima datoteka i tablice:

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

Učitavanje korisničkih tablica u ciklusu:

while (NULL != (dp = readdir(dir))) {
    char    fname[MAXNAMLEN+1],
            tabname[MAXNAMLEN+1];
    \/ * ne treba učitavati datoteke s točkom *\/ 
    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);
}

Nakon toga, stara baza podataka se zamjenjuje novom.

U primjerima iznad poziv funkcije process_crontab provjerava postoji li korisnik koji odgovara imenu datoteke tablice (osim ako to nije superuser), nakon čega poziva load_user. Ova funkcija već čita samu datoteku redak po redak:

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

Ovdje se ili postavlja varijabla okruženja (nizovi oblika VAR=value) pomoću funkcija load_env / env_set, ili se čita opis zadatka (* * * * * /path/to/exec) pomoću funkcije load_entry.

Entitet entry koji vraća load_entry je naš zadatak, koji se stavlja u zajednički popis zadataka. U samoj funkciji se vrši detaljna analiza formata vremena, a nas više zanima formiranje varijabli okruženja i parametara za pokretanje zadatka:

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

S aktuelnim popisom zadataka funkcionira glavni ciklus.

Glavna petlja

Izvorni cron iz Version 7 Unix radio je vrlo jednostavno: u ciklusu je ponovo čitao konfiguraciju, pokretao superkorisničke zadatke te spavao do početka sljedeće minute. Ovaj jednostavan pristup zahtijevao je previše resursa na starim mašinama.

U SysV je predložena alternativna verzija, u kojoj je demon spavao ili do najbliže minute za koju je definiran zadatak, ili 30 minuta. Resursa za ponovo čitanje konfiguracije i provjeru zadataka u tom režimu je trošeno manje, ali brzo ažuriranje popisa zadataka postalo je nezgodno.

Vixie cron se vratio provjeri popisa zadataka svake minute, srećom, do kraja 80-ih resursa na standardnim Unix mašinama postalo je znatno više:

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

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

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

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

Izravnim izvršenjem zadataka bavi se funkcija cron_sleep, koja poziva funkcije job_runqueue (pretraga i pokretanje zadataka) i do_command (pokretanje svakog pojedinog zadatka). Posljednja funkcija se isplati detaljnije razraditi.

Pokretanje zadatka

Funkcija do_command implementirana je na dobar Unix način, to jest, za asinkrono izvršavanje zadatka koristi fork. Roditeljski proces nastavlja s pokretanjem zadataka, a dječji se bavi pripremom procesa zadatka:

switch (fork()) {
case -1:
    /*nije uspjelo izvršiti fork */
    break;
case 0:
    /* dječji proces: za svaki slučaj još jednom pokušavamo uhvatiti glavnu zaključavanje */
    acquire_daemonlock(1);
    /* prelazimo na formiranje procesa zadatka */
    child_process(e, u);
    /* po dovršenju, dječji proces završava rad */
    _exit(OK_EXIT);
    break;
default:
    /* roditeljski proces nastavlja rad */
    break;
}

U child_process postoji mnogo logike: on preuzima standardne tokove izlaza i grešaka kako bi ih zatim poslao na email (ako je u tabeli zadataka postavljena varijabla okruženja MAILTO), i konačno, čeka na završetak glavnog procesa zadatka.

Proces zadatka se formira još jednim fork-om:

switch (vfork()) {
case -1:
    /* pri grešci odmah se zatvara rad * /
    exit(ERROR_EXIT);
case 0:
    /* proces-dete formira novu seansu, terminal itd.
     */
    (void) setsid();

    /*
     * ovde sledi dugotrajno podešavanje izlaza procesa, preskočićemo radi sažetosti
     */

    /* promena direktorijuma, korisnika i grupe korisnika,
     * dakle proces više nije superkorisnički
     */
    setgid(e->gid);
    setuid(e->uid);
    chdir(env_get("HOME", e->envp));

    /* pokretanje same komande
     */
    {
        /* varijabla okruženja SHELL pokazuje na interpreter za pokretanje * /
        char    *shell = env_get("SHELL", e->envp);

        /* proces se pokreće bez prosleđivanja okruženja roditeljskog procesa,
         * dakle baš onako kako je opisano u tabeli zadataka korisnika  */
        execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);

        /* greška — i proces se nije pokrenuo? kraj rada * /
        perror("execl");
        _exit(ERROR_EXIT);
    }
    break;
default:
    /* sam proces nastavlja rad: čeka na završetak i izlaz * /
    break;
}

Eto, to je ukratko ceo cron. Neka zanimljiva detalja, poput računanja udaljenih korisnika, sam izostavio, ali glavnu stvar sam izložio.

Epilog

Cron — iznenađujuće jednostavan i koristan program, izveden u najboljim tradicijama Unix sveta. Ne radi ništa suvišno, ali svoju funkciju obavlja izvanredno već decenijama. Upoznavanje sa kodom verzije koja dolazi sa Ubuntu-om trajalo je manje od jednog sata, a uživao sam u njemu! Nadam se da sam mogao podeliti to iskustvo sa vama.

Ne znam kako vam, ali meni je pomalo tužno svesno da moderno programiranje sa svojom sklonosti ka prekompleksnosti i preabstrahiranju već dugo ne omogućava sličnu jednostavnost.

Postoji mnogo modernih alternativa cron-u: systemd-timers omogućavaju organizaciju složenih sistema sa zavisnostima, u fcron-u se može fleksibilnije regulisati potrošnja resursa od strane zadataka. Ali meni je lično uvek bilo dovoljno najjednostavnijeg crontab-a.

Jednom rečju, volite Unix, koristite jednostavne programe i ne zaboravite da čitate manove za vašu platformu!

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster