Cron sur Linux: historique, utilisation et appareil


Le classique a écrit que les happy hours ne sont pas observés. Dans ces temps fous, il n'y avait ni programmeurs ni Unix, mais de nos jours les programmeurs le savent très bien: au lieu d'eux, cron suivra le temps.


Les utilitaires de ligne de commande sont à la fois une faiblesse et une routine. sed, awk, wc, cut et d'autres anciens programmes sont exécutés quotidiennement par des scripts sur nos serveurs. Beaucoup d'entre eux sont conçus comme des tâches pour cron, un planificateur des années 70.


Pendant longtemps, j'ai utilisé cron superficiellement, sans entrer dans les détails, mais une fois, ayant rencontré une erreur lors de l'exécution du script, j'ai décidé de le comprendre à fond. Cet article est donc apparu, lors de l'écriture dont j'ai pris connaissance avec POSIX crontab, les principales variantes de cron dans les distributions Linux populaires et le périphérique de certaines d'entre elles.


Utiliser Linux et exécuter des tâches dans cron? Intéressé par l'architecture d'application du système Unix? Alors nous sommes en route!


Table des matières



Origine des espèces


L'exécution périodique de programmes utilisateur ou système est un besoin évident pour tous les systèmes d'exploitation. Par conséquent, le besoin de services qui permettent une planification et une exécution centralisées des tâches, les programmeurs ont réalisé depuis très longtemps.


Les systèmes d'exploitation de type Unix ont leur pedigree de la version 7 Unix, développé dans les années 1970 par Bell Labs, y compris le célèbre Ken Thompson. Avec la version 7 Unix, cron, un service pour l'exécution régulière des tâches de superutilisateur, a également été fourni.


Un cron moderne typique est un programme simple, mais l'algorithme de la version originale était encore plus simple: le service se réveillait une fois par minute, lisait la plaque des tâches à partir d'un seul fichier (/ etc / lib / crontab) et exécutait les tâches qui devaient être effectuées pour le superutilisateur à la minute courante .


Par la suite, des options avancées pour un service simple et utile sont venues avec tous les systèmes d'exploitation de type Unix.


Des descriptions généralisées du format crontab et des principes de base de l'utilitaire en 1992 ont été incluses dans la norme principale des systèmes d'exploitation de type Unix - POSIX - et ainsi le cron de la norme de facto est devenu la norme de jure.


En 1987, Paul Vixie, après avoir interrogé les utilisateurs d'Unix pour des suggestions de cron, a publié une autre version du démon qui corrige certains des problèmes du cron traditionnel et étend la syntaxe des fichiers de table.


Par la troisième version, Vixie cron a commencé à répondre aux exigences de POSIX, en plus, le programme avait une licence libérale, ou plutôt il n'y avait pas de licence du tout, sauf pour les souhaits dans README: l'auteur ne donne aucune garantie, vous ne pouvez pas supprimer le nom de l'auteur, et vous ne pouvez vendre le programme qu'avec code source. Ces exigences se sont avérées compatibles avec les principes du logiciel libre, qui gagnait en popularité au cours de ces années, de sorte que certaines des principales distributions Linux apparues au début des années 90 ont pris Vixie cron comme système et le développent toujours.


En particulier, Red Hat et SUSE développent la fourchette cron-cronie Vixie, tandis que Debian et Ubuntu utilisent la cron Vixie originale avec de nombreux correctifs.


Tout d'abord, familiarisons-nous avec l'utilitaire crontab défini par l'utilisateur décrit dans POSIX, après quoi nous analyserons les extensions de syntaxe présentées dans Vixie cron et l'utilisation des variations de Vixie cron dans les distributions Linux populaires. Et enfin, la cerise sur le gâteau est une analyse du périphérique démon cron.


Posix crontab


Si le cron d'origine a toujours fonctionné pour le superutilisateur, les planificateurs modernes s'occupent souvent des tâches des utilisateurs ordinaires, ce qui est plus sûr et plus pratique.


Les cron sont livrés avec un ensemble de deux programmes: le démon cron en cours d'exécution et l'utilitaire crontab disponibles pour les utilisateurs. Ce dernier vous permet de modifier les tables de tâches spécifiques à chaque utilisateur du système, tandis que le démon démarre les tâches à partir des tables utilisateur et système.


Le standard POSIX ne décrit pas le comportement du démon, et seul le programme utilisateur crontab est formalisé. L'existence de mécanismes de lancement des tâches utilisateur, bien sûr, est implicite, mais n'est pas décrite en détail.


Il y a quatre choses que vous pouvez faire avec l'utilitaire crontab: modifier la table des tâches utilisateur dans l'éditeur, charger la table à partir du fichier, afficher la table des tâches actuelle et effacer la table des tâches. Exemples de l'utilitaire crontab:


crontab -e #    crontab -l #    crontab -r #    crontab path/to/file.crontab #      

Lors de l'appel de crontab -e , l'éditeur spécifié dans la EDITOR environnement EDITOR standard sera utilisé.


Les tâches elles-mêmes sont décrites dans le format suivant:


 # -  # # ,   * * * * * /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 

Les cinq premiers champs d'enregistrement: minutes [1..60], heures [0..23], jours du mois [1..31], mois [1..12], jours de la semaine [0..6], où 0 - dimanche. Le dernier, sixième champ, est une chaîne qui sera exécutée par l'interpréteur de commandes standard.


Dans les cinq premiers champs, les valeurs peuvent être répertoriées avec une virgule:


 # ,         1,10 * * * * /path/to/exec -a -b -c 

Ou par un tiret:


 # ,          0-9 * * * * /path/to/exec -a -b -c 

L'accès des utilisateurs à la planification des tâches est réglementé dans les fichiers POSIX cron.allow et cron.deny qui répertorient respectivement les utilisateurs ayant accès à crontab et les utilisateurs n'ayant pas accès au programme. La norme ne réglemente pas l'emplacement de ces fichiers.


Les programmes en cours d'exécution, conformément à la norme, doivent recevoir au moins quatre variables d'environnement:


  1. HOME est le répertoire personnel de l'utilisateur.
  2. LOGNAME - connexion utilisateur.
  3. PATH est le chemin par lequel trouver les utilitaires système standard.
  4. SHELL est le chemin vers le shell utilisé.

Il est Ă  noter que POSIX ne dit rien sur la provenance des valeurs de ces variables.


Best-seller - Vixie cron 3.0pl1


L'ancêtre commun des variantes cron populaires est Vixie cron 3.0pl1, présenté sur la liste de diffusion comp.sources.unix de 1992. Nous examinerons plus en détail les principales caractéristiques de cette version.


Vixie cron est disponible en deux programmes (cron et crontab). Comme d'habitude, le démon est responsable de la lecture et du démarrage des tâches à partir de la table des tâches système et des tables des tâches des utilisateurs individuels, et l'utilitaire crontab est responsable de la modification des tables utilisateur.


Table des tâches et fichiers de configuration


La table des tâches du superutilisateur se trouve dans / etc / crontab. La syntaxe de la table système correspond à la syntaxe de Vixie cron, ajustée du fait que la sixième colonne indique le nom de l'utilisateur pour le compte duquel la tâche est lancée:


 #     vlad * * * * * vlad /path/to/exec 

Les tables de tâches utilisateur courantes se trouvent dans / var / cron / tabs / username et utilisent une syntaxe commune. Lorsque l'utilitaire crontab est lancé, ces fichiers sont modifiés au nom de l'utilisateur.


Les listes d'utilisateurs ayant accès à crontab sont gérées dans les fichiers / var / cron / allow et / var / cron / deny, où il suffit d'ajouter le nom d'utilisateur sur une ligne distincte.


Syntaxe Ă©tendue


Par rapport à la crontab POSIX, la solution de Paul Vixie contient plusieurs modifications très utiles à la syntaxe de la table des tâches utilitaires.


Une nouvelle syntaxe de table est devenue disponible: par exemple, vous pouvez spécifier des jours de la semaine ou des mois par nom (Lun, Mar, etc.):


 #         * * * Jan Mon,Tue /path/to/exec 

Vous pouvez spécifier l'étape à travers laquelle les tâches sont lancées:


 #       */2 * * * Mon,Tue /path/to/exec 

Les étapes et les intervalles peuvent être mélangés:


 #             0-10/2 * * * * /path/to/exec 

Des alternatives intuitives à la syntaxe régulière sont prises en charge (redémarrage, annuel, annuel, mensuel, hebdomadaire, quotidien, minuit, horaire):


 #     @reboot /exec/on/reboot #     @daily /exec/daily #     @hourly /exec/daily 

Environnement d'exécution des tâches


Vixie cron vous permet de changer l'environnement des applications en cours d'exécution.


Les variables d'environnement USER, LOGNAME et HOME ne sont pas seulement fournies par le démon, mais sont extraites du fichier passwd . La variable PATH obtient la valeur "/ usr / bin: / bin" et SHELL obtient la valeur "/ bin / sh". Les valeurs de toutes les variables sauf LOGNAME peuvent être modifiées dans les tables utilisateur.


Certaines variables d'environnement (principalement SHELL et HOME) sont utilisées par cron lui-même pour exécuter la tâche. Voici à quoi ressemble bash au lieu du sh standard pour exécuter des tâches personnalisées:


 SHELL=/bin/bash HOME=/tmp/ # exec   bash-  /tmp/ * * * * * /path/to/exec 

En fin de compte, toutes les variables d'environnement définies dans le tableau (utilisées par cron ou nécessaires au processus) seront transférées vers la tâche en cours d'exécution.


L'utilitaire crontab utilise l'éditeur spécifié dans la variable d'environnement VISUAL ou EDITOR pour modifier des fichiers. Si ces variables ne sont pas définies dans l'environnement où crontab a été lancé, alors "/ usr / ucb / vi" est utilisé (ucb est probablement l'Université de Californie, Berkeley).


cron sur Debian et Ubuntu


Debian et les développeurs de dérivés ont publié une version hautement modifiée de Vixie cron version 3.0pl1. Il n'y a aucune différence dans la syntaxe des fichiers de table; pour les utilisateurs, il s'agit du même cron Vixie. Nouvelles fonctionnalités les plus importantes: prise en charge de syslog , SELinux et PAM .


Parmi les changements moins visibles, mais tangibles - l'emplacement des fichiers de configuration et des tables de tâches.


Les tables utilisateur dans Debian sont situées dans le répertoire / var / spool / cron / crontabs, la table système est toujours là dans / etc / crontab. Les tables de tâches spécifiques à Debian sont placées dans /etc/cron.d, d'où le démon cron les lit automatiquement. Le contrôle d'accès des utilisateurs est réglementé par les fichiers /etc/cron.allow et /etc/cron.deny.


Le shell / bin / sh par défaut est toujours utilisé comme shell par défaut. Debian agit comme un petit shell de tableau de bord compatible POSIX lancé sans lire aucune configuration (en mode non interactif).


Cron lui-même dans les dernières versions de Debian est lancé via systemd, et la configuration de lancement peut être consultée dans /lib/systemd/system/cron.service. Il n'y a rien de spécial dans la configuration du service; toute gestion plus fine des tâches peut se faire via des variables d'environnement déclarées directement dans la crontab de chaque utilisateur.


cronie sur RedHat, Fedora et CentOS


cronie - fork de Vixie cron version 4.1. Comme dans Debian, la syntaxe n'a pas changé, mais la prise en charge de PAM et SELinux, travaillant dans un cluster, le suivi des fichiers à l'aide d'inotify et d'autres fonctionnalités ont été ajoutées.


La configuration par défaut est aux endroits habituels: la table système est dans / etc / crontab, les packages mettent leurs tables dans /etc/cron.d, les tables utilisateur sont dans / var / spool / cron / crontabs.


Le démon s'exécute sous systemd, la configuration du service est /lib/systemd/system/crond.service.


Au démarrage, les distributions de type Red Hat utilisent / bin / sh par défaut, dont le rôle est bash standard. Il convient de noter que lors de l'exécution de tâches cron via / bin / sh, le shell bash démarre en mode compatible POSIX et ne lit aucune configuration supplémentaire lorsqu'il fonctionne en mode non interactif.


cronie dans SLES et openSUSE


La distribution SLES allemande et son dérivé openSUSE utilisent le même cronie. Le démon s'exécute ici également sous systemd, la configuration du service se trouve dans /usr/lib/systemd/system/cron.service. Configuration: / etc / crontab, /etc/cron.d, / var / spool / cron / tabs. Comme / bin / sh agit de la même manière, lancé en mode non interactif compatible POSIX.


Dispositif cron Vixie


Les descendants modernes de cron n'ont pas changé radicalement par rapport au cron Vixie, mais ils ont néanmoins acquis de nouvelles capacités qui ne sont pas nécessaires pour comprendre les principes du programme. Beaucoup de ces extensions sont désordonnées et confondent le code. Le code source original de cron par Paul Vixie est un plaisir à lire.


Par conséquent, j'ai décidé d'analyser le périphérique cron en utilisant l'exemple d'un programme commun pour les deux branches du développement cron - Vixie cron 3.0pl1. Je vais simplifier les exemples en supprimant les ifdefs qui compliquent la lecture et en omettant les détails secondaires.


Le travail du démon peut être divisé en plusieurs étapes:


  1. Initialisation du programme.
  2. Collectez et mettez à jour la liste des tâches à exécuter.
  3. L'opération principale de boucle cron.
  4. Lancement de la tâche.

Trions-les dans l'ordre.


Initialisation


Une fois lancé, après avoir vérifié les arguments du processus, cron installe les gestionnaires de signaux SIGCHLD et SIGHUP. Le premier enregistre la fin du processus enfant, le second ferme le descripteur de fichier du fichier journal:


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

Le démon cron du système fonctionne toujours seul, uniquement en tant que superutilisateur et à partir du répertoire principal cron. Les appels suivants créent un verrou de fichier avec le PID du processus démon, assurez-vous que l'utilisateur est correct et changez le répertoire en cours en celui principal:


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

Le chemin par défaut est défini, qui sera utilisé lors du démarrage des processus:


 setenv("PATH", _PATH_DEFPATH, 1); 

Ensuite, le processus est «diabolisé»: il crée une copie enfant du processus en appelant fork et une nouvelle session dans le processus enfant (en appelant setsid). Il n'y a plus besoin du processus parent - et il termine le travail:


 switch (fork()) { case -1: /*      */ exit(0); break; case 0: /*   */ (void) setsid(); break; default: /*     */ _exit(0); } 

La fin du processus parent libère le verrou sur le fichier de verrouillage. De plus, vous devez mettre à jour le PID dans le fichier pour l'enfant. Après cela, la base de données des tâches est remplie:


 /*    */ acquire_daemonlock(0); /*   */ database.head = NULL; database.tail = NULL; database.mtime = (time_t) 0; load_database(&database); 

Plus de cron passe au cycle de travail principal. Mais avant cela, jetez un œil au chargement de la liste des tâches.


Collecte et mise à jour de la liste des tâches


La fonction load_database est responsable du chargement de la liste des tâches. Il vérifie la crontab du système principal et le répertoire avec les fichiers utilisateur. Si les fichiers et le répertoire n'ont pas changé, la liste des tâches n'est pas relue. Sinon, une nouvelle liste de tâches commence à se former.


Téléchargement d'un fichier système avec des noms de fichiers et de tables spéciaux:


 /*     ,  */ if (syscron_stat.st_mtime) { process_crontab("root", "*system*", SYSCRONTAB, &syscron_stat, &new_db, old_db); } 

Chargement des tables utilisateur dans une boucle:


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

Ensuite, l'ancienne base de données est remplacée par une nouvelle.


Dans les exemples ci-dessus, l'appel de la fonction process_crontab garantit que l'utilisateur existe qui correspond au nom du fichier de table (sauf s'il s'agit du superutilisateur), puis appelle load_user. Ce dernier lit déjà le fichier lui-même ligne par ligne:


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

Ici, soit la variable d'environnement (lignes de la forme VAR = valeur) est définie par les fonctions load_env / env_set, soit la description de la tâche (* * * * * / path / to / exec) est lue par la fonction load_entry.


L'entité d'entrée renvoyée par load_entry est notre tâche placée dans la liste générale des tâches. Dans la fonction elle-même, une longue analyse du format de l'heure est effectuée, mais nous nous intéressons davantage à la formation des variables d'environnement et des paramètres de lancement des tâches:


 /*         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); 

Le cycle principal fonctionne également avec la liste actuelle des tâches.


Cycle principal


Le cron original de la version 7 Unix fonctionnait tout simplement: dans un cycle, j'ai relu la configuration, exécuté les tâches de la minute actuelle en tant que superutilisateur et dormi jusqu'à ce que la minute suivante commence. Cette approche simple sur des machines plus anciennes nécessitait trop de ressources.


Une version alternative a été proposée dans SysV, dans laquelle le démon s'est endormi soit jusqu'à la minute suivante, pour laquelle la tâche a été définie, soit pendant 30 minutes. Les ressources pour relire la configuration et vérifier les tâches dans ce mode étaient moins consommées, mais il était devenu gênant de mettre à jour rapidement la liste des tâches.


Vixie cron a recommencé à vérifier les listes de tâches une fois par minute, car à la fin des années 80, les ressources sur les machines Unix standard étaient devenues beaucoup plus importantes:


 /*    */ load_database(&database); /*  ,       */ run_reboot_jobs(&database); /*  TargetTime    */ cron_sync(); while (TRUE) { /*  ,     TargetTime    ,    */ cron_sleep(); /*   */ load_database(&database); /*      */ cron_tick(&database); /*  TargetTime     */ TargetTime += 60; } 

La fonction cron_sleep, qui appelle les fonctions job_runqueue (énumération et début des tâches) et do_command (début de chaque tâche individuelle), est directement impliquée dans l'exécution des tâches. La dernière fonction doit être considérée plus en détail.


Lancement de la tâche


La fonction do_command est exécutée dans un bon style Unix, c'est-à-dire qu'elle effectue un fork pour l'exécution de tâches asynchrones. Le processus parent continue de lancer des tâches, le processus enfant prépare le processus de tâche:


 switch (fork()) { case -1: /*   fork */ break; case 0: /*  :          */ acquire_daemonlock(1); /*      */ child_process(e, u); /*       */ _exit(OK_EXIT); break; default: /*     */ break; } 

Il y a beaucoup de logique dans child_process: il prend la sortie standard et les flux d'erreurs sur lui-même, de sorte qu'il peut ensuite être envoyé par courrier (si la variable d'environnement MAILTO est spécifiée dans la table des tâches), et, enfin, il attend que le processus de tâche principale se termine.


Le processus de tâche est formé par un autre fork:


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

Ici, en général, et tout le cron. J'ai omis certains détails intéressants, par exemple, la comptabilité des utilisateurs distants, mais j'ai souligné l'essentiel.


Postface


Cron est un programme étonnamment simple et utile, fabriqué dans les meilleures traditions du monde Unix. Elle ne fait rien de superflu, mais elle fait remarquablement son travail depuis plusieurs décennies. Apprendre à connaître le code de la version fournie avec Ubuntu n'a pas pris plus d'une heure et je me suis beaucoup amusé! J'espère que je pourrais le partager avec vous.


Je ne sais pas pour vous, mais c'est un peu triste pour moi de réaliser que la programmation moderne, avec sa tendance à se compliquer et à se résumer, a depuis longtemps cessé d'avoir une telle simplicité.


Il existe de nombreuses alternatives modernes à cron: systemd-timers vous permet d'organiser des systèmes complexes avec des dépendances, dans fcron vous pouvez contrôler de manière plus flexible la consommation de ressources par tâches. Mais personnellement, j'ai toujours eu le crontab le plus simple.


En un mot, adorez Unix, utilisez des programmes simples et n'oubliez pas de lire du mana pour votre plateforme!

Source: https://habr.com/ru/post/fr468061/


All Articles