Cron во Linux: историја, употреба и уред

Cron во Linux: историја, употреба и уред

Класикот напиша дека среќните часови не гледаат. Во тие диви времиња немаше ниту програмери, ниту Unix, но денес програмерите сигурно знаат: cron ќе го следи времето наместо нив.

Комуналните услуги на командната линија ми се и слабост и скучна работа. sed, awk, wc, cut и други стари програми се извршуваат од скрипти на нашите сервери секој ден. Многу од нив се дизајнирани како задачи за cron, распоредувач првично од 70-тите.

Долго време го користев cron површно, без да навлегувам во детали, но еден ден, кога наидов на грешка при извршување на скрипта, решив да го разгледам темелно. Вака се појави овој напис, додека го пишував се запознав со POSIX crontab, главните опции за cron во популарните дистрибуции на Linux и структурата на некои од нив.

Дали користите Linux и извршувате cron задачи? Дали сте заинтересирани за архитектура на системски апликации во Unix? Тогаш ние сме на пат!

содржина

Потекло на видовите

Периодичното извршување на кориснички или системски програми е очигледна потреба во сите оперативни системи. Затоа, програмерите одамна ја сфатија потребата за услуги кои им овозможуваат централно да планираат и извршуваат задачи.

Оперативните системи слични на Unix го следат своето потекло до верзијата 7 Unix, развиена во 70-тите години на минатиот век во Bell Labs, вклучително и од познатиот Кен Томпсон. Верзијата 7 Unix исто така вклучуваше cron, услуга за редовно извршување на задачите на суперкорисниците.

Типичен модерен cron е едноставна програма, но оперативниот алгоритам на оригиналната верзија беше уште поедноставен: услугата се будеше еднаш во минута, читаше табела со задачи од една датотека (/etc/lib/crontab) и извршуваше за суперкорисник оние задачи што требаше да се извршат во тековниот момент .

Последователно, подобрените верзии на едноставната и корисна услуга беа испорачани со сите оперативни системи слични на Unix.

Генерализирани описи на форматот crontab и основните принципи на работењето на алатката беа вклучени во главниот стандард на оперативните системи слични на Unix - POSIX - во 1992 година, и на тој начин cron од де факто стандард стана де јуре стандард.

Во 1987 година, Пол Викси, откако ги анкетираше корисниците на Unix за нивните желби за cron, објави друга верзија на демонот што коригираше некои од проблемите на традиционалниот cron и ја прошири синтаксата на датотеките на табелата.

До третата верзија на Vixie cron почна да ги исполнува барањата на POSIX, покрај тоа, програмата имаше либерална лиценца, поточно немаше лиценца воопшто, освен желбите во README: авторот не дава гаранции, името на авторот не може да се избрише, а програмата може да се продава само заедно со изворниот код. Овие барања се покажаа како компатибилни со принципите на слободниот софтвер кој се здобива со популарност во тие години, така што некои од клучните дистрибуции на Linux што се појавија во раните 90-ти го земаа Vixie cron како системски систем и сè уште го развиваат денес.

Конкретно, Red Hat и SUSE развиваат вилушка на Vixie cron - cronie, а Debian и Ubuntu го користат оригиналното издание на Vixie cron со многу закрпи.

Ајде прво да се запознаеме со корисничкиот кронтаб опишан во POSIX, по што ќе ги разгледаме синтаксичките екстензии обезбедени во Vixie cron и употребата на варијации на Vixie cron во популарните дистрибуции на Linux. И конечно, црешата на тортата е анализа на уредот cron daemon.

Кронтаб POSIX

Ако оригиналниот cron секогаш работел за суперкорисникот, современите распоредувачи често се занимаваат со задачи на обичните корисници, што е посигурно и поудобно.

Crons се испорачуваат како збир од две програми: постојано работи cron daemon и алатката crontab достапна за корисниците. Вториот ви овозможува да ги уредувате табелите со задачи специфични за секој корисник во системот, додека демонот стартува задачи од кориснички и системски табели.

В POSIX стандард однесувањето на демонот не е опишано на кој било начин и само корисничката програма е формализирана crontab. Постоењето на механизми за започнување задачи на корисникот, се разбира, се подразбира, но не е детално опишано.

Со повикување на алатката crontab, можете да направите четири работи: да ја уредите табелата со задачи на корисникот во уредникот, да ја вчитате табелата од датотека, да ја прикажете тековната табела со задачи и да ја исчистите табелата со задачи. Примери за тоа како функционира алатката crontab:

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

Кога ќе се повикаат crontab -e ќе се користи уредникот наведен во стандардната променлива на животната средина EDITOR.

Самите задачи се опишани во следниот формат:

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

Првите пет полиња од записите: минути [1..60], часови [0..23], денови од месецот [1..31], месеци [1..12], денови во неделата [0. .6], каде 0 е недела. Последното, шесто, поле е линија што ќе биде извршена од стандардниот преведувач на команди.

Во првите пет полиња, вредностите може да се наведат одделени со запирки:

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

Или со цртичка:

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

Корисничкиот пристап до распоредот на задачи е регулиран во POSIX со датотеките cron.allow и cron.deny, кои ги наведуваат корисниците со пристап до crontab и корисниците без пристап до програмата, соодветно. Стандардот на кој било начин не ја регулира локацијата на овие датотеки.

Според стандардот, најмалку четири променливи на животната средина мора да се пренесат на стартуваните програми:

  1. HOME - домашен директориум на корисникот.
  2. LOGNAME — корисничко најавување.
  3. PATH е патеката каде што можете да најдете стандардни системски комунални услуги.
  4. SHELL - патека до користениот преведувач на команди.

Имено, POSIX не кажува ништо за тоа од каде доаѓаат вредностите за овие променливи.

Најпродаван - Vixie cron 3.0pl1

Заедничкиот предок на популарните варијанти на cron е Vixie cron 3.0pl1, воведен во мејлинг листата на comp.sources.unix во 1992 година. Ќе ги разгледаме главните карактеристики на оваа верзија подетално.

Vixie cron доаѓа во две програми (cron и crontab). Како и обично, демонот е одговорен за читање и извршување на задачите од системската табела со задачи и табелите со поединечни задачи на корисникот, а алатката crontab е одговорна за уредување на корисничките табели.

Табела со задачи и конфигурациски датотеки

Табелата со задачи за суперкорисник се наоѓа во /etc/crontab. Синтаксата на системската табела одговара на синтаксата на Vixie cron, со исклучок што шестата колона во неа го означува името на корисникот во чие име е стартувана задачата:

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

Табелите со задачи за редовни корисници се наоѓаат во /var/cron/tabs/корисничко име и ја користат истата синтакса. Кога ја извршувате алатката crontab како корисник, ова се датотеките што се уредуваат.

Списоците на корисници со пристап до crontab се управуваат во датотеките /var/cron/allow и /var/cron/deny, каде што само треба да го внесете корисничкото име во посебна линија.

Проширена синтакса

Во споредба со Crontab POSIX, решението на Paul Vixey содржи неколку многу корисни модификации на синтаксата на табелите со задачи на алатката.

Стана достапна нова синтакса на табелата: на пример, можете да наведете денови во неделата или месеци по име (пон, вторник и така натаму):

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

Можете да го одредите чекорот низ кој се стартуваат задачите:

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

Може да се мешаат чекори и интервали:

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

Поддржани се интуитивни алтернативи на вообичаената синтакса (рестартирање, годишно, годишно, месечно, неделно, дневно, полноќ, час):

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

Околина за извршување на задачи

Vixie cron ви овозможува да ја промените околината на активните апликации.

Променливите на животната средина USER, LOGNAME и HOME не се само обезбедени од демонот, туку се земени од датотека passwd. Променливата PATH е поставена на „/usr/bin:/bin“, а променливата SHELL е поставена на „/bin/sh“. Вредностите на сите променливи освен LOGNAME може да се менуваат во корисничките табели.

Некои променливи на околината (најзначајни SHELL и HOME) се користат од самиот cron за извршување на задачата. Еве како може да изгледа користењето bash наместо стандардно sh за извршување на сопствени задачи:

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

На крајот на краиштата, сите променливи на животната средина дефинирани во табелата (кои се користат од cron или се потребни од процесот) ќе бидат префрлени на извршувањето на задачата.

За уредување датотеки, crontab го користи уредникот наведен во променливата на околината VISUAL или EDITOR. Ако средината каде што беше извршен crontab ги нема овие променливи дефинирани, тогаш се користи „/usr/ucb/vi“ (ucb е веројатно Универзитетот во Калифорнија, Беркли).

cron на Debian и Ubuntu

Програмерите на Debian и деривативните дистрибуции објавија многу модифицирана верзија Vixie cron верзија 3.0pl1. Нема разлики во синтаксата на табелите, за корисниците е истиот Vixie cron. Најголема нова карактеристика: Поддршка системски лог, SELinux и ПЕМ.

Помалку забележливи, но опипливи промени ја вклучуваат локацијата на конфигурациските датотеки и табелите со задачи.

Корисничките табели во Debian се наоѓаат во директориумот /var/spool/cron/crontabs, системската табела сè уште е таму - во /etc/crontab. Табелите со задачи специфични за пакетите на Debian се сместени во /etc/cron.d, од каде што cron демонот автоматски ги чита. Контролата за пристап на корисникот е контролирана од датотеките /etc/cron.allow и /etc/cron.deny.

Стандардната школка сè уште е /bin/sh, што во Debian е мала школка во согласност со POSIX цртичка, стартуван без читање каква било конфигурација (во неинтерактивен режим).

Самиот Cron во најновите верзии на Debian се лансира преку systemd, а конфигурацијата за лансирање може да се гледа во /lib/systemd/system/cron.service. Нема ништо посебно во конфигурацијата на услугата; секое посуптилно управување со задачи може да се направи преку променливите на околината декларирани директно во кронатабот на секој корисник.

cronie на RedHat, Fedora и CentOS

крони — вилушка на Vixie cron верзија 4.1. Како и во Debian, синтаксата не е променета, но додадена е поддршка за PAM и SELinux, работа во кластер, следење датотеки користејќи inotify и други функции.

Стандардната конфигурација е на вообичаените места: системската табела е во /etc/crontab, пакетите ги ставаат своите табели во /etc/cron.d, корисничките табели одат во /var/spool/cron/crontabs.

Демонот работи под системска контрола, конфигурацијата на услугата е /lib/systemd/system/crond.service.

На дистрибуции слични на Red Hat, /bin/sh стандардно се користи при стартување, што е стандарден баш. Треба да се забележи дека кога се извршуваат задачите на cron преку /bin/sh, школката на bash започнува во режим усогласен со POSIX и не чита никаква дополнителна конфигурација, работи во неинтерактивен режим.

cronie во SLES и openSUSE

Германската дистрибуција SLES и нејзиниот дериват openSUSE го користат истиот cronie. Демонот овде исто така е лансиран под systemd, конфигурацијата на услугата се наоѓа во /usr/lib/systemd/system/cron.service. Конфигурација: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh е истиот баш кој работи во неинтерактивен режим усогласен со POSIX.

Vixie cron уред

Современите потомци на cron не се радикално променети во споредба со Vixie cron, но сепак стекнаа нови функции кои не се потребни за да се разберат принципите на програмата. Многу од овие екстензии се лошо дизајнирани и го збунуваат кодот. Оригиналниот изворен код на cron од Пол Викси е задоволство да се прочита.

Затоа, решив да го анализирам уредот cron користејќи го примерот на програмата cron заедничка за двете гранки на развој - Vixie cron 3.0pl1. Ќе ги поедноставам примерите со отстранување на ifdefs кои го комплицираат читањето и испуштање ситни детали.

Работата на демонот може да се подели на неколку фази:

  1. Иницијализација на програмата.
  2. Собирање и ажурирање на списокот со задачи за извршување.
  3. Работи главната кронска јамка.
  4. Започнете задача.

Ајде да ги погледнеме по редослед.

Иницијализација

Кога се стартува, по проверка на аргументите на процесот, cron ги инсталира ракувачите со сигнали SIGCHLD и SIGHUP. Првиот прави запис во дневникот за завршување на процесот на дете, вториот го затвора дескрипторот на датотеката на датотеката за евиденција:

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

Cron демонот секогаш работи сам на системот, само како суперкорисник и од главниот cron директориум. Следниве повици создаваат датотека за заклучување со PID на процесот на демонот, проверете дали корисникот е точен и сменете го тековниот директориум во главниот:

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

Поставена е стандардна патека, која ќе се користи при започнување на процесите:

setenv("PATH", _PATH_DEFPATH, 1);

Потоа процесот е „демонизиран“: тој создава детска копија од процесот со повикување fork и нова сесија во процесот на дете (повикување setsid). Родителскиот процес повеќе не е потребен и тој излегува:

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

Прекинувањето на матичниот процес го ослободува заклучувањето на датотеката за заклучување. Покрај тоа, потребно е да се ажурира PID во датотеката на детето. По ова, базата на задачи се пополнува:

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

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

Потоа cron преминува на главниот работен циклус. Но, пред тоа, вреди да се погледне во вчитувањето на списокот со задачи.

Собирање и ажурирање на списокот со задачи

Функцијата load_database е одговорна за вчитување на списокот со задачи. Го проверува главниот системски кронтаб и директориумот со кориснички датотеки. Ако датотеките и директориумот не се променети, списокот со задачи не се чита повторно. Во спротивно, почнува да се формира нова листа на задачи.

Вчитување системска датотека со специјални имиња на датотеки и табели:

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

Вчитување на кориснички табели во јамка:

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

По што старата база на податоци се заменува со нова.

Во горните примери, повикот на функцијата process_crontab потврдува дека постои корисник што одговара на името на датотеката на табелата (освен ако не е суперкорисник) и потоа го повикува load_user. Вториот веќе ја чита самата датотека линија по ред:

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

Овде, или променливата на околината е поставена (линии од формата VAR=вредност) користејќи ги функциите load_env / env_set, или описот на задачата се чита (* * * * * /path/to/exec) со помош на функцијата load_entry.

Влезниот ентитет што го враќа load_entry е нашата задача, која е сместена во општата листа на задачи. Самата функција врши опширно парсирање на временскиот формат, но ние сме повеќе заинтересирани за формирање на променливи на околината и параметри за стартување задачи:

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

Главната јамка работи со тековната листа на задачи.

Главна јамка

Оригиналниот cron од верзијата 7 Unix работеше сосема едноставно: повторно ја читаше конфигурацијата во циклус, ги стартуваше задачите од тековната минута како суперкорисник и спиеше до почетокот на следната минута. Овој едноставен пристап на постарите машини бараше премногу ресурси.

Во SysV беше предложена алтернативна верзија, во која демонот спиеше или до најблиската минута за која беше дефинирана задачата или 30 минути. Потрошени се помалку ресурси за повторно читање на конфигурацијата и проверка на задачите во овој режим, но брзото ажурирање на списокот со задачи стана незгодно.

Vixie cron се врати на проверка на листите со задачи еднаш во минута, за среќа до крајот на 80-тите имаше значително повеќе ресурси на стандардните Unix машини:

/* первичная загрузка задач */
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 е директно вклучена во извршувањето на задачите, повикувајќи ги функциите job_runqueue (набројување и извршување задачи) и do_command (извршување на секоја поединечна задача). Последната функција вреди да се испита подетално.

Водење задача

Функцијата do_command се извршува во добар стил на Unix, односно прави вилушка за да ја изврши задачата асинхроно. Родителскиот процес продолжува да стартува задачи, детскиот процес го подготвува процесот на задачи:

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

Има доста логика во child_process: зема стандарден излез и проток на грешки на себе, за потоа да го испрати на пошта (ако променливата на околината MAILTO е наведена во табелата со задачи) и, конечно, ја чека главната процесот на задачата да се заврши.

Процесот на задача е формиран од друга вилушка:

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

Тоа е во основа сè што е cron. Испуштив некои интересни детали, на пример, сметководство за оддалечени корисници, но ја наведов главната работа.

Поговор

Cron е изненадувачки едноставна и корисна програма, направена според најдобрите традиции на светот на Unix. Таа не прави ништо дополнително, но прекрасно си ја врши работата веќе неколку децении. Добивањето на кодот за верзијата што доаѓа со Ubuntu траеше не повеќе од еден час и многу се забавував! Се надевам дека успеав да го споделам со вас.

Не знам за вас, но малку ми е тажно што сфаќам дека модерното програмирање, со својата тенденција за прекомплицирано и преапстрактно, долго време не придонесува за таква едноставност.

Постојат многу модерни алтернативи за cron: systemd-timers ви овозможуваат да организирате сложени системи со зависности, fcron ви овозможува пофлексибилно да ја регулирате потрошувачката на ресурси по задачи. Но, лично, наједноставниот кронтаб секогаш ми беше доволен.

Накратко, сакајте го Unix, користете едноставни програми и не заборавајте да ја прочитате маната за вашата платформа!

Извор: www.habr.com

Додадете коментар