Klasik je napisao da sretni sati ne gledaju. U tim divljim vremenima nije bilo ni programera ni Unixa, ali danas programeri sigurno znaju: cron će pratiti vrijeme umjesto njih.
Pomoćni programi naredbenog retka za mene su i slabost i zadatak. sed, awk, wc, cut i drugi stari programi pokreću se skriptama na našim poslužiteljima svaki dan. Mnogi od njih dizajnirani su kao zadaci za cron, planer izvorno iz 70-ih.
Dugo sam koristio cron površno, ne ulazeći u detalje, ali jednog dana, kada sam naišao na grešku prilikom pokretanja skripte, odlučio sam to temeljito ispitati. Tako je nastao ovaj članak, dok sam ga pisao upoznao sam se s POSIX crontabom, glavnim cron opcijama u popularnim Linux distribucijama i strukturom nekih od njih.
Koristite li Linux i izvodite cron zadatke? Jeste li zainteresirani za arhitekturu aplikacija sustava u Unixu? Onda krećemo!
Periodično izvršavanje korisničkih ili sistemskih programa očigledna je potreba u svim operacijskim sustavima. Stoga su programeri davno shvatili potrebu za servisima koji im omogućuju centralno planiranje i izvršavanje zadataka.
Operativni sustavi slični Unixu vuku svoje podrijetlo od verzije 7 Unixa, razvijene 70-ih godina prošlog stoljeća u Bell Labsu, uključujući i slavnog Kena Thompsona. Verzija 7 Unixa također je uključivala cron, uslugu za redovito izvršavanje zadataka superkorisnika.
Tipičan moderni cron je jednostavan program, ali algoritam rada izvorne verzije bio je još jednostavniji: servis se budio jednom u minuti, čitao tablicu sa zadacima iz jedne datoteke (/etc/lib/crontab) i izvodio za superkorisnik oni zadaci koji su trebali biti izvršeni u trenutnom trenutku.
Nakon toga, poboljšane verzije jednostavne i korisne usluge isporučene su sa svim operativnim sustavima sličnim Unixu.
Generalizirani opisi crontab formata i osnovnih principa rada uslužnog programa uključeni su 1992. godine u glavni standard Unix-like operativnih sustava - POSIX, te je tako cron od standarda de facto postao standard de jure.
Godine 1987. Paul Vixie, nakon što je ispitao korisnike Unixa o njihovim željama za cron, izdao je drugu verziju demona koji je ispravio neke od problema tradicionalnog crona i proširio sintaksu tabličnih datoteka.
Do treće verzije Vixie cron je počeo ispunjavati zahtjeve POSIX-a, osim toga, program je imao liberalnu licencu, točnije licence uopće nije bilo, osim želja u README-u: autor ne daje jamstva, ime autora ne može se izbrisati, a program se može prodati samo zajedno s izvornim kodom. Pokazalo se da su ovi zahtjevi kompatibilni s načelima slobodnog softvera koji je tih godina dobivao na popularnosti, pa su neke od ključnih distribucija Linuxa koje su se pojavile početkom 90-ih uzele Vixie cron kao sustav i razvijaju ga i danas.
Konkretno, Red Hat i SUSE razvijaju fork Vixie cron - cronie, a Debian i Ubuntu koriste originalno izdanje Vixie cron s mnogo zakrpa.
Najprije se upoznajmo s korisničkim uslužnim programom crontab opisanim u POSIX-u, nakon čega ćemo pogledati proširenja sintakse u Vixie cron-u i korištenje varijacija Vixie cron-a u popularnim distribucijama Linuxa. I na kraju, trešnja na torti je analiza cron daemon uređaja.
POSIX crontab
Ako je izvorni cron uvijek radio za superkorisnika, moderni planeri često se bave zadacima običnih korisnika, što je sigurnije i praktičnije.
Crons se isporučuju kao skup od dva programa: stalno pokrenut cron daemon i crontab uslužni program dostupan korisnicima. Potonji vam omogućuje uređivanje tablica zadataka specifičnih za svakog korisnika u sustavu, dok demon pokreće zadatke iz korisničkih i sistemskih tablica.
В POSIX standard ponašanje daemona nije opisano ni na koji način i samo je korisnički program formaliziran crontab. Postojanje mehanizama za pokretanje korisničkih zadataka se, naravno, podrazumijeva, ali nije detaljno opisano.
Pozivanjem uslužnog programa crontab možete učiniti četiri stvari: urediti korisničku tablicu zadataka u uređivaču, učitati tablicu iz datoteke, prikazati trenutnu tablicu zadataka i očistiti tablicu zadataka. Primjeri kako radi uslužni program crontab:
crontab -e # редактировать таблицу задач
crontab -l # показать таблицу задач
crontab -r # удалить таблицу задач
crontab path/to/file.crontab # загрузить таблицу задач из файла
Na poziv crontab -e koristit će se uređivač naveden u standardnoj varijabli okruženja EDITOR.
Sami zadaci opisani su 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], gdje je 0 nedjelja. Posljednje, šesto, polje je linija koju će izvršiti standardni interpreter naredbi.
U prvih pet polja mogu se navesti vrijednosti odvojene zarezima:
# задача, выполняемая в первую и десятую минуты каждого часа
1,10 * * * * /path/to/exec -a -b -c
Ili s crticom:
# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -c
Korisnički pristup raspoređivanju zadataka reguliran je u POSIX-u datotekama cron.allow i cron.deny, koje navode korisnike s pristupom crontabu i korisnike koji nemaju pristup programu. Standard ni na koji način ne regulira mjesto tih datoteka.
Prema standardu, najmanje četiri varijable okruženja moraju se proslijediti pokrenutim programima:
HOME - početni imenik korisnika.
LOGNAME — prijava korisnika.
PATH je staza na kojoj možete pronaći standardne pomoćne programe sustava.
SHELL — put do korištenog tumača naredbi.
Naime, POSIX ne govori ništa o tome odakle dolaze vrijednosti za ove varijable.
Najprodavaniji - Vixie cron 3.0pl1
Zajednički predak popularnih cron varijanti je Vixie cron 3.0pl1, predstavljen na comp.sources.unix mailing listi 1992. godine. Detaljnije ćemo razmotriti glavne značajke ove verzije.
Vixie cron dolazi u dva programa (cron i crontab). Kao i obično, demon je odgovoran za čitanje i pokretanje zadataka iz tablice zadataka sustava i pojedinačnih tablica zadataka korisnika, a uslužni program crontab odgovoran je 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 tim da šesti stupac u njoj označava ime korisnika u čije ime se pokreće zadatak:
Redovne tablice korisničkih zadataka nalaze se u /var/cron/tabs/username i koriste istu sintaksu. Kada pokrenete uslužni program crontab kao korisnik, ovo su datoteke koje se uređuju.
Popisima korisnika s pristupom crontabu upravlja se u datotekama /var/cron/allow i /var/cron/deny, gdje samo trebate unijeti korisničko ime u poseban redak.
Proširena sintaksa
U usporedbi s POSIX crontabom, rješenje Paula Vixeya sadrži nekoliko vrlo korisnih izmjena sintakse tablica zadataka uslužnog programa.
Nova sintaksa tablice postala je dostupna: na primjer, možete navesti dane u tjednu ili mjesece po imenu (pon, uto i tako dalje):
# Запускается ежеминутно по понедельникам и вторникам в январе
* * * Jan Mon,Tue /path/to/exec
Možete odrediti korak kroz koji se zadaci pokreću:
# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/exec
Koraci i intervali mogu se miješati:
# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/exec
Podržane su intuitivne alternative uobičajenoj sintaksi (ponovno pokretanje, godišnje, godišnje, mjesečno, tjedno, dnevno, ponoć, svaki sat):
# Запускается после перезагрузки системы
@reboot /exec/on/reboot
# Запускается раз в день
@daily /exec/daily
# Запускается раз в час
@hourly /exec/daily
Okruženje izvršenja zadatka
Vixie cron omogućuje promjenu okruženja pokrenutih aplikacija.
Varijable okoline USER, LOGNAME i HOME nisu samo dane od demona, već su preuzete iz datoteke passwd. Varijabla PATH postavljena je na "/usr/bin:/bin", a varijabla SHELL na "/bin/sh". Vrijednosti svih varijabli osim LOGNAME mogu se mijenjati u korisničkim tablicama.
Neke varijable okruženja (najviše SHELL i HOME) koristi sam cron za pokretanje zadatka. Evo kako bi korištenje basha umjesto standardnog sh-a za pokretanje prilagođenih zadataka moglo izgledati:
SHELL=/bin/bash
HOME=/tmp/
# exec будет запущен bash-ем в /tmp/
* * * * * /path/to/exec
Naposljetku, sve varijable okruženja definirane u tablici (koje koristi cron ili su potrebne procesu) bit će proslijeđene zadatku koji se izvodi.
Za uređivanje datoteka, crontab koristi uređivač naveden u varijabli okruženja VISUAL ili EDITOR. Ako okruženje u kojem je pokrenut crontab nema definirane ove varijable, tada se koristi "/usr/ucb/vi" (ucb je vjerojatno Sveučilište Kalifornije, Berkeley).
cron na Debianu i Ubuntuu
Programeri Debiana i izvedenih distribucija objavili su visoko modificirana verzija Vixie cron verzija 3.0pl1. Nema razlika u sintaksi tabličnih datoteka; za korisnike je to isti Vixie cron. Najveća nova značajka: podrška syslog, Uklanjanje poteškoća и PAM.
Manje uočljive, ali opipljive promjene uključuju lokaciju konfiguracijskih datoteka i tablica zadataka.
Korisničke tablice u Debianu nalaze se u direktoriju /var/spool/cron/crontabs, sistemska tablica je još uvijek tamo - u /etc/crontab. Tablice zadataka specifičnih za Debian pakete smještene su u /etc/cron.d, odakle ih cron demon automatski čita. Kontrolu korisničkog pristupa kontroliraju datoteke /etc/cron.allow i /etc/cron.deny.
Zadana ljuska je i dalje /bin/sh, što je u Debianu mala ljuska kompatibilna s POSIX-om crtica, pokrenut bez čitanja konfiguracije (u neinteraktivnom načinu).
Sam Cron se u zadnjim verzijama Debiana pokreće preko systemd-a, a konfiguraciju pokretanja možete vidjeti u /lib/systemd/system/cron.service. Nema ničeg posebnog u konfiguraciji usluge; svako suptilnije upravljanje zadacima može se obaviti putem varijabli okruženja deklariranih izravno u crontabu svakog korisnika.
prijatelj na RedHatu, Fedori i CentOS-u
kroni — fork Vixie cron verzije 4.1. Kao iu Debianu, sintaksa se nije promijenila, ali je dodana podrška za PAM i SELinux, rad u klasteru, praćenje datoteka pomoću inotifyja i druge značajke.
Zadana konfiguracija je na uobičajenim mjestima: sistemska tablica je u /etc/crontab, paketi stavljaju svoje tablice u /etc/cron.d, korisničke tablice idu u /var/spool/cron/crontabs.
Daemon radi pod kontrolom systemd, konfiguracija usluge je /lib/systemd/system/crond.service.
Na distribucijama sličnim Red Hatu, /bin/sh se koristi prema zadanim postavkama pri pokretanju, što je standardni bash. Treba imati na umu da prilikom pokretanja cron poslova putem /bin/sh, bash shell se pokreće u POSIX-kompatibilnom načinu rada i ne čita nikakve dodatne konfiguracije, radeći u neinteraktivnom načinu rada.
cronie u SLES-u i openSUSE-u
Njemačka distribucija SLES i njezina izvedenica openSUSE koriste istog prijatelja. Daemon se ovdje također pokreće pod systemd, konfiguracija usluge nalazi se u /usr/lib/systemd/system/cron.service. Konfiguracija: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh je isti bash koji radi u POSIX-kompatibilnom neinteraktivnom načinu rada.
Vixie cron uređaj
Moderni potomci crona nisu se radikalno promijenili u usporedbi s Vixie cron, ali su ipak stekli nove značajke koje nisu potrebne za razumijevanje principa programa. Mnoga od ovih proširenja su loše dizajnirana i zbunjuju kod. Izvorni cron izvorni kod Paula Vixeya užitak je čitati.
Stoga sam odlučio analizirati cron uređaj na primjeru cron programa zajedničkog za obje grane razvoja - Vixie cron 3.0pl1. Pojednostavit ću primjere uklanjanjem ifdefova koji kompliciraju čitanje i izostavljanjem manjih detalja.
Rad demona može se podijeliti u nekoliko faza:
Inicijalizacija programa.
Prikupljanje i ažuriranje popisa zadataka za pokretanje.
Glavna cron petlja radi.
Pokrenite zadatak.
Pogledajmo ih redom.
Inicijalizacija
Kada se pokrene, nakon provjere argumenata procesa, cron instalira rukovatelje signalima SIGCHLD i SIGHUP. Prvi pravi unos u dnevnik o prekidu podređenog procesa, drugi zatvara deskriptor datoteke dnevnika:
Cron demon uvijek radi sam na sustavu, samo kao superkorisnik i iz glavnog cron direktorija. Sljedeći pozivi stvaraju datoteku zaključavanja s PID-om daemon procesa, uvjeravaju se da je korisnik ispravan i mijenjaju trenutni direktorij u glavni:
Postavlja se zadana staza koja će se koristiti prilikom pokretanja procesa:
setenv("PATH", _PATH_DEFPATH, 1);
Zatim se proces “demonizira”: stvara podređenu kopiju procesa pozivanjem fork-a i nove sesije u podređenom procesu (pozivanjem setsid). Nadređeni proces više nije potreban i izlazi:
switch (fork()) {
case -1:
/* критическая ошибка и завершение работы */
exit(0);
break;
case 0:
/* дочерний процесс */
(void) setsid();
break;
default:
/* родительский процесс завершает работу */
_exit(0);
}
Prekid nadređenog procesa otključava datoteku zaključavanja. Osim toga, potrebno je ažurirati PID u datoteci za dijete. Nakon toga se popunjava baza zadataka:
Zatim cron prelazi na glavni radni ciklus. Ali prije toga, vrijedi pogledati učitavanje popisa zadataka.
Prikupljanje i ažuriranje popisa zadataka
Funkcija load_database odgovorna je za učitavanje popisa zadataka. Provjerava crontab glavnog sustava i direktorij s korisničkim datotekama. Ako se datoteke i direktorij nisu promijenili, popis zadataka se ne čita ponovno. U protivnom se počinje formirati novi popis zadataka.
Učitavanje sistemske datoteke s posebnim nazivima datoteka i tablica:
/* если файл системной таблицы изменился, перечитываем */
if (syscron_stat.st_mtime) {
process_crontab("root", "*system*",
SYSCRONTAB, &syscron_stat,
&new_db, old_db);
}
Učitavanje korisničkih tablica u petlji:
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);
}
Nakon toga se stara baza podataka zamjenjuje novom.
U gornjim primjerima, poziv funkcije process_crontab provjerava postoji li korisnik koji odgovara nazivu datoteke tablice (osim ako nije superkorisnik), a zatim poziva load_user. Potonji već čita samu datoteku red po red:
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;
}
}
Ovdje se ili postavlja varijabla okruženja (redovi u obliku VAR=vrijednost) pomoću funkcija load_env / env_set ili se opis zadatka čita (* * * * * /path/to/exec) pomoću funkcije load_entry.
Entitet unosa koji load_entry vraća je naš zadatak, koji se nalazi na općem popisu zadataka. Sama funkcija provodi detaljnu analizu formata vremena, ali nas više zanima formiranje varijabli okruženja i parametara pokretanja 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);
Glavna petlja radi s trenutnim popisom zadataka.
Glavna petlja
Izvorni cron iz verzije 7 Unixa radio je prilično jednostavno: ponovno je čitao konfiguraciju u petlji, pokretao zadatke tekuće minute kao superkorisnik i spavao do početka sljedeće minute. Ovaj jednostavan pristup na starijim strojevima zahtijevao je previše resursa.
U SysV-u je predložena alternativna verzija, u kojoj je demon odlazio u stanje mirovanja ili do najbliže minute za koju je zadatak definiran, ili do 30 minuta. Potrošeno je manje resursa za ponovno čitanje konfiguracije i provjeru zadataka u ovom načinu rada, ali brzo ažuriranje popisa zadataka postalo je nezgodno.
Vixie cron vratio se provjeravanju popisa zadataka jednom u minuti, srećom do kraja 80-ih bilo je znatno više resursa na standardnim Unix strojevima:
/* первичная загрузка задач */
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 izravno je uključena u izvršavanje zadataka, pozivajući funkcije job_runqueue (nabrajanje i pokretanje zadataka) i do_command (izvođenje svakog pojedinačnog zadatka). Posljednju funkciju vrijedi detaljnije ispitati.
Pokretanje zadatka
Funkcija do_command izvodi se u dobrom stilu Unixa, to jest, radi račvanje za asinkrono izvođenje zadatka. Roditeljski proces nastavlja s pokretanjem zadataka, podređeni proces priprema proces zadatka:
switch (fork()) {
case -1:
/*не смогли выполнить fork */
break;
case 0:
/* дочерний процесс: на всякий случай еще раз пробуем захватить главный лок */
acquire_daemonlock(1);
/* переходим к формированию процесса задачи */
child_process(e, u);
/* по завершению дочерний процесс заканчивает работу */
_exit(OK_EXIT);
break;
default:
/* родительский процесс продолжает работу */
break;
}
Postoji dosta logike u child_processu: prihvaća standardni izlaz i tokove pogrešaka, zatim ih šalje na mail (ako je varijabla okoline MAILTO navedena u tablici zadatka), i na kraju čeka da glavni proces zadatka potpuna.
Proces zadatka formira još jedna vilica:
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 zapravo sve što cron jest. Izostavio sam neke zanimljive detalje, na primjer, računovodstvo za udaljene korisnike, ali sam iznio ono glavno.
pogovor
Cron je iznenađujuće jednostavan i koristan program, napravljen u najboljim tradicijama Unix svijeta. Ona ne radi ništa posebno, ali već nekoliko desetljeća odlično radi svoj posao. Proučavanje koda za verziju koja dolazi s Ubuntuom nije trajalo više od sat vremena i jako sam se zabavio! Nadam se da sam to uspio podijeliti s vama.
Ne znam za vas, ali ja sam pomalo tužan kada shvatim da moderno programiranje, sa svojom tendencijom prekompliciranja i apstrahiranja, već dugo nije bilo pogodno za takvu jednostavnost.
Postoji mnogo modernih alternativa cronu: systemd-timeri omogućuju organiziranje složenih sustava s ovisnostima, fcron vam omogućuje fleksibilniju regulaciju potrošnje resursa po zadacima. Ali osobno, najjednostavniji crontab mi je uvijek bio dovoljan.
Ukratko, volite Unix, koristite jednostavne programe i ne zaboravite pročitati manu za svoju platformu!