
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 nije opisano ponašanje demona, a samo je formaliziran korisnički program . 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 datotekeKada 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.logPrvih 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 -cIli crticom:
# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -cPristup 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:
- HOME — korisnička početna mapa.
- LOGNAME — korisničko ime.
- PATH — put koji se koristi za pronalaženje standardnih sistemskih alata.
- 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/execTablice 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/execMožete odrediti korak prema kojem se pokreću zadaci:
# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/execKoraci i intervalu se mogu kombinirati:
# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/execPodrž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/dailyOkruž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 . 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/execNa 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 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 , i .
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 , 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
— 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:
- Inicijalizacija programa.
- Prikupljanje i ažuriranje liste zadataka za pokretanje.
- Rad glavnog ciklusa crona.
- 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
