Grátis
256 pág.

Denunciar
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