Bibliotecas padrão C em bare metal


24

Estou desenvolvendo principalmente dispositivos que portam o Linux, para que a biblioteca C padrão ofereça muita funcionalidade através da implementação de chamadas do sistema com comportamento padronizado.

No entanto, para o bare metal, não há SO subjacente. Existe um padrão relacionado a como a biblioteca de CA deve ser implementada ou você precisa reaprender a peculiaridade de implementações de uma biblioteca quando muda para uma nova placa que fornece um BSP diferente?


4
Site errado para sua pergunta.
21416

8
Estou votando para fechar esta questão como off-topic porque ela pertence ao Stack Overflow .
uint128_t

1
Geralmente você fica sem. Por que você precisaria dessas coisas sem um sistema operacional para apoiá-las? memcpy e com tanta certeza. Os sistemas de arquivos, não necessariamente, embora implementados fopen, close, etc, são triviais contra o ram, por exemplo. printf () é muito, muito, muito pesado, toneladas e toneladas de código necessárias, sem. qualquer E / S substitui ou dispensa. newlib é bastante extremo, mas ajuda se você não puder ficar sem, mas você precisa implementar o sistema no back-end de qualquer maneira, então você precisa de uma camada extra?
old_timer

12
Embora essa pergunta seja sobre software, ela é muito específica para programação incorporada, que geralmente é rejeitada pelo SO. Como já temos boas respostas aqui, a migração não é apropriada.
Dave Tweed

1
Embora o newlib seja mencionado abaixo em uma resposta, você também pode considerar o newlib-nano útil - ele pretende ser uma versão simplificada para uso em sistemas embarcados com recursos limitados. Eu o uso em projetos em Cortex M0 MCUs. Vários compiladores (sendo o Atollic TrueSTUDIO um) darão a opção de usar newlib ou newlib-nano.
jjmilburn

Respostas:


20

Sim, existe um padrão, simplesmente a biblioteca padrão C . As funções da biblioteca não requerem um sistema operacional "completo" ou qualquer sistema operacional, e existem várias implementações disponíveis sob medida para o código "bare metal", o Newlib talvez seja o mais conhecido.

Tomando o Newlib como exemplo, é necessário que você escreva um pequeno subconjunto de funções principais, principalmente como os arquivos e a alocação de memória são tratados no seu sistema. Se você estiver usando uma plataforma de destino comum, é provável que alguém já tenha feito esse trabalho por você.

Se você estiver usando linux (provavelmente também OSX e talvez até cygwin / msys?) E digite man strlen, ele deve ter uma seção chamada algo como CONFORMING TO, que informaria que a implementação está em conformidade com um padrão específico. Dessa forma, você pode descobrir se algo que você está usando é uma função padrão ou se depende de um sistema operacional específico.


1
Estou curioso para saber como um stdlibimplementa stdiosem depender do sistema operacional. como fopen(), fclose(), fread(), fwrite(), putc()e getc()? e como malloc()funciona sem falar com o sistema operacional?
224166 robert bristow-johnson

4
Com o Newlib, há uma camada abaixo chamada "libgloss", que contém (ou você escreve) algumas dúzias de funções para sua plataforma. Por exemplo, a getchare putcharque conhecem o UART do seu hardware; em seguida, as camadas Newlib printfsobre elas. A E / S de arquivo também dependerá de algumas primitivas.
Brian Drummond

Sim, eu não li o segundo parágrafo do pipe com atenção. além de lidar com stdine stdoute stderr (que cuida de putchar()e getchar()) que direciona a E / S de / para um UART, se sua plataforma possui armazenamento de arquivos, como com um flash, você deve escrever cola para isso também. e você tem que ter os meios para malloc()e free(). Eu acho que se você cuidar desses problemas, poderá executar C portátil em seu destino incorporado (não argvnem argc).
Robert Bristow-johnson

2
Newlib também é grande se você está lidando com MCU com 1 ou 2 kB de espaço de código ...
Brian Drummond

2
@dwelch Você não está criando seu próprio sistema operacional, está criando a biblioteca C. Se você não quer isso, então sim, é desnecessário grande.
pipe

8

Existe um padrão relacionado a como a biblioteca de CA deve ser implementada ou você precisa reaprender a peculiaridade de implementações de uma biblioteca quando muda para uma nova placa que fornece um BSP diferente?

Primeiro, o padrão C define algo chamado de implementação "independente", em oposição a uma implementação "hospedada" (que é o que a maioria de nós conhece, a gama completa de funções C suportadas pelo sistema operacional subjacente).

Uma implementação "autônoma" precisa definir apenas um subconjunto dos cabeçalhos da biblioteca C, ou seja, aqueles que não precisam de suporte ou mesmo a definição de funções (eles apenas fazem #defines e typedefs):

  • <float.h>
  • <iso646.h>
  • <limits.h>
  • <stdalign.h>
  • <stdarg.h>
  • <stdbool.h>
  • <stddef.h>
  • <stdint.h>
  • <stdnoreturn.h>

Ao dar o próximo passo em direção a uma implementação hospedada, você descobrirá que existem muito poucas funções que realmente precisam interagir com o "sistema" de qualquer forma, com o restante da biblioteca sendo implementável sobre essas "primitivas" " Ao implementar o PDCLib , fiz um esforço para isolá-los em um subdiretório separado para facilitar a identificação ao transportar a lib para uma nova plataforma (exemplos para a porta Linux entre parênteses):

  • getenv()( extern char * * environ)
  • system()( fork()/ execve()/ wait())
  • malloc()e free()( brk()/ sbrk())
  • _Exit()( _exit())
  • time() (ainda não implementado)

E para <stdio.h>(sem dúvida o mais "envolvido em SO" dos cabeçalhos C99):

  • alguma maneira de abrir um arquivo ( open())
  • alguma maneira de fechá-lo ( close())
  • alguma maneira de removê-lo ( unlink())
  • alguma maneira de renomeá-lo ( link()/ unlink())
  • alguma maneira de escrever para ele ( write())
  • alguma maneira de ler ( read())
  • alguma maneira de reposicionar dentro dele ( lseek())

Certos detalhes da biblioteca são opcionais, com o padrão apenas oferecendo a implementação de uma maneira padrão, mas não tornando essa implementação um requisito.

  • A time()função pode legalmente retornar apenas (time_t)-1se não houver mecânica de controle de tempo disponível.

  • Os manipuladores de sinal descritos para <signal.h>não precisam ser invocados por outra coisa que não seja uma chamada para raise(), não há nenhum requisito de que o sistema realmente envie algo semelhante SIGSEGVao aplicativo.

  • O cabeçalho C11 <threads.h>, que é (por razões óbvias) muito dependente do sistema operacional, não precisa ser fornecido se a implementação definir __STDC_NO_THREADS__...

Existem mais exemplos, mas não os tenho em mãos agora.

O restante da biblioteca pode ser implementado sem nenhuma ajuda do ambiente. (*)


(*) Advertência: A implementação do PDCLib ainda não está concluída, portanto, talvez eu tenha esquecido uma coisa ou duas. ;-)


4

O padrão C é realmente definido separadamente do ambiente operacional. Nenhuma suposição é feita sobre a presença de um SO host e as partes dependentes do host são definidas como tal.

Ou seja, o padrão C já é bastante bare metal.

Obviamente, as partes da linguagem que tanto gostamos, as bibliotecas, costumam ser o local onde a linguagem principal empurra essas coisas específicas. Portanto, o material típico do compilador cruzado "xxx-lib" encontrado para muitas ferramentas de plataforma bare metal.


3

Exemplo mínimo executável newlib

Aqui, forneço um exemplo altamente automatizado e documentado que mostra newlib em ação no QEMU .

Com o newlib, você implementa suas próprias chamadas de sistema para sua plataforma baremetal.

Por exemplo, no exemplo acima, temos um programa de exemplo exit.c:

#include <stdio.h>
#include <stdlib.h>

void main(void) {
    exit(0);
}

e em um arquivo C separado common.c, implementamos o exitcom semi - hospedagem ARM :

void _exit(int status) {
    __asm__ __volatile__ ("mov r0, #0x18; ldr r1, =#0x20026; svc 0x00123456");
}

Os outros syscalls típicos que você implementará são:

  • writepara produzir resultados para o host. Isso pode ser feito com:

    • mais semi-hospedagem
    • um hardware UART
  • brkpara malloc.

    Fácil no baremetal, já que não precisamos nos preocupar com paginação!

TODO Gostaria de saber se é realista alcançar a execução de syscalls de agendamento preventivo sem entrar em um RTOS completo, como Zephyr ou FreeRTOS .

O interessante do Newlib é que ele implementa todas as coisas específicas que não são do SO, como string.hpara você, e permite implementar apenas os stubs do SO.

Além disso, você não precisa implementar todos os stubs, mas apenas os necessários. Por exemplo, se o seu programa precisar apenas exit, você não precisará fornecer a print.

A árvore de origem Newlib já possui algumas implementações, incluindo uma implementação de semi-hospedagem do ARM newlib/libc/sys/arm, mas, na maioria das vezes, você precisa implementar suas próprias. No entanto, fornece uma base sólida para a tarefa.

A maneira mais fácil de configurar o Newlib é criar seu próprio compilador com o crosstool-NG, basta dizer que você deseja usar o Newlib como a biblioteca C. Minha instalação lida com isso automaticamente com esse script , que usa as configurações newlib presentes em crosstool_ng_config.

Eu acho que C ++ também funcionará, mas TODO testá-lo.


3
@ downvoters: explique para que eu possa aprender e melhorar as informações. Futuros leitores espero que possa ver o valor da configuração única introdutório newlib disponíveis na web que simplesmente funciona :-)
Ciro Santilli新疆改造中心法轮功六四事件

2

Quando você o usa baremetal, descobre algumas dependências não implementadas e precisa lidar com elas. Todas essas dependências são sobre o ajuste interno de acordo com a personalidade do seu sistema. Por exemplo, quando tentei usar sprintf (), que usa malloc () dentro. O Malloc possui o símbolo da função "t_sbrk" como um gancho no código, que deve ser implementado pelo usuário para impor as restrições de hardware. Aqui eu posso implementá-lo ou criar meu próprio malloc () se achar que posso fazer um melhor para o hardware incorporado, principalmente para outros usos, não apenas para o sprintf.


Por que a sprintf precisa de malloc ()?
Supercat 22/03

Eu não sei. Eu acho que o seu ponto é o buffer que ele já possui, não é? Mas mesmo o printf não deve precisar de malloc. Talvez para alocar dinamicamente algumas variáveis ​​internas, quando o cálculo da saída solicitada for mais pesado do que a previsão da alocação empilhada (variáveis ​​dinâmicas da função)? Estou certo de que o sprintf exigiu malloc (arm-none-eabi-newlib). Agora eu experimentei um programa simples que usa o sprintf no computador (glibc). Nunca chamou malloc. Então usei printf. Chamava-se malloc. Malloc era falso, sempre retornando 0. Mas funcionou bem. Eles deveriam imprimir uma string e uma variável decimal. @supercat
Ayhan

Eu mesmo fiz algumas versões de métodos printf ou similares, personalizados para suportar os formatos que meus aplicativos usavam. A saída decimal requer um buffer longo o suficiente para armazenar o maior número possível, mas, caso contrário, a rotina base aceita uma estrutura que, cujo primeiro membro é uma função de saída, aceita um ponteiro para essa estrutura junto com os dados a serem emitidos. Esse design torna possível adicionar variantes printf que saem para consoles, soquetes, etc., do tipo maldição. Nunca tive a necessidade de "malloc" nesse tipo de coisa.
23616
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.