
Klasiskā literatūra apgalvo, ka laimīgi cilvēki neskatās uz pulksteni. Tajos mežonīgajos laikos vēl nebija ne programmatūras izstrādātāju, ne Unix, bet mūsdienās programmētāji zina: vietā viņiem laiku uzraudzīs cron.
Komandlīnijas rīki man ir gan vājums, gan rutīna. sed, awk, wc, cut un citas vecās programmas ikdienā tiek izsistītas ar skriptiem mūsu serveros. Daudzas no tām ir noformētas kā uzdevumi cron, plānotājs, kas radies 70. gados.
Ilgu laiku esmu izmantojis cron virspusēji, nenogrūžoties detaļās, bet vienu dienu, saskaroties ar kļūdu skripta palaišanas laikā, nolēmu izpētīt to detalizēti. Tā arī radās šis raksts, kura rakstīšanas gaitā es iepazinos ar POSIX crontab, pamata cron variantiem populāros Linux izplatījumos un to iekārtām.
Vai izmantojat Linux un palaistat uzdevumus cron? Vai jūs interesē sistēmu lietojumprogrammu arhitektūra Unix? Tad mums jāsatiekas!
Satelīte
Suatu veidu izcelsme
Periodiska lietotāju vai sistēmas programmu izpilde ir acīmredzama nepieciešamība visos operētājsistēmās. Tādēļ vajadzība pēc pakalpojumiem, kas ļauj centralizēti plānot un izpildīt uzdevumus, programmētāji ir sapratuši jau sen.
Unix līdzīgas operētājsistēmas cēlušas savu ciltsrakstu no Version 7 Unix, ko 70. gados izstrādāja Bell Labs, tostarp slavenais Ken Thomspons. Kopā ar Version 7 Unix tika piegādāts arī cron, superlietotāja uzdevumu regulāras izpildes pakalpojums.
Tipisks mūsdienu cron ir vienkārša programma, bet oriģinālā varianta darbības algoritms bija vēl vienkāršāks: pakalpojums pamodās reizi minūtē, izlasīja uzdevumu tabulu no vienīgā faila (/etc/lib/crontab) un izpildīja superlietotājam tos uzdevumus, kas bija jāizpilda pašreizējā minūtē.
Vēlāk uzlabotie vienkāršā un noderīgā pakalpojuma varianti tika iekļauti visās Unix līdzīgās operētājsistēmās.
Vispārējie apraksti crontab formātam un pamatprincipiem 1992. gadā bija iekļauti galvenajā standarta Unix līdzīgajās operētājsistēmās — POSIX — un tādējādi cron no de-facto standarta kļuva par de-jure standartu.
1987. gadā Pols Viksijs (angl. Paul Vixie), aptaujājot Unix lietotājus par viņu vēlmēm attiecībā uz cron, izlaida vēl vienu demona versiju, labojot dažas tradicionālo cron problēmas un paplašinot tabulu failu sintaksi.
Trešajā Vixie cron versijā tika izpildīti POSIX prasības, un programmai bija liberāla licence, precīzāk, vispār nebija nekādas licences, ja neskaita vēlmes README: autors nesniedz garantijas, autora vārdu nedrīkst dzēst, bet programmu var pārdot tikai kopā ar avota kodu. Šie nosacījumi izrādījās saderīgi ar tajos laikos pieaugošā brīvā programmatūra principiem, tādēļ daži no svarīgākajiem 90. gadu sākumā parādījušajiem Linux izplatījumiem izvēlējās Vixie cron kā sistēmas iedaļu un turpina to attīstīt vēl šodien.
Proti, Red Hat un SUSE attīsta Vixie cron forku — cronie, bet Debian un Ubuntu izmanto oriģinālo Vixie cron izdevumu ar daudzām pakotnēm.
Sāksim ar POSIX aprakstītās lietotāja utilītas crontab iepazīšanu, pēc tam izskatīsim Vixie cron piedāvātos sintakses paplašinājumus un Vixie cron variāciju izmantošanu populārajās Linux izplatījumos. Un visbeidzot — ķersimies pie cron demona struktūras analīzes.
POSIX crontab
Ja oriģinālais cron vienmēr darbojās superadministratora līmenī, tad mūsdienu plānotāji biežāk sastopas ar parasto lietotāju uzdevumiem, kas ir drošāk un ērtāk.
Croni tiek piegādāti kā divu programmu komplekts: pastāvīgi darbināms cron demoms un lietotājiem pieejamā utilītas crontab. Pēdējā ļauj rediģēt uzdevumu tabulas, kas specifiskas katram lietotājam sistēmā, bet demoms izpilda uzdevumus no lietotāju un sistēmas tabulām.
No nav nekur aprakstīts demona uzvedība un formāla ir tikai lietotāja programma . Lietotāju uzdevumu izpildes mehānismu esamība, protams, tiek pieņemta, bet netiek aprakstīta detalizēti.
Izsaucot utilītu crontab, var izdarīt četras lietas: rediģēt lietotāja uzdevumu tabulu redaktorā, ielādēt tabulu no faila, parādīt pašreizējo uzdevumu tabulu un notīrīt uzdevumu tabulu. Crontab utilīta darbojas šādi:
crontab -e # rediģēt uzdevumu tabulu
crontab -l # parādīt uzdevumu tabulu
crontab -r # dzēst uzdevumu tabulu
crontab path/to/file.crontab # ielādēt uzdevumu tabulu no failaIzsaucot crontab -e tiks izmantot redaktoru, kas norādīts standarta vides mainīgajā REDAKTORS.
Paši uzdevumi ir aprakstīti šādā formātā:
# строки-комментарии игнорируются
#
# задача, выполняемая ежеминутно
* * * * * /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.logPirmie pieci lauku ieraksti: minūtes [1..60], stundas [0..23], mēneša dienas [1..31], mēneši [1..12], nedēļas dienas [0..6], kur 0 ir svētdiena. Pēdējais, sestais lauks - virkne, kas tiks izpildīta standarta komandrindas interpretatorā.
Pirmajos piecos laukos vērtības var ierakstīt atsevišķi ar komatu:
# задача, выполняемая в первую и десятую минуты каждого часа
1,10 * * * * /path/to/exec -a -b -cVai ar svītru:
# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -cLietotāju piekļuve uzdevumu plānošanai tiek regulēta POSIX failos cron.allow un cron.deny, kuros attiecīgi ir uzskaitīti lietotāji ar piekļuvi crontab un lietotāji bez piekļuves programmai. Šo failu atrašanās vieta nav noteikta standartā.
Izpildāmajām programmām, saskaņā ar standartu, jānosūta vismaz četras vides mainīgās:
- HOME — lietotāja mājas direktorija.
- LOGNAME — lietotāja pieteikšanās vārds.
- PATH — ceļš, pa kuru var atrast standarta sistēmas utilītus.
- SHELL — ceļš uz izmantoto komandrindas interpretatoru.
Interesanti, ka POSIX neko nesaka par to, no kurienes nāk šo mainīgo vērtības.
Vispārdotākais — Vixie cron 3.0pl1
Parastā dzimtā okupanta Vixie cron 3.0pl1, kas tika ieviests komandas comp.sources.unix sarakstā 1992. gadā. Galvenās šīs versijas iespējas mēs aplūkosim detalizētāk.
Vixie cron nāk ar divām programmām (cron un crontab). Parasti, demons ir atbildīgs par uzdevumu lasīšanu un izpildīšanu no sistēmas uzdevumu tabulas un konkrētu lietotāju uzdevumu tabulām, bet utilīta crontab ir atbildīga par lietotāju tabulu rediģēšanu.
Uzdevumu tabula un konfigurācijas faili
Superlietotāja uzdevumu tabula atrodas /etc/crontab. Sistēmas tabulas sintakse atbilst Vixie cron sintaksei ar labojumu, ka tajā sestajā kolonnā norādīts lietotāja vārds, kura vārdā uzdevums tiek izpildīts:
# Запускается ежеминутно от пользователя vlad
* * * * * vlad /path/to/execParasto lietotāju uzdevumu tabulas atrodas /var/cron/tabs/username un izmanto kopējo sintaksi. Kad utilīta crontab tiek palaista lietotāja vārdā, tieši šie faili tiek rediģēti.
Lietotāju sarakstu, kuriem ir piekļuve crontab, pārvaldība notiek failos /var/cron/allow un /var/cron/deny, kuros pietiek uzrakstīt lietotāja vārdu atsevišķā rindā.
Paplašinātā sintakse
Salīdzinājumā ar POSIX crontab, Pola Vixie ieteikums satur dažas ļoti noderīgas modifikācijas uzdevumu tabulas sintaksē.
Ir pieejams jauns tabulu sintakse veids: piemēram, var norādīt nedēļas dienas vai mēnešus pa vārdam (Mon, Tue utt.):
# Запускается ежеминутно по понедельникам и вторникам в январе
* * * Jan Mon,Tue /path/to/execVar norādīt soli, pēc kura tiek izpildītas uzdevumi:
# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/execSoļus un intervālus var kombinēt:
# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/execAtbalsta intuitīvas alternatīvas standarta sintaksei (reboot, yearly, annually, monthly, weekly, daily, midnight, hourly):
# Запускается после перезагрузки системы
@reboot /exec/on/reboot
# Запускается раз в день
@daily /exec/daily
# Запускается раз в час
@hourly /exec/dailyUzdevumu izpildes vide
Vixie cron ļauj mainīt palaidējspēku vides iestatījumus.
Vides mainīgie USER, LOGNAME un HOME netiek tikai sniegti parauga programmatūrai, bet tiek ņemti no faila . Mainīgais PATH iegūst vērtību "/usr/bin:/bin", bet SHELL — "/bin/sh". Visi mainīgie, izņemot LOGNAME, ir maināmi lietotāju tabulās.
Daži vides mainīgie (galvenokārt SHELL un HOME) paši cron izmanto uzdevumu izpildei. Lūk, kā var izskatīties basha izmantošana vietā standartam sh uzdevumu izpildei:
SHELL=/bin/bash
HOME=/tmp/
# exec tiks izpildīts basha režīmā /tmp/
* * * * * /path/to/execGalu galā visi tabulā noteiktie vides mainīgie (ko izmanto cron vai kas nepieciešami procesam) tiks nodoti palaistajam uzdevumam.
Lai rediģētu failus ar utilītu crontab, tiek izmantots redaktors, kas norādīts vides mainīgajā VISUAL vai EDITOR. Ja vidē, kurā tika palaists crontab, šie mainīgie nav definēti, tiek izmantota "/usr/ucb/vi" (ucb, visticamāk, ir Kalifornijas universitāte, Berklija).
cron Debian un Ubuntu
Debian un atvasināto distribūciju izstrādātāji ir izlaiduši Vixie cron 3.0pl1. Atšķirības tabulu failu sintaksē nav, lietotājiem tas ir tas pats Vixie cron. Lielākās jaunās iespējas: atbalsts , un .
Mazāk pamanāmām, bet taustāmām izmaiņām — konfigurācijas failu un uzdevumu tabulu izvietojums.
Lietotāju tabulas Debian atrodas direktorijā /var/spool/cron/crontabs, sistēmas tabula tā pati — /etc/crontab. Specifiskas Debian pakotēm uzdevumu tabulas tiek ievietotas /etc/cron.d, no kuras cron demons tās automātiski lasa. Lietotāju piekļuves pārvaldību regulē faili /etc/cron.allow un /etc/cron.deny.
Kā noklusējuma komandrindas intefeiss joprojām tiek izmantots /bin/sh, kuru Debian kontekstā aizstāj neliela POSIX saderīga shell , kas tiek palaists bez jebkādas konfigurācijas lasīšanas (neinteraktīvajā režīmā).
Pašreizējās Debian versijās cron tiek palaists caur systemd, un palaišanas konfigurāciju var skatīt /lib/systemd/system/cron.service. Pakalpojuma konfigurācijā nav nekā īpaša, jebkura smalkāka uzdevumu pārvaldība ir iespējama, izmantojot vides mainīgos, kas deklarēti tieši katra lietotāja crontab.
cronie RedHat, Fedora un CentOS
— Vixie cron 4.1 versijas forks. Tāpat kā Debian, sintakse nav mainījusies, taču tika pievienota PAM un SELinux atbalsta, klasteru darbības, failu uzraudzības caur inotify un citu iespēju atbalsts.
Noklusējuma konfigurācija atrodas parastajās vietās: sistēmas tabula — /etc/crontab, paketes ievieto savas tabulas /etc/cron.d, lietotāju tabulas nonāk /var/spool/cron/crontabs.
Dēmons darbojas systemd vadībā, pakalpojuma konfigurācija — /lib/systemd/system/crond.service.
Red Hat līdzīgos izplatījumos noklusējuma palaišanai tiek izmantots /bin/sh, kurā lomu spēlē standarta bash. Jāatzīmē, ka, palaižot cron uzdevumus caur /bin/sh, komandu apvalks bash tiek palaižs POSIX saskanīgā režīmā un nelasa nekāda papildu konfigurāciju, strādājot neinteraktīvā režīmā.
cronie SLES un openSUSE
Vācu izplatījums SLES un tā atvasinājums openSUSE arī izmanto to pašu cronie. Dēmons šeit arī tiek palaists caur systemd, pakalpojuma konfigurācija atrodas /usr/lib/systemd/system/cron.service. Konfigurācija: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. Kā /bin/sh tiek izmantots tas pats bash, palaists POSIX saskanīgā neinteraktīvā režīmā.
Vixie cron iekārta
Mūsdienu cron pēcnācēji salīdzinājumā ar Vixie cron nav radikāli mainījušies, taču tie tomēr ir ieguvuši jaunas iespējas, kas nav nepieciešamas, lai saprastu programmas darbības principus. Daudzas no šīm paplašinājumiem ir neprecīzi izstrādātas un sajauc kodu. Oriģinālais cron avota kods Pola Viksi izpildījumā ir patīkams lasīšanai.
Tāpēc es nolēmu analizēt cron struktūru, izmantojot Vixie cron 3.0pl1 — kopīgu abām cron programmas attīstības zarām. Es vienkāršošu piemērus, izņemot grūti lasāmos ifdef un izslēdzot sekundārās detaļas.
Dēmonu darbību var sadalīt vairākos posmos:
- Programmas inicializācija.
- Uzdevumu saraksta vākšana un atjaunināšana.
- Cron galvenā cikla darbība.
- Uzdevuma palaišana.
Apskatīsim tos secībā.
Inicializācija
Kad cron procesa argumenti ir pārbaudīti, tas iestata SIGCHLD un SIGHUP signālu apstrādātājus. Pirmais pieraksta apakšprocesa pabeigšanās ziņojumu žurnālā, otrais slēdz žurnāla faila failu apreizu:
signal(SIGCHLD, sigchld_handler);
signal(SIGHUP, sighup_handler);Cron demons sistēmā vienmēr darbojas viens, tikai kā superlietotājs un no galvenās cron mapes. Nākamie izsaukumi izveido lokfailu ar demona procesa PID, pārliecinās par pareizā lietotāja identitāti un maina pašreizējo mapi uz galveno:
acquire_daemonlock(0);
set_cron_uid();
set_cron_cwd();Iestata noklusējuma ceļu, kuru izmantos procesi:
setenv("PATH", _PATH_DEFPATH, 1);Tālāk process "demonizējas": izveido apakšprocesa kopiju, izmantojot fork izsaukumu, un jaunu sesiju apakšprocesā (izsaukuma setsid). Vecākam procesam vairs nav nepieciešamības — un tas beidz darbu:
switch (fork()) {
case -1:
\/\* kritiska kļūda un darba beigšana *\/
exit(0);
break;
case 0:
\/\* apakšprocess *\/
(void) setsid();
break;
default:
\/\* vecāks process beidz darbu *\/
_exit(0);
}
Vecā procesa beigšana atbrīvo loks failā. Turklāt jāturpina atjaunināt PID failā uz apakšprocesa. Pēc tam aizpildās uzdevumu datu bāze:
/* повторный захват лока */
acquire_daemonlock(0);
/* Заполнение БД */
database.head = NULL;
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);Tālāk cron pāriet uz galveno darba ciklu. Bet pirms tam vajadzētu aplūkot uzdevumu saraksta ielādi.
Uzdevumu saraksta vākšana un atjaunināšana
Uzdevumu saraksta ielādi veic funkcija load_database. Tā pārbauda galveno sistēmas crontab un lietotāju failu direktoriju. Ja faili un direktorija nav mainījušies, uzdevumu saraksts netiek pārlasīts. Pretējā gadījumā sākas jaunā uzdevumu saraksta veidošana.
Sistēmas faila ielāde ar īpašajiem failu nosaukumiem un tabulu:
/* если файл системной таблицы изменился, перечитываем */
if (syscron_stat.st_mtime) {
process_crontab("root", "*system*",
SYSCRONTAB, &syscron_stat,
&new_db, old_db);
}Lietotāju tabulu ielāde ciklā:
while (NULL != (dp = readdir(dir))) {
char fname[MAXNAMLEN+1],
tabname[MAXNAMLEN+1];
\/\* lasīt failus ar punktu nav nepieciešams *\/
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);
}
Pēc tam vecā datu bāze tiek aizvietota ar jauno.
Iepriekš minētajos piemēros funkcijas process_crontab izsaukums pārliecinās par lietotāja eksistenci, kas atbilst tabulas faila nosaukumam (ja vien tas nav superlietotājs), pēc tam tas izsauc load_user. Pēdējā jau lasa pa rindiņām failu:
kamēr ((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;
}
}Šeit tiek iestatīta vides mainīgā vērtība (stila VAR=value) ar load_env vai env_set funkcijām, vai tiek nolasīts uzdevuma apraksts (* * * * * /path/to/exec) ar load_entry funkciju.
Ievada entry, ko atgriež load_entry, ir mūsu uzdevums, kas ievietots kopējā uzdevumu sarakstā. Ieraksta funkcijā tiek veikta plaša laika formāta analīze, bet mūs vairāk interesē vides mainīgo un uzdevuma palaišanas parametru veidošana:
/* пользователь и группа для запуска задачи берутся из 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);Ar aktuālo uzdevumu sarakstu darbojas galvenais cikls.
Galvenais cikls
Oriģinālais cron no Version 7 Unix darbojās ļoti vienkārši: ciklā pārskatīja konfigurāciju, palaižot superlietotāja uzdevumus pašreizējai minūtei, un aizmiga līdz nākamās minūtes sākumam. Šī vienkāršā pieeja uz vecām mašīnām prasīja pārāk daudz resursu.
SysV tika piedāvāta alternatīva versija, kurā demons aizmiga vai nu līdz tuvākajai minūtai, kurai definēts uzdevums, vai uz 30 minūtēm. Šādā režīmā resursi, kas patērēti konfigurācijas pārskatīšanai un uzdevumu pārbaudei, bija mazāki, bet ātri atjaunot uzdevumu sarakstu kļuva neērti.
Vixie cron atgriezās pie uzdevumu sarakstu pārbaudes reizi minūtē, jo labā ziņā 80. gadu beigās standartizētās Unix mašīnās resursi kļuva ievērojami lielāki:
/* первичная загрузка задач */
load_database(&database);
/* запустить задачи, поставленные к выполнению после перезагрузки системы */
run_reboot_jobs(&database);
/* сделать TargetTime началом ближайшей минуты */
cron_sync();
while (TRUE) {
/* выполнить задачи, после чего спать до TargetTime с поправкой на время, потраченное на задачи */
cron_sleep();
/* перечитать конфигурацию */
load_database(&database);
/* собрать задачи для данной минуты */
cron_tick(&database);
/* перевести TargetTime на начало следующей минуты */
TargetTime += 60;
}
Tieši uzdevumu izpildīšanu nodrošina funkcija cron_sleep, kas aicina job_runqueue (uzdevumu pāreja un palaišana) un do_command (katra atsevišķā uzdevuma palaišana). Pēdējo funkciju vērts izpētīt detalizētāk.
Uzdevuma palaišana
Funkcija do_command ir īstenota labā Unix stilā, t.i., asynhronai uzdevuma izpildei tā veic fork. Vecākais process turpina uzdevumu palaišanu, bet jaunais — nodarbojas ar uzdevuma procesa sagatavošanu:
switch (fork()) {
case -1:
/* neizdevās veikt fork */
break;
case 0:
/* bērnu process: priekš iespējas vēlreiz mēģinam iegūt galveno loku */
acquire_daemonlock(1);
/* pārejam uz uzdevuma procesa veidošanu */
child_process(e, u);
/* pēc pabeigšanas bērnu process beidz darbu */
_exit(OK_EXIT);
break;
default:
/* vecāks process turpina darbu */
break;
}Child_process ir diezgan daudz loģikas: tas pieņem standarta izvadstrādas un kļūdu plūsmas uz sevi, lai pēc tam nosūtītu pa e-pastu (ja uzdevumu tabulā ir norādīta vides mainīgā MAILTO), un visbeidzot, gaida galvenā uzdevuma procesa pabeigšanu.
Uzdevuma process tiek veidots vēl ar vienu fork:
switch (vfork()) {
case -1:
exit(ERROR_EXIT);
case 0:
(void) setsid();
setgid(e->gid);
setuid(e->uid);
chdir(env_get("HOME", e->envp));
{
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;
}Lūk, praktiski viss cron. Dažas interesantas detaļas, piemēram, attālo lietotāju atzīšana, es izlaidīju, bet galveno es izklāstīju.
Pēcvārds
Cron - pārsteidzoši vienkārša un noderīga programma, kas izpildīta labākajās Unix pasaules tradīcijās. Tā neko lieku nedara, bet savu darbu izpilda lieliski jau vairākus gadu desmitus. Iepazīšanās ar tā versijas kodu, kas piegādāta ar Ubuntu, man aizņēma ne vairāk kā stundu, un es guvu milzīgu prieku! Ceru, ka varēju to dalīties ar jums.
Nezinu, kā jums, bet man ir nedaudz skumji saprast, ka mūsdienu programmēšana ar tās tieksmi pārmērīgi sarežģīt un pārabstraktizēt, jau sen vairs neveicina šādu vienkāršību.
Pastāv daudz mūsdienu alternatīvu cron: systemd-timers ļauj organizēt sarežģītas sistēmas ar atkarībām, fcron var elastīgāk regulēt uzdevumu resursu patēriņu. Bet personīgi man vienmēr pietika ar vissvarīgāko crontab.
Vārdu sakot, mīliet Unix, izmantojiet vienkāršas programmas un neaizmirstiet lasīt manas jūsu platformai!
Avots: habr.com
