
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:
- HOME é o diretório inicial do usuário.
- LOGNAME - login do usuário.
- PATH é o caminho pelo qual encontrar os utilitários de sistema padrão.
- 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:
- Inicialização do programa.
- Colete e atualize a lista de tarefas a serem executadas.
- A operação principal do loop cron.
- 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:
e->uid = pw->pw_uid; e->gid = pw->pw_gid; 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); } 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); cron_sync(); while (TRUE) { cron_sleep(); load_database(&database); cron_tick(&database); 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: 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)); { 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!