GNU ARM Cross-toolchain – Configurando stack e heap

GNU ARM Cross-toolchain heap

Na primeira parte da série, foi cross-compilada uma aplicação simples e em seguida foi utilizado o OpenOCD para realizar a gravação do binário gerado na memória Flash do microcontrolador. Mas caso seja necessário escrever uma aplicação mais complexa, que faça uso, por exemplo, de alocação dinâmica de memória, o que é necessário alterar no projeto? Além disso, é interessante deixar o tamanho da pilha de uma aplicação configurável. Como fazer isso? Este artigo responderá essas duas perguntas.

 

Antes de entrarmos em maiores detalhes, é necessário possuir uma cópia local do projeto disponível no github. Caso não tenham sido realizados os passos listados no artigo mencionado acima, é interessante que esses sejam executados de forma a tirar maior proveito dos conceitos apresentados a seguir.

 

Uma vez que o ambiente esteja criado corretamente, basta atualizar o projeto, tendo em vista que ele vem sofrendo alterações/modificações com o tempo. Execute os seguintes comandos:

 

 

 

Alocação dinâmica de memória

 

Para uso de alocação dinâmica de memória, a biblioteca C padrão, como especificado pelo padrão ISO/IEC 9899:2011, oferece algumas funções que gerenciam o pool de memória do sistema, tais como:

 

  • malloc;
  • calloc;
  • realloc;
  • free.

 

Para um teste inicial, vamos usar a função malloc para requisitar um bloco de memória da área de memória chamada heap. Você deve estar se perguntando agora: “Onde foi configurado o tamanho dessa área de memória?”. Nos próximos parágrafos vamos responder a essa pergunta!

 

É necessário que a macro PROJ_TEST_DINAMIC_MEM esteja definida no projeto para que esse teste seja realizado. Mas nada impede que sejam adicionadas ao projeto outras chamadas às funções mencionadas anteriormente. Portanto, altere o arquivo src/project.h de forma a conter a seguinte diretiva de compilação:

 

 

Pronto! Agora é só compilar o projeto.

 

 

Funcionou, correto? Não? Ocorreram erros de compilação? Algo como isso?

 

 

Faltou alguma coisa? Sim! Precisamos entender o conceito de syscalls, mas antes disso é necessário estudar a implementação da “C runtime library” utilizada pelo cross-toolchain, a Newlib.

 

 

Newlib

 

A Newlib, embora não seja um produto GNU, é uma “C runtime library” muito utilizada em projetos de sistemas embarcados bare-metal e GNU-based, já que suporta uma grande quantidade de arquiteturas de processadores. Como explicado nesse artigo, uma parte da biblioteca é dependente do sistema target, chamada Syscalls.

 

 

Syscalls

 

Uma syscall é a ligação entre a biblioteca C padrão e o dispositivo target. São funções que executam serviços referentes a recursos do ambiente de runtime do sistema target. Quando utilizado um sistema operacional, esse é responsável por fornecer tais funções já implementadas. No entanto, quando é construído um sistema bare-metal, as implementações de acesso ao sistema target devem ser fornecidas pelo próprio desenvolvedor. Entre tais funções, pode-se citar: acesso de baixo nível do sistema de arquivos, requisições para expansão da memória de dados, gerenciamento de processos, etc.

 

O erro obtido durante a compilação anterior é justamente referente à ausência da implementação de uma dessas funções, _sbrk, cuja responsabilidade é receber requisições para expansão da memória de dados do programa, dentro da memória heap. A implementação da função malloc da newlib faz uso do algoritmo de Doug Lea, que usa a macro MORECORE, definida da seguinte forma no seu código:

 

 

Agora é possível entender a causa do erro, certo? Mais outra dúvida: o que é a função _sbrk_r? A função _sbrk_r é a versão reentrante da função _sbrk, que recebe um argumento extra, um ponteiro para uma estrutura de controle de reentrância. Não é o foco deste artigo tratar reentrância, pois geralmente esse problema é resolvido usando um RTOS. Geralmente esse tipo de sistema operacional traz a sua própria implementação de memory allocator, não sendo necessário, portanto, o uso da função malloc da biblioteca C.

 

A seguir é fornecida a implementação da função _sbrk utilizada no projeto. Percebe-se que é feito uso de duas variáveis não definidas na aplicação: _end e _heap_end. Elas são os delimitadores da memória heap disponível no sistema target. Quando a memória disponível para alocação dinâmica acaba, _sbrk indica esse evento à função malloc, e essa, por sua vez, notifica a aplicação retornando o valor NULL. Caso contrário, é retornado o próximo endereço livre no heap à função malloc.

 

 

Para que essa função seja definida no projeto, altere o arquivo src/project.h e habilite o define PROJ_IMPLEMMENT_SYSCALLS, da seguinte forma:

 

 

Recompile a aplicação e verifique que o erro deixou de ocorrer. Vamos configurar as memórias heap e stack agora?

 

 

Configurando Heap e Stack

  

Tanto o heap quanto o stack são regiões alocadas na memória RAM do sistema, as quais podem ser configuradas de diversas formas. Neste artigo, esse procedimento é realizado no linker script do projeto. Tal arquivo é responsável por especificar a localização e tamanho dos blocos de memória do target, além de descrever o layout do arquivo executável, detalhando o mapeamento das seções dos arquivos de entrada no arquivo de saída.

 

Foram criados dois linker scripts no projeto:

 

  • project/stm32f4_flash.ld: especifica os blocos de memória do microcontrolador (RAM, FLASH, etc.) e configura os tamanhos das regiões heap e stack;
  • project/sections.ld: detalha o layout da memória do arquivo executável.

 

O arquivo sections.ld pode ser reaproveitado em outros projetos, pois trata-se de uma espécie de template de configuração de um arquivo executável. Já o arquivo stm32f4_flash.ld traz configurações de um projeto em específico, tal como mapeamento físico da memória do microcontrolador. Nesse último arquivo foram criadas algumas variáveis para configuração do heap e stack:

 

  • _heap_size: tamanho do heap em bytes;
  • _stack_size: tamanho do stack em bytes.

 

Para facilitar a visualização do layout da memória utilizado no projeto, veja a figura abaixo. O executável final, assim como os objetos compilados ao longo do processo de build, são formados por seções, tais como .text, .data e .bss, especificadas pelo padrão ELF. O trabalho de alocação dessas seções no arquivo binário executável é responsabilidade do linker, orientado pelas configurações indicadas no linker script.

 

Mapeamento da memoria

 

A memória destinada para stack está localizada no final da região RAM, cujo endereço inicial é calculado pelo linker e é indicado no vetor de interrupções/exceções do microcontrolador. Logo abaixo dessa região está localizada a memória para heap, utilizada para alocação dinâmica de memória.

 

O sentido de crescimento da memória de stack é o oposto da memória de heap. A primeira cresce para endereços mais baixos, já a segunda avança para endereços maiores. Dessa forma, caso haja um erro no dimensionamento dessas regiões, uma pode sobrepor a outra em algum momento durante a execução da aplicação. Como dimensionar corretamente tais regiões? Esse é um assunto para outro artigo.

 

E vocês? Como configuram essas regiões em seus projetos? Deixem suas dicas!

Outros artigos da série

<< GNU ARM Cross-toolchain - Compilação e OpenOCDGNU ARM Cross-toolchain – OpenOCD + GDB >>
NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Henrique Rossi
Engenheiro eletricista com ênfase em eletrônica e pós-graduado em Engenharia de Software. Comecei um mestrado, mas o interrompi. Especialista na área de sistemas embarcados, com mais de 12 anos de experiência em desenvolvimento de firmware (sistemas baremetal e baseados em RTOS) e Linux Embarcado. Atualmente sou administrador do site Embarcados, trabalho num fabricante de Set-Top Box e atuo como consultor/desenvolvedor na área de sistemas embarcados.

7
Deixe um comentário

avatar
 
7 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
0 Comment authors
GNU ARM Cross-toolchain – FreeRTOS + GCC + STM32F4Discovery - Parte 1Embarcados – Sua fonte de informações sobre Sistemas Embarcados GNU Cross-toolchain - Processo de buildEmbarcados – Sua fonte de informações sobre Sistemas Embarcados GNU ARM Cross-toolchain – Eclipse + FreeRTOS + GCC - Parte 2Embarcados – Sua fonte de informações sobre Sistemas Embarcados GNU ARM Cross-toolchain – Eclipse + FreeRTOS + GCC - Parte 1Embarcados – Sua fonte de informações sobre Sistemas Embarcados GNU ARM Cross-toolchain – FreeRTOS + GCC + STM32F4Discovery - Parte 2 Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
trackback

[…] artigo GNU ARM Cross-toolchain – Configuração stack e heap foi mencionado como reservar espaços da memória volátil do sistema para tais finalidades. Em […]

trackback

[…] GNU ARM Cross-toolchain – Configurando stack e heap […]

trackback

[...] GNU ARM Cross-toolchain – Configurando stack e heap [...]

trackback

[...] GNU ARM Cross-toolchain – Configurando stack e heap [...]

trackback

[...] GNU ARM Cross-toolchain – Configurando stack e heap [...]

trackback

[...] GNU ARM Cross-toolchain – Configurando stack e heap [...]

trackback

[...] GNU ARM Cross-toolchain – Configurando stack e heap [...]