A maior rede de estudos do Brasil

Grátis
256 pág.
Descobrindo o STM32

Pré-visualização | Página 41 de 47

de código, em última
análise, ela precisa de acesso a uma API do sistema operacional. A fim de
usar a newlib, nós devemos fornecer a funcionalidade ausente na forma de
procedimentos. Algumas delas nós substituiremos com esboços simples, outros
(read e write) com o código que acessa uma UART.
Como mencionamos, a libc aloca memória – um monte dela – a partir
do “heap”. A libc fornece funções para gerenciar o heap (malloc e free),
mas depende de uma função do sistema _sbrk para alocar a memória que
ela gerencia. Considere a Figura 15.1 repetida do Capítulo 2, que mostra a
utilização da RAM por um programa de execução. A heap cresce para cima
a partir dos dados alocados pelo compilador na direção da pilha que cresce
para baixo. Dentro do heap, a memória é alocada pela malloc e desalocada
pela free; no entanto, sempre que malloc tem espaço livre insuficiente, ele
pede mais memória via _sbrk, que tem o efeito de mover o heap para cima
(ou para baixo para um pedido de negativo). Em um sistema desktop, isso
é implementado alocando-se mais páginas de memória, de fato malloc geral-
mente pede blocos de memória que são números inteiros de páginas (p. ex.:
4096 bytes), o que é um problema em um sistema limitado de memória, como
a placa Discovery.
A fim de implementar _sbrk precisamos saber onde o heap começa
(isto é, o final da memória alocada pelo compilador/linkeditor) e qual é o seu
limite físico (ou seja, o valor máximo para o final do heap). No script do linker,
definimos dois valores _end – o fim do segmento de dados – e _stackend que
é o limite do espaço reservado para a pilha. 1
Antes de desenvolver uma implementação de _sbrk, é esclarecedora a
leitura da página manual do Linux para sbrk (que chama a função do sistema
1Calcular o tamanho da stack (pilha) pode ser desafiador – especialmente na presença
de uma biblioteca de código. É melhor ser relativamente conservador!
Revision: (None) ((None)) 221
CAPÍTULO 15. NEWLIB
Data
Heap End
Main Stack
SP
RAM Start (low)
RAM End (high)
Heap Start
Figura 15.1: Program Memory Model
_sbrk).
void *sbrk(intptr_t increment);
...
As funções brk() e sbrk() alteram o local de término do pro-
grama, o qual define o fim do segmento de dados do processo (ou
seja, o ponto de término do programa é o primeiro local após o
final do segmento de dados não inicializado). Aumentar o local de
término de um programa tem o efeito de atribuição de memória
para o processo; diminuir o local término desaloca memória.
Alguns outros pontos-chave serão vistos a seguir. A função _sbrk(0)
retorna o atual “término do programa”( “program break”). Se a chamada não
puder ser satisfeita, _sbrk retorna -1 e define o errno atual para um código
de erro apropriado. Na newlib, errno é parte de uma estrutura usada para
fazer a biblioteca reentrante – esta estrutura “reent” é outra grande fonte de
uso de memória que deve ser replicada em um ambiente multi-threaded. Uma
implementação da _sbrk é mostrada na Listagem 15.2
Os esboços (stubs) restantes são fornecidos na Listagem 15.3. Opera-
ções de read e write utilizam a interface putc e getc descritas no Capítulo 5;
em ambos os casos, restringimos nossas transferências para um único carac-
tere. Observe que a maioria dos esboços restantes simplesmente retornam um
código de erro; uma exceção é a _isatty que retorna 1 uma vez que estamos
usando a UART como um terminal. A _fstat que fornece meta-dados para
222 Revision: (None) ((None))
15.1. HELLO WORLD
#include <errno.h>
// defined in linker script
extern caddr_t _end, _stackend;
caddr_t _sbrk(int nbytes){
static caddr_t heap_ptr = NULL;
caddr_t base;
if (heap_ptr == NULL) {
heap_ptr = (caddr_t)&_end;
}
if ((caddr_t) &_stackend > heap_ptr + nbytes) {
base = heap_ptr;
heap_ptr += nbytes;
return (base);
} else {
errno = ENOMEM;
return ((caddr_t)-1);
}
}
Listing 15.2: Implementação de _sbrk
arquivos abertos sempre define o modo do arquivo para a S_IFCHR que indica
um dispositivo orientado a caractere.
Há um detalhe final requerido para utilizar a newlib. O código de
inicialização deve chamar a _libc_init_array(void) antes do main(). Isso
garante que todas as estruturas de dados necessárias estarão corretamente
inicializadas. Em nosso código de inicialização, nós fornecemos uma imple-
mentação padrão “fraca” para o caso de se compilar sem a libc.
void __attribute__ ((weak)) __libc_init_array (void){}
void Reset_Handler(void) {
...
__libc_init_array();
main();
...
}
Exercise 15.1 Hello World
Revision: (None) ((None)) 223
CAPÍTULO 15. NEWLIB
Complete e teste o exemplo ”Olá mundo.”Você deve colocar todo o
código stub em um único arquivo – syscalls.c e compilar este com o seu
código.
224 Revision: (None) ((None))
15.1. HELLO WORLD
int _close(int file) {
errno = ENOTSUP;
return -1;
}
int _fstat(int file, struct stat *st) {
st->st_mode = S_IFCHR; // character device
return 0;
}
int _isatty(int file) {
return 1;
}
int _link(char *old, char *new) {
errno = EMLINK;
return -1;
}
int _lseek(int file, int ptr, int dir) {
errno = ENOTSUP;
return -1;
}
int _open(const char *name, int flags, int mode) {
errno = ENOTSUP;
return -1;
}
int _read(int file, char *ptr, int len) {
if (len){
*ptr = (char) uart_getc(USART1);
return 1;
}
return 0;
}
int _unlink(char *name) {
errno = ENOENT;
return -1;
}
int _write(int file, char *ptr, int len) {
if (len) {
uart_putc(*ptr, USART1);
return 1;
}
return 0;
}
Listing 15.3: Stubs Básicos de Syscall
Revision: (None) ((None)) 225
CAPÍTULO 15. NEWLIB
15.2 Construindo a newlib
A distribuição da newlib com a ferramenta Sourcery não foi compi-
lada para o uso mínimo de memória. Você pode construir sua própria versão
baixando as fontes newlib e usando o seguinte processo de compilação:
mkdir newlib-build
cd newlib-build
export CFLAGS_FOR_TARGET=''-g -O2 -DSMALL_MEMORY''
/path_to_newlib_source/configure --target=arm-none-eabi
↪→--prefix=/target-directory --disable-newlib-supplied-syscalls
↪→--disable-libgloss --disable-nls
226 Revision: (None) ((None))
Capítulo 16
Sistemas Operacionais de
Tempo-Real
O hardware do STM32 é capaz de realizar ações simultaneamente em
todos os seus barramentos de comunicação – p.ex.: ler arquivos de áudio de um
cartão SD no barramento SPI, enquanto toca esses arquivos de áudio através
do DAC, monitorando Nunchuks sobre o barramento I2C e registrar mensa-
gens através da UART. No entanto, a coordenação dessas atividades paralelas
através de software pode ser um verdadeiro desafio – especialmente quando
restrições de tempo devem ser atendidas. Uma estratégia comum é particio-
nar a responsabilidade para tarefas múltiplas entre threads separados – cada
um dos quais atuando de forma autônoma – e que estão escalonados (schedu-
led) com base em prioridades. Por exemplo, thread com grandes restrições de
tempo recebem prioridades mais elevadas do que outros threads.
Os thread fornecem uma maneira de dividir a lógica de um programa
em tarefas separadas. Cada thread tem seu próprio estado e aparentemente
executa como um programa autônomo enquanto compartilha dados com ou-
tros threads. Em um uni-processador, como o STM32, threads são executados
de forma intercalada com acesso ao processador controlado por um escalona-
dor (scheduler). Sempre que ocorre uma interrupção, há uma oportunidade
de suspender o thread atual e retomar um thread interrompido. A interrup-
ção por temporizador fornece o mecanismo para “time-slice” (“fatiamento do
tempo”) do processador, permitindo que cada thread pronto para executar
possa ser executado.
A coordenação das tarefas de hardware por vários threads é ativado
através objetos de sincronização. Por exemplo, um thread que está tentando
Revision: (None) ((None)) 227
CAPÍTULO 16. SISTEMAS OPERACIONAIS

Crie agora seu perfil grátis para visualizar sem restrições.