
Klasik je pisal, da srečni ure ne opazujejo. V tistih divjih časih še ni bilo programerjev, niti Unix-a, a v današnjih dneh programerji trdno vedo: namesto njih bo čas spremljal cron.
Orodja ukazne vrstice so zame hkrati šibkost in rutina. sed, awk, wc, cut in drugi stari programi se vsakodnevno izvajajo s skripti na naših strežnikih. Mnogi od njih so bili oblikovani v naloge za cron, načrtovalec iz 70-ih.
Dolge časa sem površno uporabljal cron, ne da bi se poglobil v podrobnosti, a enkrat, ko sem se srečal z napako pri zagonu skripte, sem se odločil, da se temeljito raziščem. Tako je nastal ta članek, med pisanjem katerega sem se seznanil s POSIX crontab, osnovnimi različicami cron v priljubljenih distribucijah Linuxa in strukturo nekaterih od njih.
Uporabljate Linux in izvajate naloge v cron? Vas zanima arhitektura sistemskih aplikacij v Unixu? Torej smo na pravi poti!
Vsebina
Izvor vrst
Periodično izvajanje uporabniških ali sistemskih programov je očitna potreba v vseh operacijskih sistemih. Zato so programerji to potrebo za storitve, ki omogočajo centralizirano načrtovanje in izvajanje nalog, prepoznali že dolgo nazaj.
Unix-podobni operacijski sistemi izvirajo iz Version 7 Unix, ki jo je v 70. letih prejšnjega stoletja razvilo podjetje Bell Labs, tudi slavni Ken Thompson. Skupaj z Version 7 Unix je bil dostavljen tudi cron, storitev za redno izvajanje nalog superuporabnika.
Tipičen sodoben cron je enostaven program, vendar je bila algoritma dela originalne različice še enostavnejša: storitev se je prebujala enkrat na minuto, prebrala je tabelo nalog iz enega samega datoteka (/etc/lib/crontab) in izvajala naloge superuporabnika, ki jih je bilo treba opraviti v trenutni minuti.
Kasneje so izboljšane različice preprostega in uporabnega servisa prihajale z vsemi Unix-podobnimi operacijskimi sistemi.
Poenostavljeni opisi formata crontab in osnovnih načel delovanja orodja so bili leta 1992 vključeni v glavni standard Unix-podobnih operacijskih sistemov — POSIX — in tako je cron iz de facto standarda postal de jure standard.
V letu 1987 je Paul Vixie, ki je povpraševal uporabnike Unixa o željah glede cron, izdal še eno različico demona, ki odpravlja nekatere težave tradicionalnih cron in širi sintakso datotečnih tabel.
Do tretje različice je Vixie cron izpolnjeval zahteve POSIX, poleg tega je imel liberalno licenco, pravzaprav ni imel nobene licence, razen želja v README: avtor ne daje garancij, ime avtorja se ne sme odstranjati, program pa se lahko prodaja samo skupaj z izvorno kodo. Te zahteve so se izkazale za združljive z načeli, ki so v tistih letih pridobivala priljubljenost, zato so nekatere ključne distribucije Linuxa, ki so se pojavile na začetku 90-ih, vzele Vixie cron kot sistemsko orodje in jih še vedno razvijajo.
Specifično, Red Hat in SUSE razvijata fork Vixie cron - cronie, medtem ko Debian in Ubuntu uporabljata izvirno izdajo Vixie cron z mnogimi popravki.
Najprej se spoznamo s uporabniškim orodjem crontab, opisanim v POSIX, nato pa bomo obravnavali razširitve sintakse, predstavljene v Vixie cron, in uporabo različic Vixie cron v priljubljenih distribucijah Linuxa. In končno, češka na torti - pregled delovanja demona cron.
POSIX crontab
Če je izvirni cron vedno deloval za superuporabnika, sodobni časovni načrtovalniki pogosto obravnavajo naloge navadnih uporabnikov, kar je bolj varno in priročno.
Cron-i prihajajo kot komplet dveh programov: nenehno delujočega demona cron in orodja crontab, dostopnega uporabnikom. Zadnje omogoča urejanje tabel nalog, specifičnih za vsakega uporabnika v sistemu, demon pa zažene naloge iz uporabniških in sistemskih tabel.
V ne opisuje vedenja demona in formalizira le uporabniški program . Obstoječi mehanizmi za zagon nalog uporabnikov se seveda predpostavljajo, vendar niso podrobno opisani.
Z klicem orodja crontab lahko naredite štiri stvari: uredite uporabniško tabelo nalog v urejevalniku, naložite tabelo iz datoteke, prikažete trenutno tabelo nalog in očistite tabelo nalog. Primeri delovanja orodja crontab:
crontab -e # urejanje tabele nalog
crontab -l # prikaz tabele nalog
crontab -r # izbris tabele nalog
crontab path/to/file.crontab # nalaganje tabele nalog iz datotekeOb klicu crontab -e bodo se uporabljati urejevalnik, določen v standardni spremenljivki okolja UREJEVALEC.
Naloge so opisane v naslednjem 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 polj zapisov: minute [1..60], ure [0..23], dnevi v mesecu [1..31], meseci [1..12], dnevi v tednu [0..6], kjer je 0 — nedelja. Zadnja, šesta, vrstica je niz, ki ga bo izvedel standardni interpretator ukazov.
V prvih petih poljih so vrednosti lahko naštevanje ločene z vejico:
# задача, выполняемая в первую и десятую минуты каждого часа
1,10 * * * * /path/to/exec -a -b -cAli z daljico:
# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -cDostop uporabnikov do načrtovanja nalog se ureja v datotekah POSIX cron.allow in cron.deny, v katerih so naštevanje, ustrezno, uporabniki z dostopom do crontab in uporabniki brez dostopa do programa. Lokacija teh datotek standard ne ureja.
Zaganjanim programom je po standardu potrebno prenesti vsaj štiri spremenljivke okolja:
- DOMAČA — domači imenik uporabnika.
- LOGNAME — prijavno ime uporabnika.
- PATH — pot, po kateri lahko najdete standardne sisteme.
- SHELL — pot do uporabljenega interpretatorja ukazov.
Opazno je, da POSIX nič ne pove o tem, od kod izvirajo vrednosti za te spremenljivke.
Best seller — Vixie cron 3.0pl1
Skupni prednik priljubljenih različic cron — Vixie cron 3.0pl1, predstavljen v razdelku comp.sources.unix leta 1992. Osnovne funkcije te različice bomo podrobneje preučili.
Vixie cron je na voljo v dveh programih (cron in crontab). Kot običajno, demon odgovarja za branje in zagon nalog iz sistemske tabele nalog in tabel nalog posameznih uporabnikov, medtem ko je utilita crontab odgovorna za urejanje uporabniških tabel.
Tabela nalog in konfiguracijske datoteke
Tabela nalog superuporabnika se nahaja v /etc/crontab. Sintaksa sistemske tabele ustreza sintaksi Vixie cron, s tem, da je v njej šesta stolpec navedeno ime uporabnika, v imenu katerega se naloga zažene:
# Запускается ежеминутно от пользователя vlad
* * * * * vlad /path/to/execTabele nalog običajnih uporabnikov se nahajajo v /var/cron/tabs/ime_uporabnika in uporabljajo splošno sintakso. Ob zagonu utilite crontab v imenu uporabnika se prav ti datoteki urejajo.
Upravljanje seznamov uporabnikov, ki imajo dostop do crontab, se izvaja v datotekah /var/cron/allow in /var/cron/deny, kamor je dovolj, da vnesete ime uporabnika z ločeno vrsto.
Razširjena sintaksa
V primerjavi s POSIX crontab rešitev Pola Vixija vključuje več zelo koristnih modifikacij v sintaksi tabel nalog utilite.
Na voljo je nova sintaksa tabel: na primer, lahko določite dni v tednu ali mesece po imenu (Mon, Tue itd.):
# Запускается ежеминутно по понедельникам и вторникам в январе
* * * Jan Mon,Tue /path/to/execLahko določite korak, po katerem se naloge zagonijo:
# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/execKorake in intervale je mogoče mešati:
# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/execPodprte so intuitivne alternative običajni sintaksi (reboot, yearly, annually, monthly, weekly, daily, midnight, hourly):
# Запускается после перезагрузки системы
@reboot /exec/on/reboot
# Запускается раз в день
@daily /exec/daily
# Запускается раз в час
@hourly /exec/dailyIzvajalno okolje nalog
Vixie cron omogoča spreminjanje okolja zagonjenih aplikacij.
Spremenljivke okolja USER, LOGNAME in HOME ne zagotavlja le demon, temveč jih prevzame iz datoteke . Spremenljivka PATH dobi vrednost "\/usr\/bin:\/bin", SHELL pa "\/bin\/sh". Vse vrednosti spremenljivk, razen LOGNAME, je mogoče spremeniti v uporabniških tabelah.
Nekatere spremenljivke okolja (predvsem SHELL in HOME) uporablja sam cron za zagon naloge. Tako bi lahko izgledalo uporabo bash namesto standardnega sh za zagon uporabniških nalog:
SHELL=\/bin\/bash
HOME=\/tmp\/\n# exec bo zagnan z bash-om v \/tmp\/\n* * * * * \/path\/to\/execNa koncu bodo vse spremenljivke okolja, določene v tabeli (ki jih uporablja cron ali so potrebne procesu), prenesene na zagnano nalogo.
Za urejanje datotek se uporablja orodje crontab z urejevalnikom, določenim v spremenljivki okolja VISUAL ali EDITOR. Če v okolju, kjer je bil zagnan crontab, ti spremenljivki nista določeni, se uporabi "\/usr\/ucb\/vi" (ucb najverjetneje pomeni University of California, Berkeley).
cron v Debian in Ubuntu
Razvijalci Debiana in izpeljanih distribucij so izdali različico Vixie cron 3.0pl1. Ni razlik v sintaksi datotek, za uporabnike je to enak Vixie cron. Največje nove funkcionalnosti: podpora , in .
Med manj opaznimi, a otipljivimi spremembami so lokacije konfiguracijskih datotek in tabel nalog.
Uporabniške tabele v Debiana so v imeniku \/var\/spool\/cron\/crontabs, sistemska tabela pa je še vedno v \/etc\/crontab. Tabele nalog specifične za pakete Debian so postavljene v \/etc\/cron.d, od koder jih demon cron samodejno prebere. Uporabniški dostop se ureja z datotekama \/etc\/cron.allow in \/etc\/cron.deny.
Kot privzeta ukazna lupina še vedno uporabljamo \/bin\/sh, ki ga v Debiana zastopa majhna POSIX-združljiva lupina , zagnana brez branja kakršne koli konfiguracije (v neinteraktivnem načinu).
Cron v najnovših verzijah Debian se zažene prek systemd, konfiguracijo zažene pa lahko pogledate v /lib/systemd/system/cron.service. V konfiguraciji storitve ni nič posebnega, vsako bolj fino upravljanje nalog je mogoče izvesti preko okolijskih spremenljivk, napovedanih neposredno v crontab vsakega uporabnika.
cronie v RedHat, Fedora in CentOS
je fork Vixie cron različice 4.1. Tako kot v Debianu se sintaksa ni spremenila, dodana pa je bila podpora za PAM in SELinux, delovanje v klasterju, spremljanje datotek s pomočjo inotify in druge možnosti.
Privzeta konfiguracija se nahaja na običajnih mestih: sistemska tabela — v /etc/crontab, paketi postavijo svoje tabele v /etc/cron.d, uporabniške tabele pa pridejo v /var/spool/cron/crontabs.
Demon se zažene pod upravljanjem systemd, konfiguracija storitve — /lib/systemd/system/crond.service.
V Red Hat-podobnih distribucijah se pri zagonu privzeto uporablja /bin/sh, katerega vlogo prevzema standardni bash. Treba je opozoriti, da se pri zagonu nalog cron preko /bin/sh ukazna lupina bash zažene v načinu združljivega s POSIX in ne prebere nobene dodatne konfiguracije, deluje pa v neinteraktivnem načinu.
cronie v SLES in openSUSE
Nemška distribucija SLES in njeni derivati openSUSE še vedno uporabljajo cronie. Demon se tukaj prav tako zažene pod systemd, konfiguracija storitve leži v /usr/lib/systemd/system/cron.service. Konfiguracija: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. V vlogi /bin/sh deluje isti bash, zagnan v neinteraktivnem načinu združljivem s POSIX.
Struktura Vixie cron
Sodobni potomci crona v primerjavi z Vixie cronom niso radikalno spremenjeni, a vseeno so pridobili nove možnosti, ki niso potrebne za razumevanje načel delovanja programa. Mnoga od teh razširitev so oblikovana nepregledno in zmedejo kodo. Originalni izvorni kodec crona v izvedbi Paula Vixieja je branje čista užitek.
Zato sem se odločil, da bom razčlenitev delovanja crona izvedel na primeru skupne različice za obe veji razvoja programa cron — Vixie cron 3.0pl1. Primere bom poenostavil, odstranil bom zaplete, ki otežujejo branje, ifdef-e in izpustil stranske podrobnosti.
Delo demona lahko razdelimo na več faz:
- Inicijalizacija programa.
- Zbiranje in posodabljanje seznama nalog za zagon.
- Delovanje glavnega cikla crona.
- Zagon naloge.
Razdelimo jih po vrsti.
Inicijalizacija
При запуску после проверки аргумента procesa cron устанавливает обработчики сигналов SIGCHLD и SIGHUP. Первый записывает в журнал информацию о завершении работы дочернего процесса, а второй закрывает файловый дескриптор файла журнала:
signal(SIGCHLD, sigchld_handler);
signal(SIGHUP, sighup_handler);Демон cron в системе всегда работает один, только в роли суперпользователя и из главной директории cron. Следующие вызовы создают файл блокировки с PID процесса-демона, проверяют правильность пользователя и меняют текущую директорию на главную:
acquire_daemonlock(0);
set_cron_uid();
set_cron_cwd();Устанавливается путь по умолчанию, который будет использоваться при запуске процессов:
setenv("PATH", _PATH_DEFPATH, 1);Затем процесс «демонизируется»: создаётся дочерняя копия процесса с помощью вызова fork и новая сессия в дочернем процессе (вызов setsid). В родительском процессе больше необходимости нет — и он завершает работу:
switch (fork()) {
case -1:
\/\* критическая ошибка и завершение работы *\/
exit(0);
break;
case 0:
\/\* дочерний процесс *\/
(void) setsid();
break;
default:
\/\* родительский процесс завершает работу *\/
_exit(0);
}
Завершение родительского процесса освобождает лок на файле блокировки. Кроме того, требуется обновить PID в файле на дочерний. После этого заполняется база задач:
/* повторный захват лока */
acquire_daemonlock(0);
/* Заполнение БД */
database.head = NULL;
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);Далее cron переходит к главному циклу работы. Но перед этим стоит взглянуть на загрузку списка задач.
Сбор и обновление списка задач
За загрузку списка задач отвечает функция load_database. Она проверяет главный системный crontab и директорию с пользовательскими файлами. Если файлы и директория не менялись, то список задач не перечитывается. В противном случае начинает формироваться новый список задач.
Загрузка системного файла со специальными именами файла и таблицы:
/* если файл системной таблицы изменился, перечитываем */
if (syscron_stat.st_mtime) {
process_crontab("root", "*system*",
SYSCRONTAB, &syscron_stat,
&new_db, old_db);
}Загрузка пользовательских таблиц в цикле:
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);
}
После чего старая база данных заменяется новой.
В примерах выше вызов функции process_crontab убеждается в существовании пользователя, соответствующего имени файла таблицы (если только это не суперпользователь), после чего вызывает load_user. Последняя уже читает сам файл построчно:
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;
}
}Tuk se bodisi nastavi okoljska spremenljivka (niz tipa VAR=value) z funkcijama load_env / env_set, bodisi prebere opis naloge (* * * * * /pot/do/izvedbe) z funkcijo load_entry.
Entiteta entry, ki jo vrne load_entry, je pravzaprav naš opravek, ki ga vstavimo v skupni seznam nalog. V sami funkciji se izvede obsežno razčlenjevanje časa, nas pa bolj zanima oblikovanje okoljskih spremenljivk in parameter za zagon 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);Glavni cikel se ukvarja s trenutnim seznamom nalog.
Glavni cikel
Izvirni cron iz različice 7 Unix je deloval zelo preprosto: v ciklu je prebral konfiguracijo, izvajal naloge trenutne minute kot superuporabnik in spal do začetka naslednje minute. Ta preprost pristop je na starih strojih zahteval preveč virov.
V SysV je bila predlagana alternativna različica, v kateri je demon zaspal bodisi do najbližje minute, za katero je bila določena naloga, bodisi za 30 minut. Virov za ponovno branje konfiguracije in preverjanje nalog v tem načinu je bilo porabljenih manj, toda hitro posodabljanje seznama nalog je postalo neprijetno.
Vixie cron se je vrnil k preverjanju seznamov nalog enkrat na minuto, na srečo pa so proti koncu 80-ih virov na standardnih Unix-strojih postali znatno več:
/* первичная загрузка задач */
load_database(&database);
/* запустить задачи, поставленные к выполнению после перезагрузки системы */
run_reboot_jobs(&database);
/* сделать TargetTime началом ближайшей минуты */
cron_sync();
while (TRUE) {
/* выполнить задачи, после чего спать до TargetTime с поправкой на время, потраченное на задачи */
cron_sleep();
/* перечитать конфигурацию */
load_database(&database);
/* собрать задачи для данной минуты */
cron_tick(&database);
/* перевести TargetTime на начало следующей минуты */
TargetTime += 60;
}
Za neposredno izvrševanje nalog skrbi funkcija cron_sleep, ki kliče funkcije job_runqueue (preverjanje in zagon nalog) in do_command (zagon vsake posamezne naloge). Ta zadnja funkcija zahteva podrobnejšo razpravo.
Zagon naloge
Funkcija do_command je zasnovana v dobrem Unix slogu, torej za asinhrono izvajanje naloge ustvari fork. Starševski proces nadaljuje z zagonom nalog, otroški pa se posveča pripravi procesa naloge:
switch (fork()) {
case -1:
/* nismo mogli izvesti forka */
break;
case 0:
/* otroški proces: za vsak slučaj še enkrat poskusimo pridobiti glavno zaklepanje */
acquire_daemonlock(1);
/* preidemo k oblikovanju procesa naloge */
child_process(e, u);
/* ob zaključku otrok zaključuje delo */
_exit(OK_EXIT);
break;
default:
/* starševski proces nadaljuje z delom */
break;
}V child_process je precej logike: sprejema standardne tokove izhoda in napak ter jih nato pošlje po elektronski pošti (če je v tabeli nalog navedena okoljska spremenljivka MAILTO), končno pa čaka na zaključek glavnega procesa naloge.
Proces naloge se oblikuje še z enim fork:
switch (vfork()) {
case -1:
/* v primeru napake se delo takoj konča */
exit(ERROR_EXIT);
case 0:
/* otrok proces oblikuje novo sejo, terminal itd.
*/
(void) setsid();
/*
* naprej obsežna nastavitev izhoda procesa, preskočimo za krajšo razlago
*/
/* sprememba direktorija, uporabnika in skupine uporabnika,
* torej proces ne bo več superuporabniški
*/
setgid(e->gid);
setuid(e->uid);
chdir(env_get("HOME", e->envp));
/* zagon same ukazne vrstice
*/
{
/* okoljska spremenljivka SHELL označuje interpret za zagon */
char *shell = env_get("SHELL", e->envp);
/* proces se zažene brez prenosa okolja starševskega procesa,
* torej točno tako, kot je opisano v tabeli nalog uporabnika */
execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
/* napaka — proces se ni zagnal? končanje dela */
perror("execl");
_exit(ERROR_EXIT);
}
break;
default:
/* sam proces nadaljuje z delom: čaka na zaključek in izhod */
break;
}In to je v glavnem vse o cron-u. Opravil sem nekaj zanimivih podrobnosti, na primer o upoštevanju oddaljenih uporabnikov, a glavno sem predstavil.
Zadnja misel
Cron je presenetljivo preprost in koristen program, narejen v najboljših tradicijah sveta Unix. Ne dela nič odveč, a svoje delo izvaja odlično že več desetletij. Seznanitev s kodo tiste različice, ki se dobavlja z Ubuntu, je trajala le eno uro, ob tem pa sem se izjemno zabaval! Upam, da sem to veselje delil tudi z vami.
Ne vem, kako je vam, a zdi se mi nekoliko žalostno spoznavati, da sodobno programiranje s svojo nagnjenjem k zapletanju in prenapredovanju že dolgo ne nudi take preprostosti.
Obstaja veliko sodobnih alternativ cron-u: systemd-timers omogočajo organizacijo zapletenih sistemov z odvisnostmi, fcron pa omogoča bolj prilagodljivo upravljanje porabe virov nalog. A osebno mi je vedno zadostoval najprej crontab.
Skratka, ljubite Unix, uporabljajte preproste programe in ne pozabite brati manov za vašo platformo!
Izvor: habr.com
