
QRV Operating System
QRV é uma adaptação e reimplementação completa do sistema operacional QNX Neutrino
6.4 para hardware moderno de 64 bits, com RISC-V (rv64g)
como arquitetura primária e x86-64 como alvo secundário. O
projeto começou na véspera de Natal de 2020. O nome QRV evita deliberadamente
qualquer associação com a marca QNX.
QRV é um sistema operacional completo, não apenas um kernel. O microkernel é
seu coração — mas a maior parte do trabalho foi investida em tudo que o
cerca. O mais notável é o taskman, o gerenciador de processos / memória / caminhos
em modo de usuário (o procnto do QNX), que foi profundamente reformulado e
retirado do kernel para o espaço do usuário; junto com ele, a biblioteca C, os drivers
de dispositivo, o sistema de arquivos, o carregador dinâmico e os servidores do
sistema foram todos portados, adaptados para 64 bits e, em muitos lugares, substancialmente
reescritos. O microkernel é pequeno por design; o sistema operacional ao redor
dele é onde a maior parte do QRV reside.
Isto não é um fork que meramente compila código antigo em um novo compilador. É um
port cuidadoso, módulo por módulo, para um modelo verdadeiro LP64, com a
fronteira proprietária do procnto desmontada, a maquinaria IFS/startup
substituída e — a partir das versões mais recentes — a trava Big Kernel Lock
do kernel completamente removida e o gerenciador de processos/memória/caminhos movido
para fora do kernel para um servidor em modo de usuário.
Este README descreve o QRV v0.43.
O blog de desenvolvimento, com a história completa do port, está em
https://r-tty.blogspot.com. Uma narrativa em formato de livro, The QRV Porting
Story, reside na árvore de fontes em doc/tex/PortingStory/.
O QRV foi desenvolvido em estreita colaboração com o Claude Code, a ferramenta de codificação agente da Anthropic — grande parte do port, da depuração SMP e da documentação (incluindo este README) foi realizada como um esforço de programação em parceria humano–IA, trabalhando junto com o autor.
obtain_proj.sh e a árvore os/TM_PRIVdevb-nvme e fs-qrvQNX é um sistema operacional de tempo real microkernel cuja ideia definidora é a passagem síncrona de mensagens. No QNX, o próprio kernel é minúsculo — ele sabe como agendar threads, passar mensagens, entregar sinais, lidar com temporizadores e interrupções, e pouco mais. Tudo que um SO monolítico colocaria dentro do kernel — o gerenciador de processos, o gerenciador de memória, o sistema de arquivos, os drivers de dispositivo, a pilha de rede — é executado em processos comuns de usuário chamados gerenciadores de recursos, e eles se comunicam entre si e com seus clientes através da mesma primitiva IPC enviar / receber / responder.
Essa arquitetura é o que torna o QNX elegante, e é exatamente o que o QRV preserva. Um programa que deseja abrir um arquivo envia uma mensagem; o servidor de sistema de arquivos a recebe, realiza o trabalho e responde. O kernel apenas intermedia o encontro. O resultado é um sistema onde um driver pode falhar e ser reiniciado sem derrubar o kernel, onde a base de computação confiável é medida em dezenas de kilobytes, e onde a fronteira entre "kernel" e "aplicação" é uma mensagem, não uma muralha de privilégios cheia de chamadas de sistema.
O QRV pega as fontes comunitárias do QNX Neutrino 6.4 da era 2009 e traz esse design adiante:
int/uint32_t/pid_t em ponteiros
que o código de 32 bits está repleto.qemu-system-riscv64 (a máquina
virt) e a placa de desenvolvimento SiFive Unmatched (FU740). x86-64 é
mantida em compilação como verificação de portabilidade.mkifs (QRV usa
o formato padrão CPIO); não há separação startup/kernel
(startup é vinculado diretamente com o kernel); não
há callouts e nem mini-drivers.Kconfig estilo Linux, um
link incremental do kernel (módulos são adicionados e testados um de cada vez, não jogados
no linker como um monólito 32→64), e uma cadeia de ferramentas de cross-compilação
(riscv64-linux-gnu-gcc).procnto é taskman (o
Gerenciador de Tarefas) em toda parte; todas as referências a "Neutrino" são eliminadas.QRV não implementa fork() (programas iniciam via posix_spawn()),
e não possui paginagem sob demanda e nem swap — as mesmas escolhas que o QNX
fez em sua geração 8.0.
QRV é regido por duas licenças ao mesmo tempo, e entender qual é qual é essencial antes de compilar ou redistribuir qualquer coisa.
O código próprio do QRV é Apache License 2.0. Tudo escrito do zero
para este projeto — o port RISC-V, o novo sistema de compilação, a separação do taskman
em modo de usuário, a reformulação livre de trava do kernel, os drivers e ferramentas que
criamos — é Apache 2.0. O texto completo está em LICENSE.txt.
O código derivado do QNX é a BlackBerry QNX Community License (QCL) 2.0. As partes do QRV que descendem das fontes comunitárias do QNX Neutrino de 2009 permanecem sob a QCL, que permite o uso não comercial e acadêmico das fontes derivadas. QRV não pode, e não irá, relicenciar o código do QNX.
Esta realidade de licenciamento duplo é precisamente a razão pela qual este repositório não contém uma árvore de fontes pronta para compilação. Não temos permissão para redistribuir as fontes derivadas do QNX. Então, em vez de enviar o código, este repositório envia uma receita (veja a próxima seção): um mapa de onde cada arquivo QNX vai, mais os patches QRV que o transformam. Você obtém as fontes comunitárias upstream do QNX por conta própria, a partir de seu espelho público, e a receita reconstrói a árvore QRV em sua máquina. Sua cópia é sua; redistribuímos apenas nossos próprios patches e metadados licenciados sob Apache.
QRV também incorpora código sob outras licenças permissivas — por exemplo
componentes licenciados sob BSD adotados do FreeBSD (substituindo módulos QNX antigos),
um driver de bloco virtio licenciado sob MIT de ascendência xv6, e o shell Korn
MirBSD (mksh) como shell do sistema. **WHAT_IS_WHAT.md é a referência
autorizada, quebra por componente, do que está sob qual licença e de onde
veio — consulte-o sempre que estiver em dúvida sobre um arquivo ou subsistema específico.
Finalmente, o repositório contém PETITION.md: um pedido aberto
à QNX Software Systems e à BlackBerry para relicenciar as fontes históricas
do Neutrino de 2007–2009 sob uma licença permissiva aprovada pela OSI. Se você gostaria
que os fundamentos deste trabalho se tornem um dia totalmente livres, esse documento
é onde adicionar seu nome.
obtain_proj.sh e a árvore os/Como as fontes derivadas do QNX não podem ser redistribuídas aqui, este repositório é uma distribuição de reconstrução de fontes. Ele contém:
$ ./obtain_proj.sh
1. **Clona o espelho da comunidade upstream QNX** (`github.com/vocho/openqnx`,
um clone raso).
2. **Coloca os arquivos** de acordo com `placement.txt`, copiando cada arquivo
upstream para sua localização QRV em `os/`. Relata quantos arquivos foram
colocados, já existentes ou ausentes.
3. **Remove o clone** assim que a colocação for concluída.
4. **Aplica a série de patches QRV** de `patches/series`, em ordem. Cada
patch é comprimido em LZ4 (`*.patch.lz4`) e aplicado com
`lz4cat … | patch -p1`. Os patches contêm a versão da release atual.
5. **Define permissões de execução** nos poucos scripts que precisam delas
(por exemplo, `emu.sh`, `host_tools/mkgpt.py`).
As ferramentas necessárias são `git`, `patch` e `lz4cat` (do pacote
`lz4`); o script verifica antecipadamente e informa como instalar
as que estiverem faltando.
O produto final é **`os/`** — uma árvore de código fonte QRV completa e compilável:```
os/
├── kernel/ Everything linked into the qrv-kernel binary
│ ├── arch/riscv/ RISC-V port: vectors, traps, SBI, context switch,
│ │ ├── startup/ arch-specific startup (head.S, mmu.c, …)
│ │ ├── platform/ qemu_virt/, unmatched/
│ │ └── include/ context.h, cpu_paging.h, sbi.h, …
│ ├── startup/ arch-independent startup (hardware_init, smp, …)
│ ├── nano/ core nanokernel: messaging, scheduling, sync, xfer
│ ├── kext/ kernel extensions (kerexts)
│ └── include/ kernel-internal headers
├── taskman/ The Task Manager (QNX's "procnto"), a user-mode server
│ ├── sys/ system manager: main, ELF loader, support
│ ├── proc/ process manager: spawn, wait, …
│ ├── mem/ memory manager: page tables, physical allocator
│ │ └── pageman/ page-granularity virtual-memory operations
│ └── path/ path manager: namespace, /dev/*, /proc/*
├── lib/ C library and runtime
├── include/ User-space-visible headers (the public ABI)
├── userland/ Shell, utilities, drivers, servers (resource managers)
├── servers/ pci, slogger
├── dev/ Device drivers (virtio block, 8250 UART, …)
├── boot/ Boot artifacts and deploy helpers
├── host_tools/ Host-side tooling (mkgpt.py, …)
├── doc/ Documentation, incl. The QRV Porting Story (LaTeX)
├── Kconfig, Makefile, def.mk, common.mk, emu.sh
From there, cd os && make builds the system (see §9).
QRV é um sistema microkernel verdadeiro. A pilha de privilégios, do hardware para cima, fica assim no RISC-V:``` ┌───────────────────────────────────────────────────────────────────────┐ │ User space (U-mode) │ │ │ │ sh pidin lspci sloginfo login ... │ │ │ │ │ │ │ │ │ devb-nvme fs-qrv devc-ser* pci slogger ← resource managers │ │ │ │ │ │ │ │ │ │ └──────┴───────┴────────┴─────────┴───────┘ │ │ │ libc (send / receive / reply stubs) │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ taskman — process · memory · path manager │ │ │ │ a U-mode server, privileged via TM_PRIV │ │ │ └─────────────────────────────────────────────────────────────────┘ │ └────────────────────────────────────│──────────────────────────────────┘ │ ecall (syscall / message) ┌───────────────────────────────────────────────────────────────────────┐ │ QRV microkernel (S-mode) │ │ │ │ message passing · channels & connections · scheduling · │ │ threads · synchronization · signals · timers & clocks · │ │ interrupts · syscall dispatch · TM_PRIV kernel extensions │ └───────────────────────────────────────│───────────────────────────────┘ │ SBI ecall ┌───────────────────────────────────────────────────────────────────────┐ │ OpenSBI firmware (M-mode) │ └───────────────────────────────────────│───────────────────────────────┘ │ ┌───────────────────────────────────────────────────────────────────────┐ │ RISC-V 64-bit hardware — QEMU virt · SiFive Unmatched U740 │ └───────────────────────────────────────────────────────────────────────┘
**O kernel (modo S)** é o único componente que executa com privilégios no sentido clássico. Seus subsistemas são pequenos e focados:
- **Passagem de mensagens** — `ker_message.c`, `ker_fastmsg.c`, `nano_message.c`
- **Canais e conexões** — `ker_channel.c`, `ker_connect.c`
- **Threads e escalonamento** — `ker_thread.c`, `ker_sched.c`, `nano_sched.c`
- **Sincronização** — `ker_sync.c`, `nano_sync.c`
- **Sinais** — `ker_signal.c`
- **Temporizadores e relógios** — `ker_timer.c`, `ker_clock.c`
- **Interrupções** — `ker_interrupt.c`
- **Despacho de chamadas de sistema** — `ker_call_table.c`
- **Transferência de dados** — a família `nano_xfer*.c` (o mecanismo de cópia entre espaços de endereço que move cargas úteis de mensagens de forma segura entre processos)
A interface entre o bootstrapper e o kernel é a **syspage** (`include/sys/syspage.h`); o estado por CPU reside na **cpupage**; o contexto completo de registradores é `RISCV_CPU_REGISTERS`. No RISC-V, cada "CPU" é identificada por seu **hart ID** em toda parte — existe um único namespace de nomeação de CPU, de ponta a ponta.
**Todo o resto é um processo de usuário.** O gerenciador de processo/memória/caminho, os drivers de bloco e serial, o sistema de arquivos, o servidor PCI, o sistema de log — todos são gerenciadores de recursos acessados enviando uma mensagem. O kernel não contém um sistema de arquivos; ele contém a capacidade de um processo pedir a outro para *ser* um sistema de arquivos.
---
## 5. Taskman e a chamada de sistema privilegiada `TM_PRIV`
**`taskman`** é o nome do QRV para o que o QNX chamava de `procnto`: o combinado **gerenciador de processos, gerenciador de memória e gerenciador de caminhos (namespace)**. Em um sistema QNX clássico, esse código é fundido na imagem do kernel. Uma das maiores conquistas estruturais do QRV é que **taskman agora executa em modo de usuário** — é um processo comum em modo U, não parte do kernel privilegiado.
Isso levanta uma questão óbvia: se o taskman reside no espaço do usuário, como ele faz as coisas profundamente privilegiadas que um gerenciador de processos e memória deve fazer — manipular tabelas de páginas, alocar RAM física, criar e destruir espaços de endereçamento, entregar sinais e pulsos, destruir processos?
A resposta é um único gateway rigorosamente controlado: **`__KER_TM_PRIV`**, slot de chamada de sistema 2. É a *única* porta pela qual o taskman alcança operações privilegiadas do kernel, e atrás desse único slot reside uma tabela de despacho de **~98 sub-operações** — pequenas **extensões do kernel** ("kerexts") que cada uma executa uma ação privilegiada bem definida e retorna. Algumas famílias representativas:
- **Ciclo de vida do processo** — `PROCESS_CREATE`, `PROCESS_EXEC`, `PROCESS_DESTROY`, `PROCESS_STARTUP`, `PROCESS_SHUTDOWN`, `REPARENT`
- **Memória física e virtual** — `PA_ALLOC`, `PA_FREE`, `PA_QUANTUM_TO_PADDR`, `PAGE_CONT`, `STACK_CONT`, `ASPACE_MEMCPY`
- **Credenciais e limites** — `CRED_GET`, `CRED_SET`, `LIMITS_GET/SET`
- **Entrega e objetos** — `PULSE_DELIVER`, `SIGNAL_DELIVER`, `QUERY_OBJECT`, `CHANNEL_DESTROY`, `CONNECT_DETACH`
- **SMP e plataforma** — `SMP_BRINGUP`, `LEGAL_CPU_MASK`, `GET_KERN_PGDIR`, `ICACHE_SYNC` (a operação de coerência do cache de instruções RISC-V, nova na v0.43)
Esse design mantém a **base de computação confiável pequena** — o kernel real permanece mínimo — enquanto dá ao taskman exatamente os primitivos privilegiados de que precisa e **nada mais**. Crucialmente, o heap do kernel e outros internos do kernel permanecem inacessíveis ao modo U (páginas do kernel mantêm `PTE_U=0`); o taskman realiza seu trabalho através de kerexts de copiar-para-dentro/copiar-para-fora, nunca recebendo um ponteiro bruto do kernel. O enum está em `kernel/include/ker+tm/tm_kercalls.h`; a tabela de despacho está em `kernel/ker_tm_priv.c`.
---
## 6. O Grande Bloqueio do Kernel — e sua remoção
O QRV inicial — como a geração do QNX da qual veio em sistemas multiprocessadores — protegia o kernel com um único **Grande Bloqueio do Kernel (BKL)**: uma palavra global `inkernel` que admitia exatamente um hart no kernel por vez. Não importava quantas CPUs estivessem rodando, toda chamada de sistema e toda mensagem do taskman serializava através desse único bloqueio. Correto, simples — e um teto duro na escalabilidade SMP.
**A partir da v0.42, o BKL desapareceu.** Esta foi a manchete de uma longa linha de candidatos a lançamento e o assunto dos capítulos 9–14 de *The QRV Porting Story*. Chamadas de sistema e mensagens do taskman agora executam **concorrentemente entre harts** sob bloqueios refinados, por objeto:
- **Bloqueio por objeto.** Um bloqueio por `tChannel` e por `tConnect` protegem as filas de mensagens; um `vec_slock` por processo protege o vetor de threads; um `sched_slock` por despacho protege as filas de execução; um `alloc_slock` protege o heap do kernel.
- **Passagem de mensagens sem bloqueio.** `MsgSend` / `MsgReceive` / `MsgReply` e a família `Sync*` não adquirem **nenhum bloqueio global**. O rendezvous entre harts é coordenado por bits de ponte por thread e barreiras de memória de hardware, em vez de exclusão mútua.
- **Reclamação SMR (equivalente ao RCU).** Threads, conexões e canais são retirados através de safe-memory-reclamation para que buscas sem bloqueio nunca desreferenciem um objeto liberado.
No QEMU `virt` com `-smp 8`, o kernel inicializa de forma confiável até um prompt `login:` e sustenta um loop de estresse de 300 iterações do `pidin` sem paradas.
---
## 7. Armazenamento: `devb-nvme` e `fs-qrv`
Fiel ao modelo de micronúcleo, o armazenamento no QRV é **dois processos de usuário cooperantes**, não um subsistema do kernel:
- **`devb-nvme`** — o driver de dispositivo de bloco. Ele fala NVMe sobre PCIe (com análise de partição GPT embutida), descobre o controlador através do servidor PCI e apresenta dispositivos de bloco como `/dev/nvme0n1` e suas partições. (Um irmão `devb-virtio` aciona o dispositivo virtio-blk do QEMU para o alvo emulado.)
- **`fs-qrv`** — o gerenciador de recursos do sistema de arquivos. Ele monta uma partição e serve o namespace do sistema de arquivos POSIX via passagem de mensagens: as chamadas `open`/`read`/`write`/`close` de um aplicativo tornam-se mensagens que o `fs-qrv` responde.
Uma inicialização típica monta uma partição NVMe real e executa programas a partir dela:```
mount -t qrv /dev/nvme0n1p5 /disk2
Este caminho — driver de bloco, particionamento, servidor de sistema de arquivos e o kernel intermediando cada mensagem entre eles — funciona de ponta a ponta tanto no QEMU quanto no drive NVMe do SiFive Unmatched.
O QRV inicializa através de sysinit/init, ativa o driver do console serial (devc-ser8250 no QEMU, devc-sersifive no FU740), o servidor PCI, a pilha de armazenamento e getty/login, e leva você a um prompt de shell. Os principais programas do espaço do usuário:
sh — mksh, o Korn shell MirBSD. Um shell POSIX real e programável é o shell do sistema; scripts de inicialização (level1.sh, …) são scripts de shell comuns.pidin — a ferramenta clássica de "informações de processo" do QNX: lista processos, threads, seus estados, memória e mais. É a sonda principal do QRV para "o sistema está vivo e bem?" e sua carga de trabalho padrão de teste de estresse.lspci — enumera o barramento PCI/PCIe através do servidor PCI.sloginfo — despeja o log do sistema coletado pelo servidor slogger.Ao lado destes estão os blocos de construção de um sistema multiusuário utilizável: getty e login (com suporte a credenciais/autenticação), mount, shutdown, pipe e utilitários principais (ls, cat, …). Cada programa QRV é multi-threaded — no mínimo uma thread principal e uma thread de sistema — que é exatamente por que a sincronização SMP correta (veja §6) é tão importante.
Cross-compiler : riscv64-linux-gnu-gcc CPU flags : -march=rv64g -mcmodel=medany -mno-relax Build style : -nostdinc -nostdlib -ffreestanding
### Comandos comuns (execute dentro de `os/`)```bash
make # Build everything: startup + kernel + module package
make -Bj # Force a full parallel rebuild (do this after header changes)
make startup # Build startup only
make kernel # Build kernel only
make modpkg # Create the module package (CPIO)
make qemu # Build and run in QEMU
./emu.sh # Run in QEMU (4 CPUs, 256M RAM, virt machine)
./emu.sh -P 1 # Run with a single hart
./emu.sh -gdb # Run with the GDB remote stub (port 1234)
A plataforma de teste principal é qemu-system-riscv64 na máquina virt. A configuração é baseada em Kconfig.
O alvo secundário, mas sério, do QRV é a SiFive Unmatched (FU740) — uma placa de estação de trabalho RISC-V real. Conseguir que um micronúcleo que inicializa corretamente no QEMU também inicialize corretamente em silício físico revelou uma classe de bugs que a emulação simplesmente não exibe, e persegui-los é grande parte do que se trata dos lançamentos recentes.
O exemplo definidor, corrigido na v0.43: por dois meses o QRV funcionou perfeitamente no QEMU e falhava no hardware real. Na FU740, qualquer programa — pidin, lspci, qualquer um — crashava após algumas criações de processos, cada crash diferente do anterior, o contador de programa vagando para lixo. A causa acabou não sendo corrupção de memória, mas incoerência de cache de instruções: RISC-V não garante coerência entre armazenamentos de dados e busca de instruções, então código de programa recém-carregado é invisível para a unidade de busca de um hart até que esse hart execute fence.i — e código que será executado em um hart diferente daquele que o carregou precisa de um fence.i remoto lá. O carregador do QRV não fazia nenhum dos dois (ele até calculava uma flag de "invalidar cache I" e depois a descartava). O QEMU não modela cache de instruções, então o bug era invisível lá e determinístico na U74.
A correção — um fence.i local mais transmitido via SBI (cpu_icache_sync_all()) em cada ponto onde uma página se torna executável — transformou um loop de criação de processos que falhava a cada poucas execuções em um que rodou mais de 600 criações consecutivas sem falhas na FU740. A investigação completa, incluindo a hipótese errada que produziu inicialmente e o diagnóstico que a derrubou, é a seção final do capítulo 14 de The QRV Porting Story.
Os marcos de hardware alcançados até agora incluem inicialização até um prompt login: no taskman em modo de usuário na FU740, e montagem e execução de programas de teste a partir de uma partição NVMe real.
O QNX é um dos designs de micronúcleo mais influentes já lançados. Seu modelo de envio/recebimento/resposta ensinou a gerações de engenheiros de sistemas como uma arquitetura limpa de SO pode ser. E, no entanto, o código que o incorpora passou mais de uma década em um limbo peculiar: visível o suficiente para ser estudado sob uma licença comunitária, mas não livre o suficiente para ser redistribuído, evoluído ou construir um ecossistema vivo ao redor. Um design tão bom merece mais do que ser preservado apenas como um artefato somente leitura.
Essa é a razão pela qual este trabalho existe. O QRV é, francamente, um veículo transitório — uma forma de aprender a arquitetura profundamente ao portá-la, ao desmontá-la e remontá-la em novo hardware, ao remover o bloqueio do kernel e elevar o gerenciador de processos para o espaço do usuário e descobrir exatamente quais suposições eram estruturais. Cada bug perseguido em silício real, cada subsistema reescrito para ser limpo em 64 bits, cada fronteira proprietária desmantelada, é conhecimento que uma implementação verdadeiramente livre precisará.
Porque o objetivo de longo prazo não é manter para sempre uma cópia corrigida das fontes de outra pessoa. É um sistema operacional totalmente livre, feito do zero, que seja compatível com as interfaces do QNX e fiel à sua filosofia de micronúcleo, mas que não deve nada a código proprietário — um que possa ser usado, ensinado, distribuído e melhorado sem pedir permissão a ninguém. O QRV é como provamos que tal sistema não é apenas possível, mas prático, e como ganhamos a experiência para construí-lo adequadamente.
Se você gostaria que as próprias fundações históricas se tornassem livres, adicione seu nome a PETITION.md. E se você gostaria de ver como é um micronúcleo moderno e honestamente projetado por dentro — clone a receita, execute obtain_proj.sh e leia o código.
QRV — adaptação e reimplementação do QNX Neutrino 6.4 para hardware de 64 bits. Iniciado na véspera de Natal de 2020. Apache 2.0 (código próprio do QRV) + BlackBerry QCL 2.0 (fontes derivadas do QNX). Veja WHAT_IS_WHAT.md para a divisão componente por componente.
| Arquivo/diretório | Finalidade |
|---|
obtain_proj.sh | O script de reconstrução — execute este. |
placement.txt | Mapeia cada caminho QNX upstream para seu caminho QRV (≈680 entradas). |
patches/ | A série de patches QRV, comprimida com LZ4, mais um arquivo de ordem series. |
LICENSE.txt | Apache License 2.0. |
WHAT_IS_WHAT.md | Licenciamento e proveniência componente por componente. |
PETITION.md | A petição de relicenciamento. |