Cron i Linux: historik, användning och enhet

Cron i Linux: historik, användning och enhet

Klassikern skrev att happy hours inte tittar. I dessa vilda tider fanns det varken programmerare eller Unix, men idag vet programmerare med säkerhet: cron kommer att hålla koll på tiden istället för dem.

Kommandoradsverktyg är både en svaghet och ett jobb för mig. sed, awk, wc, cut och andra gamla program körs av skript på våra servrar varje dag. Många av dem är designade som uppgifter för cron, en schemaläggare från 70-talet.

Under lång tid använde jag cron ytligt, utan att gå in på detaljer, men en dag, när jag stötte på ett fel när jag körde ett skript, bestämde jag mig för att undersöka det noggrant. Så här såg den här artikeln ut, medan jag skrev den blev jag bekant med POSIX crontab, de viktigaste cron-alternativen i populära Linux-distributioner och strukturen för några av dem.

Använder du Linux och kör cron-uppgifter? Är du intresserad av systemapplikationsarkitektur i Unix? Då är vi på väg!

Innehåll

Artens ursprung

Regelbunden körning av användar- eller systemprogram är en uppenbar nödvändighet i alla operativsystem. Därför har programmerare insett behovet av tjänster som gör det möjligt för dem att centralt planera och utföra uppgifter för länge sedan.

Unix-liknande operativsystem spårar sitt ursprung tillbaka till version 7 Unix, utvecklad på 70-talet av förra seklet på Bell Labs, inklusive av den berömda Ken Thompson. Version 7 Unix inkluderade även cron, en tjänst för att regelbundet köra superanvändaruppgifter.

En typisk modern cron är ett enkelt program, men driftalgoritmen för den ursprungliga versionen var ännu enklare: tjänsten vaknade en gång i minuten, läste en tabell med uppgifter från en enda fil (/etc/lib/crontab) och utfördes för superanvändare de uppgifter som borde ha utförts för närvarande.

Därefter levererades förbättrade versioner av den enkla och användbara tjänsten med alla Unix-liknande operativsystem.

Generaliserade beskrivningar av crontab-formatet och de grundläggande principerna för verktygets funktion inkluderades i huvudstandarden för Unix-liknande operativsystem - POSIX - 1992, och därmed blev cron från en de facto-standard en de jure-standard.

1987 släppte Paul Vixie, efter att ha undersökt Unix-användare om deras önskemål om cron, en annan version av demonen som korrigerade några av problemen med traditionell cron och utökade syntaxen för tabellfiler.

I den tredje versionen av Vixie började cron uppfylla POSIX-kraven, dessutom hade programmet en liberal licens, eller snarare fanns det ingen licens alls, förutom önskemålen i README: författaren ger inga garantier, författarens namn kan inte raderas, och programmet kan endast säljas tillsammans med källkod. Dessa krav visade sig vara kompatibla med principerna för fri programvara som växte i popularitet under dessa år, så några av de viktigaste Linux-distributionerna som dök upp i början av 90-talet tog Vixie cron som sitt system ett och utvecklar det fortfarande idag.

I synnerhet utvecklar Red Hat och SUSE en gaffel av Vixie cron - cronie, och Debian och Ubuntu använder originalutgåvan av Vixie cron med många patchar.

Låt oss först bekanta oss med användarverktyget crontab som beskrivs i POSIX, varefter vi kommer att titta på syntaxtilläggen som tillhandahålls i Vixie cron och användningen av varianter av Vixie cron i populära Linux-distributioner. Och slutligen, körsbäret på kakan är analysen av cron daemon-enheten.

POSIX crontab

Om den ursprungliga cronen alltid fungerade för superanvändaren, hanterar moderna schemaläggare ofta uppgifter för vanliga användare, vilket är säkrare och bekvämare.

Crons levereras som en uppsättning av två program: den ständigt körande cron-demonen och crontab-verktyget som är tillgängligt för användare. Den senare låter dig redigera uppgiftstabeller specifika för varje användare i systemet, medan demonen startar uppgifter från användar- och systemtabeller.

В POSIX standard demonens beteende beskrivs inte på något sätt och endast användarprogrammet är formaliserat crontab. Förekomsten av mekanismer för att starta användaruppgifter är naturligtvis underförstådd, men inte beskriven i detalj.

Genom att anropa crontab-verktyget kan du göra fyra saker: redigera användarens uppgiftstabell i editorn, ladda tabellen från en fil, visa den aktuella uppgiftstabellen och rensa uppgiftstabellen. Exempel på hur crontab-verktyget fungerar:

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

När du ringer crontab -e editorn som anges i standardmiljövariabeln kommer att användas EDITOR.

Själva uppgifterna beskrivs i följande 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örsta fem fälten i posterna: minuter [1..60], timmar [0..23], dagar i månaden [1..31], månader [1..12], veckodagar [0. .6], där 0 är söndag. Det sista, sjätte fältet är en rad som kommer att exekveras av standardkommandotolken.

I de första fem fälten kan värden listas separerade med kommatecken:

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

Eller med ett bindestreck:

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

Användaråtkomst till uppgiftsschemaläggning regleras i POSIX av filerna cron.allow och cron.deny, som listar användare med åtkomst till crontab respektive användare utan åtkomst till programmet. Standarden reglerar inte placeringen av dessa filer på något sätt.

Enligt standarden måste minst fyra miljövariabler skickas till lanserade program:

  1. HEM - användarens hemkatalog.
  2. LOGNAME — användarinloggning.
  3. PATH är sökvägen där du kan hitta standardsystemverktyg.
  4. SHELL — sökväg till den använda kommandotolken.

Noterbart säger POSIX ingenting om var värdena för dessa variabler kommer ifrån.

Bästsäljare - Vixie cron 3.0pl1

Den gemensamma förfadern till populära cron-varianter är Vixie cron 3.0pl1, som introducerades i e-postlistan comp.sources.unix 1992. Vi kommer att överväga huvuddragen i denna version mer i detalj.

Vixie cron finns i två program (cron och crontab). Som vanligt är demonen ansvarig för att läsa och köra uppgifter från systemuppgiftstabellen och individuella användaruppgiftstabeller, och crontab-verktyget ansvarar för att redigera användartabeller.

Uppgiftstabell och konfigurationsfiler

Superanvändaruppgiftstabellen finns i /etc/crontab. Syntaxen för systemtabellen motsvarar syntaxen för Vixie cron, med undantaget att den sjätte kolumnen i den anger namnet på användaren under vars räkning uppgiften startas:

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

Tabeller för vanliga användaruppgifter finns i /var/cron/tabs/användarnamn och använder samma syntax. När du kör crontab-verktyget som användare är det dessa filer som redigeras.

Listorna över användare med åtkomst till crontab hanteras i filerna /var/cron/allow och /var/cron/deny, där du bara behöver ange användarnamnet på en separat rad.

Utökad syntax

Jämfört med POSIX crontab innehåller Paul Vixeys lösning flera mycket användbara modifieringar av syntaxen för verktygets uppgiftstabeller.

En ny tabellsyntax har blivit tillgänglig: till exempel kan du ange veckodagar eller månader efter namn (mån, tis och så vidare):

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

Du kan ange steget genom vilket uppgifter startas:

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

Steg och intervaller kan blandas:

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

Intuitiva alternativ till den vanliga syntaxen stöds (starta om, årligen, årligen, månadsvis, veckovis, dagligen, midnatt, varje timme):

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

Miljö för uppdragsexekvering

Vixie cron låter dig ändra miljön för att köra applikationer.

Miljövariablerna USER, LOGNAME och HOME tillhandahålls inte bara av demonen, utan tas från en fil passwd. PATH-variabeln är satt till "/usr/bin:/bin" och SHELL-variabeln är satt till "/bin/sh". Värdena för alla variabler utom LOGNAME kan ändras i användartabeller.

Vissa miljövariabler (främst SHELL och HOME) används av cron själv för att köra uppgiften. Så här kan det se ut att använda bash istället för standard sh för att köra anpassade uppgifter:

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

I slutändan kommer alla miljövariabler som definieras i tabellen (används av cron eller behövs av processen) att skickas till den pågående uppgiften.

För att redigera filer använder crontab den redigerare som anges i miljövariabeln VISUAL eller EDITOR. Om miljön där crontab kördes inte har dessa variabler definierade, så används "/usr/ucb/vi" (ucb är förmodligen University of California, Berkeley).

cron på Debian och Ubuntu

Utvecklarna av Debian och derivatdistributioner har släppt mycket modifierad version Vixie cron version 3.0pl1. Det finns inga skillnader i syntaxen för tabellfiler, för användare är det samma Vixie cron. Största nya funktionen: Support syslog, SELinux и PAM.

Mindre märkbara, men påtagliga förändringar inkluderar placeringen av konfigurationsfiler och uppgiftstabeller.

Användartabeller i Debian finns i katalogen /var/spool/cron/crontabs, systemtabellen finns kvar - i /etc/crontab. Debianpaketspecifika uppgiftstabellerna placeras i /etc/cron.d, varifrån cron-demonen automatiskt läser dem. Användaråtkomstkontroll kontrolleras av filerna /etc/cron.allow och /etc/cron.deny.

Standardskalet är fortfarande /bin/sh, som i Debian är ett litet POSIX-kompatibelt skal streck, startas utan att läsa någon konfiguration (i icke-interaktivt läge).

Cron själv i de senaste versionerna av Debian lanseras via systemd, och startkonfigurationen kan ses i /lib/systemd/system/cron.service. Det finns inget speciellt i tjänstekonfigurationen; mer subtil uppgiftshantering kan göras genom miljövariabler som deklareras direkt i crontab för varje användare.

cronie på RedHat, Fedora och CentOS

Cronie — gaffel av Vixie cron version 4.1. Liksom i Debian har syntaxen inte ändrats, men stöd för PAM och SELinux, arbete i ett kluster, spårning av filer med inotify och andra funktioner har lagts till.

Standardkonfigurationen är på de vanliga platserna: systemtabellen är i /etc/crontab, paket lägger sina tabeller i /etc/cron.d, användartabeller går i /var/spool/cron/crontabs.

Daemonen körs under systemkontroll, tjänstens konfiguration är /lib/systemd/system/crond.service.

På Red Hat-liknande distributioner används /bin/sh som standard vid uppstart, vilket är standardbash. Det bör noteras att när du kör cron-jobb via /bin/sh, startar bash-skalet i POSIX-kompatibelt läge och läser ingen ytterligare konfiguration, körs i icke-interaktivt läge.

cronie i SLES och openSUSE

Den tyska distributionen SLES och dess derivata openSUSE använder samma kumpan. Demonen här lanseras också under systemd, tjänstens konfiguration finns i /usr/lib/systemd/system/cron.service. Konfiguration: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh är samma bash som körs i POSIX-kompatibelt icke-interaktivt läge.

Vixie cron enhet

Moderna ättlingar till cron har inte förändrats radikalt jämfört med Vixie cron, men ändå fått nya funktioner som inte krävs för att förstå programmets principer. Många av dessa tillägg är dåligt utformade och förvirrar koden. Den ursprungliga cron-källkoden av Paul Vixey är ett nöje att läsa.

Därför bestämde jag mig för att analysera cron-enheten med exemplet på ett cron-program som är gemensamt för båda utvecklingsgrenarna - Vixie cron 3.0pl1. Jag kommer att förenkla exemplen genom att ta bort ifdefs som komplicerar läsningen och utelämna mindre detaljer.

Demonens arbete kan delas in i flera steg:

  1. Programinitiering.
  2. Samla in och uppdatera listan över uppgifter som ska köras.
  3. Main cron loop igång.
  4. Starta en uppgift.

Låt oss titta på dem i ordning.

initiering

När den startas, efter att ha kontrollerat processargumenten, installerar cron signalhanterarna SIGCHLD och SIGHUP. Den första gör en loggpost om avslutningen av den underordnade processen, den andra stänger filbeskrivningen för loggfilen:

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

Cron-demonen körs alltid ensam på systemet, bara som en superanvändare och från huvudkatalogen för cron. Följande anrop skapar en låsfil med PID för demonprocessen, se till att användaren är korrekt och ändra den aktuella katalogen till den huvudsakliga:

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

Standardsökvägen är inställd, som kommer att användas när processer startas:

setenv("PATH", _PATH_DEFPATH, 1);

Sedan "demoniseras" processen: den skapar en underordnad kopia av processen genom att anropa gaffel och en ny session i underordnad processen (anropar setsid). Den överordnade processen behövs inte längre och den avslutas:

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

Avslutande av föräldraprocessen frigör låset på låsfilen. Dessutom krävs det att PID i filen uppdateras till barnet. Efter detta fylls uppgiftsdatabasen i:

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

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

Sedan går cron vidare till huvudarbetscykeln. Men innan dess är det värt att ta en titt på att ladda uppgiftslistan.

Samla och uppdatera uppgiftslistan

Funktionen load_database ansvarar för att ladda listan med uppgifter. Den kontrollerar huvudsystemets crontab och katalogen med användarfiler. Om filerna och katalogen inte har ändrats läses inte aktivitetslistan igen. Annars börjar en ny lista med uppgifter att bildas.

Ladda en systemfil med speciella fil- och tabellnamn:

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

Ladda användartabeller 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);
}

Därefter ersätts den gamla databasen med en ny.

I exemplen ovan verifierar funktionsanropet process_crontab att det finns en användare som matchar tabellfilnamnet (såvida det inte är en superanvändare) och anropar sedan load_user. Den senare läser redan själva filen rad för rad:

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

Här sätts antingen miljövariabeln (rader av formen VAR=värde) med load_env / env_set-funktionerna, eller så läses uppgiftsbeskrivningen (* * * * * /path/to/exec) med load_entry-funktionen.

Inmatningsentiteten som load_entry returnerar är vår uppgift, som placeras i den allmänna listan över uppgifter. Funktionen själv utför en utförlig analys av tidsformatet, men vi är mer intresserade av bildandet av miljövariabler och uppgiftsstartparametrar:

/* пользователь и группа для запуска задачи берутся из 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);

Huvudslingan fungerar med den aktuella listan över uppgifter.

Huvudslinga

Den ursprungliga cronen från version 7 Unix fungerade ganska enkelt: den läste om konfigurationen i en loop, startade uppgifterna för den aktuella minuten som en superanvändare och sov till början av nästa minut. Detta enkla tillvägagångssätt på äldre maskiner krävde för mycket resurser.

En alternativ version föreslogs i SysV, där demonen gick i vila antingen till närmaste minut som uppgiften definierades för, eller i 30 minuter. Färre resurser förbrukades för att läsa om konfigurationen och kontrollera uppgifter i det här läget, men att snabbt uppdatera listan med uppgifter blev obekvämt.

Vixie cron återgick till att kontrollera uppgiftslistor en gång i minuten, lyckligtvis i slutet av 80-talet fanns det betydligt fler resurser på vanliga 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 är direkt involverad i att utföra uppgifter, anropa funktionerna job_runqueue (uppräkna och köra uppgifter) och do_command (kör varje enskild uppgift). Den sista funktionen är värd att undersöka mer i detalj.

Köra en uppgift

Funktionen do_command exekveras i bra Unix-stil, det vill säga den gör en gaffel för att utföra uppgiften asynkront. Föräldraprocessen fortsätter att starta uppgifter, underordnad processen förbereder uppgiftsprocessen:

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

Det finns ganska mycket logik i child_process: den accepterar standardutdata och felströmmar, skickar dem sedan till e-post (om miljövariabeln MAILTO anges i uppgiftstabellen), och slutligen väntar på att huvudprocessen för uppgiften ska komplett.

Uppgiftsprocessen bildas av en annan 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 är i princip allt cron är. Jag utelämnade några intressanta detaljer, till exempel redovisning av fjärranvändare, men jag beskrev det viktigaste.

efterordet

Cron är ett förvånansvärt enkelt och användbart program, gjort i Unix-världens bästa traditioner. Hon gör inget extra, men hon har gjort sitt jobb fantastiskt i flera decennier nu. Att komma igenom koden för versionen som följer med Ubuntu tog inte mer än en timme, och jag hade väldigt roligt! Jag hoppas att jag kunde dela det med dig.

Jag vet inte om dig, men jag är lite ledsen över att inse att modern programmering, med sin tendens att överkomplicera och överabstrakt, inte har bidragit till sådan enkelhet på länge.

Det finns många moderna alternativ till cron: systemd-timers låter dig organisera komplexa system med beroenden, fcron låter dig mer flexibelt reglera resursförbrukning efter uppgifter. Men personligen räckte den enklaste crontab alltid för mig.

Kort sagt, älska Unix, använd enkla program och glöm inte att läsa manan för din plattform!

Källa: will.com

Lägg en kommentar