Thiago Cantero

Tecnologia e Entretenimento

CProgramaçãoSistemas Operacionais

Gerenciamento de Memória em Sistemas Operacionais aplicado para a Programação

O Gerenciamento de Memória é, senão, uma das principais funcionalidades essenciais de todo Sistema Operacional.  Nas décadas de 1980, os Sistemas Operacionais, dos então IBM PC  fora projetados para terem módicos (nos dias atuais) 1024Kb de Memória RAM, dentre este montante, 640Kb eram reservados para o Sistema Operacional e programas, 384Kb para uso da UMA (Upper Memory Area), esta que servia para interfaces de vídeos, redes, modem, BIOS, Shadow RAM, e também um sistema que auxiliava na expansão da memória da memória primária (RAM), utilizando a interfaces ISA que seriam adicionadas como espécies de banco de memórias de hardware, tecnologia desenvolvida pela Lotus, Intel e Microsoft. Logo viram que não dariam conta, pois estávamos saindo dos Computadores Pessoais que rodavam Interpretadores Basic, como o MSX, Commodore 64, ZX-Spectrum, dentre uma outra infinidades de lindos aparelhos (que confesso que tenho saudades), e entrando em um futuro mais multitarefa. Em um futuro não muito distante daquela época, essa interface foi abolida, pela nova arquitetura Intel 386, a qual conseguia realizar esta expansão por software. Saudades desta época da Informática ; )

Bom, como vimos neste pequeno nostálgico e breve histórico acima, sempre foi um problema gerar este recurso de tanta importância que é a Memória RAM. Vejamos um código em C abaixo:

#include <stdio.h>
/**
* @version 1.0 
* @package StackPointer  
* @author Thiago Cantero Mari Monteiro 
* @copyright Copyright (c) 2022 Thiago Cantero Mari Monteiro 
* @license http://www.thiagocantero.com.br/sobre
* o método main irá imprimir a
* soma de a + b
*/

int main(void)
{
    int a, b, soma;
    a = 1;
    b = 4;
    soma = a + b;

    printf("%d\n", soma);
    return (0);
}

Este programa, neste experimento utilizando Linux, e comiplado com Gnu Compiler Collection ou gcc, nos retorna o valor “5”

Como é o processo de gerenciamento de memória em torno dessa tarefa, e de qualquer outra que o Sistema Operacional faz quando uma aplicação é executada, um teste de software  é desenvolvido, ou até mesmo um jogo, com suas variáveis diversas são computadas?

Pelo gerenciamento de memória, e estas basicamente são a Memória STACK, e a Memória HEAP.

A Memória Stack é uma alocação de memória estática, e tem sua determinação variando de Sistema Operacional para outros, as variáveis no código do exemplo acima, são armazenadas diretamente na Stack (pilha), isso mesmo uma pilha, para ter acesso rápido ao compilador, que por sua vez, no atributo do processador (CPU), provê a execução. Este é basicamente um esboço de como funciona a gestão do Sistema Operacional com relação à execução de uma aplicação, o assunto não se esgota por aqui, portanto se faz necessária a leitura. Recomendo  a obra “Sistema Operacionais Modernos” de Andrew S. Tanenbaum.

Bom, voltando ao que importa, quando a função, neste caso uma função Main tem sua chamada, ela finaliza, no entanto se tivermos mais funções (estas organizadas na pilha ‘Stack’), a execução das demais ficam suspensas até que a última função retorne seu valor. A pilha obedece uma ordem LIFO (Last In, First Out), cujo tem um bloco reservado nesta mesma pilha para este controle. Veja o diagrama abaixo que exemplifica o que falamos a pouco:

 

 

Observe a pilha, representando as áreas da Memória RAM em que as informações são gerenciadas, lembrando que a Random Access Memory (Memória de Acesso Aleatório), tem o objetivo de auxiliar o processador nas tarefas de Pipeline, como falei acima, são conceitos bem profundos que carecem de mais atenção. Veja que as variáveis ficam dispostas no stack frame (inserida na Stack), além do método Main que irá ser executado pelo compilador. Abaixo estão os dados reservas não inicializados, os dados inicializados que estão na Stack, e na última camada, o código fonte do programa.

É interessante salientar que linguagens de alto nível como PHP, Ruby, Python, Javascript elas possuem no seu interpretador, um gerenciador de memória, ou seja, você não consegue realocar memória para memória heap através delas, diferentemente da Rust, Object Pascal, C, C++, Java, C#.

Vejamos o código na versão 1.1 em que fora injetado no método Main os argumentos de Funções para manipulações de Inteiros e Strings (Int argc e char *argc[]).

#include <stdio.h>
/**
* @version 1.1 
* @package StackPointer  
* @author Thiago Cantero Mari Monteiro 
* @copyright Copyright (c) 2022 Thiago Cantero Mari Monteiro 
* @license http://www.thiagocantero.com.br/sobre
* o método main irá imprimir a
* soma de a + b
* adiciona pausa de execução para analisar a quantidade
* disponibilizada pela Stack.
*/

int main(int argc, char *argv[])
{
    int a, b, soma;
    a = 1;
    b = 4;
    soma = a + b;

    scanf("%d", &soma);

    printf("%d\n", soma);
    return (0);
}

agora vamos compilar o programa novamente:

agora é possível observar que o programa ficou pausado, ou seja a palavra reservada scanf verifica o ponteiro e paralisa o programa, neste ínterim, abriremos outro terminal e façamos a seguinte verificação:

ao executarmos ps asw | grep teste, verificamos que o processo (Pid Process Id) é o 15353. No linux os processos são armazenados na pasta /proc. Os processos realizados pelo usuário, são criadas pastas com os respectivos Pid, nela é contida informações de chamada de sistemas, dentre outras, então vamos executar os seguintes comandos:

Na pasta há vários arquivos que variam de chamados do Sistema (Kernel do Linux), Sessão do Usuário, etc. Há muita documentação legal sobre diretórios do Linux, eu recomendo o Guia Foca Linux .

Vamos focar no artigo e vejamos o que há no arquivo maps (que referencia o mapeamento feito na memória RAM de nossa aplicação em C).

Vimos que a área de reserva para o endereçamento da Stack é o: 7ffce9da7000-7ffce9dc8000, logo para sabermos a quantidade disponibilizada basta em uma calculadora científica subtrair os valores (do maior ao menor) e converter em decimal:

Realizando o cálculo, resulta em 135 Bytes de armazenamento na Memória RAM.

Bom, entendemos como funciona a Memória Stack, ela é reservada pelos Sistemas Operacionais (cada qual com suas peculiaridades), tem acesso rápido e tamanhos pré-definidos, ou seja, possui sua alocação estática.

Vejamos agora sobre a Memória HEAP, se pararmos para pensar, a Memória HEAP o restante da memória. Pensamos no seguinte contexto:
Tenho um notebook com 8Gb de RAM, se minha aplicação, mais as funções do meu Sistema Operacional, além de Serviços, como Servidores Gráficos, Servidores de Banco de Dados, Antivírus, dentre outras aplicações mil estiverem rodando em segundo plano, sobraria a HEAP.

Vejamos abaixo a quantidade que disponho de memória:

Eu, “probre” e desprovido de dinheiro, tenho meros 8Gb no meu notebook, e ele está com 5,7Gb de memória ocupada (cerca de 74%), ou seja, restando para mim 2,3Gb, no entanto existe também a memória SWAP (troca) que no meu caso está bem amparada e quase compenso essa falta, pois tenho SSD! : )

O que eu quis dizer sobre isso, além de atestar minha pobreza, e sim dizer que o restante é a área destinada para a HEAP, e ela há algumas desvantagens, pois é mais lento o acesso ao que for destinado para elas. Mas as linguagens de programação utilizam ela, se por exemplo no C# criarmos uma aplicação para Windows e usarmos a biblioteca Windows Forms, e por consequência, instanciamos este objeto, iremos utilizar a HEAP. Como no diagrama abaixo:

No exemplo acima, na Stack temos as variáveis “Nome” atribuído a ela o valor “João das Couves” (String) e “Idade” com o valor “32” (Idade), ao centro instanciamos o objeto para ser criado, e por fim, na memória Heap, o Objeto é criado. Resumindo, a Stack serve para alocar (com tamanho previamente fixo), a Heap serve para subsidiar e alocar o que estiver fora de alcance volumétrico da Stack.

Vamos ver a versão 1.2 de nosso programa?

#include <stdlib.h>
#include <stdio.h>
/**
* @version 1.2 
* @package HeapPointer  
* @author Thiago Cantero Mari Monteiro 
* @copyright Copyright (c) 2022 Thiago Cantero Mari Monteiro
* @license http://www.thiagocantero.com.br/sobre
* o método main irá imprimir a
* soma de a + b
* adiciona pausa de execução para analisar a quantidade
* disponibilizada pela Stack.
*/

int main(int argc, char *argv[])
{
    int a, b, soma;
    a = 1;
    b = 4;
    soma = a + b;
    int *ponteiro;


    ponteiro = (int *) malloc(1024);
    scanf("%d", &soma);

    printf("%d\n", soma);
    return (0);
}

Feita a inclusão da biblioteca <stdlib.h> Standard Library, que provê manipulações de endereçamento de memória, através de um ponteiro, colocando a variável e determinando o espaço para ela, através da palavra reservada malloc, que neste exemplo deixei para um 1Kb (1024 bytes).

Para não ficar repetitivo, façamos os mesmo passos descritos ao compilar o arquivo novamente, executá-lo, verificar o seu Pid (Process Id), e por consequência localizar ele na pasta /proc, e por fim, realizar o cálculo.

Bom, como vimos anteriormente gerou um novo Pid, fizemos o comando cat, e a diferença é que agora tem endereçamento destinado para ser alocado na HEAP (55952bd1d000-55952bd3e000), façamos o cálculo:

Voilá, os mesmos 135 bytes, isso é feito pela própria linguagem, pois é um programa bem simplório que não tem muitos dados para serem computados, no entanto transferimos ele de um endereçamento para outro.

O que podemos concluir, é que a Stack é usada para quando você sabe o tamanho dos dados que irá manipular, e por consequência estes extrapolarem, se estiver usando linguagem de baixo nível, deverá realizar estes ajustes como no final. No caso de trabalhar com Linguagem de Alto Nível, também deve ter preocupação, pois dá o famoso Stackoverflow, ou seja o esvaziamento de pilha. Problemas comuns como este eram as famosas telas azuis do Windows 2000, XP, 7 (Kernel NT), ou os erros  de tela branca nos Windows 95,98 e ME. Quem é das antigas vai lembrar! ; )

Cabe salientar a importância de ambas formas de gerenciamento, em ambientes complexos de múltiplos processos rodando, como uma aplicação corporativa, tanto a Stack, como a Heap trabalharão em conjunto, além das demais formas de gerenciamento realizadas pelo Sistema Operacional.

Espero ter contribuído ou aguçado sua curiosidade!

Até mais, tome café e boa viagem! ; )