====== Alocador de memória ====== Este projeto faz parte do [[PPOS-v2:start|PPOS v2]]. Este projeto visa construir um alocador de memória //heap// para o PPOS. Com ele funcionando, não precisaremos mais usar o alocador da biblioteca C (''malloc''/''free''). ==== Estrutura geral ==== A área de memória //heap//, usada para alocação dinâmica, será representada em nosso SO por um grande vetor estático de bytes, a ser definido em ''memory.c'': // tamanho total da heap: 64 MB #define HEAP_SIZE 64*1024*1024 static char heap[HEAP_SIZE]; Essa área de memória poderá ser alocada pelas aplicações em blocos de tamanho variável, usando o algoritmo de alocação //first-fit//, que aloca sempre o primeiro bloco de memória livre com tamanho suficiente. A figura abaixo mostra a organização da memória //heap//, com blocos alocados e blocos livres: {{ alocacao.png |}} Para gerenciar os blocos de memória (livres ou alocados), sugere-se definir um vetor de //descritores de bloco// contendo, para cada bloco: * endereço inicial do bloco * tamanho do bloco em bytes * estado do bloco (livre ou alocado) * número do bloco vizinho da esquerda * número do bloco vizinho da direita Observe que neste projeto **não podemos usar** uma lista de descritores dinâmica, nem a biblioteca de filas genéricas desenvolvida anteriormente. Ambas precisam da alocação dinâmica de memória //heap//, que é justamente o objetivo deste projeto. A abordagem mais simples neste caso é definir um vetor estático de descritores de blocos. ==== Algoritmo de alocação ==== A **alocação** de um novo bloco usa a seguinte estratégia: - ajusta o tamanho solicitado para um múltiplo de 16 bytes - na lista de blocos livres, procura um bloco com tamanho suficiente (//first-fit//) - se não encontrar, retorna NULL (alocação falhou) - se o tamanho do bloco livre encontrado for maior que o necessário, divide-o em dois blocos: * um bloco a ser alocado, com o tamanho solicitado * um bloco livre, com o restante do tamanho anterior - marca o bloco como ocupado - retorna um ponteiro para o início do bloco A figura a seguir ilustra a alocação consecutiva de 4 blocos de memória, em um //heap// inicialmente livre: {{ mem-alloc.png |Alocação de memória}} A **liberação** de um bloco usa a seguinte estratégia: - localiza o número do bloco alocado a partir do endereço fornecido - se não encontrar o bloco, retorna erro - se o bloco já estiver livre, retorna erro - marca o bloco como livre - se o bloco à sua esquerda também estiver livre, funde-os em um só bloco - se o bloco à sua direita também estiver livre, funde-os em um só bloco Ao liberar um bloco, deve-se fazer a **coalescência** dos blocos livres: se um seus vizinhos estiver livre, o bloco recém-liberado e seu vizinho livre devem ser fundidos em um bloco livre maior, **recursivamente**. Esse procedimento permite obter blocos livres maiores, diminuindo a fragmentação da memória. A coalescência é ilustrada no exemplo abaixo: {{ mem-free.png |Liberação e coalescência de blocos livres}} ==== Funções a implementar ==== As seguintes funções, com suas respectivas estruturas de dados de apoio, devem ser implementadas em ''memory.c'': === Inicia a memória === void mem_init() Inicia o subsistema de memória, preparando as estruturas de dados de controle do mesmo (tabelas/listas de descritores, etc.). === Aloca memória === void *mem_alloc(int size) Aloca um bloco de memória com o tamanho indicado em bytes; retorna um ponteiro para a área alocada ou NULL se houver erro. Cada bloco alocado deve ter um tamanho mínimo de 16 bytes e deve ser alinhado em 16 bytes, ou seja, seu tamanho e endereço inicial devem ser múltiplos de 16. O ajuste de um valor para o próximo múltiplo de 16 pode ser feito assim: // versão mais rápida value = ((value - 1) | 0x00000F) + 1; // versão mais simples while (value % 16) value++ ; === Libera memória === int mem_free(void *addr) Libera um bloco de memória previamente alocado, indicado pelo endereço ''addr''; retorna 0 se executar sem problemas ou -1 se o ponteiro for NULL ou indicar uma área não-alocada. === Memória total === int mem_size() Informa a quantidade de memória total, em bytes. === Memória livre === int mem_avail() Informa a quantidade total de memória disponível, em bytes. Não se preocupa se essa memória livre está fragmentada ou não. === Informa sobre o uso da memória === void mem_report() Imprime um relatório sobre o uso atual da memória, com o seguinte formato: heap: 435 KB allocated (16 blocks), 65100 KB free (1 blocks) heap: block 0: 0x0000565154c56340 - 0x0000565154c5644f aloc prev 16 next 1 size 272 heap: block 1: 0x0000565154c56450 - 0x0000565154c5646f aloc prev 0 next 2 size 32 heap: block 2: 0x0000565154c56470 - 0x0000565154c5648f aloc prev 1 next 3 size 32 heap: block 3: 0x0000565154c56490 - 0x0000565154c5659f aloc prev 2 next 4 size 272 heap: block 4: 0x0000565154c565a0 - 0x0000565154c5e59f aloc prev 3 next 5 size 32768 heap: block 5: 0x0000565154c5e5a0 - 0x0000565154c5e5bf aloc prev 4 next 6 size 32 heap: block 6: 0x0000565154c5e5c0 - 0x0000565154c6d91f aloc prev 5 next 7 size 62304 heap: block 7: 0x0000565154c6d920 - 0x0000565154c7025f aloc prev 6 next 8 size 10560 heap: block 8: 0x0000565154c70260 - 0x0000565154c7f94f aloc prev 7 next 9 size 63216 heap: block 9: 0x0000565154c7f950 - 0x0000565154c84c8f aloc prev 8 next 10 size 21312 heap: block 10: 0x0000565154c84c90 - 0x0000565154c9319f aloc prev 9 next 11 size 58640 heap: block 11: 0x0000565154c931a0 - 0x0000565154c9f82f aloc prev 10 next 12 size 50832 heap: block 12: 0x0000565154c9f830 - 0x0000565154ca645f aloc prev 11 next 13 size 27696 heap: block 13: 0x0000565154ca6460 - 0x0000565154cac40f aloc prev 12 next 14 size 24496 heap: block 14: 0x0000565154cac410 - 0x0000565154cb82ff aloc prev 13 next 15 size 48880 heap: block 15: 0x0000565154cb8300 - 0x0000565154cc321f aloc prev 14 next 16 size 44832 heap: block 16: 0x0000565154cc3220 - 0x0000565158c5633f FREE prev 15 next 0 size 66662688 Cada entrada do relatório deve informar: * número do bloco * endereços inicial e final * status (livre ou ocupado) * vizinho à esquerda e à direita * tamanho em bytes ===== Observações: ===== Após a implementação do alocador, você deve usá-lo em todo o seu projeto. Nos arquivos ''kernel/*.c'' e ''lib/*.c'' você deve: * trocar todas as ocorrências de ''malloc'' por ''mem_alloc'' * trocar todas as ocorrências de ''free'' por ''mem_free'' * remover todas as inclusões de ''stdlib.h'' Sua implementação deve funcionar corretamente com o programa de teste ''test/pingpong-mqueue.c''. Esse programa vai usar intensivamente a alocação de memória na criação e destruição de filas genéricas, tarefas, semáforos e filas de mensagens. ===== Arquivos ===== Os seguintes arquivos são relevantes para este projeto: * ''kernel/memory.h'': interface da gestão de memória * ''kernel/memory.c'': implementação gestão de memória * ''test/pingpong-memory.c'': programa de teste simples * ''test/pingpong-memory.txt'': saída esperada do teste * ''test/pingpong-mqueue.c'': teste mais complexo ===== Outras informações ===== * Duração estimada: 6 horas. * Dependências: * [[Tarefas cooperativas]] * [[Despachante de tarefas]] * [[Preempção por Tempo]] * [[Tarefas que esperam]] * [[Tarefas que dormem]] * [[Spinlocks e Semáforos]]