O Linux não usa segmentação, mas apenas paginação?

24

A interface de programação do Linux mostra o layout de um espaço de endereço virtual de um processo. Cada região do diagrama é um segmento?

insira a descrição da imagem aqui

De Entendendo o kernel do Linux ,

está correto que o seguinte significa que a unidade de segmentação na MMU mapeia os segmentos e as compensações nos segmentos para o endereço de memória virtual e a unidade de paginação mapeia o endereço de memória virtual para o endereço de memória física?

A Unidade de Gerenciamento de Memória (MMU) transforma um endereço lógico em um endereço linear por meio de um circuito de hardware chamado unidade de segmentação; subseqüentemente, um segundo circuito de hardware chamado unidade de paginação transforma o endereço linear em um endereço físico (veja a Figura 2-1).

insira a descrição da imagem aqui

Então, por que diz que o Linux não usa segmentação, mas apenas paginação?

A segmentação foi incluída nos microprocessadores 80x86 para incentivar os programadores a dividir seus aplicativos em entidades logicamente relacionadas, como sub-rotinas ou áreas de dados globais e locais. No entanto, o Linux usa a segmentação de uma maneira muito limitada. De fato, a segmentação e a paginação são um pouco redundantes, porque ambas podem ser usadas para separar os espaços de endereços físicos dos processos: a segmentação pode atribuir um espaço de endereço linear diferente para cada processo, enquanto a paginação pode mapear o mesmo espaço de endereço linear em diferentes espaços de endereços físicos . O Linux prefere a paginação à segmentação pelos seguintes motivos:

• O gerenciamento de memória é mais simples quando todos os processos usam os mesmos valores de registro de segmento - ou seja, quando compartilham o mesmo conjunto de endereços lineares.

• Um dos objetivos de design do Linux é a portabilidade para uma ampla variedade de arquiteturas; As arquiteturas RISC, em particular, têm suporte limitado à segmentação.

A versão 2.6 do Linux usa a segmentação somente quando exigida pela arquitetura 80x86.

Tim
fonte
Você pode especificar as edições, por favor. Também pode ser útil especificar nomes de autores. Eu sei que pelo menos o primeiro é de uma figura proeminente. No entanto, os dois nomes de títulos são um pouco genéricos, não estava claro para mim a princípio que você estava falando de livros :-).
sourcejedi
2
Re "A segmentação foi incluída nos microprocessadores 80x86 ...": Isso não é verdade. É um legado dos processadores 808x, que tinham ponteiros de dados de 16 bits e segmentos de memória de 64 Kb. Os indicadores de segmento permitiram alternar segmentos para endereçar mais memória. Essa arquitetura foi transferida para o 80x86 (com o tamanho do ponteiro aumentado para 33 bits). Atualmente, no modelo x86_64, você tem ponteiros de 64 bits que podem (teoricamente - acho que apenas 48 bits são realmente usados) endereçar 16 exabytes, portanto, segmentos não são necessários.
jamesqf
2
@jamesqf, bem, o modo protegido de 32 bits no 386 suporta segmentos bastante diferentes dos ponteiros em escala de 16 bytes que estão no 8086, por isso não é apenas um legado. Isso não quer dizer nada sobre a utilidade deles, é claro.
ilkkachu
@jamesqf 80186 tinha o mesmo modelo de memória como 8086, há "33 bits"
Jasen
Não é digno de resposta, portanto, apenas um comentário: Segmentos e páginas são comparáveis ​​apenas no contexto de troca (por exemplo, troca de página versus troca de segmento) e nesse contexto, a troca de página apenas expulsa a troca de segmento da água. Se você trocar ou não segmentos, precisará trocar o segmento inteiro , que pode ser de 2 a 4 GB. Isso nunca foi algo real a ser usado no x86. Com páginas, você sempre pode trabalhar em unidades de 4KB. Quando se trata de acessar a memória, os segmentos e as páginas são relacionados através de tabelas de páginas e a comparação seria de maçãs para laranjas.
vhu 17/09/18

Respostas:

20

A arquitetura x86-64 não usa a segmentação no modo longo (modo de 64 bits).

Quatro dos registradores de segmento: CS, SS, DS e ES são forçados a 0 e o limite a 2 ^ 64.

https://en.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

Não é mais possível para o sistema operacional limitar quais intervalos dos "endereços lineares" estão disponíveis. Portanto, ele não pode usar a segmentação para proteção de memória; deve confiar inteiramente na paginação.

Não se preocupe com os detalhes das CPUs x86 que só se aplicariam quando executados nos modos de 32 bits herdados. O Linux para os modos de 32 bits não é tão usado. Pode até ser considerado "em um estado de negligência benigna por vários anos". Veja Suporte de 32 bits x86 no Fedora [LWN.net, 2017].

(Ocorre que o Linux de 32 bits também não usa segmentação. Mas você não precisa confiar em mim, basta ignorá-lo :-).

sourcejedi
fonte
Isso é um exagero. base / limite são fixados em 0 / -1 no modo longo para os segmentos 8086 originais herdados (CS / DS / ES / SS), mas FS e GS ainda têm base de segmento arbitrária. E o descritor de segmento carregado no CS determina se a CPU é executada no modo de 32 ou 64 bits. E o espaço do usuário no x86-64 Linux usa o FS para armazenamento local de threads ( mov eax, [fs:rdi + 16]). O kernel usa o GS (depois swapgs) para encontrar a pilha de kernel do processo atual no syscallponto de entrada. Mas sim, a segmentação não é usada como parte do principal mecanismo de gerenciamento / proteção de memória do SO.
Peter Cordes
Isso é basicamente o que a citação na pergunta significa "A versão 2.6 do Linux usa a segmentação somente quando exigida pela arquitetura 80x86". Mas o seu segundo parágrafo está basicamente errado. O Linux usa a segmentação basicamente idêntica nos modos de 32 e 64 bits. ou seja, base = 0 / limite = 2 ^ 32 ou 2 ^ 64 para os segmentos clássicos (CS / DS / ES / SS) que são usados ​​implicitamente por instruções normais. Não há nada "extra" para se preocupar no código Linux de 32 bits; a funcionalidade HW está lá, mas não é usada.
Peter Cordes
@ PeterCordes você está basicamente interpretando a resposta errada :-). Então, eu o editei para tentar tornar meu argumento menos ambíguo.
sourcejedi
Boa melhoria, agora não é enganosa. Eu concordo totalmente com o seu argumento real, que é que você pode e deve ignorar totalmente a segmentação x86, porque é usada apenas para coisas de gerenciamento de sistemas osdev e para TLS. Se você quiser aprender sobre isso, é muito mais fácil entender depois de entender o x86 asm com um modelo de memória simples.
Peter Cordes
8

Como o x86 possui segmentos, não é possível não usá-los. Mas os endereços base cs(segmento de código) e ds(segmento de dados) estão definidos como zero, portanto, a segmentação não é realmente usada. Uma exceção são dados locais de encadeamento, um dos segmentos normalmente não utilizados registra pontos para encadear dados locais. Mas isso é principalmente para evitar a reserva de um dos registros de uso geral para esta tarefa.

Não diz que o Linux não usa segmentação no x86, pois isso não seria possível. Você já destacou uma parte, o Linux usa a segmentação de uma maneira muito limitada . A segunda parte é que o Linux usa segmentação somente quando exigido pela arquitetura 80x86

Você já citou os motivos, a paginação é mais fácil e mais portátil.

RalfFriedl
fonte
7

Cada região do diagrama é um segmento?

Não.

Enquanto o sistema de segmentação (no modo protegido de 32 bits em um x86) foi projetado para oferecer suporte a segmentos separados de código, dados e pilha, na prática, todos os segmentos estão definidos na mesma área de memória. Ou seja, eles começam em 0 e terminam no final da memória (*) . Isso torna os endereços lógicos e lineares iguais.

Isso é chamado de modelo de memória "plano" e é um pouco mais simples que o modelo em que você tem segmentos distintos e, em seguida, ponteiros dentro deles. Em particular, um modelo segmentado requer ponteiros mais longos, pois o seletor de segmentos deve ser incluído além do ponteiro de deslocamento. (Seletor de segmento de 16 bits + deslocamento de 32 bits para um total de ponteiro de 48 bits; versus apenas um ponteiro plano de 32 bits.)

O modo longo de 64 bits realmente não suporta segmentação que não seja o modelo de memória plana.

Se você programasse no modo protegido de 16 bits no 286, teria mais necessidade de segmentos, pois o espaço de endereço é de 24 bits, mas os ponteiros são de apenas 16 bits.

(* Observe que não me lembro como o Linux de 32 bits lida com a separação do kernel / espaço do usuário. A segmentação permitiria isso através da definição dos limites dos segmentos do espaço do usuário para que eles não incluíssem o espaço do kernel. A paginação permite isso, pois fornece um nível de proteção por página.)

Então, por que diz que o Linux não usa segmentação, mas apenas paginação?

O x86 ainda possui os segmentos e você não pode desativá-los. Eles são usados ​​o menos possível. No modo protegido de 32 bits, os segmentos precisam ser configurados para o modelo plano e, mesmo no modo de 64 bits, eles ainda existem.

ilkkachu
fonte
Acho que um kernel de 32 bits talvez possa mitigar o Meltdown mais barato do que alterar as tabelas de páginas, definindo limites de segmento no CS / DS / ES / SS que impedem o acesso do espaço do usuário acima de 2G ou 3G. (O vuln Meltdown é uma solução alternativa para o bit do kernel / usuário nas entradas da tabela de páginas, permitindo que o espaço do usuário leia de páginas mapeadas somente para o kernel). As páginas do VDSO podem ser mapeadas na parte superior do 4G, no entanto: / wrfsbaseé ilegal no modo protegido / compatível, somente no modo longo, portanto, em um espaço de usuário do kernel de 32 bits, não é possível definir o FS como alto.
Peter Cordes
Em um kernel de 64 bits, o espaço do usuário de 32 bits pode potencialmente saltar para um segmento de código de 64 bits, portanto você não pode depender dos limites de segmento para proteção contra Meltdown, apenas talvez em um kernel de 32 bits puro. (O que tem grandes desvantagens em máquinas com muita RAM física, por exemplo, ficando sem memória baixa para pilhas de threads.) De qualquer forma, sim, o Linux protege a memória do kernel com paginação, deixando base / limit = 0 / -1 no espaço do usuário para o normal segmentos (não FS / GS que são usados ​​para armazenamento local de threads).
Pedro Cordes
Antes do suporte ao bit NX nas tabelas de páginas de hardware (PAE), alguns patches de segurança anteriores usavam a segmentação para criar pilhas não executáveis ​​para o código de espaço do usuário. por exemplo linux.com/news/exec-shield-new-linux-security-feature (post de Ingo Molnar menciona "excelente do Designer Solar "patch de pilha não-exec"".)
Peter Cordes
3

O Linux x86 / 32 não usa segmentação no sentido de inicializar todos os segmentos no mesmo endereço e limite linear. A arquitetura x86 exige que o programa tenha segmentos: o código pode ser executado apenas a partir do segmento de código, a pilha pode ser localizada apenas no segmento de pilha, os dados podem ser manipulados apenas em um dos segmentos de dados. O Linux ignora esse mecanismo definindo todos os segmentos da mesma maneira (com exceções que seu livro não menciona de qualquer maneira), para que o mesmo endereço lógico seja válido em qualquer segmento. Isso é de fato equivalente a não ter segmentos.

Dmitry Grigoryev
fonte
2

Cada região do diagrama é um segmento?

Estes são 2 usos quase totalmente diferentes da palavra "segmento"

  • segmentação x86 / registradores de segmentos: os sistemas operacionais x86 modernos usam um modelo de memória plana em que todos os segmentos têm a mesma base = 0 e limite = max no modo de 32 bits, o mesmo que o hardware impõe no modo de 64 bits , tornando a segmentação um pouco vestigial . (Exceto para FS ou GS, usado para armazenamento local de encadeamento, mesmo no modo de 64 bits.)
  • Seções / segmentos do vinculador / carregador de programas. ( Qual é a diferença de seção e segmento no formato de arquivo ELF )

Os usos têm uma origem comum: se você estivesse usando um modelo de memória segmentada (especialmente sem memória virtual paginada), poderá ter dados e endereços BSS relativos à base do segmento DS, pilha relativa à base SS e código relativo à Endereço base do CS.

Portanto, vários programas diferentes podem ser carregados em endereços lineares diferentes ou até movidos após a inicialização, sem alterar os desvios de 16 ou 32 bits em relação às bases do segmento.

Mas então você precisa saber a qual segmento um ponteiro é relativo, para ter "ponteiros distantes" e assim por diante. (Os programas x86 reais de 16 bits geralmente não precisavam acessar seu código como dados, portanto, poderia usar um segmento de código de 64k em algum lugar e talvez outro bloco de 64k com DS = SS, com a pilha crescendo de compensações altas e dados em ou um pequeno modelo de código com todas as bases de segmentos iguais).


Como a segmentação x86 interage com a paginação

O mapeamento de endereço no modo 32/64 bits é:

  1. segmento: deslocamento (base do segmento implícita no registro que mantém o deslocamento ou substituída por um prefixo de instrução)
  2. Endereço virtual linear de 32 ou 64 bits = base + deslocamento. (Em um modelo de memória plana como o Linux usa, ponteiros / deslocamentos = endereços lineares também. Exceto ao acessar o TLS em relação ao FS ou GS.)
  3. as tabelas de páginas (armazenadas em cache pelo TLB) são mapeadas linearmente para o endereço físico 32 (modo herdado), 36 (PAE herdado) ou 52 bits (x86-64). ( /programming/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the ).

    Esta etapa é opcional: a paginação deve ser ativada durante a inicialização, definindo um pouco em um registro de controle. Sem paginação, endereços lineares são endereços físicos.

Observe que a segmentação não permite que você use mais de 32 ou 64 bits de espaço de endereço virtual em um único processo (ou thread) , porque o espaço de endereço simples (linear) em que tudo é mapeado tem apenas o mesmo número de bits que as compensações. (Esse não era o caso do x86 de 16 bits, onde a segmentação era realmente útil para usar mais de 64k de memória, com registros e deslocamentos principalmente de 16 bits.)


A CPU armazena em cache os descritores de segmento carregados do GDT (ou LDT), incluindo a base do segmento. Quando você desreferencia um ponteiro, dependendo do registro em que ele está, o padrão é DS ou SS como o segmento. O valor do registro (ponteiro) é tratado como um deslocamento da base do segmento.

Como a base do segmento é normalmente zero, as CPUs fazem isso em casos especiais. Ou a partir de outra perspectiva, se você faz tem uma base de segmento não-zero, cargas têm latência extra porque o caso "especial" (normal) de contornar adicionando o endereço de base não se aplica.


Como o Linux configura os registros do segmento x86:

A base e o limite de CS / DS / ES / SS são todos 0 / -1 no modo de 32 e 64 bits. Isso é chamado de modelo de memória plana porque todos os ponteiros apontam para o mesmo espaço de endereço.

(Os arquitetos de CPU AMD neutralizaram a segmentação ao impor um modelo de memória plana para o modo de 64 bits porque os sistemas operacionais principais não o usavam de qualquer maneira, exceto pela proteção sem execução, que era fornecida de uma maneira muito melhor paginando com o PAE ou x86- Formato de tabela de 64 páginas.)

  • TLS (Thread Local Storage): FS e GS não são fixos na base = 0 no modo longo. (Eles eram novos no 386 e não eram usados ​​implicitamente por nenhuma instrução, nem mesmo reppelas instruções de cadeia de caracteres que usam ES). x86-64 Linux define o endereço base do FS para cada thread como o endereço do bloco TLS.

    por exemplo, mov eax, [fs: 16]carrega um valor de 32 bits de 16 bytes no bloco TLS para este encadeamento.

  • o descritor do segmento CS escolhe em que modo a CPU está (modo protegido de 16/32/64 bits / modo longo). O Linux usa uma única entrada GDT para todos os processos de espaço do usuário de 64 bits e outra entrada GDT para todos os processos de espaço do usuário de 32 bits. (Para que a CPU funcione corretamente, o DS / ES também deve ser definido como entradas válidas, assim como o SS). Ele também escolhe o nível de privilégio (kernel (anel 0) vs. usuário (anel 3)), portanto, mesmo ao retornar ao espaço do usuário de 64 bits, o kernel ainda precisa providenciar a alteração do CS, usando iretou em sysretvez de um normal instruções de pular ou reter.

  • No x86-64, o syscallponto de entrada usa swapgspara alternar o GS do GS do espaço do usuário para o kernel, que ele usa para encontrar a pilha de kernel desse encadeamento. (Um caso especializado de armazenamento local de encadeamento). A syscallinstrução não altera o ponteiro da pilha para apontar para a pilha do kernel; ainda está apontando para a pilha do usuário quando o kernel atinge o ponto de entrada 1 .

  • O DS / ES / SS também deve ser definido como descritores de segmento válidos para que a CPU funcione no modo protegido / modo longo, mesmo que a base / limite desses descritores seja ignorada no modo longo.

Então, basicamente, a segmentação x86 é usada para TLS e para os itens obrigatórios do osdev x86 que o hardware exige que você faça.


Nota de rodapé 1: Histórico de diversão: há arquivos de listas de discussão entre desenvolvedores do kernel e arquitetos da AMD alguns anos antes do lançamento do AMD64 silicon, resultando em ajustes no design, syscallpara que ele fosse utilizável. Veja os links nesta resposta para obter detalhes.

Peter Cordes
fonte