Executando verificação de segurança...
15

explorando a bolha tech: Bare-Metal

Esse é o primeiro de prováveis muitos outros posts, onde eu vou explorar a bolha de tecnologia e tentar entender como os dev de cada área escolhem suas tecnologias e descobrir porque certas tecnologias são mais populares que outras.

Atenção!

  1. Eu não sou nenhum grande profissional com décadas de experiência, tudo citado nesse post é apenas minha opinião feita com base nas minhas pesquisas pessoais, sugestões, correções e críticas são bem vindas.

  2. apesar do termo bare-metal, o post trata exclusivamente de sistemas microcontrolados.

A Bolha Bare-Metal:

Antes de tudo, embarcados é uma área enorme, então vamos definir desenvolvimento embarcado bare-metal como qualquer coisa desde um Attiny e que não seja capaz de rodar um OS (exceto microkernels).

dito isso, vamos tentar entender as necessidades da área passando por tópicos como memória, performance e portabilidade e assim tentar entender porque lang como Rust e Zig tem mais atenção nesse nicho que Go ou D e porque langs como Java e python são usadas em ambientes tão limitados?

gerenciamento de Memória:

não é surpresa pra ninguém que memória nesses ambientes é limitada, então só se deve usar langs que fazem pouco uso da heap e evitar tudo que usa GC certo? na verdade não é tão simples, claro, menos gasto de memória é sempre bom, e hardwares simples como attiny não tem opção, mas hardwares mais parrudos como os da familia ESP 32 e outros MCU médios como os STM32Fxxx poderiam facilmente suportar um GC, na verdade ESP32 e STM32 são famílias populares para makers que usam Python, Lua e até JS, mas então, se ter GC não é problema para makers, porque profissionalmente é?

Porque o problema não é o GC, nunca foi, o problema é ele ser obrigatório, veja linguagens populares como C++ e Rust possuem meios de imitar GC-like, usando ARC, eles tem gerenciamento dinâmico de memória, só não é obrigatório.

Ok e porque ser obrigatório é um problema? existem vários motivos, mas o principal deles é:

non-deterministic behavior:

em ambiente bare-metal profissional existe uma necessidade chamada “real-time”, que significa que tarefas tem um tempo específico para poder executar, coisa como GC podem adicionar tempo indesejado a essa tarefa, visto que não tem como controlar exatamente o tempo de execução, porque durante esse tempo, são chamadas funções que você como programador não tem controle e afeta a performance da tarefa, um exemplo

imagine um carro autônomo a 250Km/h , esse carro verifica o sensor de presença a cada 50ms, imagina que no meio da verificação o GC ativou, parou a verificação, e gastou 100 ms, o sensor volta e detecta algo e ativa o freio, mas por conta dessas 100ms a mais não dá tempo de parar o carro e alguém é atropelado.

(Esse exemplo é obviamente exagerado, uma tarefa dessas teria altíssima prioridade, talvez até crítica)

fragmentação de memória:

alguns controladores não tem MMU outros tem muita pouca memória, alocar memória e desalocar pode gerar problemas como fragmentação, o que não é problema pra langs como Rust, Zig, C++ porque nesses casos é só evitar a heap, é aí que langs como Go e Java perdem popularidade, mesmo sendo langs de sistemas, elas podem alocar na heap sem você saber
(elas ainda são opções boas nos MCUs que não tem esse problema, mas esse topico causa um efeito na popularidade geral)

hidden allocations:

langs que usam GC normalmente usam desse mecanismo para esconder coisas do Dev, regras de inicialização, alocações automáticas, Exception (depende da lang), etc…, isso cai nos mesmo 2 problemas citados acima, falta de controle temporal e fragmentação
(hidden allocations na stack também são um problema, e langs como C++ e Rust também sofrem com isso, apenas não foi citado porque o tópico é sobre heap).

OK, agora nós sabemos que apesar da memória limitada, muitos MCUs não tem problema lidando com GC, e que o principal problema nesses MCUs são as questões temporais associadas ao tempo de execução.

Performance:

esse é um tópico interessante, e extremamente complexo de se chegar a uma conclusão, mesmo que cada lang tenha um overhead na performance baseado na quantidade de features, boa parte da performance vem da qualidade compilador, afinal é ele que vai gerar as instruções, mas uns processadores de MCUs são super simples e tem poucas instruções,e com opções limitadas de compiladores e outros são o completo oposto; fica difícil analisar performance de forma justa, claro C++ vai ser mais MUITO mais rápido que micropython, mas agr comparar C++, Rust, C, Zig e outras langs compiladas, é muito complexo.

mas de qualquer forma, se engana quem acha que todo projeto em bare-metal é focado 100% em performance, na verdade, devido a o real-time, se a lang que você usar comprir as necessidades temporais do projeto, tanto faz o resto.(apenas modo de dizer, performace é importe)

Portabilidade:

é aqui que a maioria das langs morrem, até o momento eu falei como se todas as langs citadas tem portabilidade para tudo, mas a verdade tá longe disso, temos várias arquiteturas AVR, PIC, XTENSA, ARM-CORTEX, Risc-V…etc, e cada MCU pode ter um set de features completamente diferentes entre si, é a portabilidade que vai definir o grau de aceitação de uma lang

linguagens como Go, Java, Python, JS, Lua etc… rodam num número limitado de hardwares, diria que Go seja capaz de rodar em umas 100 placas (TinyGo marca 85, mas tem uns quebrados a mais) por aí, mas só a família STM32 tem mais de 1300 por exemplo.

uma das principais características para uma boa portabilidade no bare-metal é o freestanding, o quão independente uma linguagem é de um sistema/runtime, ele tem q ter:

  • meios de acessar diretamente o hardware (ponteiro)
  • o mínimo possível hidden allocations e hidden control flow
  • suporte para no standard library
  • suporte para várias archs e targets dessas archs (aqui ta incluso os meios de representar features do hardware, linkerscripts e etc...)

Todos os langs com GC caem aqui, como já falamos elas tem hidden allocations, e tem targets que não suportariam alocações, e obviamente, depende de uma biblioteca padrão para poder gerenciar a memória.
(ISSO É SOBRE POPULARIDADE, você pode usar o seu Go ou o seu Java embarcado, novamente, só estou falando da aceitação profissional)

C++ também sofre bastante, apesar de não ter nada de muito especial, Exceptions, RTTI, herança,** virtual** até a keyword new ( em AVR)
(curiosidade: C++ na verdade é uma linguagem bastante underrated em bare-metal, sua maior força vem com linux embarcado)
,

Um dos principais fatores da popularidade de Rust em sistemas, se dá porque a lang diretamente corrige esses problemas de C++, ou você achava que Result<Ok, Err> foi só questão de gosto?

mas Rust também não escapa, ele corrige os problema de C++, mas tem seus próprios, isso pode me fazer ser apedrejado mas… Rust é meio bloated.

e por fim tem C, a linguagem é estupidamente simples, tem compila até pra OVNI marciano, é não depende de quase nada, só no Brasil, 70% dos projetos embarcados é C
(https://embarcados.com.br/relatorio-da-pesquisa-sobre-o-mercado-brasileiro-de-sistemas-embarcados-e-iot-2023/)

C é de longe a campeã do bare-metal, sem nem competição….por enquanto, Zig 0.12 ta vindo com força, todas as vantagens do C, unido com as vantagens de uma lang moderna, se vc gosta de bare-metal, experimente Zig, vc não vai se arrepender.

No geral é isso, obrigado por ler até aqui, embarcados é uma área enorme e eu só conseguir citar tópicos superficiais, quem sabe eu faça uma parte 2 com topicos mais profundos, qualquer sugestão, crítica ou correção, por favor deixe nos comentários, eu vou adorar ler, bom… é isso tchau.

Carregando publicação patrocinada...
3

Artigo muito massa, circulei muitas palavras e termos que não conhecia na área(Apesar de ter visto na prática). Você poderia continuar com a série de artigos é uma ótima ideia!! Também voto na ideia de você fazer uma introdução para as pessoas que são mais leigas nesse assunto e querem aprender mais, com certeza vai agregar bastante aqueles que tem interesse em assuntos a nível mais bare-metal mas não sabem por ondem começar

2

Gostaria de fazer uma correção:

Um servidor bare metal também conhecido como servidor dedicado é um computador geralmente de maior porte e mais sofisticado que computador doméstico que é utilizado como servidor de arquivos e aplicações sem utilização de camada de software entre o hardware e o sistema operacional, ou seja, não utiliza virtualização. (Wikipedia)

Ou seja. Baremetal é quando o software é instalado direto na máquina.

Você pode fazer um servidor WEB bare-metal instalando as tecnologias direto no SO, sem usar VMs ou Containers.

Acredito que em todo momento você estava se referindo apenas a embarcados.

Embarcados geralemnte são bare-metal pois não tem nenhum tipo de virtualização.

2
2
1

olá, obrigado pela correção, sim no artigo eu me refiro apenas a bare-metal embarcados, mais especificamente apenas a controladores que não são capazes de rodar um OS, vou editar para deixar mais claro

2

Nossa, sem palavras, gostei muito do seu artigo, e com exemplos bem simples e claros para ilustrar as suas explicações. Eu gosto muito de usar linguagens de "sistema", C, C++ e Rust. Apenas pelo gosto de entender os detalhes, e de certa forma uma boa parte da industria da computação foi construida tendo como base C e C++.
Eu me lembro em 2011 quando comecei a desenvolver para iOS e tinha apenas objective-c, e não era tão popular, exatamente pelo fator de gerenciar a memoria, o swift chegou e programar para iOS virou commoditie. As abstrações tem as vantagens mas como tudo tem os seus custos.
O coletor de lixo, de fato simplifica muito o desenvolvimento, porém priva o programador de entender um pouco sobre minucias da memória e a questão de nem querer saber o que é o stack ou heap, pode ajudar na produtividade, mas por outro lado, vai limitar muito as possibilidades de aprender mais detalhes.
Mas enfim, cada qual com seus objetivos.

Mas na verdade, é para parabenizar pelo seu texto e espero que tenhamos prazer de ler outros artigos criados por você.
Muita generosidade em compartilhar seus conhecimentos.

Grande abraço.

2

Poderia clarificar o significado de "bloated"? Bloated no sentido da linguagem (Rust) produzir binários maiores do que o habitual devido ao processo de realizar static linking na maioria das libraries e afins (algo que vem com seus prós e contras); ou bloated no sentido da linguagem ter muitos recursos que você pessoalmente considera desnecessários?

1

No sentido de features mesmo, não é que eu acho desnecessario mas Rust tem toda a questão de "memory safety", fazendo operaçoes direto no hardware, voce acaba com muito codigo unsafe, não tem como evitar, acesso direto a endereços da memoria, binding pra APIs em C que usam "void*" em tudo..., pra projetos grandes as regrinhas de Rust é uma benção, pra projetinhos, fica meio chato.

falando de uma forma mais tecnica, usar um HAL feito em Rust é tão facil quanto usar uma HAL feito em C ou C++, agora lida diratamente com uma PAC em Rust é mais complexo que por exemplo C CMSIS (puramente minha opinião)

1

Ah, sim isso eu posso compreender sua posição nesse quesito. Mas acho meio complicado chamar isso de "bloat", é meio que o objetivo inteiro da linguagem ser basicamente assim (e sejamos justos, se você fizer o encapsulamento apropriado vai acabar precisando bem pouco comparativamente de utilizar código unsafe diretamente). Com macros você também pode abstrair mesmo as questões de usar void* nas bindings de C, o que pode ou não ser mais desejável do que encapsulação por tipos.

Mas também tenho certa defesa na questão dos projetinhos menores, acredito que a linguagem pode ter suas vantagens (deixo a seu cargo julgar essa posição), mesmo fora da noção de "memory safety", tenho uma certa sensação de que a quantidade de bugs lógicos que escrevo com ela é situacionalmente menor do que em outras linguagens (devido às restrições naturais da linguagem), e sinto que a expressividade dela também auxilia na estruturação de programas previsíveis (por exemplo, é uma das poucas linguagens onde você pode legitimamente realizar operações internas que ocasionem transformações no tipo do objeto e restrições ao uso por conta do sistema de tipos subestrutural). Mas admito também que essas mesmas features podem ser incrivelmente chatas em algumas situações, e que eu mesmo não o uso tanto por conta disso, geralmente pra um projeto de pequeno/médio porte, usaria algo como C# ou mesmo Kotlin (caso fosse fazer UI).

2

é acho que "bloat" foi uma expressão ruim,acho que a palavra que melhor descreve o que eu quis dizer é "redundância". Rust é minha lang favorita nesse nicho(por enquanto, zig ta ganhando um espaço no meu coração), eu basicamente adoro todas as features da lang.

o que acontece é que o "normal" nesse nicho é unsafe na visao do Rust, o void* e o acesso direto a memória são exemplo de coisas que pra Rust vc tem que trata, algumas não tem perigo algum(nesse nicho, fazer isso em outro lugar é 100% unsafe mesmo) outras talvez ja tenham seus próprios mecanismos de segurança, mas voce tem q ir lá e separar o "safe" do "unsafe", pq esse é o design da lang,por isso "é chato", mas não é um defeito, é um preço justo a se pagar.

1

Descreveu um pouco hard-RT vs soft-RT, mas faltou dar nome; esse ultimo vc ta falando de portabilidade de linguagem? Nao quer dizer capacidade? Portabilidade eh uma caracteristica de codigo, nao de linguagem. Em relacao aos requisitos, o unico real eh a quantidade de memoria e o tipo de RT que vc precisa, nada mais. Todos SoC's rodam algum tipo de ASM, e especialmente os de 32bits pra cima, todos tem algum ASM fazendo o bootstrap inicial da CPU, da stack e da libc. Tecnicamente, se vc codar o seu runtime em ASM, vc nao precisa de C, C++, Rust, Zig, etc, so de memoria msm.
E vc esqueceu de falar do quao contencioso eh floating point em embedded. Mtos uC's nao tem unidades de execução para tal, entao o compilador tem que gerar codigo que executa essas operacoes emulando essas unidades. No caso do GCC, ele usa a libgcc.