
Klassikeren skrev at lykkelige timer ikke blir overvåket. I de ville tidene fantes det verken programmerere eller Unix, men i dag vet programmerere fast: I stedet for dem vil cron passe på tiden.
Kommandolinjeverktøy er både en svakhet og en rutine for meg. sed, awk, wc, cut og andre gamle programmer kjøres av skript på våre servere daglig. Mange av dem er satt opp som oppgaver for cron, en planlegger fra 70-tallet.
Jeg har lenge brukt cron overfladisk, uten å gå i dybden, men en gang, da jeg støtte på en feil under kjøring av et skript, bestemte jeg meg for å finne ut av det ordentlig. Dette førte til at jeg skrev denne artikkelen, der jeg ble kjent med POSIX crontab, hovedvariantene av cron i populære Linux-distribusjoner og oppsettet av noen av dem.
Bruker du Linux og kjører oppgaver i cron? Er du interessert i arkitekturen til systemapplikasjoner i Unix? Da er vi på samme vei!
Innhold
Artenes opprinnelse
Periodisk kjøring av bruker- eller systemprogrammer er en åpenbar nødvendighet i alle operativsystemer. Derfor har programmerere lenge vært klar over behovet for tjenester som kan sentralt planlegge og utføre oppgaver.
Unix-liknende operativsystemer stammer fra Version 7 Unix, utviklet på 70-tallet i Bell Labs, inkludert den berømte Ken Thompson. Sammen med Version 7 Unix ble også cron levert, en tjeneste for regelmessig utføring av oppgaver av superbrukeren.
En typisk moderne cron er et enkelt program, men algoritmen til den originale versjonen var enda enklere: tjenesten våknet en gang i minuttet, leste oppgavelisten fra en enkelt fil (/etc/lib/crontab) og utførte de oppgavene som måtte utføres av superbrukeren i det gjeldende minuttet.
Senere ble forbedrede versjoner av denne enkle og nyttige tjenesten levert med alle Unix-liknende operativsystemer.
Generelle beskrivelser av crontab-formatet og de grunnleggende prinsippene for driften av tjenesten ble i 1992 inkludert i hovedstandarden for Unix-liknende operativsystemer — POSIX — og dermed ble cron fra et de facto-standard til en de jure-standard.
I 1987 lanserte Paul Vixie, etter å ha spurt Unix-brukere om deres ønsker til cron, en ny versjon av demon som rettet opp noen av problemene med tradisjonell cron og utvidet syntaksen for tabellfiler.
I den tredje versjonen ble Vixie cron i samsvar med POSIX-standardene, og programmet hadde en liberal lisens, eller rettere sagt, det fantes ingen lisens, bortsett fra ønskene i README: ingen garantier fra forfatteren, forfatterens navn kan ikke fjernes, og programmet kan bare selges sammen med kildekoden. Disse kravene viste seg å være kompatible med prinsippene for fri programvare, som ble mer populært på den tiden, derfor tok noen av de populære Linux-distribusjonene fra tidlig på 90-tallet Vixie cron som sitt system og utviklet det videre.
Blant annet utvikler Red Hat og SUSE en fork av Vixie cron - cronie, mens Debian og Ubuntu bruker den originale utgaven av Vixie cron med mange patcher.
La oss begynne med å bli kjent med den brukervennlige verktøyet crontab som er beskrevet i POSIX, deretter vil vi gå gjennom syntakseutvidelsene presentert i Vixie cron og bruken av Vixie cron-varianter i populære Linux-distribusjoner. Og til slutt, kirsebæret på toppen - en gjennomgang av strukturen til cron-demonen.
POSIX crontab
Mens den originale cron alltid fungerte for superbrukeren, håndterer moderne planleggere oftere oppgaver fra vanlige brukere, noe som er sikrere og mer praktisk.
Cronene leveres som et sett med to programmer: den kontinuerlig kjørende cron-demonen og det brukervennlige verktøyet crontab. Sistnevnte lar deg redigere oppgavetabeller som er spesifikke for hver bruker i systemet, mens demon kjører oppgaver fra brukerens og systemtabeller.
I beskriver ikke atferden til demon og formaliserer bare det brukervennlige programmet . Eksistensen av mekanismer for å kjøre brukeroppgaver er selvfølgelig implisert, men ikke beskrevet i detalj.
Ved å kalle crontab-verktøyet kan du gjøre fire ting: redigere brukerens oppgavetabell i en editor, laste opp tabellen fra en fil, vise den nåværende oppgavetabellen og tømme oppgavetabellen. Eksempler på bruk av crontab-verktøyet:
crontab -e # redigere oppgavetabellen
crontab -l # vise oppgavetabellen
crontab -r # slette oppgavetabellen
crontab path/to/file.crontab # laste opp oppgavetabellen fra en filVed å kalle crontab -e den redaktøren som er angitt i standard miljøvariabelen vil bli brukt EDITOR.
Oppgavene er beskrevet i følgende format:
# строки-комментарии игнорируются
#
# задача, выполняемая ежеминутно
* * * * * /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.logDe første fem feltene i postene: minutter [1..60], timer [0..23], dager i måneden [1..31], måneder [1..12], dager i uken [0..6], der 0 er søndag. Det sjette og siste feltet er en streng som vil bli utført av standard kommandotolk.
I de første fem feltene kan verdier oppgis med komma:
# задача, выполняемая в первую и десятую минуты каждого часа
1,10 * * * * /path/to/exec -a -b -cEller med bindestrek:
# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -cBrukerens tilgang til oppgaveplanlegging reguleres av POSIX-filene cron.allow og cron.deny, som henholdsvis oppregner brukere med tilgang til crontab og brukere uten tilgang til programmet. Standard definert plassering av disse filene er ikke regulert.
Kjørbare programmer må i henhold til standarden motta minst fire miljøvariabler:
- HOME — brukerens hjemmekatalog.
- LOGNAME — brukerens påloggingsnavn.
- PATH — stien der systemets standardverktøy kan finnes.
- SHELL — stien til den brukt kommandotolken.
Det er bemerkelsesverdig at POSIX ikke sier noe om hvor disse variablene får sine verdier fra.
Bestselgeren — Vixie cron 3.0pl1
Den felles forfaderen til populære varianter av cron er Vixie cron 3.0pl1, som ble presentert i nyhetsbrevet comp.sources.unix i 1992. Vi vil se nærmere på hovedfunksjonene i denne versjonen.
Vixie cron leveres i to programmer (cron og crontab). Som vanlig er démon ansvarlig for å lese og kjøre oppgaver fra systemets oppgavetable og individuelle brukeroppgavetable, mens verktøyet crontab er ansvarlig for redigering av brukertabeller.
Oppgavetabell og konfigurasjonsfiler
Oppgavetabellen for superbrukeren ligger i /etc/crontab. Syntaxen til systemets tabell følger syntaxen til Vixie cron, med den modifikasjonen at den sjette kolonnen angir brukernavnet som oppgaven kjøres under:
# Запускается ежеминутно от пользователя vlad
* * * * * vlad /path/to/execOppgavene til vanlige brukere ligger i /var/cron/tabs/username og bruker en felles syntax. Når crontab-verktøyet kjøres under en bestemt bruker, redigeres akkurat disse filene.
Håndtering av lister over brukere med tilgang til crontab skjer i filene /var/cron/allow og /var/cron/deny, hvor det er tilstrekkelig å legge inn brukernavnet på en egen linje.
Utvidet syntaks
Sammenlignet med POSIX crontab inneholder Paul Vixies løsning flere svært nyttige modifikasjoner i syntaxen til oppgavetabellene til verktøyet.
Den nye tabellsynatksen er tilgjengelig: for eksempel kan du spesifisere ukedagene eller månedene ved navn (Mon, Tue, osv.):
# Запускается ежеминутно по понедельникам и вторникам в январе
* * * Jan Mon,Tue /path/to/execDet er mulig å spesifisere et intervall som oppgavene skal kjøres med:
# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/execIntervaller og trinn kan blandes:
# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/execIntuitive alternativer til vanlig syntaks støttes (reboot, yearly, annually, monthly, weekly, daily, midnight, hourly):
# Запускается после перезагрузки системы
@reboot /exec/on/reboot
# Запускается раз в день
@daily /exec/daily
# Запускается раз в час
@hourly /exec/dailyKjøringsmiljø for oppgaver
Vixie cron lar deg endre miljøet til de kjørende applikasjonene.
Miljøvariablene USER, LOGNAME og HOME blir ikke bare gitt av demonprosessen, men hentes fra filen . Variabelen PATH får verdien “/usr/bin:/bin”, mens SHELL får “/bin/sh”. Alle variabelverdier, unntatt LOGNAME, kan endres i brukertabellene.
Noen miljøvariabler (først og fremst SHELL og HOME) brukes av cron selv for å kjøre oppgaver. Slik kan det se ut å bruke bash i stedet for standard sh for å kjøre brukertilpassede oppgaver:
SHELL=/bin/bash
HOME=/tmp/
# exec vil bli kjørt av bash i /tmp/
* * * * * /path/to/execTil syvende og sist vil alle medlemmene av de definerte miljøvariablene (som brukes av cron eller som er nødvendige for prosessen) bli sendt til den kjørte oppgaven.
For å redigere filer med verktøyet crontab brukes editoren angitt i miljøvariabelen VISUAL eller EDITOR. Hvis disse variablene ikke er definert i miljøet der crontab ble kjørt, brukes “/usr/ucb/vi” (ucb er sannsynligvis University of California, Berkeley).
cron i Debian og Ubuntu
Debian-utviklere og avledede distribusjoner har utgitt av Vixie cron 3.0pl1. Det er ingen forskjeller i syntaksen til tabellfilene; for brukerne er det den samme Vixie cron. De største nye funksjonene: støtte for , og .
Fra mindre merkbare, men merkbare endringer - plasseringen av konfigurasjonsfiler og oppgavestabeller.
Brukertabeller i Debian finnes i katalogen /var/spool/cron/crontabs, systemtabellen ligger fortsatt i /etc/crontab. Pakke-spesifikke oppgavestabeller plasseres i /etc/cron.d, hvor cron-demonen automatisk leser dem. Brukertilgangen styres av filene /etc/cron.allow og /etc/cron.deny.
Som standard kommandoskjell brukes fortsatt /bin/sh, hvor en liten POSIX-kompatibel shell , kjøres uten å lese noen konfigurasjoner (i ikke-interaktiv modus).
Cron i dei nyaste versjonene av Debian kjøres via systemd, og oppstartskonfigurasjonen kan ses i /lib/systemd/system/cron.service. Det er ikke noe spesielt med tjenestekonfigurasjonen, mer detaljert oppgavehåndtering kan oppnås via miljøvariabler som erklæres direkte i crontab for hver bruker.
cronie i RedHat, Fedora og CentOS
— en fork av Vixie cron versjon 4.1. Som i Debian har syntaksen ikke endret seg, men støtte for PAM og SELinux, klusterarbeid, overvåkning av filer ved hjelp av inotify og andre funksjoner er lagt til.
Standardkonfigurasjonen finnes på vanlige steder: systemtabellen i /etc/crontab, pakker plasserer sine tabeller i /etc/cron.d, brukerens tabeller havner i /var/spool/cron/crontabs.
Demonen kjøres under systemd, tjenestekonfigurasjonen er /lib/systemd/system/crond.service.
I Red Hat-lignende distribusjoner brukes /bin/sh som standard ved oppstart, hvor standard bash fungerer som dette. Det er verdt å merke seg at når cron-oppgaver kjøres via /bin/sh, startes bash-skjell i POSIX-kompatibelt modus og leser ingen ekstra konfigurasjon, og arbeider i ikke-interaktiv modus.
cronie i SLES og openSUSE
Den tyske distribusjonen SLES og dens derivat openSUSE bruker fortsatt cronie. Demonens tjeneste kjøres også under systemd, og tjenestekonfigurasjonen ligger i /usr/lib/systemd/system/cron.service. Konfigurasjonen: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. Som /bin/sh brukes samme bash, kjørt i POSIX-kompatibel ikke-interaktiv modus.
Oppsettet av Vixie cron
Moderne etterkommere av cron har ikke endret seg radikalt sammenlignet med Vixie cron, men har likevel fått nye funksjoner som ikke er nødvendige for å forstå prinsippene i programmet. Mange av disse utvidelsene er dårlig dokumentert og forvirrer koden. Den originale kildekoden til cron, utført av Paul Vixie, er en glede å lese.
Derfor bestemte jeg meg for å diskutere oppbyggingen av cron med eksemplet fra den felles utviklingsgrenen av cron-programmet — Vixie cron 3.0pl1. Jeg vil forenkle eksemplene ved å fjerne unødvendige ifdef-er og utelate sekundære detaljer.
Driften av demon kan deles opp i flere trinn:
- Programinitialisering.
- Innsamling og oppdatering av oppgavelisten for kjøring.
- Kjernen av cron-hovedsløyfen.
- Oppgavekjøring.
La oss se på dem i rekkefølge.
Initialisering
Ved oppstart, etter å ha kontrollert prosessargumentene, setter cron prosess signalbehandlere for SIGCHLD og SIGHUP. Den første logger en oppføring om avslutning av barnprosessen, mens den andre lukker filbeskrivelsen til loggfilen:
signal(SIGCHLD, sigchld_handler);
signal(SIGHUP, sighup_handler);Cron daemonen i systemet kjører alltid alene, kun som superbruker og fra den hovedkatalogen for cron. Følgende kall oppretter en låsefil med daemonprosessen sin PID, sørger for at brukeren er riktig, og endrer den nåværende katalogen til hovedkatalogen:
acquire_daemonlock(0);
set_cron_uid();
set_cron_cwd();Standardstien settes, som vil bli brukt ved oppstart av prosesser:
setenv("PATH", _PATH_DEFPATH, 1);Deretter blir prosessen «demonisert»: den lager en barnekopi av prosessen ved hjelp av fork og en ny sesjon i barnet (via setsid-kall). Det er ikke mer behov for forelderen - og den avslutter:
switch (fork()) {
case -1:
/* kritisk feil og avslutning */
exit(0);
break;
case 0:
/* barnprosessen */
(void) setsid();
break;
default:
/* forelderen avslutter */
_exit(0);
}
Avslutningen av forelderen frigir låsen på låsefilen. I tillegg må PID-verdien i filen oppdateres til barnet. Deretter fylles oppgavebasen:
/* повторный захват лока */
acquire_daemonlock(0);
/* Заполнение БД */
database.head = NULL;
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);Deretter går cron over til hovedarbeidssyklusen. Men før det er det verdt å se på innlastingen av oppgavelisten.
Innhenting og oppdatering av oppgavelisten
Funksjonen load_database har ansvaret for innlastingen av oppgavelisten. Den sjekker hovedsystemets crontab og katalogen med brukerkatalogene. Hvis filene og katalogen ikke har endret seg, blir ikke oppgavelisten lest inn på nytt. I motsatt fall begynner det å forme seg en ny oppgaveliste.
Innhenting av systemfilen med spesielle filnavn og tabellen:
/* если файл системной таблицы изменился, перечитываем */
if (syscron_stat.st_mtime) {
process_crontab("root", "*system*",
SYSCRONTAB, &syscron_stat,
&new_db, old_db);
}Innhenting av brukertabeller i en løkke:
while (NULL != (dp = readdir(dir))) {
char fname[MAXNAMLEN+1],
tabname[MAXNAMLEN+1];
/* les filer med punktum er ikke nødvendig */
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);
}
Deretter byttes den gamle databasen ut med den nye.
I eksemplene ovenfor bekrefter kallene til funksjonen process_crontab eksistensen av brukeren som samsvarer med filnavnet til tabellen (med mindre det er superbrukeren), før den kaller load_user. Den siste leser selve filen linje for linje:
mens ((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;
}
}Her settes en miljøvariabel (strenger som VAR=value) av funksjonene load_env / env_set, eller leser beskrivelsen av oppgaven (* * * * * /path/to/exec) av funksjonen load_entry.
Entiteten entry som returneres av load_entry, er vår oppgave som settes inn i den generelle oppgavelisten. I selve funksjonen foregår det en omstendelig analyse av tidsformatet, men vi er mer interessert i å danne miljøvariabler og oppstartsparameter for oppgaven:
/* пользователь и группа для запуска задачи берутся из 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);Hovedsløyfen arbeider med den aktuelle oppgavelisten.
Hovedsløyfen
Den opprinnelige cron fra Version 7 Unix fungerte ganske enkelt: den leste konfigurasjonen i en sløyfe, kjørte superbrukeroppgavene for det nåværende minuttet og sov til starten av det neste minuttet. Denne enkle tilnærmingen krevde for mye ressurser på gamle maskiner.
I SysV ble det foreslått en alternativ versjon, der demon sov enten til det nærmeste minuttet med en avklart oppgave, eller i 30 minutter. Ressurser for å lese konfigurasjonen og sjekke oppgavene i denne modusen ble brukt mindre, men det ble vanskelig å oppdatere oppgavelisten raskt.
Vixie cron gikk tilbake til å sjekke oppgavelistene hvert minutt, heldigvis var ressursene på standard Unix-maskiner betydelig større mot slutten av 80-tallet:
/* первичная загрузка задач */
load_database(&database);
/* запустить задачи, поставленные к выполнению после перезагрузки системы */
run_reboot_jobs(&database);
/* сделать TargetTime началом ближайшей минуты */
cron_sync();
while (TRUE) {
/* выполнить задачи, после чего спать до TargetTime с поправкой на время, потраченное на задачи */
cron_sleep();
/* перечитать конфигурацию */
load_database(&database);
/* собрать задачи для данной минуты */
cron_tick(&database);
/* перевести TargetTime на начало следующей минуты */
TargetTime += 60;
}
Det er funksjonen cron_sleep som faktisk utfører oppgavene, kalt for å bruke job_runqueue (gjennomgå og kjøre oppgaver) og do_command (kjøre hver enkelt oppgave). Den siste funksjonen fortjener en nærmere gjennomgang.
Kjøring av oppgave
Funksjonen do_command er utført i god Unix-stil, det vil si at den gjør et fork for asynkron oppgavekjøring. Forelderen fortsetter å kjøre oppgaver, barnet forbereder oppgaveprosessen:
switch (fork()) {
case -1:
/* kunne ikke utføre fork */
break;
case 0:
/* barneprosess: prøver på nytt å ta hovedlåsen */
acquire_daemonlock(1);
/* går videre til å danne oppgaveprosessen */
child_process(e, u);
/* når den er ferdig, avslutter barneprosessen */
_exit(OK_EXIT);
break;
default:
/* forelderen fortsetter arbeidet */
break;
}I child_process er det ganske mye logikk: den tar hånd om standard utdata- og fe strømmer for deretter å sende dem på e-post (hvis miljøvariabelen MAILTO er angitt i oppgave tabellen), og til slutt venter den på at hoved prosessen skal fullføres.
Oppgaveprosessen dannes av en ny 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);
_exit(ERROR_EXIT);
}
break;
default:
break;
}Det er stort sett hele cron. Jeg lot være å dekke noen interessante detaljer, som å ta hensyn til eksterne brukere, men hovedpoengene er presentert.
Etterord
Cron er, overraskende nok, et enkelt og nyttig program, utført i beste tradisjon fra Unix-verdenen. Den gjør ingenting unødvendig, men utfører jobben sin utmerket i flere tiår. Å bli kjent med koden for den versjonen som leveres med Ubuntu tok ikke mer enn en time, og jeg fikk mye glede av det! Jeg håper jeg har klart å dele det med dere.
Jeg vet ikke med deg, men jeg synes det er litt trist å erkjenne at moderne programmering, med sin tendens til å bli overkomplisert og abstrahert, lenge har vært langt fra slik enkelhet.
Det finnes mange moderne alternativer til cron: systemd-timers lar deg organisere komplekse systemer med avhengigheter, while fcron gir mer fleksibilitet i ressursbruk for oppgaver. Men personlig har jeg alltid vært fornøyd med den enkleste crontab.
Kort sagt, elsk Unix, bruk enkle programmer, og glem ikke å lese man-sidene for plattformen din!
Kilde: habr.com
