Estou estudando sistemas operacionais e a arquitetura x86 e, enquanto lia sobre segmentação e paginação, naturalmente fiquei curioso para saber como os sistemas operacionais modernos lidam com o gerenciamento de memória. Pelo que encontrei, o Linux e a maioria dos outros sistemas operacionais basicamente evitam a segmentação em favor da paginação. Algumas das razões para isso que encontrei foram simplicidade e portabilidade.
Quais usos práticos existem para a segmentação (x86 ou não) e sempre veremos sistemas operacionais robustos usando-o ou eles continuarão a favorecer um sistema baseado em paginação.
Agora eu sei que essa é uma pergunta carregada, mas estou curioso para saber como a segmentação seria tratada com os sistemas operacionais desenvolvidos recentemente. Faz tanto sentido favorecer a paginação que ninguém considerará uma abordagem mais "segmentada"? Se sim, por quê?
E quando digo segmentação 'shun', estou sugerindo que o Linux a usa apenas na medida do necessário. Apenas 4 segmentos para código de usuário e núcleo / segmentos de dados. Ao ler a documentação da Intel, tive a sensação de que a segmentação foi projetada com soluções mais robustas em mente. Por outro lado, foi-me dito em muitas ocasiões o quão complicado o x86 pode ser.
Achei essa anedota interessante depois de ter sido vinculada ao 'anúncio' original do Linux Torvald para Linux. Ele disse isso alguns posts depois:
Simplesmente, eu diria que é impossível portar. É principalmente em C, mas a maioria das pessoas não chamaria o que eu escrevo C. Ele usa todos os recursos imagináveis do 386 que eu pude encontrar, pois também foi um projeto para me ensinar sobre o 386. Como já mencionado, ele usa um MMU , para paginação (ainda não em disco) e segmentação. É a segmentação que o torna REALMENTE dependente de 386 (cada tarefa possui um segmento de 64Mb para código e dados - máx. 64 tarefas em 4Gb. Qualquer pessoa que precise de mais de 64Mb / cookies resistentes a tarefas).
Acho que minha própria experiência com o x86 me levou a fazer essa pergunta. Linus não tinha o StackOverflow, então ele apenas o implementou para experimentá-lo.
fonte
Respostas:
Com a segmentação, seria possível, por exemplo, colocar cada objeto alocado dinamicamente (malloc) em seu próprio segmento de memória. O hardware verificaria os limites do segmento automaticamente e toda a classe de bugs de segurança (saturação de buffer) seria eliminada.
Além disso, como todas as compensações de segmento começam em zero, todo o código compilado seria automaticamente independente da posição. Chamar para outra DLL resumia-se a uma chamada remota com deslocamento constante (dependendo da função chamada). Isso simplificaria bastante os vinculadores e carregadores.
Com 4 anéis de proteção, é possível criar um controle de acesso mais refinado (com paginação, você tem apenas 2 níveis de proteção: usuário e supervisor) e kernels de SO mais robustos. Por exemplo, apenas o anel 0 tem acesso total ao hardware. Ao separar o kernel do sistema operacional principal e os drivers de dispositivo nos anéis 0 e 1, você pode criar um sistema operacional de microkernel mais robusto e muito rápido, onde a maioria das verificações de acesso relevantes seria feita pelo HW. (Os drivers de dispositivo podem obter acesso ao hardware através do bitmap de acesso de E / S no TSS.)
No entanto .. x86 é um pouco limitado. Possui apenas 4 registros de segmento de dados "gratuitos"; recarregá-los é bastante caro e é possível acessar simultaneamente apenas 8192 segmentos. (Supondo que você queira maximizar o número de objetos acessíveis, o GDT retém apenas descritores do sistema e descritores LDT.)
Agora, com a segmentação no modo de 64 bits é descrita como "herdada" e as verificações de limite de hardware são feitas apenas em circunstâncias limitadas. IMHO, um grande erro. Na verdade, não culpo a Intel, principalmente culpo os desenvolvedores, a maioria dos quais pensava que a segmentação era "muito complicada" e almejava espaço de endereço simples. Eu também culpo os escritores de SO que não tinham imaginação para usar bem a segmentação. (AFAIK, OS / 2 foi o único sistema operacional que fez pleno uso dos recursos de segmentação.)
fonte
A resposta curta é que a segmentação é um hack, usado para fazer um processador com uma capacidade limitada de endereçar a memória exceder esses limites.
No caso do 8086, havia 20 linhas de endereço no chip, o que significa que ele podia acessar fisicamente 1Mb de memória. No entanto, a arquitetura interna foi baseada em endereçamento de 16 bits, provavelmente devido ao desejo de manter a consistência com o 8080. Portanto, o conjunto de instruções incluía registradores de segmento que seriam combinados com os índices de 16 bits para permitir o endereçamento de 1Mb de memória completo . O 80286 estendeu esse modelo com uma verdadeira MMU, para oferecer suporte à proteção baseada em segmento e ao endereçamento de mais memória (iirc, 16Mb).
No caso do PDP-11, os modelos posteriores do processador forneceram uma segmentação nos espaços de Instrução e Dados, novamente para suportar as limitações de um espaço de endereço de 16 bits.
O problema com a segmentação é simples: seu programa deve solucionar explicitamente as limitações da arquitetura. No caso do 8086, isso significava que o maior bloco contíguo de memória que você podia acessar era 64k. se você precisasse acessar mais do que isso, teria que alterar seus registros de segmento. O que significava, para um programador C, que você precisava dizer ao compilador C que tipo de ponteiros ele deveria gerar.
Era muito mais fácil programar o MC68k, que tinha uma arquitetura interna de 32 bits e um espaço de endereço físico de 24 bits.
fonte
Para 80x86, existem 4 opções - "nada", apenas segmentação, somente paginação e segmentação e paginação.
Para "nada" (sem segmentação ou paginação), você acaba com uma maneira fácil de proteger um processo de si mesmo, uma maneira fácil de proteger processos um do outro, nenhuma maneira de lidar com coisas como fragmentação do espaço de endereço físico, nenhuma maneira de evitar a posição código independente, etc. Apesar de todos esses problemas, pode (em teoria) ser útil em algumas situações (por exemplo, dispositivo incorporado que executa apenas um aplicativo ou talvez algo que use JIT e virtualize tudo de qualquer maneira).
Apenas para segmentação; quase resolve o problema "proteja um processo de si mesmo", mas são necessárias muitas soluções alternativas para torná-lo utilizável quando um processo deseja usar mais de 8192 segmentos (assumindo um LDT por processo), o que o torna principalmente quebrado. Você quase resolve o problema de "proteger processos uns dos outros"; mas diferentes partes de software em execução no mesmo nível de privilégio podem carregar / usar os segmentos uns dos outros (há maneiras de contornar isso - modificar entradas GDT durante transferências de controle e / ou usar LDTs). Também resolve principalmente o problema do "código independente de posição" (pode causar um problema de "código dependente de segmento", mas isso é muito menos significativo). Ele não faz nada pelo problema "fragmentação do espaço de endereço físico".
Apenas para paginação; ele não resolve muito o problema "proteja um processo de si mesmo" (mas vamos ser honestos aqui, isso é realmente apenas um problema para código de depuração / teste escrito em linguagens inseguras, e existem ferramentas muito mais poderosas como o valgrind). Ele resolve completamente o problema "proteger processos um do outro", resolve completamente o problema "código independente de posição" e resolve completamente o problema "fragmentação do espaço de endereço físico". Como um bônus adicional, ele abre algumas técnicas muito poderosas que não são nem de longe tão práticas sem paginação; incluindo coisas como "copiar na gravação", arquivos mapeados na memória, manipulação eficiente do espaço de troca, etc.
Agora, você pensaria que o uso da segmentação e da paginação traria os benefícios de ambos; e, em teoria, pode, exceto que o único benefício que você ganha com a segmentação (que não é melhor com a paginação) é uma solução para o problema "proteger um processo de si mesmo", com o qual ninguém realmente se importa. Na prática, o que você obtém são as complexidades de ambos e a sobrecarga de ambos, para muito pouco benefício.
É por isso que quase todos os SOs projetados para 80x86 não usam segmentação para gerenciamento de memória (eles o utilizam para itens como armazenamento por CPU e por tarefa, mas isso é principalmente apenas por conveniência, para evitar o consumo de um registro de uso geral mais útil para esses coisas).
É claro que os fabricantes de CPU não são tolos - eles não vão gastar tempo e dinheiro otimizando algo que eles sabem que ninguém usa (eles vão otimizar algo que quase todo mundo usa). Por esse motivo, os fabricantes de CPU não otimizam a segmentação, o que torna a segmentação mais lenta do que poderia ser, o que faz com que os desenvolvedores de SO desejem evitá-la ainda mais. Principalmente, eles mantinham apenas a segmentação para compatibilidade com versões anteriores (o que é importante).
Eventualmente, a AMD projetou o modo longo. Não havia um código antigo / existente de 64 bits para se preocupar, então (para o código de 64 bits) a AMD se livrou do máximo de segmentação possível. Isso deu aos desenvolvedores do sistema operacional mais um motivo (nenhuma maneira fácil de portar código projetado para segmentação para 64 bits) para continuar evitando a segmentação.
fonte
Estou bastante surpreso que, desde que essa pergunta foi publicada, ninguém mencionou as origens das arquiteturas de memória segmentada e o verdadeiro poder que elas podem pagar.
O sistema original que inventou ou refinou em forma útil todos os recursos que envolvem o design e o uso de sistemas de memória virtual paginada segmentada (junto com multiprocessamento simétrico e sistemas de arquivos hierárquicos) foi o Multics (e também o site Multicians ). A memória segmentada permite que o Multics ofereça uma visão para o usuário de que tudo está na memória (virtual) e permite o nível final de compartilhamento de tudode forma direta (ou seja, diretamente endereçável na memória). O sistema de arquivos se torna simplesmente um mapa para todos os segmentos na memória. Quando usada adequadamente de maneira sistemática (como no Multics), a memória segmentada libera o usuário de muitos encargos de gerenciamento do armazenamento secundário, compartilhamento de dados e comunicação entre processos. Outras respostas fizeram algumas afirmações claras de que a memória segmentada é mais difícil de usar, mas isso simplesmente não é verdade, e Multics provou isso com um sucesso retumbante décadas atrás.
A Intel criou uma versão hobbled de memória segmentada, a 80286, que, embora seja bastante poderosa, suas limitações impediram que ela fosse usada para algo realmente útil. O 80386 aprimorou essas limitações, mas as forças do mercado na época praticamente impediram o sucesso de qualquer sistema que pudesse realmente tirar proveito dessas melhorias. Nos anos seguintes, parece que muitas pessoas aprenderam a ignorar as lições do passado.
A Intel também tentou desde o início criar um super-micro mais capaz, chamado iAPX 432, que ultrapassaria em muito qualquer outra coisa na época, e tinha uma arquitetura de memória segmentada e outros recursos fortemente orientados para a programação orientada a objetos. A implementação original era muito lenta e não foram feitas mais tentativas para corrigi-la.
Uma discussão mais detalhada de como o Multics usou a segmentação e a paginação pode ser encontrada no artigo de Paul Green sobre a memória virtual da Multics - Tutorial e Reflexões
fonte
A segmentação era um hack / solução alternativa para permitir que até 1 MB de memória fosse endereçado por um processador de 16 bits - normalmente, apenas 64 K de memória estariam acessíveis.
Quando os processadores de 32 bits surgiram, era possível endereçar até 4 GB de memória com um modelo de memória plana e não havia mais necessidade de segmentação - Os registradores de segmento foram redefinidos como seletores para o GDT / paginação no modo protegido (embora você possa têm o modo protegido de 16 bits).
Além disso, um modo de memória plana é muito mais conveniente para compiladores - você pode escrever programas segmentados de 16 bits em C , mas é um pouco complicado. Um modelo de memória plana torna tudo mais simples.
fonte
Algumas arquiteturas (como ARM) não suportam segmentos de memória. Se o Linux dependesse da origem dos segmentos, não poderia ter sido portado para essas arquiteturas com muita facilidade.
Olhando para o quadro mais amplo, a falha dos segmentos de memória tem a ver com a popularidade contínua de C e aritmética de ponteiros. O desenvolvimento em C é mais prático em uma arquitetura com memória plana; e se você quiser memória plana, escolha paginação de memória.
Houve um tempo na virada dos anos 80, quando a Intel, como organização, antecipava a popularidade futura do Ada e de outras linguagens de programação de nível superior. É basicamente daí que algumas de suas falhas mais espetaculares, como a terrível segmentação de memória APX432 e 286, surgiram. Com os 386, capitularam para programadores de memória plana; paginação e um TLB foram adicionados e os segmentos foram redimensionados para 4 GB. E então a AMD basicamente removeu segmentos com x86_64, tornando a base reg um dont-care / implied-0 (exceto fs? Para TLS, eu acho?)
Dito isto, as vantagens dos segmentos de memória são óbvias - alternar espaços de endereço sem precisar repovoar um TLB. Talvez um dia alguém crie uma CPU com desempenho competitivo que suporte segmentação, podemos programar um sistema operacional orientado a segmentação para ela e os programadores podem criar Ada / Pascal / D / Rust / outro idioma que não exija plano programas de memória para ele.
fonte
A segmentação é um enorme fardo para os desenvolvedores de aplicativos. Foi daí que surgiu o grande impulso para acabar com a segmentação.
Curiosamente, muitas vezes me pergunto quanto melhor o i86 poderia ser se a Intel distribuísse todo o suporte legado para esses modos antigos. Aqui, melhor implicaria menos energia e talvez operação mais rápida.
Eu acho que alguém poderia argumentar que a Intel azedou o leite com segmentos de 16 bits, levando a uma revolta dos desenvolvedores. Mas, convenhamos, um espaço de endereço de 64k não é nada especialmente quando você olha para o aplicativo moderno. No final, eles tiveram que fazer alguma coisa, porque a concorrência poderia e efetivamente comercializava com eficiência os problemas de espaço de endereço do i86.
fonte
A segmentação leva a traduções e trocas de página mais lentas
Por esses motivos, a segmentação foi amplamente descartada no x86-64.
A principal diferença entre eles é que:
Embora possa parecer mais inteligente ter larguras de segmento configuráveis, à medida que você aumenta o tamanho da memória de um processo, a fragmentação é inevitável, por exemplo:
acabará se tornando à medida que o processo 1 cresce:
até que uma divisão seja inevitável:
Neste ponto:
No entanto, com páginas de tamanho fixo:
Pedaços de memória de tamanho fixo são simplesmente mais gerenciáveis e dominam o design atual do sistema operacional.
Consulte também: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
fonte