Os anéis da CPU são a distinção mais clara
No modo protegido x86, a CPU está sempre em um dos quatro toques. O kernel do Linux usa apenas 0 e 3:
- 0 para o kernel
- 3 para usuários
Esta é a definição mais rígida e rápida de kernel vs userland.
Por que o Linux não usa os anéis 1 e 2: https://stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used
Como é determinado o anel atual?
O anel atual é selecionado por uma combinação de:
tabela descritor global: uma tabela na memória de entradas GDT e cada entrada possui um campo Privl
que codifica o anel.
A instrução LGDT define o endereço para a tabela atual do descritor.
Veja também: http://wiki.osdev.org/Global_Descriptor_Table
o segmento registra CS, DS, etc., que apontam para o índice de uma entrada no GDT.
Por exemplo, CS = 0
significa que a primeira entrada do GDT está ativa no momento para o código em execução.
O que cada anel pode fazer?
O chip da CPU é fisicamente construído para que:
anel 0 pode fazer qualquer coisa
o anel 3 não pode executar várias instruções e gravar em vários registros, principalmente:
não pode mudar seu próprio anel! Caso contrário, poderia definir-se para tocar 0 e os anéis seriam inúteis.
Em outras palavras, não é possível modificar o descritor de segmento atual , que determina o anel atual.
não pode modificar as tabelas da página: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
Em outras palavras, não é possível modificar o registro CR3 e a paginação em si impede a modificação das tabelas de páginas.
Isso impede que um processo veja a memória de outros processos por motivos de segurança / facilidade de programação.
não pode registrar manipuladores de interrupção. Esses são configurados gravando nos locais da memória, o que também é impedido pela paginação.
Os manipuladores executam no anel 0 e quebram o modelo de segurança.
Em outras palavras, não é possível usar as instruções LGDT e LIDT.
não pode executar instruções de E / S como in
e out
, portanto, possui acessos de hardware arbitrários.
Caso contrário, por exemplo, as permissões de arquivo seriam inúteis se algum programa pudesse ler diretamente do disco.
Mais precisamente graças a Michael Petch : é realmente possível que o sistema operacional permita instruções de E / S no anel 3; isso é realmente controlado pelo segmento de estado da tarefa .
O que não é possível é que o anel 3 se dê permissão para fazê-lo se não o tiver em primeiro lugar.
O Linux sempre o desaprova. Consulte também: https://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss
Como os programas e sistemas operacionais fazem a transição entre os anéis?
quando a CPU está ligada, ele começa a executar o programa inicial no anel 0 (bem, mas é uma boa aproximação). Você pode pensar que esse programa inicial é o kernel (mas normalmente é um gerenciador de inicialização que chama o kernel ainda no anel 0).
quando um processo da terra do usuário deseja que o kernel faça algo como gravar em um arquivo, ele usa uma instrução que gera uma interrupção como int 0x80
ousyscall
para sinalizar o kernel. x86-64 Linux syscall hello world example:
.data
hello_world:
.ascii "hello world\n"
hello_world_len = . - hello_world
.text
.global _start
_start:
/* write */
mov $1, %rax
mov $1, %rdi
mov $hello_world, %rsi
mov $hello_world_len, %rdx
syscall
/* exit */
mov $60, %rax
mov $0, %rdi
syscall
compile e execute:
as -o hello_world.o hello_world.S
ld -o hello_world.out hello_world.o
./hello_world.out
GitHub upstream .
Quando isso acontece, a CPU chama um manipulador de retorno de chamada de interrupção que o kernel registrou no momento da inicialização. Aqui está um exemplo baremetal concreto que registra um manipulador e o utiliza .
Esse manipulador é executado no anel 0, que decide se o kernel permitirá essa ação, faça a ação e reinicie o programa userland no anel 3. x86_64
quando a exec
chamada do sistema é usada (ou quando o kernel inicia/init
), o kernel prepara os registros e a memória do novo processo da terra do usuário, depois pula para o ponto de entrada e muda a CPU para tocar 3
Se o programa tentar fazer algo malicioso, como gravar em um registro ou endereço de memória proibido (por causa da paginação), a CPU também chamará algum manipulador de retorno de chamada do kernel no anel 0.
Mas como a região do usuário foi malcriada, o kernel pode interromper o processo dessa vez ou avisar com um sinal.
Quando o kernel é inicializado, ele configura um relógio de hardware com alguma frequência fixa, o que gera interrupções periodicamente.
Esse relógio de hardware gera interrupções que executam o anel 0 e permite agendar quais processos da terra do usuário serão ativados.
Dessa forma, o agendamento pode ocorrer mesmo se os processos não estiverem fazendo nenhuma chamada do sistema.
Qual o sentido de ter vários anéis?
Existem duas vantagens principais na separação do kernel e da terra do usuário:
- é mais fácil criar programas, pois você tem mais certeza de que um não interferirá no outro. Por exemplo, um processo da terra do usuário não precisa se preocupar em substituir a memória de outro programa por causa da paginação, nem em colocar o hardware em um estado inválido para outro processo.
- é mais seguro. Por exemplo, permissões de arquivo e separação de memória podem impedir que um aplicativo de hackers leia seus dados bancários. Isso supõe, é claro, que você confie no kernel.
Como brincar com isso?
Eu criei uma configuração bare metal que deve ser uma boa maneira de manipular anéis diretamente: https://github.com/cirosantilli/x86-bare-metal-examples
Infelizmente, não tive a paciência de fazer um exemplo da terra do usuário, mas fui até a configuração de paginação, portanto a terra do usuário deve ser viável. Eu adoraria ver uma solicitação de recebimento.
Como alternativa, os módulos do kernel Linux são executados no anel 0, para que você possa usá-los para experimentar operações privilegiadas, por exemplo, leia os registros de controle: https://stackoverflow.com/questions/7415515/how-to-access-the-control-registers -cr0-cr2-cr3-de-um-programa-obtendo-segmento / 7419306 # 7419306
Aqui está uma configuração conveniente do QEMU + Buildroot para testá-lo sem matar o seu host.
A desvantagem dos módulos do kernel é que outros kthreads estão em execução e podem interferir nos seus experimentos. Mas, em teoria, você pode assumir todos os manipuladores de interrupção com o seu módulo do kernel e possuir o sistema, que seria realmente um projeto interessante.
Anéis negativos
Embora os anéis negativos não sejam realmente mencionados no manual da Intel, na verdade existem modos de CPU que possuem recursos adicionais que o próprio anel 0 e, portanto, são adequados para o nome "anel negativo".
Um exemplo é o modo hypervisor usado na virtualização.
Para obter mais detalhes, consulte: https://security.stackexchange.com/questions/129098/what-is-protection-ring-1
BRAÇO
No ARM, os anéis são chamados de Níveis de exceção, mas as idéias principais permanecem as mesmas.
Existem 4 níveis de exceção no ARMv8, comumente usados como:
EL0: userland
EL1: kernel ("supervisor" na terminologia do ARM).
Introduzida com a svc
instrução (SuperVisor Call), anteriormente conhecida como swi
montagem unificada , que é a instrução usada para fazer chamadas do sistema Linux. Olá, mundo, exemplo do ARMv8:
.text
.global _start
_start:
/* write */
mov x0, 1
ldr x1, =msg
ldr x2, =len
mov x8, 64
svc 0
/* exit */
mov x0, 0
mov x8, 93
svc 0
msg:
.ascii "hello syscall v8\n"
len = . - msg
GitHub upstream .
Teste com o QEMU no Ubuntu 16.04:
sudo apt-get install qemu-user gcc-arm-linux-gnueabihf
arm-linux-gnueabihf-as -o hello.o hello.S
arm-linux-gnueabihf-ld -o hello hello.o
qemu-arm hello
Aqui está um exemplo baremetal concreto que registra um manipulador SVC e faz uma chamada SVC .
EL2: hipervisores , por exemplo, Xen .
Introduzido com a hvc
instrução (Chamada HyperVisor).
Um hypervisor é para um sistema operacional, o que é um sistema operacional para a terra do usuário.
Por exemplo, o Xen permite executar vários SOs como Linux ou Windows no mesmo sistema ao mesmo tempo, e isola os SOs uns dos outros para segurança e facilidade de depuração, assim como o Linux faz para programas da área de usuário.
Os hipervisores são uma parte essencial da infraestrutura de nuvem atual: eles permitem a execução de vários servidores em um único hardware, mantendo o uso de hardware sempre próximo de 100% e economizando muito dinheiro.
A AWS, por exemplo, usou o Xen até 2017, quando foi transferida para a KVM .
EL3: mais um nível. Exemplo TODO.
Introduzido com a smc
instrução (Secure Mode Call)
O Modelo de Referência de Arquitetura do ARMv8 DDI 0487C.a - Capítulo D1 - O Modelo do Programador de Nível de Sistema AArch64 - A Figura D1-1 ilustra isso de maneira bonita:
Observe como o ARM, talvez devido ao benefício da retrospectiva, tenha uma convenção de nomenclatura melhor para os níveis de privilégio do que x86, sem a necessidade de níveis negativos: 0 sendo o mais baixo e 3 o mais alto. Níveis mais altos tendem a ser criados com mais frequência do que os inferiores.
O EL atual pode ser consultado com a MRS
instrução: https://stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etc
O ARM não exige que todos os níveis de exceção estejam presentes para permitir implementações que não precisam do recurso para economizar área de chip. O ARMv8 "Níveis de exceção" diz:
Uma implementação pode não incluir todos os níveis de exceção. Todas as implementações devem incluir EL0 e EL1. EL2 e EL3 são opcionais.
Por exemplo, o QEMU usa EL1, mas EL2 e EL3 podem ser ativados com opções de linha de comando: https://stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulating-a53-power-up
Trechos de código testados no Ubuntu 18.10.