Cron no Linux: histórico, uso e dispositivo


O clássico escreveu que happy hours não são observados. Naqueles tempos selvagens, não havia programadores nem o Unix, mas hoje em dia os programadores sabem muito bem: em vez deles, o cron seguirá o tempo.


Os utilitários de linha de comando para mim são fracos e rotineiros. sed, awk, wc, cut e outros programas antigos são executados por scripts em nossos servidores diariamente. Muitos deles são projetados como tarefas para o cron, um agendador dos anos 70.


Por um longo tempo, usei o cron superficialmente, sem entrar em detalhes, mas uma vez, tendo encontrado um erro ao executar o script, decidi descobrir isso completamente. Assim, apareceu este artigo, ao escrever sobre o qual me familiarizei com o POSIX crontab, as principais variantes do cron nas distribuições populares do Linux e o dispositivo de algumas delas.


Usando Linux e executando tarefas no cron? Interessado na arquitetura de aplicativos do sistema Unix? Então estamos a caminho!


Conteúdo



Origem das espécies


A execução periódica de programas de usuário ou sistema é uma necessidade óbvia para todos os sistemas operacionais. Portanto, a necessidade de serviços que permitam o planejamento centralizado e a execução de tarefas, os programadores perceberam há muito tempo.


Os sistemas operacionais do tipo Unix têm origem na Versão 7 Unix, desenvolvida na década de 1970 pelo Bell Labs, incluindo o famoso Ken Thompson. Juntamente com a versão 7 Unix, o cron, um serviço para execução regular de tarefas de superusuário, também foi fornecido.


Um cron moderno típico é um programa simples, mas o algoritmo da versão original era ainda mais simples: o serviço acordava uma vez por minuto, leia a placa de tarefas de um único arquivo (/ etc / lib / crontab) e executava as tarefas que deveriam ser executadas para o superusuário no minuto atual .


Posteriormente, opções avançadas para um serviço simples e útil vieram com todos os sistemas operacionais do tipo Unix.


Descrições generalizadas do formato crontab e os princípios básicos do utilitário em 1992 foram incluídos no padrão principal de sistemas operacionais do tipo Unix - POSIX - e, portanto, o cron do padrão de fato se tornou o padrão de jure.


Em 1987, Paul Vixie, após entrevistar os usuários do Unix para obter sugestões para o cron, lançou outra versão do daemon que corrige alguns dos problemas do cron tradicional e estende a sintaxe dos arquivos de tabela.


Na terceira versão, o Vixie cron começou a atender aos requisitos do POSIX; além disso, o programa possuía uma licença liberal, ou melhor, não havia nenhuma licença, exceto os desejos do README: o autor não dá garantias, você não pode excluir o nome do autor e só pode vender o programa com código fonte Esses requisitos acabaram sendo compatíveis com os princípios do software livre, que estavam ganhando popularidade naqueles anos; portanto, algumas das principais distribuições Linux que apareceram no início dos anos 90 tomaram o Vixie cron como um sistema e ainda o estão desenvolvendo.


Em particular, a Red Hat e o SUSE estão desenvolvendo o fork do cronix do Vixie, enquanto o Debian e o Ubuntu estão usando o cron do Vixie original com muitos patches.


Primeiro, vamos nos familiarizar com o utilitário crontab definido pelo usuário descrito no POSIX, após o qual analisaremos as extensões de sintaxe apresentadas no Vixie cron e o uso de variações do Vixie cron nas distribuições populares do Linux. E, finalmente, a cereja no bolo é uma análise do dispositivo cron daemon.


Posix crontab


Se o cron original sempre funcionou para o superusuário, os agendadores modernos geralmente lidam com as tarefas dos usuários comuns, o que é mais seguro e conveniente.


Os Cron s são enviados com um conjunto de dois programas: o cron daemon em execução constante e o utilitário crontab disponível para os usuários. O último permite editar tabelas de tarefas específicas para cada usuário no sistema, enquanto o daemon inicia tarefas das tabelas de usuário e sistema.


O padrão POSIX não descreve o comportamento do daemon e apenas o programa do usuário crontab é formalizado. A existência de mecanismos para iniciar tarefas do usuário, é claro, está implícita, mas não descrita em detalhes.


Há quatro coisas que você pode fazer com o utilitário crontab: edite a tabela de tarefas do usuário no editor, carregue a tabela a partir do arquivo, mostre a tabela de tarefas atual e limpe a tabela de tarefas. Exemplos do utilitário crontab:


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

Ao chamar crontab -e , o editor especificado na EDITOR ambiente EDITOR padrão será usado.


As próprias tarefas são descritas no seguinte formato:


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

Os cinco primeiros campos de registro: minutos [1..60], horas [0..23], dias do mês [1..31], meses [1..12], dias da semana [0..6], em que 0 - domingo. O último, sexto, campo é uma sequência que será executada pelo interpretador de comando padrão.


Nos cinco primeiros campos, os valores podem ser listados com uma vírgula:


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

Ou através de um hífen:


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

O acesso do usuário ao agendamento de tarefas é regulado nos arquivos cron.allow e cron.deny do POSIX, que listam, respectivamente, usuários com acesso ao crontab e usuários sem acesso ao programa. O padrão não regula a localização desses arquivos.


Os programas em execução, de acordo com o padrão, devem receber no mínimo quatro variáveis ​​de ambiente:


  1. HOME é o diretório inicial do usuário.
  2. LOGNAME - login do usuário.
  3. PATH é o caminho pelo qual encontrar os utilitários de sistema padrão.
  4. SHELL é o caminho para o shell usado.

Vale ressaltar que o POSIX não diz nada sobre a origem dos valores para essas variáveis.


Mais vendidos - Vixie cron 3.0pl1


O ancestral comum das variantes populares do cron é o Vixie cron 3.0pl1, apresentado na lista de correspondência comp.sources.unix de 1992. Os principais recursos desta versão serão considerados em mais detalhes.


O Vixie cron vem em dois programas (cron e crontab). Como de costume, o daemon é responsável por ler e iniciar tarefas da tabela de tarefas do sistema e tabelas de tarefas de usuários individuais, e o utilitário crontab é responsável pela edição de tabelas do usuário.


Tabela de tarefas e arquivos de configuração


A tabela de tarefas do superusuário está localizada em / etc / crontab. A sintaxe da tabela do sistema corresponde à sintaxe do Vixie cron, ajustada pelo fato de a sexta coluna indicar o nome do usuário em nome de quem a tarefa é iniciada:


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

As tabelas de tarefas comuns do usuário estão localizadas em / var / cron / tabs / username e usam sintaxe comum. Quando o utilitário crontab é iniciado, esses arquivos são editados em nome do usuário.


As listas de usuários com acesso ao crontab são gerenciadas nos arquivos / var / cron / allow e / var / cron / deny, onde é suficiente adicionar o nome do usuário como uma linha separada.


Sintaxe estendida


Comparado ao crixab POSIX, a solução de Paul Vixie contém várias modificações muito úteis na sintaxe da tabela de tarefas do utilitário.


Uma nova sintaxe da tabela ficou disponível: por exemplo, você pode especificar os dias da semana ou os meses pelo nome (seg, ter e assim por diante):


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

Você pode especificar a etapa através da qual as tarefas são iniciadas:


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

Etapas e intervalos podem ser misturados:


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

Alternativas intuitivas para a sintaxe regular são suportadas (reinicialização, anual, anual, mensal, semanal, diária, meia-noite, a cada hora):


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

Ambiente de execução de tarefas


O Vixie cron permite alterar o ambiente dos aplicativos em execução.


As variáveis ​​de ambiente USER, LOGNAME e HOME não são apenas fornecidas pelo daemon, mas são obtidas do arquivo passwd . A variável PATH obtém o valor "/ usr / bin: / bin" e SHELL obtém o valor "/ bin / sh". Os valores de todas as variáveis, exceto LOGNAME, podem ser alterados nas tabelas do usuário.


Algumas variáveis ​​de ambiente (principalmente SHELL e HOME) são usadas pelo próprio cron para executar a tarefa. Aqui está o que pode parecer usar bash em vez do sh padrão para executar tarefas personalizadas:


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

Por fim, todas as variáveis ​​de ambiente definidas na tabela (usadas pelo cron ou necessárias para o processo) serão transferidas para a tarefa em execução.


O utilitário crontab usa o editor especificado na variável de ambiente VISUAL ou EDITOR para editar arquivos. Se essas variáveis ​​não estiverem definidas no ambiente em que o crontab foi iniciado, "/ usr / ucb / vi" será usado (o ucb é provavelmente a Universidade da Califórnia, Berkeley).


cron no Debian e Ubuntu


Os desenvolvedores Debian e derivados lançaram uma versão altamente modificada do Vixie cron versão 3.0pl1. Não há diferenças na sintaxe dos arquivos de tabela; para os usuários, esse é o mesmo cron do Vixie. Maiores novos recursos: suporte a syslog , SELinux e PAM .


Das mudanças menos visíveis, mas tangíveis - a localização dos arquivos de configuração e tabelas de tarefas.


As tabelas de usuário no Debian estão localizadas no diretório / var / spool / cron / crontabs, a tabela do sistema ainda está lá em / etc / crontab. As tabelas de tarefas específicas da Debian são colocadas em /etc/cron.d, de onde o daemon cron as lê automaticamente. O controle de acesso do usuário é regulado pelos arquivos /etc/cron.allow e /etc/cron.deny.


O shell / bin / sh padrão ainda é usado como o shell padrão.O Debian atua como um pequeno shell dash compatível com POSIX iniciado sem ler nenhuma configuração (no modo não interativo).


O próprio Cron nas últimas versões do Debian é lançado através do systemd, e a configuração do lançamento pode ser vista em /lib/systemd/system/cron.service. Não há nada de especial na configuração do serviço; qualquer gerenciamento de tarefas mais fino pode ser feito por meio de variáveis ​​de ambiente declaradas diretamente no crontab de cada usuário.


cronie no RedHat, Fedora e CentOS


cronie - fork do Vixie cron versão 4.1. Como no Debian, a sintaxe não mudou, mas foram adicionados suporte ao PAM e SELinux, trabalhando em um cluster, rastreando arquivos usando o inotify e outros recursos.


A configuração padrão está nos locais habituais: a tabela do sistema está em / etc / crontab, os pacotes colocam suas tabelas em /etc/cron.d, as tabelas de usuário caem em / var / spool / cron / crontabs.


O daemon é executado em systemd, a configuração do serviço é /lib/systemd/system/crond.service.


Na inicialização, as distribuições do tipo Red Hat usam / bin / sh por padrão, cuja função é o bash padrão. Deve-se observar que, ao executar tarefas cron por meio de / bin / sh, o shell bash inicia no modo compatível com POSIX e não lê nenhuma configuração adicional ao operar no modo não interativo.


cronie no SLES e no openSUSE


A distribuição SLES alemã e seu derivado do openSUSE usam o mesmo cronie. O daemon aqui também é executado em systemd, a configuração do serviço está em /usr/lib/systemd/system/cron.service. Configuração: / etc / crontab, /etc/cron.d, / var / spool / cron / tabs. Como / bin / sh age da mesma forma, iniciada no modo não interativo compatível com POSIX.


Vixie cron device


Os descendentes modernos de cron em comparação com o Vixie cron não mudaram radicalmente, mas ainda adquiriram novos recursos que não são necessários para entender os princípios do programa. Muitas dessas extensões são confusas e confundem o código. O código fonte cron original de Paul Vixie é um prazer de ler.


Portanto, decidi analisar o dispositivo cron usando o exemplo de um programa comum para os dois ramos do desenvolvimento do cron - o Vixie cron 3.0pl1. Simplificarei os exemplos removendo ifdefs que complicam a leitura e omitindo os detalhes secundários.


O trabalho do demônio pode ser dividido em várias etapas:


  1. Inicialização do programa.
  2. Colete e atualize a lista de tarefas a serem executadas.
  3. A operação principal do loop cron.
  4. Início da tarefa.

Vamos ordená-los em ordem.


Inicialização


Quando iniciado, após verificar os argumentos do processo, o cron instala os manipuladores de sinal SIGCHLD e SIGHUP. O primeiro registra a conclusão do processo filho, o segundo fecha o descritor de arquivo do arquivo de log:


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

O daemon cron no sistema sempre funciona sozinho, apenas como superusuário e no diretório principal do cron. As chamadas a seguir criam um bloqueio de arquivo com o PID do processo daemon, verifique se o usuário está correto e altere o diretório atual para o principal:


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

O caminho padrão está definido, que será usado ao iniciar os processos:


 setenv("PATH", _PATH_DEFPATH, 1); 

Em seguida, o processo é "demonizado": ele cria uma cópia filho do processo chamando fork e uma nova sessão no processo filho (chamando setsid). Não há mais necessidade do processo pai - e ele conclui o trabalho:


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

A finalização do processo pai libera o bloqueio no arquivo de bloqueio. Além disso, você precisa atualizar o PID no arquivo para o filho. Depois disso, o banco de dados de tarefas é preenchido:


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

O cron adicional prossegue para o ciclo de trabalho principal. Mas antes disso, dê uma olhada no carregamento da lista de tarefas.


Coletando e Atualizando a Lista de Tarefas


A função load_database é responsável por carregar a lista de tarefas. Ele verifica o sistema principal crontab e o diretório com os arquivos do usuário. Se os arquivos e o diretório não foram alterados, a lista de tarefas não é relida. Caso contrário, uma nova lista de tarefas começa a se formar.


Fazendo download de um arquivo do sistema com nomes especiais de arquivo e tabela:


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

Carregando tabelas do usuário em um 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); } 

Em seguida, o banco de dados antigo é substituído por um novo.


Nos exemplos acima, chamar a função process_crontab garante que o usuário exista que corresponda ao nome do arquivo de tabela (a menos que seja o superusuário) e, em seguida, chame load_user. O último já lê o próprio arquivo linha por linha:


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

Aqui, a variável de ambiente (linhas do formato VAR = value) é configurada pelas funções load_env / env_set ou a descrição da tarefa (* * * * * / path / to / exec) é lida pela função load_entry.


A entidade de entrada retornada por load_entry é nossa tarefa colocada na lista geral de tarefas. Na própria função, é realizada uma longa análise do formato da hora, mas estamos mais interessados ​​na formação de variáveis ​​de ambiente e parâmetros de inicialização de tarefas:


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

O ciclo principal também funciona com a lista atual de tarefas.


Ciclo principal


O cron original da Versão 7 Unix funcionava de maneira simples: em um ciclo, reli a configuração, executei as tarefas do minuto atual como superusuário e dormi até o início do próximo minuto. Essa abordagem simples em máquinas mais antigas exigia muitos recursos.


Uma versão alternativa foi proposta no SysV, na qual o daemon adormeceu até o próximo minuto, para o qual a tarefa foi definida, ou por 30 minutos. Os recursos para reler as tarefas de configuração e verificação nesse modo foram consumidos menos, mas tornou-se inconveniente atualizar rapidamente a lista de tarefas.


O cron Vixie voltou a verificar as listas de tarefas uma vez por minuto, já que no final dos anos 80 os recursos em máquinas Unix padrão haviam se tornado muito maiores:


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

A função cron_sleep, que chama as funções job_runqueue (iterando e iniciando tarefas) e do_command (iniciando cada tarefa individual), está diretamente envolvida na execução das tarefas. A última função deve ser considerada em mais detalhes.


Início da tarefa


A função do_command é executada em um bom estilo Unix, ou seja, faz bifurcação para execução de tarefas assíncronas. O processo pai continua a lançar tarefas, o processo filho está preparando o processo da tarefa:


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

Há muita lógica no processo child_process: leva a saída padrão e os fluxos de erros para si, para que possam ser enviados para o correio (se a variável de ambiente MAILTO estiver especificada na tabela de tarefas) e, finalmente, aguarda a conclusão do processo da tarefa principal.


O processo da tarefa é formado por outro 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; } 

Aqui, em geral, e todo o cron. Omiti alguns detalhes interessantes, por exemplo, respondendo a usuários remotos, mas descrevi o principal.


Posfácio


Cron é um programa surpreendentemente simples e útil, feito nas melhores tradições do mundo Unix. Ela não faz nada supérfluo, mas tem feito seu trabalho notavelmente nas últimas décadas. Conhecer o código da versão que acompanha o Ubuntu não levou mais de uma hora, e eu me diverti muito! Espero poder compartilhar com você.


Não conheço você, mas é um pouco triste para mim perceber que a programação moderna, com sua tendência a se complicar e se abstrair, deixou de ter tanta simplicidade.


Existem muitas alternativas modernas ao cron: systemd-timers permitem organizar sistemas complexos com dependências; no fcron, você pode controlar de forma mais flexível o consumo de recursos por tarefas. Mas, pessoalmente, eu sempre tive o crontab mais simples.


Em uma palavra, ame o Unix, use programas simples e não se esqueça de ler mana para sua plataforma!

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


All Articles