38 pontos por GN⁺ 2025-09-16 | Ainda não há 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

Ainda não há comentários.

Ainda não há comentários.