Cron i Linux: historie, bruk og enhet

Cron i Linux: historie, bruk og enhet

Klassikeren skrev at lykkelige timer ikke ser. I de ville tider var det verken programmerere eller Unix, men i dag vet programmerere med sikkerhet: cron vil holde styr på tiden i stedet for dem.

Kommandolinjeverktøy er både en svakhet og et ork for meg. sed, awk, wc, cut og andre gamle programmer kjøres av skript på våre servere hver dag. Mange av dem er designet som oppgaver for cron, en planlegger opprinnelig fra 70-tallet.

I lang tid brukte jeg cron overfladisk, uten å gå i detaljer, men en dag, da jeg oppdaget en feil ved kjøring av et skript, bestemte jeg meg for å se grundig på det. Slik så denne artikkelen ut, mens jeg skrev den ble jeg kjent med POSIX crontab, de viktigste cron-alternativene i populære Linux-distribusjoner og strukturen til noen av dem.

Bruker du Linux og kjører cron-oppgaver? Er du interessert i systemapplikasjonsarkitektur i Unix? Da er vi på vei!

Innhold

Artenes opprinnelse

Periodisk kjøring av bruker- eller systemprogrammer er en åpenbar nødvendighet i alle operativsystemer. Derfor har programmerere innsett behovet for tjenester som lar dem planlegge og utføre oppgaver sentralt for lenge siden.

Unix-lignende operativsystemer sporer sin opprinnelse tilbake til versjon 7 Unix, utviklet på 70-tallet av forrige århundre ved Bell Labs, inkludert av den berømte Ken Thompson. Versjon 7 Unix inkluderte også cron, en tjeneste for regelmessig kjøring av superbrukeroppgaver.

En typisk moderne cron er et enkelt program, men driftsalgoritmen til den opprinnelige versjonen var enda enklere: tjenesten våknet en gang i minuttet, leste en tabell med oppgaver fra en enkelt fil (/etc/lib/crontab) og utført for superbruker de oppgavene som skulle ha blitt utført for øyeblikket.

Deretter ble forbedrede versjoner av den enkle og nyttige tjenesten levert med alle Unix-lignende operativsystemer.

Generaliserte beskrivelser av crontab-formatet og de grunnleggende prinsippene for verktøyets drift ble inkludert i hovedstandarden for Unix-lignende operativsystemer - POSIX - i 1992, og dermed ble cron fra en de facto-standard en de jure-standard.

I 1987 ga Paul Vixie, etter å ha undersøkt Unix-brukere om deres ønsker for cron, ut en annen versjon av demonen som korrigerte noen av problemene med tradisjonell cron og utvidet syntaksen til tabellfiler.

Ved den tredje versjonen av Vixie begynte cron å oppfylle POSIX-kravene, i tillegg hadde programmet en liberal lisens, eller rettere sagt, det var ingen lisens i det hele tatt, bortsett fra ønskene i README: forfatteren gir ingen garantier, forfatterens navn kan ikke slettes, og programmet kan kun selges sammen med kildekode. Disse kravene viste seg å være kompatible med prinsippene for fri programvare som ble stadig mer populært i disse årene, så noen av de viktigste Linux-distribusjonene som dukket opp på begynnelsen av 90-tallet tok Vixie cron som sitt system og utvikler det fortsatt i dag.

Spesielt Red Hat og SUSE utvikler en gaffel av Vixie cron - cronie, og Debian og Ubuntu bruker den originale utgaven av Vixie cron med mange patcher.

La oss først bli kjent med brukerverktøyet crontab beskrevet i POSIX, hvoretter vi vil se på syntaksutvidelsene i Vixie cron og bruken av varianter av Vixie cron i populære Linux-distribusjoner. Og til slutt, kirsebæret på kaken er analysen av cron daemon-enheten.

POSIX crontab

Hvis den originale cronen alltid fungerte for superbrukeren, håndterer moderne planleggere ofte oppgaver til vanlige brukere, noe som er sikrere og mer praktisk.

Crons leveres som et sett med to programmer: den konstant kjørende cron-demonen og crontab-verktøyet tilgjengelig for brukere. Sistnevnte lar deg redigere oppgavetabeller som er spesifikke for hver bruker i systemet, mens daemonen starter oppgaver fra bruker- og systemtabeller.

В POSIX standard oppførselen til demonen er ikke beskrevet på noen måte, og bare brukerprogrammet er formalisert crontab. Eksistensen av mekanismer for å starte brukeroppgaver er selvfølgelig underforstått, men ikke beskrevet i detalj.

Ved å kalle opp crontab-verktøyet kan du gjøre fire ting: redigere brukerens oppgavetabell i redigeringsprogrammet, laste tabellen fra en fil, vise gjeldende oppgavetabell og tømme oppgavetabellen. Eksempler på hvordan crontab-verktøyet fungerer:

crontab -e # редактировать таблицу задач
crontab -l # показать таблицу задач
crontab -r # удалить таблицу задач
crontab path/to/file.crontab # загрузить таблицу задач из файла

Når du ringer crontab -e editoren spesifisert i standard miljøvariabelen vil bli brukt EDITOR.

Selve 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.log

De fem første feltene i postene: minutter [1..60], timer [0..23], dager i måneden [1..31], måneder [1..12], ukedager [0. .6], der 0 er søndag. Det siste, sjette feltet er en linje som vil bli utført av standard kommandotolk.

I de fem første feltene kan verdier listes opp med komma:

# задача, выполняемая в первую и десятую минуты каждого часа
1,10 * * * * /path/to/exec -a -b -c

Eller med bindestrek:

# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -c

Brukertilgang til oppgaveplanlegging reguleres i POSIX av filene cron.allow og cron.deny, som viser henholdsvis brukere med tilgang til crontab og brukere uten tilgang til programmet. Standarden regulerer ikke plasseringen av disse filene på noen måte.

I henhold til standarden må minst fire miljøvariabler sendes til lanserte programmer:

  1. HJEM - brukerens hjemmekatalog.
  2. LOGNAME — brukerpålogging.
  3. PATH er banen der du kan finne standard systemverktøy.
  4. SHELL — bane til den brukte kommandotolken.

Spesielt sier POSIX ingenting om hvor verdiene for disse variablene kommer fra.

Bestselger - Vixie cron 3.0pl1

Den vanlige stamfaren til populære cron-varianter er Vixie cron 3.0pl1, introdusert i comp.sources.unix-postlisten i 1992. Vi vil vurdere hovedtrekkene i denne versjonen mer detaljert.

Vixie cron kommer i to programmer (cron og crontab). Som vanlig er daemonen ansvarlig for å lese og kjøre oppgaver fra systemoppgavetabellen og individuelle brukeroppgavetabeller, og crontab-verktøyet er ansvarlig for å redigere brukertabeller.

Oppgavetabell og konfigurasjonsfiler

Superbrukeroppgavetabellen ligger i /etc/crontab. Syntaksen til systemtabellen tilsvarer syntaksen til Vixie cron, med unntak av at den sjette kolonnen i den indikerer navnet på brukeren under hvis vegne oppgaven startes:

# Запускается ежеминутно от пользователя vlad
* * * * * vlad /path/to/exec

Vanlige brukeroppgavetabeller er plassert i /var/cron/tabs/brukernavn og bruker samme syntaks. Når du kjører crontab-verktøyet som bruker, er dette filene som redigeres.

Listene over brukere med tilgang til crontab administreres i filene /var/cron/allow og /var/cron/deny, hvor du bare trenger å skrive inn brukernavnet på en egen linje.

Utvidet syntaks

Sammenlignet med POSIX crontab, inneholder Paul Vixeys løsning flere svært nyttige modifikasjoner av syntaksen til verktøyets oppgavetabeller.

En ny tabellsyntaks har blitt tilgjengelig: for eksempel kan du spesifisere ukedager eller måneder etter navn (man, tirsdag og så videre):

# Запускается ежеминутно по понедельникам и вторникам в январе
* * * Jan Mon,Tue /path/to/exec

Du kan spesifisere trinnet som oppgaver skal startes gjennom:

# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/exec

Trinn og intervaller kan blandes:

# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/exec

Intuitive alternativer til den vanlige syntaksen støttes (omstart, årlig, årlig, månedlig, ukentlig, daglig, midnatt, hver time):

# Запускается после перезагрузки системы
@reboot /exec/on/reboot
# Запускается раз в день
@daily /exec/daily
# Запускается раз в час
@hourly /exec/daily

Oppgaveutførelsesmiljø

Vixie cron lar deg endre miljøet for å kjøre applikasjoner.

Miljøvariablene USER, LOGNAME og HOME leveres ikke bare av daemonen, men er hentet fra en fil passwd. PATH-variabelen er satt til "/usr/bin:/bin" og SHELL-variabelen er satt til "/bin/sh". Verdiene til alle variabler unntatt LOGNAME kan endres i brukertabeller.

Noen miljøvariabler (spesielt SHELL og HOME) brukes av cron selv for å kjøre oppgaven. Slik kan det se ut å bruke bash i stedet for standard sh for å kjøre egendefinerte oppgaver:

SHELL=/bin/bash
HOME=/tmp/
# exec будет запущен bash-ем в /tmp/
* * * * * /path/to/exec

Til syvende og sist vil alle miljøvariabler definert i tabellen (brukt av cron eller nødvendig av prosessen) bli sendt til den kjørende oppgaven.

For å redigere filer bruker crontab editoren som er spesifisert i VISUAL eller EDITOR miljøvariabelen. Hvis miljøet der crontab ble kjørt ikke har disse variablene definert, brukes "/usr/ucb/vi" (ucb er sannsynligvis University of California, Berkeley).

cron på Debian og Ubuntu

Utviklerne av Debian og avledede distribusjoner har gitt ut svært modifisert versjon Vixie cron versjon 3.0pl1. Det er ingen forskjeller i syntaksen til tabellfiler; for brukere er det samme Vixie cron. Største nye funksjon: Støtte syslog, SELinux и PAM.

Mindre merkbare, men håndgripelige endringer inkluderer plasseringen av konfigurasjonsfiler og oppgavetabeller.

Brukertabeller i Debian er plassert i katalogen /var/spool/cron/crontabs, systemtabellen er fortsatt der - i /etc/crontab. Debian-pakkespesifikke oppgavetabeller er plassert i /etc/cron.d, hvorfra cron-demonen automatisk leser dem. Brukertilgangskontroll kontrolleres av filene /etc/cron.allow og /etc/cron.deny.

Standardskallet er fortsatt /bin/sh, som i Debian er et lite POSIX-kompatibelt skall dash, lansert uten å lese noen konfigurasjon (i ikke-interaktiv modus).

Cron selv i de nyeste versjonene av Debian lanseres via systemd, og lanseringskonfigurasjonen kan sees i /lib/systemd/system/cron.service. Det er ikke noe spesielt i tjenestekonfigurasjonen; enhver mer subtil oppgavebehandling kan gjøres gjennom miljøvariabler deklarert direkte i crontab for hver bruker.

cronie på RedHat, Fedora og CentOS

kjæreste — gaffel til Vixie cron versjon 4.1. Som i Debian har ikke syntaksen endret seg, men støtte for PAM og SELinux, arbeid i en klynge, sporing av filer ved hjelp av inotify og andre funksjoner er lagt til.

Standardkonfigurasjonen er på de vanlige stedene: systemtabellen er i /etc/crontab, pakker legger tabellene sine i /etc/cron.d, brukertabeller går i /var/spool/cron/crontabs.

Daemonen kjører under systemkontroll, tjenestekonfigurasjonen er /lib/systemd/system/crond.service.

På Red Hat-lignende distribusjoner brukes /bin/sh som standard ved oppstart, som er standard bash. Det skal bemerkes at når du kjører cron-jobber via /bin/sh, starter bash-skallet i POSIX-kompatibel modus og leser ingen ekstra konfigurasjon, kjører i ikke-interaktiv modus.

cronie i SLES og openSUSE

Den tyske distribusjonen SLES og dens derivat openSUSE bruker samme cronie. Daemonen her er også lansert under systemd, tjenestekonfigurasjonen ligger i /usr/lib/systemd/system/cron.service. Konfigurasjon: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh er den samme bashen som kjører i POSIX-kompatibel ikke-interaktiv modus.

Vixie cron enhet

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 til programmet. Mange av disse utvidelsene er dårlig utformet og forvirrer koden. Den originale cron-kildekoden av Paul Vixey er en fornøyelse å lese.

Derfor bestemte jeg meg for å analysere cron-enheten ved å bruke eksempelet på et cron-program som er felles for begge utviklingsgrenene - Vixie cron 3.0pl1. Jeg vil forenkle eksemplene ved å fjerne ifdefs som kompliserer lesing og utelate mindre detaljer.

Demonens arbeid kan deles inn i flere stadier:

  1. Programinitialisering.
  2. Samle og oppdatere listen over oppgaver som skal kjøres.
  3. Hoved cron loop kjører.
  4. Start en oppgave.

La oss se på dem i rekkefølge.

Initialisering

Når den startes, etter å ha sjekket prosessargumentene, installerer cron signalbehandlerne SIGCHLD og SIGHUP. Den første gjør en loggoppføring om avslutningen av den underordnede prosessen, den andre lukker filbeskrivelsen til loggfilen:

signal(SIGCHLD, sigchld_handler);
signal(SIGHUP, sighup_handler);

Cron-demonen kjører alltid alene på systemet, bare som en superbruker og fra hovedkatalogen for cron. Følgende anrop oppretter en låsefil med PID-en til demonprosessen, sørg for at brukeren er riktig og endre gjeldende katalog til den viktigste:

acquire_daemonlock(0);
set_cron_uid();
set_cron_cwd();

Standardbanen er satt, som vil bli brukt når prosesser startes:

setenv("PATH", _PATH_DEFPATH, 1);

Deretter "demoniseres" prosessen: den lager en underkopi av prosessen ved å ringe fork og en ny økt i underordnet prosessen (kalle setsid). Foreldreprosessen er ikke lenger nødvendig, og den avsluttes:

switch (fork()) {
case -1:
    /* критическая ошибка и завершение работы */
    exit(0);
break;
case 0:
    /* дочерний процесс */
    (void) setsid();
break;
default:
    /* родительский процесс завершает работу */
    _exit(0);
}

Avslutning av den overordnede prosessen frigjør låsen på låsefilen. I tillegg kreves det å oppdatere PID i filen til barnet. Etter dette fylles oppgavedatabasen ut:

/* повторный захват лока */
acquire_daemonlock(0);

/* Заполнение БД  */
database.head = NULL;
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);

Deretter går cron videre til hovedarbeidssyklusen. Men før det er det verdt å ta en titt på å laste oppgavelisten.

Samle og oppdatere oppgavelisten

load_database-funksjonen er ansvarlig for å laste inn listen over oppgaver. Den sjekker hovedsystemets crontab og katalogen med brukerfiler. Hvis filene og katalogen ikke er endret, leses ikke oppgavelisten på nytt. Ellers begynner en ny liste over oppgaver å dannes.

Laste inn en systemfil med spesielle fil- og tabellnavn:

/* если файл системной таблицы изменился, перечитываем */
if (syscron_stat.st_mtime) {
    process_crontab("root", "*system*",
    SYSCRONTAB, &syscron_stat,
    &new_db, old_db);
}

Laste brukertabeller i en løkke:

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);
}

Deretter erstattes den gamle databasen med en ny.

I eksemplene ovenfor verifiserer funksjonskallet process_crontab at det finnes en bruker som samsvarer med tabellfilnavnet (med mindre det er en superbruker) og kaller deretter load_user. Sistnevnte leser allerede selve filen linje for linje:

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;
    }
}

Her settes enten miljøvariabelen (linjer med formen VAR=verdi) ved hjelp av load_env / env_set-funksjonene, eller oppgavebeskrivelsen leses (* * * * * /path/to/exec) ved hjelp av load_entry-funksjonen.

Oppføringsenheten som load_entry returnerer er vår oppgave, som er plassert i den generelle listen over oppgaver. Funksjonen i seg selv utfører en detaljert analyse av tidsformatet, men vi er mer interessert i dannelsen av miljøvariabler og oppgavestartparametere:

/* пользователь и группа для запуска задачи берутся из 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 fungerer med gjeldende liste over oppgaver.

Hovedsløyfe

Den originale cronen fra versjon 7 Unix fungerte ganske enkelt: den leste konfigurasjonen på nytt i en løkke, startet oppgavene for det gjeldende minuttet som en superbruker og sov til begynnelsen av neste minutt. Denne enkle tilnærmingen på eldre maskiner krevde for mange ressurser.

En alternativ versjon ble foreslått i SysV, der demonen gikk i dvale enten til det nærmeste minuttet som oppgaven ble definert for, eller i 30 minutter. Færre ressurser ble brukt for å lese konfigurasjonen på nytt og sjekke oppgaver i denne modusen, men raskt å oppdatere listen over oppgaver ble upraktisk.

Vixie cron gikk tilbake til å sjekke oppgavelister en gang i minuttet, heldigvis på slutten av 80-tallet var det betydelig flere ressurser på standard Unix-maskiner:

/* первичная загрузка задач */
load_database(&database);
/* запустить задачи, поставленные к выполнению после перезагрузки системы */
run_reboot_jobs(&database);
/* сделать TargetTime началом ближайшей минуты */
cron_sync();
while (TRUE) {
    /* выполнить задачи, после чего спать до TargetTime с поправкой на время, потраченное на задачи */
    cron_sleep();

    /* перечитать конфигурацию */
    load_database(&database);

    /* собрать задачи для данной минуты */
    cron_tick(&database);

    /* перевести TargetTime на начало следующей минуты */
    TargetTime += 60;
}

Cron_sleep-funksjonen er direkte involvert i å utføre oppgaver, kaller funksjonene job_runqueue (enumerate og run tasks) og do_command (kjør hver enkelt oppgave). Den siste funksjonen er verdt å undersøke nærmere.

Kjøre en oppgave

Do_command-funksjonen utføres i god Unix-stil, det vil si at den gjør en gaffel for å utføre oppgaven asynkront. Foreldreprosessen fortsetter å starte oppgaver, barneprosessen forbereder oppgaveprosessen:

switch (fork()) {
case -1:
    /*не смогли выполнить fork */
    break;
case 0:
    /* дочерний процесс: на всякий случай еще раз пробуем захватить главный лок */
    acquire_daemonlock(1);
    /* переходим к формированию процесса задачи */
    child_process(e, u);
    /* по завершению дочерний процесс заканчивает работу */
    _exit(OK_EXIT);
    break;
default:
    /* родительский процесс продолжает работу */
    break;
}

Det er ganske mye logikk i child_process: den tar standard utdata- og feilstrømmer til seg selv, for deretter å sende den til e-post (hvis MAILTO-miljøvariabelen er spesifisert i oppgavetabellen), og til slutt venter på hoved prosessen med å fullføre oppgaven.

Oppgaveprosessen er dannet av en annen gaffel:

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;
}

Det er i grunnen alt cron er. Jeg utelot noen interessante detaljer, for eksempel regnskap for eksterne brukere, men jeg skisserte det viktigste.

etterord

Cron er et overraskende enkelt og nyttig program, laget i de beste tradisjonene i Unix-verdenen. Hun gjør ikke noe ekstra, men hun har gjort jobben sin fantastisk i flere tiår nå. Å komme gjennom koden for versjonen som følger med Ubuntu tok ikke mer enn en time, og jeg hadde det veldig gøy! Jeg håper jeg klarte å dele det med deg.

Jeg vet ikke med deg, men jeg er litt trist å innse at moderne programmering, med sin tendens til å overkomplisere og overabstrakt, ikke har bidratt til en slik enkelhet på lenge.

Det er mange moderne alternativer til cron: systemd-timere lar deg organisere komplekse systemer med avhengigheter, fcron lar deg regulere ressursforbruket mer fleksibelt etter oppgaver. Men personlig var den enkleste crontab alltid nok for meg.

Kort sagt, elsk Unix, bruk enkle programmer og ikke glem å lese manaen for plattformen din!

Kilde: www.habr.com

Legg til en kommentar