Cron u Linuxu: istorija, upotreba i uređaj

Cron u Linuxu: istorija, upotreba i uređaj

Klasik je napisao da se sretni sati ne poštuju. U tim divljim vremenima još nije bilo programera ili Unixa, ali ovih dana programeri sigurno znaju: umjesto njih, cron će pratiti vrijeme.

Uslužni programi komandne linije su za mene i slabost i rutina. sed, awk, wc, cut i drugi stari programi se svakodnevno pokreću skriptama na našim serverima. Mnogi od njih su dizajnirani kao zadaci za cron, planer iz 70-ih.

Dugo sam koristio cron površno, ne upuštajući se u detalje, ali jednog dana, suočen sa greškom pri pokretanju skripte, odlučio sam da je detaljno ispitam. Tako je nastao ovaj članak tokom kojeg sam se upoznao sa POSIX crontab-om, glavnim cron opcijama u popularnim Linux distribucijama i dizajnom nekih od njih.

Koristite li Linux i izvršavate zadatke u cron-u? Da li ste zainteresovani za arhitekturu sistemskih aplikacija u Unixu? Onda smo na putu!

Sadržaj

Poreklo vrste

Periodično izvršavanje korisničkih ili sistemskih programa je očigledna potreba u svim operativnim sistemima. Stoga su programeri prepoznali potrebu za uslugama koje omogućavaju centralizirano planiranje i izvršavanje zadataka na vrlo dugo vrijeme.

Operativni sistemi slični Unixu vuku svoje poreklo od verzije 7 Unixa, razvijene 70-ih godina prošlog veka u Bell Labs-u, uključujući čuvenog Kena Tompsona. Verzija 7 Unixa se takođe isporučuje sa cron, servisom za redovno izvršavanje zadataka superkorisnika.

Tipičan moderni cron je jednostavan program, ali algoritam originalne verzije je bio još jednostavniji: usluga se budila jednom u minuti, čitala tabelu sa zadacima iz jedne datoteke (/etc/lib/crontab) i izvodila za superkorisnika one zadatke koji su trebali biti obavljeni u trenutnom minutu .

Nakon toga, poboljšane verzije jednostavne i korisne usluge isporučene su sa svim operativnim sistemima sličnim Unixu.

Generalizovani opisi crontab formata i osnovni principi uslužnog programa 1992. godine bili su uključeni u glavni standard za operativne sisteme slične Unixu - POSIX - i tako je cron iz de facto standarda postao de jure standard.

Godine 1987, Paul Vixie, nakon što je ispitao Unix korisnike o željama za cron, objavio je drugu verziju demona koja je popravila neke od problema tradicionalnog cron-a i proširila sintaksu tabličnih datoteka.

Do treće verzije Vixie cron je počeo ispunjavati zahtjeve POSIX-a, osim toga, program je imao liberalnu licencu, odnosno nije bilo nikakve licence, osim želja u README-u: autor ne daje garancije, ime autora se ne može izbrisati, a program se može prodavati samo zajedno sa izvornim kodom. Pokazalo se da su ovi zahtjevi kompatibilni s principima slobodnog softvera, koji je tih godina postajao sve popularniji, pa su neke od ključnih Linux distribucija koje su se pojavile početkom 90-ih uzele Vixie cron kao sistemski i još ga razvijaju.

Konkretno, Red Hat i SUSE razvijaju fork Vixie cron - cronie, dok Debian i Ubuntu koriste originalno izdanje Vixie cron sa mnogo zakrpa.

Pogledajmo prvo POSIX korisnički definirani crontab uslužni program, a zatim pogledajmo proširenja sintakse uvedena u Vixie cron i korištenje Vixie cron varijacija u popularnim Linux distribucijama. I na kraju, šlag na tortu je rastavljanje uređaja cron demona.

POSIX crontab

Dok je originalni cron uvijek radio za superkorisnika, moderni planeri imaju veću vjerovatnoću da se bave redovnim korisničkim zadacima, što je sigurnije i praktičnije.

Crons dolazi sa skupom od dva programa: stalno pokrenuti cron daemon i uslužni program crontab koji je dostupan korisnicima. Potonji vam omogućava da uređujete tabele zadataka specifične za svakog korisnika u sistemu, dok demon pokreće zadatke iz korisničkih i sistemskih tabela.

В POSIX standard ponašanje demona nije opisano ni na koji način i samo je korisnički program formaliziran crontab. Postojanje mehanizama za pokretanje korisničkih zadataka se, naravno, podrazumijeva, ali nije detaljno opisano.

Postoje četiri stvari koje se mogu uraditi pozivanjem uslužnog programa crontab: urediti korisničku tabelu zadataka u uređivaču, učitati tabelu iz datoteke, prikazati trenutnu tabelu zadataka i obrisati tabelu zadataka. Primjeri uslužnog programa crontab:

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

Prilikom poziva crontab -e koristit će se uređivač specificiran u standardnoj varijabli okruženja EDITOR.

Sami zadaci su opisani u sljedećem formatu:

# строки-комментарии игнорируются
#
# задача, выполняемая ежеминутно
* * * * * /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

Prvih pet polja zapisa: minuti [1..60], sati [0..23], dani u mjesecu [1..31], mjeseci [1..12], dani u sedmici [0.. 6], gdje je 0 — nedjelja. Posljednje, šesto, polje je linija koju će izvršiti standardni interpreter komandi.

U prvih pet polja, vrijednosti se mogu navesti odvojene zarezima:

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

Ili sa crticom:

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

Pristup korisnika raspoređivanju zadataka je u POSIX-u regulisan datotekama cron.allow i cron.deny, koje navode korisnike sa pristupom crontab-u i korisnike bez pristupa programu. Standard ni na koji način ne reguliše lokaciju ovih datoteka.

Pokrenuti programi, prema standardu, moraju biti proslijeđeni najmanje četiri varijable okruženja:

  1. HOME je korisnikov početni direktorij.
  2. LOGNAME - prijava korisnika.
  3. PATH - putanja na kojoj možete pronaći standardne sistemske uslužne programe.
  4. SHELL je put do korištene ljuske.

Posebno, POSIX ne govori ništa o tome odakle potiču vrijednosti za ove varijable.

Najprodavaniji - Vixie cron 3.0pl1

Zajednički predak popularnih cron varijanti je Vixie cron 3.0pl1, uveden na comp.sources.unix mailing listu 1992. godine. Detaljnije ćemo razmotriti glavne karakteristike ove verzije.

Vixie cron dolazi u dva programa (cron i crontab). Kao i obično, demon je odgovoran za čitanje i pokretanje zadataka iz tablice sistemskih zadataka i pojedinačnih korisničkih tabela zadataka, dok je uslužni program crontab odgovoran za uređivanje korisničkih tabela.

Tabela zadataka i konfiguracijski fajlovi

Tabela zadataka superkorisnika nalazi se u /etc/crontab. Sintaksa sistemske tabele odgovara sintaksi Vixie cron, s tim što šesta kolona označava ime korisnika u čije ime je zadatak pokrenut:

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

Tablice uobičajenih korisničkih zadataka nalaze se u /var/cron/tabs/username i koriste zajedničku sintaksu. Kada pokrenete uslužni program crontab kao korisnik, uređuju se ove datoteke.

Listama korisnika koji imaju pristup crontab-u upravlja se u datotekama /var/cron/allow i /var/cron/deny, gdje je dovoljno upisati korisničko ime u poseban red.

Proširena sintaksa

U poređenju sa POSIX crontab-om, rešenje Paula Wixeya sadrži neke vrlo korisne modifikacije sintakse tabela zadataka uslužnog programa.

Nova sintaksa tabele je postala dostupna: na primjer, možete odrediti dane u sedmici ili mjesece po imenu (pon, uto, itd.):

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

Možete odrediti korak kroz koji se zadaci pokreću:

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

Koraci i intervali se mogu miješati:

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

Podržane su intuitivne alternative uobičajenoj sintaksi (ponovno pokretanje, godišnje, godišnje, mjesečno, sedmično, dnevno, ponoć, po satu):

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

Okruženje za izvršavanje zadataka

Vixie cron vam omogućava da promijenite okruženje pokrenutih aplikacija.

Varijable okruženja USER, LOGNAME i HOME nisu samo obezbeđene od strane demona, već su preuzete iz datoteke passwd. Varijabla PATH je postavljena na "/usr/bin:/bin", a varijabla SHELL je postavljena na "/bin/sh". Vrijednosti svih varijabli osim LOGNAME mogu se mijenjati u korisničkim tabelama.

Neke varijable okruženja (prvenstveno SHELL i HOME) koristi sam cron za pokretanje zadatka. Evo kako može izgledati korištenje bash umjesto zadanog sh za pokretanje korisničkih zadataka:

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

Na kraju će sve varijable okruženja definirane u tabeli (bilo koje koristi cron ili su potrebne procesu) biti proslijeđene zadatku koji se izvršava.

Za uređivanje datoteka, crontab koristi uređivač naveden u varijabli okruženja VISUAL ili EDITOR. Ako okruženje u kojem je pokrenut crontab nema definirane ove varijable, tada se koristi "/usr/ucb/vi" (ucb je vjerovatno Univerzitet Kalifornije, Berkeley).

cron na Debianu i Ubuntu

Debian i programeri derivata objavili su jako izmijenjena verzija verzija Vixie cron 3.0pl1. Nema razlika u sintaksi tabličnih datoteka; za korisnike je ovo isti Vixie cron. Najveće nove karakteristike: Podrška syslog, SELinux и PAM.

Od manje primjetnih, ali opipljivih promjena - lokacija konfiguracijskih datoteka i tabela zadataka.

Korisničke tablice u Debianu se nalaze u direktoriju /var/spool/cron/crontabs, sistemska tablica je još uvijek tamo - u /etc/crontab. Tablice zadataka specifične za Debian paket smještene su u /etc/cron.d, iz koje ih cron demon automatski čita. Kontrolom pristupa korisnika upravljaju datoteke /etc/cron.allow i /etc/cron.deny.

Zadana ljuska je i dalje /bin/sh, koja je u Debianu mala ljuska kompatibilna s POSIX-om crtica, pokrenut bez čitanja bilo kakve konfiguracije (u neinteraktivnom načinu).

Sam Cron u novijim verzijama Debiana pokreće se putem systemd-a, a konfiguracija pokretanja može se naći u /lib/systemd/system/cron.service. Nema ničeg posebnog u konfiguraciji usluge, svako finije upravljanje zadacima može se obaviti preko varijabli okruženja deklariranih direktno u crontab-u svakog korisnika.

cronie na RedHat-u, Fedori i CentOS-u

crony - fork Vixie cron verzije 4.1. Kao iu Debianu, sintaksa se nije promijenila, ali je dodana podrška za PAM i SELinux, rad u klasteru, praćenje datoteka s inotify-om i druge značajke.

Podrazumevana konfiguracija je na uobičajenim mestima: sistemska tabela je u /etc/crontab, paketi stavljaju svoje tabele u /etc/cron.d, korisničke tabele idu u /var/spool/cron/crontabs.

Daemon radi pod systemd, konfiguracija usluge je /lib/systemd/system/crond.service.

Na distribucijama sličnim Red Hatu, podrazumevano pokretanje je /bin/sh, što je standardni bash. Imajte na umu da kada se izvršavaju cron poslovi preko /bin/sh, bash shell počinje u POSIX-kompatibilnom modu i ne čita nikakvu dodatnu konfiguraciju, radi u neinteraktivnom načinu.

cronie u SLES-u i openSUSE-u

Njemačka SLES distribucija i njen openSUSE derivat koriste istog prijatelja. Daemon ovdje također radi pod systemd, konfiguracija usluge je u /usr/lib/systemd/system/cron.service. Konfiguracija: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh je isti bash koji radi u POSIX-kompatibilnom neinteraktivnom načinu.

Vixie cron uređaj

Moderni potomci crona nisu se radikalno promijenili u odnosu na Vixie cron, ali su ipak stekli nove karakteristike koje nisu potrebne za razumijevanje principa programa. Mnoga od ovih ekstenzija su neuredna i zbunjuju kod. Originalni cron izvorni kod Paul Wixie je zadovoljstvo čitati.

Stoga sam odlučio da analiziram cron uređaj na primjeru programa zajedničkog za obje grane cron razvoja - Vixie cron 3.0pl1. Pojednostavit ću primjere uklanjanjem ifdef-a koji otežavaju čitanje i izostavljanjem sekundarnih detalja.

Rad demona može se podijeliti u nekoliko faza:

  1. Inicijalizacija programa.
  2. Prikupite i ažurirajte listu zadataka za pokretanje.
  3. Rad glavne cron petlje.
  4. Pokretanje zadatka.

Uzmimo ih redom.

Inicijalizacija

Prilikom pokretanja, nakon provjere argumenata procesa, cron instalira SIGCHLD i SIGHUP obrađivače signala. Prvi bilježi unos o prekidu podređenog procesa, drugi zatvara deskriptor datoteke dnevnika:

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

Cron daemon u sistemu uvijek radi sam, samo u ulozi superkorisnika i iz glavnog cron direktorija. Sljedeći pozivi kreiraju datoteku zaključavanja s PID-om demonskog procesa, uvjerite se da je korisnik ispravan i promijenite trenutni direktorij u glavni direktorij:

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

Postavljena je zadana putanja koja će se koristiti prilikom pokretanja procesa:

setenv("PATH", _PATH_DEFPATH, 1);

Zatim se proces „demonizira“: kreira podređenu kopiju procesa pozivanjem fork i novu sesiju u podređenom procesu (pozivanjem setsid). Nadređeni proces više nije potreban - i izlazi:

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

Završetak roditeljskog procesa oslobađa zaključavanje datoteke zaključavanja. Osim toga, trebate ažurirati PID u datoteci na dijete. Nakon toga popunjava se baza zadataka:

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

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

Zatim cron prelazi na glavnu radnu petlju. Ali prije toga, vrijedi pogledati učitavanje liste zadataka.

Prikupljanje i ažuriranje liste zadataka

Funkcija load_database je odgovorna za učitavanje liste zadataka. Provjerava glavni sistemski crontab i direktorij korisničkih datoteka. Ako se datoteke i direktorij nisu promijenili, onda se lista zadataka ne čita ponovo. U suprotnom, počinje da se formira nova lista zadataka.

Učitavanje sistemske datoteke sa posebnim nazivima datoteka i tablica:

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

Učitavanje prilagođenih tabela u petlji:

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

Nakon toga, stara baza podataka se zamjenjuje novom.

U gornjim primjerima, poziv process_crontab provjerava postojanje korisnika koji odgovara imenu datoteke tabele (osim ako nije superkorisnik), a zatim poziva load_user. Potonji već čita samu datoteku red po red:

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

Ovdje se ili postavlja varijabla okruženja (nizovi poput VAR=value) funkcijama load_env / env_set, ili se opis zadatka (* * * * * /path/to/exec) čita pomoću funkcije load_entry.

Entitet unosa koji load_entry vraća je naš zadatak, koji se nalazi u općoj listi zadataka. U samoj funkciji se provodi opširno raščlanjivanje formata vremena, ali nas više zanima formiranje varijabli okruženja i parametara pokretanja zadatka:

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

Sa stvarnom listom zadataka, glavna petlja funkcionira.

Main Loop

Originalni cron iz verzije 7 Unixa radio je prilično jednostavno: ponovo je pročitao konfiguraciju u petlji, pokrenuo zadatke tekuće minute kao superkorisnik i spavao do početka sljedeće minute. Ovaj jednostavan pristup na starijim mašinama zahtevao je previše resursa.

U SysV-u je predložena alternativna verzija u kojoj demon spava ili do najbližeg minuta za koji je zadatak definiran, ili 30 minuta. Resursi za ponovno čitanje konfiguracije i provjeru zadataka u ovom načinu su manje potrošeni, ali je postalo nezgodno brzo ažurirati listu zadataka.

Vixie cron se vratio provjeravanju liste zadataka jednom u minuti, pošto je do kraja 80-ih bilo mnogo više resursa na standardnim Unix mašinama:

/* первичная загрузка задач */
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 funkcija je direktno uključena u izvršavanje zadataka, pozivajući funkcije job_runqueue (nabrajanje i pokretanje zadataka) i do_command (pokretanje svakog pojedinačnog zadatka). Posljednju funkciju vrijedi detaljnije analizirati.

Pokretanje zadatka

Funkcija do_command je urađena u dobrom Unix stilu, to jest, radi fork za asinhrono izvršavanje zadatka. Nadređeni proces nastavlja izvršavati zadatke, dok podređeni proces priprema proces zadatka:

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

Ima puno logike u child_process: uzima standardni izlaz i tokove grešaka na sebe, tako da se onda može poslati na poštu (ako je varijabla okruženja MAILTO navedena u tabeli zadataka), i, konačno, čeka da se glavni proces zadatka dovrši.

Proces zadatka je formiran drugom viljuškom:

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

To je, općenito, cijeli cron. Izostavio sam neke zanimljive detalje, na primjer, obračun udaljenih korisnika, ali sam iznio glavnu stvar.

Posle reči

Cron je iznenađujuće jednostavan i koristan program, napravljen u najboljim tradicijama Unix svijeta. Ne radi ništa dodatno, ali već nekoliko decenija odlično radi svoj posao. Trebalo mi je manje od sat vremena da pregledam kod za verziju koja se isporučuje s Ubuntuom i jako sam se zabavio! Nadam se da sam uspeo da to podelim sa vama.

Ne znam za vas, ali ja sam pomalo tužan što shvatam da moderno programiranje, sa svojom tendencijom prekompliciranja i preapstraktnosti, odavno više nije pogodno za takvu jednostavnost.

Postoje mnoge moderne alternative za cron: systemd-tajmeri vam omogućavaju da organizujete složene sisteme sa zavisnostima, u fcron-u možete fleksibilnije regulisati potrošnju resursa zadataka. Ali lično, najjednostavniji crontab mi je uvijek bio dovoljan.

Ukratko, volite Unix, koristite jednostavne programe i ne zaboravite pročitati manu za svoju platformu!

izvor: www.habr.com

Dodajte komentar