Como o processador encontra o código do kernel após uma interrupção?

13

Quando ocorre uma interrupção, o processador antecipa o processo atual e chama o código do kernel para lidar com a interrupção. Como o processador sabe onde entrar no kernel?

Entendo que existem manipuladores de interrupção que podem ser instalados para cada linha de interrupção. Mas como o processador executa apenas 'lógica conectada', é necessário que exista algum lugar predefinido que aponte para um manipulador de interrupções ou algum código que seja executado antes do manipulador (já que pode haver vários manipuladores para uma linha de interrupção, presumo que último).

Philipp Murry
fonte

Respostas:

13

Na inicialização, o kernel inicializará uma tabela de vetor de interrupção (chamada de tabela de descritor de interrupção ou IDT no x86) que aponta para um manipulador de interrupção para cada linha.

Antes do 80286, o IDT era sempre armazenado em um endereço fixo; começando com o 80286, o IDT é carregado usando oLIDT instrução

As tabelas de vetores de interrupção apontam para um único manipulador por linha de interrupção; Dito isto, um kernel pode optar por, por exemplo, fornecer um manipulador de interrupção que execute várias outras rotinas de interrupção ou fornecer um único manipulador que cubra algumas ou todas as interrupções. O Linux faz essas coisas fornecendo um manipulador de interrupção genérico que determina qual linha de interrupção foi chamada e encontra o manipulador de recebimento de dados apropriado a ser chamado.

Adam Maras
fonte
1
então o processador usa a linha de interrupção como índice para o IDT, coloca a entrada no PC e começa a executar? mas não existe uma função genérica executada antes de todos os manipuladores de interrupção? para linux seria do_IRQ (). é essa a função que toda entrada IDT aponta, independentemente da linha de interrupção?
Philipp Murry
@PhilippMurry yes. O kernel então usa seu próprio conjunto de manipuladores de interrupção (dos quais pode haver mais de um por linha) para lidar com a interrupção, antes de retornar ao código em execução anteriormente.
Adam Maras
ok, existem dois tipos de manipuladores de interrupção: aqueles que o processador chama (sempre do_IRQ ()) e aqueles que o kernel chama (aquele que eu registrei via request_irq ()). você poderia adicionar isso à sua resposta? eu acho, então eu vou aceitá-lo :) muito obrigado
Philipp Murry
1
@PhilippMurry Não sou especialista em como o Linux lida com interrupções (e como os desenvolvedores de kernel acessam esse sistema), mas adicionei algumas informações adicionais em um sentido mais amplo sobre como os kernels podem ter seu próprio gerenciamento de ISR.
Adam Maras
12

Sim, existe um local predefinido que contém o endereço do código para o qual saltar: um vetor de interrupção . Dependendo do processador, este pode ser um local específico na memória física (8088), um local específico na memória virtual, um registro do processador, um local indicado na memória por um registro (ARM, 386),…

Os detalhes variam em diferentes processadores, mas os principais elementos comuns para lidar com uma interrupção no processador são:

  • A máscara interrompe (para que qualquer interrupção subsequente precise esperar).
  • Defina o modo do processador como kernel ou modo de interrupção (se o processador tiver esses modos).
  • Salve o valor do contador de programa em um local conhecido (registro ou memória).
  • Possivelmente salve o valor de outros registros ou alterne entre bancos de registros).
  • Execute a próxima instrução (no novo valor do PC).
Gilles 'SO- parar de ser mau'
fonte
1

As outras duas respostas (no momento da redação deste documento) falam sobre interrupções e o IDT. Isso está correto, no entanto, em uma CPU moderna de estilo Intel, existem pelo menos três maneiras de acessar um kernel.

Método # 1: interrompe.

Isso é explicado acima. Você configura uma entrada na tabela de descritores de interrupção / vetor de interrupção e executa uma interrupção de software para entrar no kernel.

A principal vantagem desse método é que um kernel típico precisa ser capaz de lidar com interrupções de qualquer maneira e funciona em hardware arcaico.

Método 2: Ligue para portões.

Um portão de chamada é um tipo especial de seletor de segmentos. O destino da chamada precisa ser carregado na tabela de descritores de segmento global ou local (GDT e LDT, respectivamente). Se você executar uma instrução de chamada remota usando o portão de chamada como o segmento (o deslocamento da chamada é ignorado), isso permitirá que você chame um código mais privilegiado. Os portões de chamada são extremamente flexíveis; a arquitetura IA-32 possui quatro níveis de privilégio, e os portões de chamada permitem que você ligue para qualquer nível.

Não acredito que o Linux já tenha usado portões de chamada, mas o Windows 95 o fez. Os serviços do kernel do Win95 ( krnl386.exee kernel.dll) realmente foram executados no modo de usuário (anel 3). O nível de privilégio mais alto (anel 0) foi usado apenas para drivers e um microkernel que executava apenas a alternância de processos. A chamada para os motoristas foi feita usando portões de chamada. Isso permitiu que o código legado de 16 bits (dos quais havia muito!) Usasse drivers Win95 usando apenas uma ligação remota padrão, como sempre.

A proteção inadequada da tabela de descritores globais foi a causa de várias explorações do Windows 95, que conseguiram instalar seus próprios portões de chamada escrevendo sobre a memória.

Método # 3: SYSCALL / SYSRET e SYSENTER / SYSEXIT

Estes são dois conjuntos de instruções, inventados independentemente pela AMD e Intel, mas eles essencialmente fazem a mesma coisa. O SYSCALL / SYSRET ficou em primeiro lugar e era apenas AMD, SYSENTER / SYSEXIT era Intel, mas a AMD o implementa agora. Então, eu vou descrever SYSENTER / SYSEXIT.

Ao contrário dos portões de chamada, o SYSENTER pode ser usado apenas para transferir para o toque 0 e apenas para um local. No entanto, tem a vantagem de ter latência extremamente baixa porque, diferentemente de uma chamada ou interrupção, ela não toca na pilha.

O local da transferência é configurado usando três registradores específicos do modelo: um para as informações do segmento e um para o ponteiro de instruções e o ponteiro de pilha do código do kernel. Como nada é "empurrado" para a pilha, o código do modo de usuário é responsável por informar ao kernel para onde retornar, passando o ponteiro da instrução de retorno e o ponteiro da pilha nos registros. O kernel é responsável por restaurar o ponteiro da pilha e a instrução SYSEXIT restaura o ponteiro da instrução.

Mais informações sobre as instruções SYSENTER e SYSEXIT.

Pseudônimo
fonte