38 pontos por GN⁺ 2025-09-16 | 1 comentários | Compartilhar no WhatsApp
  • Compartilha a experiência de implementar um kernel protótipo de sistema operacional com time-sharing na arquitetura RISC-V
  • Explica, com foco em prática, o conceito e o funcionamento de um kernel de time-sharing, implementado em Zig em vez de C para melhorar a reprodutibilidade
  • Adota a abordagem de unikernel, empacotando kernel e código de usuário em um único binário, e usa OpenSBI como camada para saída no console e controle do temporizador
  • As threads executam em modo usuário (U-mode), enquanto o kernel realiza troca de contexto em modo supervisor (S-mode) por meio de interrupções de temporizador, atravessando a fronteira com chamadas de sistema
  • O ponto central é a técnica de trocar o stack frame acumulado pelo prólogo/epílogo da interrupção para restaurar o conjunto de registradores e CSR de outra thread, mudando assim o fluxo de execução
  • Com base em uma máquina virtual QEMU e no OpenSBI mais recente, oferece um ambiente de aprendizado reproduzível por qualquer pessoa e serve como material introdutório valioso para ensino e prática ao conectar conceitualmente o espectro de virtualização entre threads, processos e contêineres

Visão geral

  • Apresenta o processo de implementar diretamente um kernel de sistema operacional com time-sharing na arquitetura RISC-V
  • O público principal são iniciantes em software de sistemas e arquitetura de computadores, estudantes e engenheiros interessados em entender princípios de funcionamento de baixo nível
  • Este experimento usa a linguagem Zig em vez da tradicional linguagem C para aumentar a reprodutibilidade prática, além de simplificar a instalação
  • O código final está publicado no repositório popovicu/zig-time-sharing-kernel, embora possa haver alguma diferença em relação ao texto
    • Recomenda-se tratar a versão do repositório como a fonte única de verdade em vez dos trechos de código do artigo
    • Isso também facilita alinhar o ambiente prático com base no script do linker e nas opções de build do repositório

Leitura recomendada

  • O texto pressupõe noções básicas de arquitetura de computadores, como registradores, endereçamento de memória e interrupções
    • Como material prévio, recomenda Bare metal on RISC-V, processo de boot do SBI e exemplo de interrupção de temporizador
    • O texto sobre micro distribuição Linux também pode ser útil opcionalmente para entender a filosofia de separação entre kernel e espaço de usuário

Unikernel

  • Adota uma configuração de unikernel que vincula a aplicação e o kernel do SO em um único executável
    • Evita a complexidade de loader e linker em tempo de execução e simplifica o carregamento do código de usuário na memória junto com o kernel
    • Para fins didáticos e de reprodutibilidade, isso traz vantagens em simplicidade de distribuição e consistência de ambiente

Camada SBI

  • O RISC-V usa um modelo de privilégios com modos M/S/U, e neste experimento o OpenSBI fica no modo M enquanto o kernel roda em modo S
    • A saída no console e o controle do dispositivo de temporizador são delegados ao SBI para garantir portabilidade
    • Quando o SBI não está disponível, usa-se UART MMIO como fallback, embora o uso do OpenSBI mais recente seja recomendado para a prática

Objetivo do kernel

  • Para simplificar, oferece suporte apenas a threads estáticas, compostas por funções que nunca terminam
    • As threads executam em modo U e enviam chamadas de sistema ao kernel em modo S
    • Implementa escalonamento por time-sharing em núcleo único para permitir a troca para outra thread a cada tick do temporizador

Virtualização e o que exatamente é uma thread

  • O threading com time-sharing é uma forma de virtualização que permite executar várias tarefas em paralelo em um único núcleo sem mudar o modelo de programação
    • Diferentemente do escalonamento cooperativo, a troca ocorre por interrupção de temporizador, sem yield explícito
    • Cada thread possui seu próprio conjunto de registradores inacessível às demais e sua própria pilha, enquanto o restante da memória pode ser compartilhado

A pilha e a virtualização de memória

  • Threads precisam de pilhas separadas, essenciais para manter o contexto de execução, incluindo variáveis locais e preservação de ra conforme a convenção de chamada
    • O espectro de virtualização segue thread < processo < contêiner < VM, variando em nível de isolamento e visão (view) do ambiente
    • No Linux, contêineres são implementados por meio da combinação de mecanismos do kernel como chroot e cgroups

Virtualizando uma thread

  • O objetivo mínimo de virtualização neste experimento é manter o modelo de programação inalterado, proteger registradores e alguns CSRs e atribuir pilhas individuais
    • Destaca por que cálculos significativos se tornam impossíveis se a visão dos registradores não for protegida
    • Valores iniciais como a0 são semeados na pilha para simplificar a passagem de argumentos no início da thread

Contexto de interrupção

  • Interrupções podem ser entendidas como um modelo semelhante a chamada de função, no qual prólogo/epílogo preservam e restauram registradores na pilha
    • Para que uma interrupção assíncrona de temporizador não corrompa registradores, é obrigatório seguir a convenção de preservação
    • O assembly de exemplo preserva x0–x31 e também salva/restaura CSRs como sstatus, sepc, scause, stval

Implementação (alto nível)

Aproveitando a convenção da pilha de interrupção

  • O corpo da rotina de interrupção fica entre o prólogo e o epílogo; ao trocar sp por outra região de memória, passa-se a restaurar o conjunto de registradores de outro contexto
    • Isso corresponde diretamente a uma troca de contexto e é a ideia central da implementação de time-sharing neste experimento
    • A interrupção de temporizador entra periodicamente para alternar a execução entre o fluxo principal e o fluxo de interrupção

Separação entre kernel e espaço de usuário

  • Mantém a fronteira entre kernel em modo S e usuário em modo U, com interrupções e chamadas de sistema tratadas por um trap handler em modo S
    • O boot segue a sequência OpenSBI em modo Minicialização do kernel em modo Sinício das threads em modo U
    • Interrupções periódicas de temporizador viabilizam escalonamento e troca de contexto

Implementação (código)

Inicialização em assembly

  • Em startup.S, monta uma sequência mínima que faz a inicialização da BSS, define o stack pointer inicial e então salta para o main em Zig
    • O ponto de entrada do kernel usa a convenção export para se integrar ao ABI de C

Arquivo principal do kernel e drivers de I/O

  • O main de kernel.zig primeiro verifica a função de console do OpenSBI e, se falhar, usa UART MMIO como fallback
    • sbi.debug_print faz a chamada ajustando os registradores a0/a1/a6/a7 conforme o protocolo ECALL
    • Depois de configurar o temporizador, registra o handler de interrupção em modo S e ativa os ticks

Handler em modo S e a troca de contexto

  • O handler é escrito com a convenção naked do Zig para montar manualmente um prólogo/epílogo completo, incluindo a preservação de CSR
    • No corpo, chama handle_kernel(sp) e troca para o sp retornado para decidir se haverá mudança de contexto
    • Usa scause para distinguir entre ECALL em modo U e interrupção de temporizador, tratando cada caso por um caminho diferente

As threads em espaço de usuário

  • O código de usuário é incluído no mesmo binário único que o kernel, e as threads de exemplo repetem impressão de strings → loop de espera
    • syscall.debug_print coloca o número da chamada de sistema 64 em a7 e buffer/tamanho em a0/a1, depois executa ECALL
    • Na inicialização da thread, a pilha é semeada com endereço de retorno e valores iniciais de registradores, permitindo o uso imediato de argumentos já no primeiro retorno

Executando o kernel

  • O build é feito com zig build, e a execução acontece no QEMU especificando máquina virt + nographic + OpenSBI fw_dynamic
    • No boot, após o banner do OpenSBI, aparecem saídas periódicas intercaladas por ID de thread
    • Se compilado com -Ddebug-logs=true, são exibidos em detalhes a origem da interrupção, a pilha atual e logs de enqueue/dequeue

Conclusão

  • Este experimento moderniza um kernel educacional com a combinação RISC-V + OpenSBI + Zig, aumentando a reprodutibilidade e a legibilidade
    • Há simplificações como tratamento mínimo de erros e pilhas superdimensionadas, mas o foco está na aprendizagem da essência da troca de contexto e da separação de privilégios
    • A portabilidade para hardware real é possível desde que se ajustem constantes de linker e drivers e se garanta a disponibilidade do SBI

Nota adicional: resumo do espectro de virtualização

  • Threads: virtualização focada em registradores e pilha, com alta possibilidade de memória compartilhada
  • Processo: virtualização do espaço de endereçamento para isolamento de memória, com possibilidade de conter várias threads internamente
  • Contêiner: unidade de isolamento composta pela combinação de visões de ambiente, como filesystem e network namespaces
  • VM: busca a virtualização completa do hardware como um todo

Resumo dos principais pontos de implementação

  • Troca da pilha de interrupção para realizar a troca de contexto
  • Trap handler em modo S que preserva/restaura o estado completo, incluindo CSRs
  • Caminho de saída duplicado com SBI como prioridade e UART MMIO como fallback
  • Escalonamento simples centrado em threads estáticas, núcleo único e time slice
  • Chamadas de sistema baseadas em ECALL para deixar clara a fronteira entre modo U e modo S

1 comentários

 
GN⁺ 2025-09-16
Comentários no Hacker News
  • Dá para experimentar algo parecido em forma de "pacote" com "Operating System in 1000 Lines of Code"; eu segui isso um tempo atrás em Zig (convertendo os snippets em C para Zig ao longo do caminho) e foi muito divertido. Meu código e a VOD estão aqui: https://github.com/kristoff-it/kristos/

    • Fico curioso sobre quanto de Zig é preciso saber para fazer esse exercício; se alguém tiver conselhos realistas sobre como tentar isso sem nunca ter mexido com Zig antes, assumindo conhecimento prévio de C++, eu gostaria de ouvir
  • Conteúdo enviado separadamente pelo próprio autor do post: https://news.ycombinator.com/item?id=45236479. O autor refez ele mesmo o Tiny OS Kernel, queria especificamente fazer experimentos em RISC-V e no ambiente OpenSBI, e usou Zig em vez do C tradicional. Na verdade, acho que também daria para acompanhar facilmente com C ou Rust. O processo todo é meio bruto, mas é um experimento e uma introdução para dar os primeiros passos em desenvolvimento de kernel de SO e arquitetura de computadores. Acho que é um projeto legal para brincar no fim de semana, e o walkthrough completo com o link do Github pode ser visto acima

  • Projetos assim são realmente impressionantes. No fim das contas, o Linux também é só um kernel, mas esse trabalho abriu caminho para instalar um Unix de código aberto em bilhões de dispositivos. Acho isso muito legal

    • O mais divertido é que, quando o Linux foi distribuído pela primeira vez, o Torvalds escreveu no e-mail que era "só um hobby, não vai ser grande e profissional como o GNU" https://groups.google.com/g/comp.os.minix/c/dlNtH7RRrGA/m/SwRavCzVE7gJ

    • Eu não acho esse tipo de projeto <i>tão</i> impressionante assim; o caminho para fazer um kernel multitarefa minimalista já é conhecido há décadas. Criar um kernel que dá boot e executa tarefas simples é algo que qualquer pessoa com um certo nível de inteligência e persistência consegue fazer. Em RISC-V é um pouco mais complexo do que em x86, mas as informações de inicialização do hardware são fáceis de encontrar (veja https://wiki.osdev.org/RISC-V_Meaty_Skeleton_with_QEMU_virt_board). O próprio autor disse que esse projeto foi "refazer um exercício que tinha feito em uma aula de sistemas operacionais". Acho que está em um nível que qualquer pessoa com diploma em engenharia de software conseguiria realizar. Claro, pode haver bugs ou partes inacabadas, mas multiprocessos ou isolamento de processos com MMU em si já não são coisas difíceis hoje em dia

    • Tanto quanto o Linux, o fato de Stallman ter começado a criar um compilador C e utilitários Unix em 1984 também abriu caminho para instalar um Unix de código aberto em bilhões de máquinas

  • Zig é realmente ótimo para desenvolvimento de sistemas operacionais, e RISC-V também. Eu comecei a mesma tarefa em x86, mas cansei rápido por causa do excesso de boilerplate legado; no lado RISC-V quase não há isso. Fica muito mais fácil começar a desenvolver https://github.com/Fingel/aeros-v

    • Acho que, se você começar em x86, não há tanto boilerplate assim se escrever bem o bootloader. Normalmente o multiboot loader fica encarregado do modo real, e como a maioria das pessoas quer o modo protegido, basta configurar algumas tabelas e dar o salto. Se você quiser desligar o controlador de interrupções legado, há mais algumas coisas para mexer, mas existe a vantagem de poder dar boot em um PC desktop (com os devidos cuidados com a interface de console). No meu SO de hobby, usei boot por BIOS e alguns recursos de VGA, mas sofri com a compatibilidade ruim; console serial é muito mais fácil, só que hoje em dia muitos computadores já não têm porta serial

    • Na prática, isso é só trazer de volta a segurança de Object Pascal ou Modula-2 e reembalar com sintaxe de C; C nunca teve nada de especialmente único além de ter se espalhado por causa da licença do UNIX

    • Também quero tentar fazer isso eu mesmo, mas queria saber em que ambiente estão executando o kernel RISC-V; usam só Qemu ou existe algum hardware real recomendado?

  • Acho a ISA RISC-V realmente muito acessível. A documentação é excelente, há muitos exemplos, também existem muitos emuladores, e até o código de máquina não comprimido é fácil de ler. Estou escrevendo um livro para minha filha enquanto crio um pequeno SO com Forth e time-sharing https://punkx.org/projekt0/book/part1/os.html; acho que nunca teria tentado isso se fosse x86. Estou aprendendo Forth e assembly RISC-V ao mesmo tempo durante o processo e está sendo muito divertido. Se você quer criar um SO de brinquedo do zero, eu diria que este é o momento ideal (tão empolgante quanto os anos 1980). Pegue uma placa RISC-V barata (rp2350 etc.) e envie as seções relevantes do manual para uma IA como o Claude; isso ajuda bastante quando você trava

    • Acabei de pesquisar o preço das placas no meu país e me surpreendi porque são bem mais baratas do que eu imaginava. Estou 90% tentado a comprar uma só pela diversão de fuçar
  • Esse tipo de tentativa é sempre divertida e interessante; eu também incentivaria as pessoas a tentar coisas difíceis, até criptografia própria. O conselho de “não implemente sua própria criptografia” quer dizer para não usar em produção algo que não foi validado na prática; para experimento ou pesquisa, não há problema em tentar à vontade. Precisamos de mais sistemas operacionais e mais opções

  • (Citando uma decisão judicial da Espanha) Bloquear http até vai, mas isso aqui já é demais...

    • Ouvi dizer que na Espanha a Cloudflare (e talvez outros também) acaba sendo bloqueada por engano por causa de problemas envolvendo transmissão de futebol. Fico curioso sobre como estão contornando isso; usam VPN? Se um IP importante for bloqueado, isso também deve afetar o trabalho

    • (Na decisão) ao ver a parte que diz que isso foi iniciado pela liga espanhola de futebol profissional e pela Telefónica Audiovisual Digital, penso que essas pessoas são criminosas

    • ...o quê? Isso quer dizer que uma organização de futebol da Espanha tem autoridade para restringir o acesso à internet de um país inteiro?

  • Fico curioso sobre como conseguir hardware RISC barato

    • No AliExpress estão vendendo uma placa chamada Milk-V Duo S por 10 dólares; ela tem aparecido com frequência nas recomendações recentemente. As principais especificações incluem mestre SG2000 atualizado e 512 MB de RAM, IO mais amplo, WI-FI6/BT5 em alguns modelos (exceto os modelos 512M-Basic/eMMC), porta host USB 2.0, Ethernet 100 Mbps com suporte a PoE, dual MIPI CSI e chave para alternar o boot entre RISC-V e ARM, entre outras coisas https://aliexpress.com/w/wholesale-Milk%2525252dV-Duo-S.html

    • Já existem várias placas, mas a que eu achei interessante e financiei foi a VisionFive 2 Lite https://www.kickstarter.com/projects/starfive/visionfive-2-lite-unlock-risc-v-sbc-at-199/description. Não tenho a VisionFive2 de primeira geração, mas dizem que ela é bem avaliada e que o ecossistema está crescendo aos poucos. Ainda há partes inacabadas, mas estou ansioso para que seja entregue em breve. A que eu uso pessoalmente é a PolarFire SoC Discovery Kit, uma placa com RISC-V quad-core e FPGA; ela é um pouco cara (130 dólares) e não serve para todo mundo, mas o curioso é que a placa custa menos do que o próprio chip https://www.microchip.com/en-us/development-tool/MPFS-DISCO-KIT. A documentação e o toolchain da Microchip são antiquados e não são lá muito bons, mas, depois que você se acostuma, é realmente fácil rodar código bare-metal em RISC-V. Os exemplos de Linux/bare-metal ajudam bastante

    • Eu sugeriria experimentar primeiro em um emulador em x86 ou em uma máquina Apple, mesmo sem hardware real. O desenvolvimento é mais rápido do que em uma placa física, e com algo como o QEMU dá para começar imediatamente https://www.qemu.org/docs/master/system/target-riscv.html

    • O Raspberry Pi Pico 2 também é uma boa opção porque oferece suporte a RISC-V https://www.raspberrypi.com/products/raspberry-pi-pico-2/

    • Obrigado a todos pelas respostas; não vejo motivo para agradecer a quem deu downvote na pergunta