Primeiro, isso não tem nada a ver com RAM, realmente. Estamos falando de espaço de endereço aqui - mesmo se você tiver apenas 16 MiB de memória, ainda terá 32 bits de espaço de endereço em uma CPU de 32 bits.
Isso já responde à sua primeira pergunta, realmente - no momento em que foi projetada, os PCs do mundo real não tinham nem perto dos 4 GiB de memória completos; eles estavam mais na faixa de 1 a 16 MiB de memória. O espaço de endereço era, para todos os efeitos, gratuito.
Agora, por que exatamente 0xFFFFFFF0? A CPU não sabe quanto do BIOS existe. Alguns BIOS podem levar apenas alguns kilobytes, enquanto outros podem ocupar megabytes completos de memória - e eu nem estou entrando nas várias RAMs opcionais. A CPU deve estar conectada a algum endereço para iniciar - não há nada para configurar a CPU. Mas este é apenas um mapeamento do espaço de endereço - o endereço é mapeado diretamente no chip da ROM do BIOS (sim, isso significa que você não terá acesso aos 4 GiB de RAM completos neste momento, se você tiver muitos - mas isso não é nada de especial, muitos dispositivos exigem seu próprio intervalo no espaço de endereço). Em uma CPU de 32 bits, esse endereço fornece 16 bytes completos para a inicialização básica - o que é suficiente para configurar seus segmentos e, se necessário, o modo de endereço (lembre-se,"procedimento" de inicialização real . Neste ponto, você não usa RAM - tudo é apenas ROM mapeada. De fato, a RAM ainda não está pronta para ser usada neste momento - esse é um dos trabalhos do BIOS POST! Agora, você pode estar pensando - como um modo real de 16 bits acessa o endereço 0xFFFFFFF0? Claro, existem segmentos, então você tem espaço de endereço de 20 bits, mas isso ainda não é bom o suficiente. Bem, há um truque: os 12 bits altos do endereço são definidos até você executar seu primeiro salto em distância, fornecendo acesso ao espaço de endereços alto (enquanto rejeita o acesso a algo menor que 0xFFF00000 - até você executar um salto em comprimento) .
Tudo isso é oculto principalmente pelos programadores (para não mencionar os usuários) nos sistemas operacionais modernos. Você geralmente não tem acesso a nada tão baixo nível - algumas coisas já estão além do salvamento (você não pode alternar os modos da CPU à toa ou à toa), outras são tratadas exclusivamente pelo kernel do sistema operacional.
Portanto, uma visão mais agradável vem da codificação da velha escola no MS DOS. Outro exemplo típico de memória do dispositivo sendo mapeada diretamente para o espaço de endereçamento é o acesso direto à memória de vídeo. Por exemplo, se você deseja escrever texto rapidamente no visor, você escreveu diretamente no endereço B800:0000
(mais deslocamento - no modo de texto 80x25, isso significa que (y * 80 + x) * 2
se minha memória me servir corretamente - dois bytes por caractere, linha por linha). Se você queria desenhar pixel por pixel, utilizava o modo gráfico e o endereço inicial de A000:0000
(normalmente, 320x200 a 8 bits por pixel). Fazer algo de alto desempenho geralmente significava mergulhar nos manuais do dispositivo, para descobrir como acessá-los diretamente.
Isso sobrevive até hoje - está apenas oculto. No Windows, você pode ver os endereços de memória mapeados para dispositivos no Gerenciador de dispositivos - basta abrir propriedades de algo como sua placa de rede, vá para a guia Recursos - todos os itens do intervalo de memória são mapeamentos da memória do dispositivo para o seu espaço de endereço principal. E em 32 bits, você verá que a maioria desses dispositivos está mapeada acima da marca 2 GiB (mais tarde 3 GiB) - novamente, para minimizar conflitos com a memória utilizável pelo usuário, embora isso não seja realmente um problema com a memória virtual ( os aplicativos não chegam nem perto do espaço real de endereço de hardware - eles têm seu próprio pedaço de memória virtualizado, que pode ser mapeado para RAM, ROM, dispositivos ou arquivo de paginação, por exemplo).
Quanto à pilha, bem, deve ajudar a entender que, por padrão, a pilha cresce a partir do topo. Portanto, se você fizer um push
, o novo ponteiro da pilha estará em 0xFFFFFEC
- em outras palavras, você não está tentando gravar no endereço de inicialização do BIOS :) O que obviamente significa que as rotinas de inicialização do BIOS podem usar a pilha com segurança antes de remapear novamente em algum lugar mais útil. Na programação da velha escola, antes que a paginação se tornasse o padrão de fato, a pilha geralmente era iniciada no final da RAM e o "estouro de pilha" acontecia quando você começava a substituir a memória do aplicativo. A proteção de memória mudou muito disso, mas, em geral, mantém a compatibilidade com versões anteriores o máximo possível - observe como até a mais moderna CPU x86-64 ainda pode inicializar o MS DOS 5 - ou como o Windows ainda pode executar muitos aplicativos DOS que não têm idéia sobre paginação.