2 pontos por GN⁺ 2023-11-17 | 1 comentários | Compartilhar no WhatsApp
  • O suporte do Linux à preempção em tempo real é um trabalho que espera há quase 20 anos para entrar no mainline, e Thomas Gleixner afirmou na Linux Plumbers Conference de 2023 que o último grande obstáculo é o printk()
  • O objetivo é fazer com que o processo de maior prioridade execute com latência curta e previsível, e para isso muitas partes centrais do kernel foram reescritas ao longo de muito tempo
  • O printk() pode ser chamado em qualquer contexto, então é muito mais complicado do que uma simples saída de log, e a atual saída síncrona entra em conflito com as metas de latência em tempo real
  • Desde 2018, cerca de 300 patches entraram no upstream ou estão aguardando no linux-next, e as tarefas restantes são lidar com o handover de mensagens urgentes e com a segurança dos drivers de console
  • Quando a arrumação do printk() terminar e o restante do código de tempo real estiver pronto no linux-next, a fusão na mesma merge window também pode acontecer, mas Gleixner disse que não fará mais previsões sobre a data de conclusão

Quase 20 anos de trabalho em preempção em tempo real

  • O suporte a tempo real no Linux apareceu pela primeira vez em 2004 na LWN e, por muito tempo, pareceu estar “só mais um pouco” de ficar pronto
  • A LWN também usou em 2009 o título the realtime preemption endgame, mas na Linux Plumbers Conference de 2023 Gleixner avaliou que agora o fim realmente está próximo
  • Para Gleixner, pessoalmente, isso é um trabalho de quase 25 anos
    • Ele começou a trabalhar no suporte a tempo real do Linux em 1999
    • O projeto em si também já dura quase 20 anos
  • Ele disse que haverá “a big party” quando o trabalho terminar, mas o último grande obstáculo que resta é o printk()

A latência que a preempção em tempo real tenta reduzir

  • O objetivo da preempção em tempo real é permitir que o processo de maior prioridade sempre consiga executar com latência mínima e previsível
  • Para isso, o kernel precisa ser preemptível no maior número possível de situações, e as exceções devem ficar limitadas a um escopo estreito e claramente definido
  • A forma básica de funcionamento já se estabeleceu há muito tempo, mas resolver os problemas de detalhe levou bastante tempo
  • Nesse processo, muitas partes do kernel central foram reescritas, e os benefícios se estenderam para todo o kernel, além dos casos de uso em tempo real

Por que o printk() é o último obstáculo

  • Quando o código do kernel precisa enviar mensagens ao console e ao log, ele chama o printk() ou funções construídas sobre ele
  • Parece uma saída simples, mas o printk() precisa funcionar em quase qualquer contexto
    • Ele pode ser chamado até mesmo em handlers de non-maskable interrupt
    • Pode ser chamado novamente de dentro de outra chamada de printk()
    • Em situações de crash do sistema, as informações de saída podem ser importantes, então é difícil restringir os contextos de chamada
  • Por causa dessas exigências, o printk() envolve de forma complexa problemas de concorrência, locking e tratamento de drivers
  • O printk() do kernel atual tem uma estrutura totalmente síncrona
    • A chamada não retorna até que a mensagem seja enviada para todos os destinos configurados
    • Gleixner descreveu essa estrutura como “stupid”
    • Especialmente durante o boot, a maior parte da saída pode ser apenas ruído, mas ainda assim é preciso esperar até que tudo seja enviado
  • Esse tempo de espera entra em conflito direto com a latência que o trabalho em tempo real tenta reduzir
  • Os desenvolvedores de tempo real já haviam movido há muito tempo a saída do printk() para uma thread separada, tornando-a assíncrona, mas esse código estava mais para uma coleção de hacks do que para uma solução fundamental

A reformulação do printk() desde 2018

  • O problema do printk() passou a ser tratado de forma séria a partir de 2018, e cerca de 300 patches entraram no upstream ou estão aguardando no linux-next
  • No momento, estão em andamento os três últimos conjuntos de patches necessários para concluir o trabalho
  • Uma das tarefas mais difíceis nos detalhes é o mecanismo de handover
    • Quando o kernel precisa imprimir uma mensagem urgente, como em um crash, pode ser necessário tomar o controle de um console que está imprimindo mensagens de prioridade mais baixa
    • Não é fácil fazer isso com segurança em qualquer contexto
  • Outro desafio é marcar os drivers de console que não podem ser usados com segurança em determinados contextos
    • Por exemplo, se for preciso imprimir uma mensagem durante um non-maskable interrupt, mas isso exigir configurar modo de vídeo, não vai funcionar
  • Gleixner respondeu que, no último ano, não houve mudanças conceituais fundamentais
    • 76 drivers de console no kernel que precisam ser corrigidos
    • O código de handover foi alterado para permitir atualizar os drivers um por um, sem precisar corrigir todos de uma vez
    • Mais discussões recentes sobre o trabalho no printk() estão neste texto

Saída assíncrona e condições para a fusão no mainline

  • Quando Masami Hiramatsu perguntou quais mensagens do kernel precisariam ser impressas de forma síncrona, Gleixner respondeu que quase tudo deveria se tornar assíncrono
  • A saída assíncrona reduz a latência gerada pelas chamadas de printk() e permite ter uma thread de kernel separada para cada console
    • Assim, um console rápido pode operar no seu próprio ritmo sem esperar pelo console mais lento
  • O código foi alterado para que mensagens importantes sejam copiadas completamente para o buffer de mensagens antes de a primeira linha ser impressa
    • Isso é uma medida de proteção caso um driver de console defeituoso comprometa o sistema inteiro
  • Para uma ordem de saída mais segura, o kernel grava primeiro nos consoles conhecidos como seguros
    • Por exemplo, se houver um armazenamento em memória persistente, a mensagem é salva primeiro ali antes de ser enviada para o dispositivo físico
    • A ideia é preservar o conteúdo da saída mesmo que um driver defeituoso derrube o sistema
  • Gleixner disse que o trabalho está perto do fim, mas que o printk() é imprevisível, então não falará mais sobre a data de conclusão
  • Ainda assim, afirmou esperar que o restante do código de preempção em tempo real entre no mainline antes do 20º aniversário, no fim de 2024
  • Quando Clark Williams perguntou se, depois que os patches do printk() entrarem no upstream, o restante do código de tempo real poderia entrar na mesma merge window, Gleixner respondeu “yes” de forma condicional
    • Se todo o código estiver staged no linux-next e parecer pronto, isso pode ser tentado

1 comentários

 
GN⁺ 2023-11-17
Opiniões no Hacker News
  • QNX já fazia isso direito há décadas. O microkernel tem um limite superior para tudo o que faz, e o código tem apenas dezenas de milhares de linhas.
    O microkernel só faz alocação de memória, despacho de CPU e passagem de mensagens entre processos. Todo o resto, incluindo drivers e loggers, fica no espaço de usuário e pode sofrer preempção por threads de prioridade mais alta.
    O kernel do QNX não lida com strings. Não há parsing, formatação nem mensagens. O Linux ficou inchado demais para uso em tempo real, e sua própria estrutura não se encaixa bem em tempo real, porque seria preciso tornar preemptíveis milhões de linhas de código do kernel. Por isso levou 20 anos para consertar.

    • Um exemplo moderno é o seL4. Pelo que sei, ele não faz alocação dinâmica de memória e também foi verificado formalmente em relação a várias propriedades.
      A maior contribuição ao design de kernels talvez seja o uso generalizado de capabilities, uma forma de levar o controle para o espaço de usuário de maneira segura e flexível.
    • O QNX não é usado em sistemas de infotainment automotivos? Fico curioso sobre onde mais ele é usado.
      O inchaço do kernel em si não me preocupa muito. Há muito tempo de desenvolvimento investido no Linux e, embora desktops não tenham a mesma prioridade que servidores, o trabalho para criar um kernel de alto desempenho em lugares como dispositivos portáteis também deve beneficiar usuários de desktop.
    • O kernel do SDP 8 atual tem 15.331 linhas, incluindo comentários e Makefiles.
    • Parece uma função main bem estruturada em C ou em uma linguagem da família C. A main apenas coordena chamadas a outras funções; aqui há a diferença de que o kernel do QNX faz menos inicialização, mas o conceito geral é parecido.
      Não sou desenvolvedor de kernel, mas essa forma de mantê-lo simples parece boa.
    • Cerca de 90% desses “milhões de linhas de kernel” são drivers de dispositivos. Para rodar em hardware arbitrário, um microkernel também vai precisar deles no fim das contas.
  • Há um exemplo de como, mesmo em um sistema morrendo, o kernel tenta de alguma forma emitir mensagens de log, e de como isso é usado em ambientes reais de produção.
    https://netflixtechblog.com/kubernetes-and-kernel-panics-ed6...

  • Fico imaginando se, quando esse problema for corrigido, ele poderá substituir uma parte considerável de algumas combinações de hardware/software feitas para tempo real. Hoje há muitas opções de chips ARM e x86 baratos, de baixo consumo e com clocks altos.
    Como os clocks são tão altos, mesmo que alguns prazos sejam perdidos, muitas vezes pode haver ciclos sobrando o suficiente para que a temporalidade perfeita seja menos importante. Sei que isso não é elegante nem eficiente, mas às vezes componentes de prateleira vencem a precisão.

    • Tarefas que exigem tempo real rígido não podem ser satisfeitas com “há muitos ciclos sobrando mesmo se algo for perdido”. Também não é uma questão apenas de ciclos de CPU.
      Uma única tarefa mal feita pode segurar o kernel e impedi-lo de fazer trabalho útil. O ponto central do tempo real rígido é: “nada pode impedir a execução desta tarefa importante”. Em áreas como automotiva ou aeroespacial, os sistemas de controle precisam funcionar em qualquer circunstância.
    • Aplicações com requisitos reais de tempo real normalmente têm exigências tão fortes que nem uma possibilidade muito pequena de falha é aceitável. Pense em aviônica, dispositivos médicos, automóveis e aplicações militares.
      Se você realmente precisa de tempo real, então realmente precisa; “perto o bastante” não existe. Dito isso, é minha impressão como alguém de fora.
    • Ao criar aplicações de tempo real com chips ARM de baixo consumo e alto clock, você simplesmente não usa um sistema operacional. Para esses usos, x86 nem é considerado.
      Um sistema operacional, mesmo sendo um RTOS, atrapalha demais. Não sei o que essa mudança vai alterar. Ainda assim, depende da aplicação, e há muitos casos que precisam apenas de algo “quase em tempo real”, então pode ser útil para esses usos.
    • Certo, mas isso não vai eliminar magicamente a necessidade de núcleos dedicados. Provavelmente será algo como instruir o escalonador a colocar tarefas de tempo real não preemptíveis apenas em um núcleo LITTLE.
  • A discussão aqui está focada na distinção entre aplicações de tempo real “rígidas” e “flexíveis”. Em tempo real rígido, é bem provável que você nem queira usar um sistema operacional de propósito geral como o Linux; em tempo real flexível, como videoconferência ou reprodução de áudio, não é o fim do mundo se houver engasgos ocasionais ou alguns quadros perdidos.
    O argumento é que o RT Linux será uma solução forte para esses usos de tempo real flexível. Mas os usos flexíveis propostos já são possíveis hoje com Linux embarcado. Reprodução de vídeo ou áudio de baixa latência por software não era impossível; isso já era possível há 20 anos.
    O problema aparece em sistemas ocupados quando I/O não preemptível interfere com frequência, mas em ambientes embarcados isso é raro. Há motivos convincentes para tornar o kernel totalmente preemptível e dar mais controle sobre o escalonamento, mas isso tem pouca relação com a ideia de que o Linux deveria substituir sistemas operacionais mínimos de tempo real ou código bare-metal.
    É mais uma questão de boa higiene: resulta em um sistema operacional que se comporta melhor sob carga até mesmo em aplicações que não são de tempo real.

  • É uma boa notícia, mas mesmo que o kernel Linux se torne em tempo real, há uma grande chance de o hardware não ser em tempo real por causa do cache e da mágica complexa dentro da CPU
    Hardware grande e complexo não combina com tempo real de verdade. Por isso a AbsInt e as ferramentas de pior tempo de execução (WCET) lidam principalmente com arquiteturas de CPU simples. O 8051 realmente vai sobreviver para sempre. A propósito, também existe o Zephyr RTOS

    • Pelo que sei, os recursos das CPUs modernas não impedem o uso em tempo real. Se algo tiver um limite superior e for possível raciocinar sobre ele, pode ser usado para construir um sistema em tempo real
      Basta assumir cenários como nenhum acerto de cache e carga máxima. Se der para impor um limite superior ao tempo gasto, está tudo bem
    • Acho isso bastante útil em placas “grandes” de nível microcontrolador, como a Raspberry Pi. Existe ali uma certa cultura de tempo real e, mesmo sem fazer bit banging diretamente pela CPU, visto de fora tudo acontece no prazo
      Um timer pode receber entrada de encoder em quadratura e só enviar uma interrupção ao dar wrap, ou o sistema de GPIO pode ser conectado a DMA para transmitir memória para pinos de saída sem intervenção da CPU. Também dá para fazer streaming para um DAC ou transferência via DMA de um ADC para a memória. Essas coisas muitas vezes contornam o cache para obter latência previsível
    • A SpaceX usa processadores x86 em foguetes. O pequeno helicóptero-drone que a NASA enviou a Marte também usa um núcleo ARM “bem grande”, a ponto de conseguir rodar um Android antigo
    • Não é necessariamente correto dizer que hardware grande e complexo não serve para tempo real de verdade. Existem núcleos avançados de tempo real como o Arm Cortex-R82
      Na prática, muitos sistemas de tempo real precisam processar e agregar um volume cada vez maior de dados de sensores, então estão ficando cada vez mais potentes
    • O 68000 é que é o verdadeiro rei do tempo real
  • No começo da carreira, passei por um número frustrante de entrevistas em que os entrevistadores não sabiam o que tempo real realmente significa. Muita gente deixava passar o conceito citado no texto, “e latência previsível”, e parecia achar que tempo real significava apenas “rápido”

    • Eu também tiraria completamente a parte do “mínimo”. O ponto central de tempo real é que a tarefa tem um limite superior previsível. Isso significa que ela pode até ser, em média, mais lenta que um sistema que não é de tempo real
      Se você controla o sistema de frenagem de um carro, “latência média de 50 ms, mas máxima de 80 ms” pode ser aceitável, enquanto “latência média de 1 ms, mas que pode crescer arbitrariamente e levar alguns segundos” não é aceitável
    • Como diz o velho ditado, “real time” não é “real fast”. A distinção entre tempo real hard e soft torna isso um pouco mais nebuloso, mas acho que muitos desenvolvedores de software também não entendem direito o que tempo real realmente é
  • Logging síncrono causou problema de novo. Na empresa passamos por algo parecido por causa do GLOG, a biblioteca de logging do Google; por exemplo, se stdout for um arquivo, pode bloquear em I/O de disco
    Quando nosso serviço travava por mais de 100 ms, em 90% a 99% dos casos a causa era o GLOG

    • Tenho essa conversa com colegas com frequência sobre logging. “Temos uma API de melhor esforço e uma API de entrega garantida.” “Queremos entrega garantida!” “Se a interface de logging com entrega garantida estiver offline ou lenta, o serviço vai cair; tudo bem?” “Não, não pode cair!”
      “Se algo precisa obrigatoriamente ser registrado em log, mas não dá para registrar, o que fazemos?” Hoje em dia eu simplesmente aponto para o teorema CAP e digo que logging é igual a qualquer outro sistema distribuído. Talvez por haver um artigo da Wikipédia com um desenho de triângulo e a palavra “teorema”, as pessoas tendem a aceitar
    • Já aconteceu de todo o ambiente de produção parar quando o servidor syslog travou. Estávamos empurrando logs via TCP, e esse bloqueio se propagou para todo o ambiente de produção
      Depois disso mudamos para envio por UDP. É melhor perder alguns logs do que perder toda a produção
    • Também tivemos, na biblioteca de logging da $MSFT, um problema do tipo “a biblioteca de logging quebra tudo”. Imagine 100 threads, cada uma com um buffer de logging de 300 MB
      Naturalmente isso destruiu a memória, e o servidor caiu até no SKU mais caro do Azure App Service
    • Se a disponibilidade do produto depende de ±100 ms, ele foi profundamente mal projetado, e isso não é culpa da biblioteca de logging. O usuário não vai se importar se, depois de apertar um botão, a conclusão levar 100 ms a mais
  • Isso me trouxe uma onda de nostalgia. Uns 17 ou 18 anos atrás, compilei um kernel Debian com RT_PREEMPT para usar em equipamento científico que precisava de temporização mais precisa
    A latência e o jitter eram muito impressionantes. Quase não pensei mais nisso desde então, mas, ao criar aplicações embarcadas com Raspberry Pi quando você não quer migrar para um microcontrolador com RTOS, parece haver muitos usos possíveis

    • É interessante você mencionar a Raspberry Pi. Um ou dois dias atrás vi um texto dizendo que o RpiOS inicializa e roda em cima de um RTOS
      Achei particularmente interessante porque antes eu já tinha visto propostas de executar Linux como uma tarefa de um RTOS. A ideia é rodar no RTOS aquilo que precisa de deadlines hard de tempo real, sem sofrer o impacto das latências que o sistema de memória virtual pode causar. Não me lembro se isso era apenas uma ideia ou se chegou a ser implementado, e também só vi uma menção a RpiOS em cima de um RTOS, então fiquei curioso
  • Fico curioso sobre o que isso significa para usuários comuns. É um recurso para ativar apenas em situações muito específicas, ou também pode trazer um sistema mais responsivo para o público em geral?

    • Pelo que entendo, tempo real torna o sistema mais lento. Para ser em tempo real, tudo precisa ter uma alocação de tempo
      Cada tarefa recebe um orçamento X e não deve ultrapassá-lo. Se o melhor caso é rápido, mas o pior caso é lento, isso significa que o sistema precisa sempre assumir o pior caso
    • RT não necessariamente melhora a latência; ele dá a algumas tarefas um limite superior fixo. Mas o trabalho necessário para viabilizar RT certamente pode melhorar a latência no caso comum
      O exemplo de evitar chamadas síncronas a printk() é exatamente isso, e deve melhorar a latência sob carga mesmo sem ativar RT. Imagino que um kernel RT totalmente incorporado upstream não se comporte de modo diferente de um kernel comum, a menos que você de fato execute processos RT. O motivo de ter demorado tanto para chegar ao upstream foi a necessidade de compromissos para possibilitar RT e, segundo o texto, agora não restam muitos desses compromissos
    • Se por usuário “comum” você quer dizer usuário de desktop, não muda muita coisa. Mas para dispositivos embarcados, como controle industrial e equipamentos de telecomunicações, é algo importante
      Porque passa a ser possível usar o kernel mainline mais recente mesmo quando for necessário escalonamento em tempo real
    • Pelo que entendi, o Linux passa a ser uma opção em situações que exigem um RTOS. É voltado a sistemas críticos, como aviação e dispositivos médicos, e tem pouco impacto para usuários comuns
    • Os usuários finais de desktop que mais comumente podem se beneficiar são pessoas que trabalham com áudio. Nesse caso, a latência, especialmente o jitter, pode ser bem problemática
  • Fico curioso sobre o que vocês acham do Xenomai[1]. Uso há alguns anos sem problemas
    No BeagleBone Black, normalmente obtenho jitter na casa de centenas de nanossegundos, e considero isso tempo real “hard”. Dá para escalonar tarefas periódicas na faixa de dezenas de microssegundos sem nunca perder uma
    Ao contrário do Real-Time Linux, que tenta tornar o próprio Linux preemptível, o Xenomai é essencialmente seu próprio kernel e executa o Linux como uma tarefa sobre ele. Ele fornece uma ABI para que tarefas criadas pelo usuário rodem lado a lado com o Linux, ou com prioridade mais alta. Por exemplo, ele consegue contornar o problema do printk(): o Xenomai não se importa e faz de bom grado uma troca de contexto a partir do printk para executar a tarefa do usuário
    A desvantagem é que, dentro do contexto do Xenomai, você não pode fazer chamadas de sistema comuns. Até pode, mas naturalmente isso quebra o modelo de tempo real. Por exemplo, se chamar printf() ou malloc() dentro de uma tarefa Xenomai, isso não será preemptível. A ABI do Xenomai replica, no lado das chamadas de sistema, o máximo possível do que você possa precisar, e funciona muito bem se você estiver satisfeito em lidar diretamente com a alocação de heap
    [1]: https://xenomai.org/