
A klasszikus azt írta, hogy a boldog emberek nem nézik az órát. Azokban a vad időkben még nem volt sem programozó, sem Unix, de manapság a programozók tudják: a cron fog vigyázni az időre helyettük.
A parancssori segédprogramok egyidejűleg gyengéim és rutinom. A sed, awk, wc, cut és más régi programok naponta futnak szervereinken script segítségével. Sokuk feladatként van beállítva a cron-ban, amely egy 70-es évekből származó ütemező.
Hosszan csak felületesen használtam a cront, anélkül hogy mélyebbre ásnék, de egyszer, amikor hibába ütköztem egy script futtatásakor, úgy döntöttem, hogy alaposan utánanézek. Így született ez a cikk, amely közben megismertem a POSIX crontabbal, a cron főbb változataival a népszerű Linux disztribúciókban és annak néhány felépítésével.
Linuxot használsz és cronban indítasz feladatokat? Érdekel a Unix rendszerszoftverek architektúrája? Akkor nekünk útban kell lennünk!
Tartalom
A fajok eredete
A felhasználói vagy rendszerszoftverek időszakos futtatása nyilvánvaló szükséglet az összes operációs rendszerben. Ezért a programozók már régóta felismerték a központosított feladatütemező és végrehajtó szolgáltatások iránti igényt.
A Unix-szerű operációs rendszerek a 70-es években a Bell Labs-nél kifejlesztett Version 7 Unix-ból származnak, amelyet többek között a híres Ken Thompson is készített. A Version 7 Unix-hez tartozott a cron, a felhasználói adminisztrátor feladatainak rendszeres végrehajtására szolgáló szolgáltatás.
A tipikus modern cron nem bonyolult program, de az eredeti változat működési algoritmus még egyszerűbb volt: a szolgáltatás egy percenként felébredt, olvasta a feladatok táblázatát az egyetlen fájlból (\/etc\/lib\/crontab) és végrehajtotta azokat a feladatokat, amelyeket az aktuális percben hajtani kellett.
Később a javított változatokat, a hasznos szolgáltatást minden Unix-szerű operációs rendszerrel együtt szállították.
A crontab formátumának általános leírásai és az eszköz alapvető működési elvei 1992-ben bekerültek a Unix-szerű operációs rendszerek fő szabványába — POSIX — és így a cron de facto szabványból de jure szabvány lett.
1987-ben Paul Vixie, kérdéseket intézve a Unix felhasználókhoz a cron iránti elképzeléseikről, kiadott egy újabb verziót a démonból, amely javította a hagyományos cron egyes problémáit és bővítette a táblázatfájlok szintaxisát.
A Vixie cron harmadik verziója megfelelt a POSIX követelményeinek, ráadásul a program liberális licenc alatt állt, vagyis valójában semmilyen licenc nem volt, kivéve a README-ben található kívánalmakat: az író nem ad garanciát, az író nevét eltávolítani nem lehet, és a programot csak a forráskóddal együtt lehet értékesíteni. Ezek a követelmények összhangban álltak a népszerű szabad szoftver elveivel, így az 1990-es évek elején megjelent néhány kulcsfontosságú Linux disztribúció a Vixie cront választotta rendszerük ellenőrző programjául, és azóta is fejlesztik.
Különösen a Red Hat és a SUSE a Vixie cron forkját, a croniet fejlesztették, míg a Debian és az Ubuntu az eredeti Vixie cron kiadást használja, számos javítással.
Először is ismerkedjünk meg a POSIX-ben leírt felhasználói eszközzel, a crontab-bal, majd megvizsgáljuk a Vixie cron által bemutatott szintaxisbővítmények használatát, valamint a Vixie cron variációit a népszerű Linux disztribúciókban. Végül pedig a hab a tortán — a cron démon felépítésének elemzése.
POSIX crontab
Míg az eredeti cron mindig a superuser számára működött, a modern ütemezők gyakrabban foglalkoznak a normál felhasználók feladataival, ami biztonságosabb és kényelmesebb.
A cronok egy két programból álló csomagban érkeznek: a folyamatosan futó cron démonból és a felhasználók számára elérhető crontab eszközből. Az utóbbi lehetővé teszi az egyes felhasználók specifikus feladat táblázatainak szerkesztését, míg a démon végrehajtja a feladatokat a felhasználói és a rendszertábla táblázatából.
A nem írja le a démon viselkedését, és csak a felhasználói program van formalizálva. . A felhasználói feladatok indításának mechanizmusának létezése természetesen implicit módon fel van tételezve, de nem kerül részletesen leírásra.
A crontab eszköz hívásával négy dolgot lehet tenni: szerkeszteni a felhasználói feladat táblázatot egy szerkesztőben, fájlból betölteni a táblázatot, megjeleníteni az aktuális feladat táblázatot, és törölni a feladat táblázatot. A crontab eszköz használatának példái:
crontab -e # feladat táblázat szerkesztése
crontab -l # feladat táblázat megjelenítése
crontab -r # feladat táblázat törlése
crontab path/to/file.crontab # feladat táblázat betöltése fájlbólEz megtehető JSON formátumban is. crontab -e az a szerkesztő fog használni, amelyet a szabványos környezeti változó határoz meg EDITOR.
A feladatok maguk a következő formátumban vannak leírva:
# строки-комментарии игнорируются
#
# задача, выполняемая ежеминутно
* * * * * /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.logAz első öt mező a bejegyzésekben: percek [1..60], órák [0..23], hónap napjai [1..31], hónapok [1..12], hét napjai [0..6], ahol 0 — vasárnap. Az utolsó, hatodik mező — egy karakterlánc, amelyet a szabványos parancsértelmező hajt végre.
Az első öt mezőben az értékeket vesszővel lehet felsorolni:
# задача, выполняемая в первую и десятую минуты каждого часа
1,10 * * * * /path/to/exec -a -b -cVagy kötőjellel:
# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -cA felhasználók hozzáférését a feladatütemezéshez a POSIX fájlok cron.allow és cron.deny szabályozza, amelyekben a hozzáféréssel rendelkező felhasználók, illetve a programhoz való hozzáféréssel nem rendelkező felhasználók szerepelnek. E fájlok helyét a szabvány nem szabályozza.
A futtatandó programoknak a szabvány szerint legalább négy környezeti változót kell átadni:
- HOME — a felhasználó otthoni könyvtára.
- LOGNAME — a felhasználó bejelentkezési neve.
- PATH — az útvonal, ahol a rendszer standard segédprogramjait megtalálhatjuk.
- SHELL — az alkalmazott parancsértelmező útvonala.
Érdekes, hogy a POSIX nem mond semmit arról, honnan származnak e változók értékei.
Best seller — Vixie cron 3.0pl1
A cron népszerű változatainak közös őséje a Vixie cron 3.0pl1, amelyet a comp.sources.unix hírlevélben mutattak be 1992-ben. E verzió főbb lehetőségeit fogjuk részletesebben áttekinteni.
A Vixie cron két programban (cron és crontab) érhető el. Mint általában, a démon felelős a feladatok olvasásáért és futtatásáért a rendszer feladatlistájából és a felhasználók egyéni feladatlistáiból, míg a crontab segédprogram a felhasználói táblák szerkesztéséért felelős.
Feladatlista és konfigurációs fájlok
A superuser feladatlistája az /etc/crontab útvonalon található. A rendszer táblájának szintaxisa megfelel a Vixie cron szintaxisának azzal a kiegészítéssel, hogy a hatodik oszlopban a felhasználó neve szerepel, akinek nevében a feladatot futtatják:
# Запускается ежеминутно от пользователя vlad
* * * * * vlad /path/to/execA normál felhasználók feladatlistái az /var/cron/tabs/username útvonalon találhatók, és közös szintaxist használnak. A crontab segédprogram indításakor éppen ezeket a fájlokat szerkesztjük a felhasználó nevében.
A crontabhoz való hozzáféréssel rendelkező felhasználók lista kezelését az /var/cron/allow és /var/cron/deny fájlokban végzik, ahová elegendő egy sorban feltüntetni a felhasználó nevét.
Kiterjesztett szintaxis
A POSIX crontabhoz képest Paul Vixie megoldása számos nagyon hasznos módosítást tartalmaz a feladatlisták szintaxisában.
Elérhetővé vált az új táblázatszintaxis: például meg lehet adni a hét napjait vagy a hónapokat név szerint (Hétfő, Kedd és így tovább):
# Запускается ежеминутно по понедельникам и вторникам в январе
* * * Jan Mon,Tue /path/to/execMeg lehet adni a lépést, amelynek alapján a feladatok elindulnak:
# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/execA lépéseket és intervallumokat össze lehet keverni:
# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/execTámogatottak a hagyományos szintaxis intuitív alternatívái (újraindítás, évente, évente, havonta, hetente, naponta, éjfélkor, óránként):
# Запускается после перезагрузки системы
@reboot /exec/on/reboot
# Запускается раз в день
@daily /exec/daily
# Запускается раз в час
@hourly /exec/dailyFeladatok végrehajtási környezete
A Vixie cron lehetővé teszi a futtatott alkalmazások környezetének megváltoztatását.
A USER, LOGNAME és HOME környezeti változókat nemcsak a démon biztosítja, hanem a fájlból is nyeri. A PATH változó értéket kap a „/usr/bin:/bin” értékről, míg a SHELL a „/bin/sh”. Minden változó értékét, a LOGNAME kivételével, módosítható a felhasználói táblákban.
Néhány környezeti változót (elsősorban a SHELL és a HOME) a cron maga használ a feladat futtatásához. Így nézhet ki a bash használata a felhasználói feladatok indításához a standard sh helyett:
SHELL=/bin/bash
HOME=/tmp/
# az exec bash-el lesz indítva a /tmp/-ban
* * * * * /path/to/execVégül minden táblázatban meghatározott környezeti változót (amelyet a cron használ vagy amely a folyamat számára szükséges) átadnak a futtatott feladatnak.
A crontab utility fájljaiban a VISUAL vagy EDITOR környezeti változóban megadott szövegszerkesztőt használják. Ha a crontab indítási környezetében ezek a változók nincsenek definiálva, akkor a „/usr/ucb/vi” használatos (az ucb valószínűleg a Kaliforniai Egyetem, Berkeley rövidítése).
cron Debianban és Ubuntu alatt
A Debian és az abból származó disztribúciók fejlesztői kiadták a Vixie cron 3.0pl1. A fájl-táblázatok szintaxisában nincs eltérés, a felhasználók számára ez a Vixie cron ugyanaz. A legnagyobb új lehetőségek: PAM támogatás. , és .
A felhasználói táblák a Debianban a /var/spool/cron/crontabs mappában találhatók, a rendszer táblája ugyanott - a /etc/crontab. A Debian-ra specifikus feladat táblák a /etc/cron.d könyvtárba kerülnek, ahonnan a cron démon automatikusan olvassa őket. A felhasználói hozzáférés kezelését a /etc/cron.allow és /etc/cron.deny fájlok szabályozzák.
Alapértelmezett parancsfeldolgozóként továbbra is a /bin/sh használatos, amelynek szerepét a Debianban egy kis POSIX-kompatibilis shell
dash , запущенный без чтения какой-либо конфигурации (в неинтерактивном режиме).
A cron az utolsó Debian verziókban a systemd-n keresztül fut, a konfigurációs beállításokat pedig a /lib/systemd/system/cron.service fájlban lehet megnézni. A szolgáltatás konfigurációjában nincsenek különösebb dolgok, a feladatok finomabb kezelése a felhasználók crontab-jában közvetlenül deklarált környezeti változókon keresztül lehetséges.
A cronie RedHat, Fedora és CentOS rendszerekben
— a Vixie cron 4.1-es verziójának forkja. A Debianhoz hasonlóan a szintaxis nem változott, de PAM és SELinux támogatás, klaszter munkavégzés, inotify segítségével történő fájlfigyelés és egyéb lehetőségek kerültek hozzáadásra.
Az alapértelmezett konfiguráció a szokásos helyeken található: a rendszer táblája a /etc/crontab fájlban, a csomagok a saját tábláikat a /etc/cron.d fájlba helyezik, a felhasználói táblák pedig a /var/spool/cron/crontabs könyvtárba kerülnek.
A démon a systemd irányítása alatt fut, a szolgáltatás konfigurációs fájlja a /lib/systemd/system/crond.service.
A Red Hat-alapú disztribúciókban az alapértelmezett indításkor a /bin/sh-t használják, aminek a szerepét a standard bash tölti be. Érdemes megjegyezni, hogy a cron feladatok /bin/sh-n keresztüli indításakor a bash parancssor POSIX-kompatibilis módban fut, és nem olvas el semmilyen további konfigurációt, nem interaktív módban működik.
cronie SLES és openSUSE alatt
A német SLES disztribúció és az openSUSE nevű derivátcijuk ugyancsak a cronie-t használja. Itt is a systemd irányítása alatt fut a démon, a szolgáltatás konfigurációja a /usr/lib/systemd/system/cron.service fájlban található. A konfiguráció: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. A /bin/sh pedig ugyanaz a bash, amely POSIX-kompatibilis nem interaktív módban fut.
Vixie cron felépítése
A modern cron utódok a Vixie cronhoz képest nem változtak radikálisan, de mégis új lehetőségekkel gazdagodtak, amelyek megértéséhez nem szükségesek a program működésének alapelvei. E kiterjesztések többsége nem túl átláthatóan van megvalósítva, és kavarja a kódot. Az eredeti cron forráskód, Paul Vixie értelmezésében, igazi élvezetet nyújt.
Ezért a cron felépítésének elemzését a cron program fejlődésének közös példájával, a Vixie cron 3.0pl1-jével döntöttem el bemutatni. Az példákat egyszerűsítem, eltüntetve a bonyolító ifdef-eket és figyelmen kívül hagyva a másodlagos részleteket.
A démon működése több szakaszra bontható:
- A program inicializálása.
- A végrehajtásra váró feladatok listájának összegyűjtése és frissítése.
- A cron fő ciklusának a működése.
- A feladat elindítása.
Elemzésük sorrendben.
Inicializálás
A cron folyamat argumentumainak ellenőrzése után az első lépés a SIGCHLD és SIGHUP jelek kezelőinek beállítása. Az első a gyermekfolyamat befejezéséről készít naplóbejegyzést, míg a második lezárja a naplófájl fájlellistáját:
signal(SIGCHLD, sigchld_handler);
signal(SIGHUP, sighup_handler);A cron démon mindig egy példányban fut a rendszeren, kizárólag superuser jogokkal és a cron fő könyvtárából. A következő függvényhívások létrehoznak egy lock fájlt a démon PID-jével, ellenőrzik, hogy a felhasználó helyes, és megváltoztatják a jelenlegi könyvtárat a fő könyvtárra:
acquire_daemonlock(0);
set_cron_uid();
set_cron_cwd();Beállítjuk az alapértelmezett elérési utat, amelyet a folyamatok elindításakor használni fognak:
setenv("PATH", _PATH_DEFPATH, 1);Ezután a folyamat "démonszerűsödik": egy fork hívásával létrehoz egy gyermekváltozatot és új szekciót a gyermekfolyamatban (setsid hívás). A szülőfolyamatnak már nincs szüksége a továbbiakra — így véget ér:
switch (fork()) {
case -1:
/* kritikus hiba és a folyamat befejezése */
exit(0);
break;
case 0:
/* gyermekfolyamat */
(void) setsid();
break;
default:
/* szülőfolyamat véget ér */
_exit(0);
}
A szülőfolyamat befejezése feloldja a lock-ot a lock fájlon. Ezen kívül frissíteni kell a PID-t a gyermekre a fájlban. Ezután feltöltjük a feladatok adatbázisát:
/* повторный захват лока */
acquire_daemonlock(0);
/* Заполнение БД */
database.head = NULL;
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);Ezután a cron átmegy a fő munkaciklusra. De előtte érdemes áttekinteni a feladatok listájának betöltését.
A feladatok listájának összegyűjtése és frissítése
A feladatok listájának betöltéséért a load_database függőség felelős. Ellenőrzi a fő rendszer crontab-ját és a felhasználói fájlok könyvtárát. Ha a fájlok és a könyvtár nem változott, a feladatok listáját nem olvassa újra. Ellenkező esetben új feladatlistát kezd összeállítani.
A rendszerfájl betöltése speciális fájlnevekkel és táblázattal:
/* если файл системной таблицы изменился, перечитываем */
if (syscron_stat.st_mtime) {
process_crontab("root", "*system*",
SYSCRONTAB, &syscron_stat,
&new_db, old_db);
}A felhasználói táblák betöltése ciklusban:
while (NULL != (dp = readdir(dir))) {
char fname[MAXNAMLEN+1],
tabname[MAXNAMLEN+1];
/* fájlok ponttal való olvasása nem szükséges */
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);
}
Ezt követően a régi adatbázis lecserélődik az újra.
A fenti példákban a process_crontab függvény ellenőrzi a felhasználó létezését, amely megfelel a táblázat fájl nevének (ha nem superuser), majd a load_user hívása következik. Az utóbbi olvassa a fájlt soronként:
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;
}
}Itt vagy környezeti változót állítanak be (VAR=value formátum) a load_env / env_set függvények által, vagy a load_entry függvény olvassa be a feladat leírását (***** /path/to/exec).
A load_entry által visszaadott entry entitás maga a feladatunk, amely bekerül a feladatok közös listájába. A funkción belül részletes elemzés történik az idő formátumáról, minket azonban jobban érdekel a környezeti változók és a feladat indítási paramétereinek létrehozása:
/* пользователь и группа для запуска задачи берутся из 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);A már meglévő feladatlistával működik a fő ciklus.
Fő ciklus
Az eredeti cron a Version 7 Unix-ból nagyon egyszerűen működött: ciklusban újraolvasta a konfigurációt, a jelenlegi perc feladatait superuserként indította el, majd aludt a következő perc kezdetéig. Ez az egyszerű megközelítés régi gépeken túl sok erőforrást igényelt.
A SysV alternatív verziót javasolt, amelyben a démon vagy a legközelebbi percre aludt, amelyhez feladatot rendeltek, vagy 30 percig. Az ilyen módban a konfiguráció újraolvasásához és a feladatok ellenőrzéséhez kevesebb erőforrásra volt szükség, de nehézkessé vált a feladatlista gyors frissítése.
A Vixie cron visszatért a feladatlisták percenkénti ellenőrzéséhez, hála annak, hogy az 1980-as évek végére az alap Unix gépeken jelentősen nőtt az erőforrások mennyisége:
/* первичная загрузка задач */
load_database(&database);
/* запустить задачи, поставленные к выполнению после перезагрузки системы */
run_reboot_jobs(&database);
/* сделать TargetTime началом ближайшей минуты */
cron_sync();
while (TRUE) {
/* выполнить задачи, после чего спать до TargetTime с поправкой на время, потраченное на задачи */
cron_sleep();
/* перечитать конфигурацию */
load_database(&database);
/* собрать задачи для данной минуты */
cron_tick(&database);
/* перевести TargetTime на начало следующей минуты */
TargetTime += 60;
}
A feladatok végrehajtásáért a cron_sleep függvény felel, amely megkísérli a job_runqueue (feladatok átvizsgálása és indítása) és a do_command (egyedi feladatok indítása) funkciók hívását. Az utóbbi funkciót érdemes részletesebben elemezni.
Feladat indítása
A do_command funkció jó Unix-stílusban van megvalósítva, azaz a feladat aszinkron végrehajtásához fork-ot hajt végre. A szülőfolyamat továbbra is a feladatokat indítja, a gyermekfolyamat a feladat folyamatának előkészítésével foglalkozik:
switch (fork()) {
case -1:
/* nem tudtuk végrehajtani a fork-ot */
break;
case 0:
/* gyermek folyamat: minden bizonnyal újra megpróbálja megszerezni a fő lokot */
acquire_daemonlock(1);
/* áttérünk a feladat folyamatának létrehozására */
child_process(e, u);
/* a gyermek folyamat befejezi a munkáját a befejezés után */
_exit(OK_EXIT);
break;
default:
/* a szülő folyamat folytatja a munkát */
break;
}A child_process has quite a bit of logic: it takes standard output and error streams upon itself, then forwards them to email (if the MAILTO environment variable is specified in the task table), and finally, it waits for the main task process to complete.
The task process is created with another fork:
switch (vfork()) {
case -1:
/* if there is an error, exit immediately */
exit(ERROR_EXIT);
case 0:
/* the child process creates a new session, terminal, etc.
*/
(void) setsid();
/*
* further verbose process output configuration, skipped for brevity
*/
/* changing directory, user and group,
* that is, the process is no longer running as a superuser
*/
setgid(e->gid);
setuid(e->uid);
chdir(env_get("HOME", e->envp));
/* executing the command itself
*/
{
/* the SHELL environment variable indicates the interpreter to launch */
char *shell = env_get("SHELL", e->envp);
/* the process is launched without passing the parent process's environment,
* that's precisely how it is specified in the user task table */
execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
/* error — and the process did not start? exit */
perror("execl");
_exit(ERROR_EXIT);
}
break;
default:
/* the main process continues working: waits for completion and output */
break;
}Well, that's basically the entire cron. I skipped some interesting details, like accounting for remote users, but I covered the main points.
Utószó
Cron is surprisingly a simple and useful program, executed in the best traditions of the Unix world. It doesn't do anything unnecessary, but it performs its job wonderfully for several decades now. Familiarizing myself with the code from the version provided with Ubuntu took no more than an hour, and I gained a lot of pleasure from it! I hope to share that pleasure with you.
I don’t know about you, but it makes me a little sad to realize that modern programming, with its tendency toward overcomplication and over-abstraction, no longer allows such simplicity.
There are many modern alternatives to cron: systemd-timers allow for organizing complex systems with dependencies, while fcron can more flexibly regulate the resource consumption of tasks. But for me personally, the simplest crontab has always been enough.
In short, love Unix, use simple programs, and don’t forget to read the man pages for your platform!
Forrás: habr.com
