Cron i Linux: historie, brug og enhed

Cron i Linux: historie, brug og enhed

Klassikeren skrev, at glade timer ikke ser til. I de vilde tider var der hverken programmører eller Unix, men i dag ved programmører med sikkerhed: cron vil holde styr på tiden i stedet for dem.

Kommandolinjeværktøjer er både en svaghed og en opgave for mig. sed, awk, wc, cut og andre gamle programmer køres af scripts på vores servere hver dag. Mange af dem er designet som opgaver for cron, en skemalægger oprindeligt fra 70'erne.

I lang tid brugte jeg cron overfladisk uden at gå i detaljer, men en dag, da jeg stødte på en fejl ved kørsel af et script, besluttede jeg mig for at undersøge det grundigt. Sådan så denne artikel ud, mens jeg skrev den, blev jeg bekendt med POSIX crontab, de vigtigste cron-muligheder i populære Linux-distributioner og strukturen af ​​nogle af dem.

Bruger du Linux og kører cron-opgaver? Er du interesseret i systemapplikationsarkitektur i Unix? Så er vi på vej!

Indhold

Arternes oprindelse

Periodisk udførelse af bruger- eller systemprogrammer er en indlysende nødvendighed i alle operativsystemer. Derfor indså programmører behovet for tjenester, der giver dem mulighed for centralt at planlægge og udføre opgaver for længe siden.

Unix-lignende operativsystemer sporer deres oprindelse tilbage til Version 7 Unix, udviklet i 70'erne af forrige århundrede på Bell Labs, herunder af den berømte Ken Thompson. Version 7 Unix inkluderede også cron, en tjeneste til regelmæssigt at køre superbrugeropgaver.

En typisk moderne cron er et simpelt program, men betjeningsalgoritmen for den originale version var endnu enklere: tjenesten vågnede en gang i minuttet, læste en tabel med opgaver fra en enkelt fil (/etc/lib/crontab) og udførte for superbruger de opgaver, der skulle have været udført på nuværende tidspunkt.

Efterfølgende blev der leveret forbedrede versioner af den enkle og nyttige service med alle Unix-lignende styresystemer.

Generaliserede beskrivelser af crontab-formatet og de grundlæggende principper for værktøjets drift blev inkluderet i hovedstandarden for Unix-lignende operativsystemer - POSIX - i 1992, og dermed blev cron fra en de facto standard en de jure standard.

I 1987 udgav Paul Vixie, efter at have undersøgt Unix-brugere om deres ønsker for cron, en anden version af dæmonen, der korrigerede nogle af problemerne med traditionel cron og udvidede syntaksen af ​​tabelfiler.

Ved den tredje version af Vixie begyndte cron at opfylde POSIX-kravene, derudover havde programmet en liberal licens, eller rettere sagt var der ingen licens overhovedet, bortset fra ønskerne i README: forfatteren giver ingen garantier, forfatterens navn kan ikke slettes, og programmet kan kun sælges sammen med kildekode. Disse krav viste sig at være kompatible med principperne for fri software, der vandt popularitet i disse år, så nogle af de vigtigste Linux-distributioner, der dukkede op i begyndelsen af ​​90'erne, tog Vixie cron som deres system og udvikler det stadig i dag.

Især Red Hat og SUSE udvikler en gaffel af Vixie cron - cronie, og Debian og Ubuntu bruger den originale udgave af Vixie cron med mange patches.

Lad os først stifte bekendtskab med brugerværktøjet crontab beskrevet i POSIX, hvorefter vi vil se på syntaksudvidelserne i Vixie cron og brugen af ​​variationer af Vixie cron i populære Linux-distributioner. Og endelig er kirsebæret på kagen analysen af ​​cron daemon-enheden.

POSIX crontab

Hvis den originale cron altid fungerede for superbrugeren, håndterer moderne skemalæggere ofte almindelige brugeres opgaver, hvilket er mere sikkert og bekvemt.

Crons leveres som et sæt af to programmer: den konstant kørende cron-dæmon og crontab-værktøjet, der er tilgængeligt for brugerne. Sidstnævnte giver dig mulighed for at redigere opgavetabeller, der er specifikke for hver bruger i systemet, mens dæmonen starter opgaver fra bruger- og systemtabeller.

В POSIX standard dæmonens adfærd er ikke beskrevet på nogen måde, og kun brugerprogrammet er formaliseret crontab. Eksistensen af ​​mekanismer til at starte brugeropgaver er selvfølgelig underforstået, men ikke beskrevet i detaljer.

Ved at kalde crontab-værktøjet kan du gøre fire ting: redigere brugerens opgavetabel i editoren, indlæse tabellen fra en fil, vise den aktuelle opgavetabel og rydde opgavetabellen. Eksempler på, hvordan crontab-værktøjet fungerer:

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

Når du ringer crontab -e editoren angivet i standard miljøvariablen vil blive brugt EDITOR.

Selve opgaverne 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 første fem felter i posterne: minutter [1..60], timer [0..23], dage i måneden [1..31], måneder [1..12], ugedage [0. .6], hvor 0 er søndag. Det sidste, sjette felt er en linje, der vil blive udført af standardkommandofortolkeren.

I de første fem felter kan værdier angives adskilt med kommaer:

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

Eller med en bindestreg:

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

Brugeradgang til opgaveplanlægning reguleres i POSIX af filerne cron.allow og cron.deny, som viser brugere med henholdsvis adgang til crontab og brugere uden adgang til programmet. Standarden regulerer ikke placeringen af ​​disse filer på nogen måde.

Ifølge standarden skal mindst fire miljøvariabler overføres til lancerede programmer:

  1. HJEM - brugerens hjemmemappe.
  2. LOGNAVN — brugerlogin.
  3. PATH er stien, hvor du kan finde standard systemværktøjer.
  4. SHELL — sti til den brugte kommandofortolker.

Navnlig siger POSIX intet om, hvor værdierne for disse variabler kommer fra.

Bestseller - Vixie cron 3.0pl1

Den fælles forfader til populære cron-varianter er Vixie cron 3.0pl1, introduceret på comp.sources.unix-mailinglisten i 1992. Vi vil overveje hovedfunktionerne i denne version mere detaljeret.

Vixie cron kommer i to programmer (cron og crontab). Som sædvanlig er dæmonen ansvarlig for at læse og køre opgaver fra systemopgavetabellen og individuelle brugeropgavetabeller, og crontab-værktøjet er ansvarlig for at redigere brugertabeller.

Opgavetabel og konfigurationsfiler

Superbruger opgavetabellen er placeret i /etc/crontab. Syntaksen for systemtabellen svarer til syntaksen for Vixie cron, med den undtagelse, at den sjette kolonne i den angiver navnet på den bruger, under hvis vegne opgaven startes:

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

Normale brugeropgavetabeller er placeret i /var/cron/tabs/brugernavn og bruger den samme syntaks. Når du kører crontab-værktøjet som bruger, er disse filer, der redigeres.

Listerne over brugere med adgang til crontab administreres i filerne /var/cron/allow og /var/cron/deny, hvor du blot skal indtaste brugernavnet i en separat linje.

Udvidet syntaks

Sammenlignet med POSIX crontab indeholder Paul Vixeys løsning adskillige meget nyttige modifikationer til syntaksen af ​​hjælpeprogrammets opgavetabeller.

En ny tabelsyntaks er blevet tilgængelig: for eksempel kan du angive ugedage eller måneder ved navn (man, tirsdag og så videre):

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

Du kan angive det trin, hvorigennem opgaver skal startes:

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

Trin og intervaller kan blandes:

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

Intuitive alternativer til den sædvanlige syntaks understøttes (genstart, årligt, årligt, månedligt, ugentligt, dagligt, midnat, hver time):

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

Opgaveudførelsesmiljø

Vixie cron giver dig mulighed for at ændre miljøet for at køre applikationer.

Miljøvariablerne USER, LOGNAME og HOME leveres ikke blot af dæmonen, men er taget fra en fil passwd. PATH-variablen er sat til "/usr/bin:/bin" og SHELL-variablen er sat til "/bin/sh". Værdierne for alle variable undtagen LOGNAME kan ændres i brugertabeller.

Nogle miljøvariabler (især SHELL og HOME) bruges af cron selv til at køre opgaven. Sådan kan det se ud at bruge bash i stedet for standard sh til at køre tilpassede opgaver:

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

I sidste ende vil alle miljøvariabler defineret i tabellen (brugt af cron eller nødvendige af processen) blive videregivet til den kørende opgave.

For at redigere filer bruger crontab den editor, der er angivet i miljøvariablen VISUAL eller EDITOR. Hvis miljøet, hvor crontab blev kørt, ikke har disse variable defineret, så bruges "/usr/ucb/vi" (ucb er sandsynligvis University of California, Berkeley).

cron på Debian og Ubuntu

Udviklerne af Debian og afledte distributioner har frigivet stærkt modificeret version Vixie cron version 3.0pl1. Der er ingen forskelle i syntaksen af ​​tabelfiler; for brugere er det den samme Vixie cron. Største nye funktion: Support syslog, SELinux и PAM.

Mindre mærkbare, men håndgribelige ændringer omfatter placeringen af ​​konfigurationsfiler og opgavetabeller.

Brugertabeller i Debian er placeret i mappen /var/spool/cron/crontabs, systemtabellen er der stadig - i /etc/crontab. Debian-pakkespecifikke opgavetabeller placeres i /etc/cron.d, hvorfra cron-dæmonen automatisk læser dem. Brugeradgangskontrol styres af filerne /etc/cron.allow og /etc/cron.deny.

Standardskallen er stadig /bin/sh, som i Debian er en lille POSIX-kompatibel skal Dash, lanceret uden at læse nogen konfiguration (i ikke-interaktiv tilstand).

Cron selv i de seneste versioner af Debian lanceres via systemd, og startkonfigurationen kan ses i /lib/systemd/system/cron.service. Der er ikke noget særligt i servicekonfigurationen; enhver mere subtil opgavestyring kan udføres gennem miljøvariabler, der erklæres direkte i hver brugers crontab.

cronie på RedHat, Fedora og CentOS

cronie — gaffel af Vixie cron version 4.1. Som i Debian er syntaksen ikke ændret, men understøttelse af PAM og SELinux, arbejde i en klynge, sporing af filer ved hjælp af inotify og andre funktioner er blevet tilføjet.

Standardkonfigurationen er på de sædvanlige steder: systemtabellen er i /etc/crontab, pakker placerer deres tabeller i /etc/cron.d, brugertabeller går i /var/spool/cron/crontabs.

Dæmonen kører under systemkontrol, servicekonfigurationen er /lib/systemd/system/crond.service.

På Red Hat-lignende distributioner bruges /bin/sh som standard ved opstart, som er standard bash. Det skal bemærkes, at når du kører cron-job via /bin/sh, starter bash-skallen i POSIX-kompatibel tilstand og læser ikke nogen yderligere konfiguration, idet den kører i ikke-interaktiv tilstand.

cronie i SLES og openSUSE

Den tyske distribution SLES og dens afledte openSUSE bruger den samme cronie. Dæmonen her er også lanceret under systemd, servicekonfigurationen er placeret i /usr/lib/systemd/system/cron.service. Konfiguration: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh er den samme bash, der kører i POSIX-kompatibel ikke-interaktiv tilstand.

Vixie cron enhed

Moderne efterkommere af cron har ikke ændret sig radikalt sammenlignet med Vixie cron, men har stadig fået nye funktioner, der ikke er nødvendige for at forstå programmets principper. Mange af disse udvidelser er dårligt designet og forvirrer koden. Den originale cron-kildekode af Paul Vixey er en fornøjelse at læse.

Derfor besluttede jeg at analysere cron-enheden ved at bruge eksemplet med et cron-program, der er fælles for begge udviklingsgrene - Vixie cron 3.0pl1. Jeg vil forenkle eksemplerne ved at fjerne ifdefs, der komplicerer læsning, og udelade mindre detaljer.

Dæmonens arbejde kan opdeles i flere faser:

  1. Initialisering af programmet.
  2. Indsamling og opdatering af listen over opgaver, der skal køres.
  3. Hoved cron loop kører.
  4. Start en opgave.

Lad os se på dem i rækkefølge.

Initialisering

Når den startes, efter at have kontrolleret procesargumenterne, installerer cron signalbehandlerne SIGCHLD og SIGHUP. Den første laver en logindtastning om afslutningen af ​​den underordnede proces, den anden lukker filbeskrivelsen for logfilen:

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

Cron-dæmonen kører altid alene på systemet, kun som en superbruger og fra cron-hovedbiblioteket. Følgende kald opretter en låsefil med dæmonprocessens PID, sørg for at brugeren er korrekt og skift den aktuelle mappe til den primære:

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

Standardstien er indstillet, som vil blive brugt, når processer startes:

setenv("PATH", _PATH_DEFPATH, 1);

Derefter "dæmoniseres" processen: den opretter en underordnet kopi af processen ved at kalde fork og en ny session i underordnet processen (kalder setsid). Forældreprocessen er ikke længere nødvendig, og den afsluttes:

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

Afslutning af forældreprocessen frigiver låsen på låsefilen. Derudover er det påkrævet at opdatere PID i filen til barnet. Herefter udfyldes opgavedatabasen:

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

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

Derefter går cron videre til den primære arbejdscyklus. Men før det er det værd at tage et kig på at indlæse opgavelisten.

Indsamling og opdatering af opgavelisten

load_database-funktionen er ansvarlig for at indlæse listen over opgaver. Det kontrollerer hovedsystemets crontab og mappen med brugerfiler. Hvis filerne og mappen ikke er ændret, genlæses opgavelisten ikke. Ellers begynder en ny liste over opgaver at blive dannet.

Indlæsning af en systemfil med specielle fil- og tabelnavne:

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

Indlæsning af brugertabeller i en loop:

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

Hvorefter den gamle database udskiftes med en ny.

I eksemplerne ovenfor verificerer funktionskaldet process_crontab, at der findes en bruger, der matcher tabelfilnavnet (medmindre det er en superbruger), og kalder derefter load_user. Sidstnævnte læser 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 indstilles enten miljøvariablen (linjer med formen VAR=værdi) ved hjælp af funktionerne load_env / env_set, eller opgavebeskrivelsen læses (* * * * * /path/to/exec) ved hjælp af load_entry-funktionen.

Indgangsenheden, som load_entry returnerer, er vores opgave, som er placeret i den generelle liste over opgaver. Funktionen selv udfører en detaljeret parsing af tidsformatet, men vi er mere interesserede i dannelsen af ​​miljøvariabler og opgavestartparametre:

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

Hovedsløjfe

Den originale cron fra Version 7 Unix fungerede ganske enkelt: den genlæste konfigurationen i en loop, lancerede det aktuelle minuts opgaver som superbruger og sov indtil starten af ​​det næste minut. Denne enkle tilgang på ældre maskiner krævede for mange ressourcer.

En alternativ version blev foreslået i SysV, hvor dæmonen gik i dvale enten indtil det nærmeste minut, som opgaven var defineret for, eller i 30 minutter. Færre ressourcer blev brugt til at genlæse konfigurationen og kontrollere opgaver i denne tilstand, men hurtigt at opdatere listen over opgaver blev ubelejligt.

Vixie cron vendte tilbage til at tjekke opgavelister en gang i minuttet, heldigvis i slutningen af ​​80'erne var der betydeligt flere ressourcer 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-funktionen er direkte involveret i udførelsen af ​​opgaver, kalder funktionerne job_runqueue (enumerate og run tasks) og do_command (kør hver enkelt opgave). Den sidste funktion er værd at undersøge nærmere.

Kører en opgave

Do_command-funktionen udføres i god Unix-stil, det vil sige, at den udfører opgaven asynkront. Forældreprocessen fortsætter med at starte opgaver, underordnet processen forbereder opgaveprocessen:

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

Der er ret meget logik i child_process: det tager standard output og fejlstrømme ind i sig selv for derefter at sende det til mail (hvis MAILTO miljøvariablen er angivet i opgavetabellen), og til sidst venter på hoved processen med at udføre opgaven.

Opgaveprocessen er dannet af en anden 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 stort set alt, hvad cron er. Jeg udelod nogle interessante detaljer, for eksempel at tage højde for fjernbrugere, men jeg skitserede det vigtigste.

efterskrift

Cron er et overraskende enkelt og nyttigt program, lavet i de bedste traditioner i Unix-verdenen. Hun gør ikke noget ekstra, men hun har gjort sit arbejde vidunderligt i flere årtier nu. Det tog ikke mere end en time at komme igennem koden til den version, der følger med Ubuntu, og jeg havde det meget sjovt! Jeg håber, jeg kunne dele det med dig.

Jeg ved ikke med dig, men jeg er lidt ked af at indse, at moderne programmering, med dens tendens til at overkomplicere og overabstrakt, ikke har været befordrende for en sådan enkelhed i lang tid.

Der er mange moderne alternativer til cron: systemd-timere giver dig mulighed for at organisere komplekse systemer med afhængigheder, fcron giver dig mulighed for mere fleksibelt at regulere ressourceforbrug efter opgaver. Men personligt var den enkleste crontab altid nok for mig.

Kort sagt, elsk Unix, brug simple programmer og glem ikke at læse manaen til din platform!

Kilde: www.habr.com

Tilføj en kommentar