Se a supervisora ficar esperando pelo baldinho, ela não poderá atender às demais requisições das crianças, causando grande consternação audível (mesmo sendo de uma só criança por vez). Por esta razão, ela volta a atender as requisições do parquinho e espera que o atendente avise quando encontrar o que foi solicitado.
Quando o atendente encontrar, ele precisa avisar à supervisora que encontrou o que foi pedido. Para este tipo de situação, a fadinha utiliza novamente opager, que é acionado pelo pelos atendentes dos quiosques. Ao perceber que opagerfoi acionado, a fadinha vai até a supervisora e a acorda. No pager tem o número do quiosque que enviou a mensagem. A supervisora vai até o quiosque e pega o dispositivo. Em seguida, procura em sua caderneta quem pediu aquele dispositivo, e o coloca na caixa de areia correspondente.
Como o mecanismo de pegar brinquedos pode ser diferente dependendo do quiosque (por exemplo, para pegar um baldinho usa-se um mecanismo diferente do que para pegar areia, ou água), o número do quiosque já é suficiente para que a supervisora saiba como pegar aquele brinquedo. O atendente nunca sai do quiosque, e por isso, um mensageiro dedicado àquela tarefa é usado para transportar o baldinho. Para cada quiosque há um mensageiro especializado.
Nesta versão estendida da analogia, temos que:
• as fadas corresponem à CPU. Existem tantas fadas para dar vida ao parquinho quantas núcleos em uma CPUs.
• os quiosques são os dispositivos periféricos (disco, teclado, mouse, etc.). Quase todos tem uma CPU dedicada a executar o que foi requisitado pela supervisora. Porém, eles não tem acesso ao parquinho.
• quando a requisição ao quiosque fica disponível, o atendente ativa o pager, que em termos técnicos é chamadosinterrupção. Uma CPU sabe que houve uma interrupção quando um pino específico (normalmente chamado deINTR) é ativado. • a supervisora é um processo. Ela executa código, e tem “subrotinas” dedicadas a tratar as requisições dos dispositivos.
Cada dispositivo pode ser tratado por um trecho de código diferente, e uma forma de acessar o trecho de código correto é através do número que aparece no pager. Após perceber que o pinoINTRfoi ativado, a CPU “pergunta” quem mandou a interrupção, e o dispositivo responde com o seu número.
É importante observar que a supervisora só pode fazer as atividades descritas em uma lista de serviços. Estes serviços incluem buscar baldinho, mas não inclui comprar cachorro quente, por exemplo. A lista de serviços aos quais um sistema operacional responde varia de um S.O. para outro.
3.4
Interrupções
A analogia da seção anterior é bastante fiel ao que realmente ocorre em um computador para que os processos “conversem” com os dispositivos. A conversa ocorre através das chamadas de sistema, e esta seção aumenta o nível de detalhe do que ocorre dentro de uma arquitetura. Cada arquitetura trata estes eventos de maneiras diferentes, e aqui apresentaremos como funciona o modelo de tratamento das arquiteturas da família AMD64.
A “conversa” entre um processo e os dispositivos externos envolve dois passos. O primeiro ocorre quando o processo solicita dados ao dispositivo, e o segundo ocorre quando o dispositivo coloca os dados na área virtual do processo.
Por uma questão didática, descreveremos inicialmente o segundo passo e depois o primeiro.
A seção 3.4.1 explica como trazer os dados de um dispositivo externo (um quiosque) para dentro da área virtual de ende- reçamento de um processo (a caixa de areia). Esta ação é executada utilizando um mecanismo chamado de “interrupção de hardware”.
Por fim, a seção 3.4.2 descreve as “interrupções de sofware” e uma delas em especial, aquela que executa as chamadas ao sistema.
3.4.1
Interrupção de Hardware
Considere que um dispositivo de hardware terminou uma tarefa. Por exemplo, que o disco acabou de ler o bloco de dados que lhe foi solicitado. Neste ponto, o dispositivo (no caso, disco) envia um sinal para a CPU. Na maioria dos computadores, este sinal ativa um pino específico chamado “INTR”. A este evento dá-se o nome de “interrupção”.
Quando este pino é ativado, a CPU termina a instrução que está executando, e suspende a execução para tratar a interrupção. Porém, como todos os dispositivos estão ligados no mesmo pino, a CPU só sabe que alguém chamou, mas ainda não sabe qual foi o dispositivo que ativou a interrupção.
Para descobrir, a CPU “pergunta” quem foi que disparou a interrupção, e o dispositivo que o fez envia o seu número para a CPU (cada dispositivo tem um número diferente).
Agora, a CPU já sabe quem ativou o pino, porém não há uma forma padronizada para “buscar” os dados que estão lá. Por exemplo, a forma de recuperar informações de um pendrive é diferente de recuperar informações do mouse. Cada dispositivo
tem uma forma diferente de ser acessado (alguns tem até uma linguagem própria). Para recuperar os dados, é necessário saber qual o dispositivo para saber como acessá-lo usando a forma correta (ou linguagem correta).
Por causa disto, o sistema operacional contém uma lista de funções projetadas para acessar os dispositivos de acordo com suas especificidades. Cada função trata uma (ou mais) interrupção de hardware, e é conhecido como “driver” do dispositivo.
Como existem vários drivers, a CPU precisa ativar o driver correto para cada interrupção. Para tal, ela usa o número do dispositivo como índice em um vetor. Este vetor contém os endereços das funções que de cada driver. Este mecanismo parece complicado à primeira vista, e para deixá-lo mais claro, vamos dividir sua descrição em duas fases:
1. Colocar os endereços dos drivers no vetor. Este processo é chamado de “registro de driver”. 2. Usar o vetor para disparar o driver correto;
3.4.1.1
Registrando o driver
O algoritmo 36 mostra como armazenar os endereços de cada driver no vetor. Isto é feito durante o carregamento do sistema operacional (boot) para a memória.
Neste exemplo, a funçãodriver0corresponde ao trecho de código do sistema operacional que faz o tratamento de inter- rupção do dispositivo de número zero.
Na prática, nem todos os drivers precisam ser carregados, uma vez que nem todos os dispositivos estão presentes em algumas máquinas. 1
. . .
2driver0 () { . . . }
3driver1 () { . . . }
4. . .
5driverN () { . . . }
6. . .
7alocaDriverEmVetor () {
8vetorInterr[0] = &driver0;
9vetorInterr[1] = &driver1;
10. . .
11vetorInterr[N] = &driverN;
12}
Algoritmo 36: Carregamento dos endereços dos drivers no vetor.
3.4.1.2
Disparando o driver correto
O algoritmo 37 mostra esquematicamente uma rotina de encaminhamento de interrupção de hardware. O seu parâmetro é o número do dispositivo.
A variáveldriverCertoé uma variável que contém o endereço de uma função. Basta copiar o endereço contido no vetor de Interrupção para esta variável e dispará-la para que o driver apropriado seja executado.
1
. . .
2
void TrataInterrupHardw (int numeroDispositivo) {
3void* (*driverCerto)();
4. . .
5driverCerto = driverInterr[numeroDispositivo];
6driverCerto ();
7. . .
8}
Algoritmo 37: Direcionamento para o driver correto.
3.4. Interrupções 67
• o vetor de interrupções não é uma variável do sistema operacional, e sim um conjunto de endereços fixos na memória. Nos processadores da família x86, por exemplo, o início deste vetor é configurável [28] porém como referência, considere que começa no endereço0x0(zero absoluto da memória).
• quem dispara o driver não é o sistema operacional, mas sim a própria CPU. No contexto de uma interrupção, ao receber o número do dispositivo, a CPU usa este número como índice do vetor de interrupção, e coloca o endereço lá contido no
%rip.
Em linux, o elenco de interrupções é armazenado no arquivo “/proc/interrupts”, e pode ser visualizado da seguinte forma:
> cat /proc/interrupts
A explicação do resultado obtido foge ao escopo deste livro, mas pode ser facilmente encontrada através de buscadores da internet.
Uma questão final: nem todas as entradas do vetor de interrupção são preenchidas. Considere que temos 10 dispositivos de hardware. Eles não precisam ser mapeados entre os índices [0..9], podendo ser mapeados, por exemplo, entre os índices [0..19]. Isto implica dizer que podem existir “buracos” no vetor de interrupção.
3.4.1.3
Exemplo
As interrupções de hardware, como o próprio nome já diz, são para atender às requisições dos dispositivos de hardware. Alguns deles são de utilidade óbvia como disco, mouse, teclado, vídeo, entre outros.
Porém existem alguns cuja utilidade não é aparente. Um destes dispositivos é o timer. Este dispositivo gera um impulso em intervalos regulares (como o relógio da seção 3.2).
Este dispositivo é um oscilador de cristal que gera interrupções em intervalos regulares, e é tratado como interrupção de hardware6
A interrupção que este dispositivo gera não é usada para transferir dados, mas sim para “acordar” o sistema operacional, que então pode realizar tarefas administrativas, como por exemplo, trocar o processo em execução por outro processo.
É por isso que quando a “fadinha” da seção 3.2 ouve o barulho do relógio, ela ilumina o supervisor para que ele indique uma nova criança para a fada iluminar.
Normalmente, quando o timer dispara o sistema operacional, a primeira tarefa do S.O. é desabilitar as interrupções, para que ele não possa ser incomodado durante sua execução. Esta é uma das razões para que o S.O. não entre em loop infinito.
3.4.2
Interrupção de Sofware
A seção anterior mostrou que o vetor de interrupções é alimentado com os números dos dispositivos. Porém, também é possível colocar endereços de drivers que não estão associados com dispositivos, mas sim com eventos de software.
Os drivers contidos nestes índices evidentemente não serão ativados por eventos de hardware, mas sim através do que se chama de “interrupções de software” ou por “armadilhas”7. O primeiro termo normalmente é usado para indicar eventos previs- tos pelo programa, como as chamadas ao sistema. O segundo termo é usado para eventos causados por condições excepcionais como divisão por zero, página inválida, etc.
Nos AMD64, as interrupções de software são ativadas pelo comando assemblysyscallque desvia o fluxo para uma entrada específica do vetor de interrupção.
Assim como nas interrupções de hardware, quem alimenta o vetor interrupção com os endereços das rotinas de tratamento das interrupções e software é o sistema operacional durante o boot.
No linux, estas rotinas são parte integrante do sistema operacional, ou seja,syscallé uma forma de disparar a execução de um trecho de código do sistema operacional.
Como um processo regular não pode acessar a área do sistema operacional (e muito menos o vetor de interrupção), a instru- çãosyscallprimeiramente muda o modo de execução de “modo usuário” para “modo supervisor” e em seguida, desvia o fluxo para o endereço indicado no vetor de interrupção.
Os processadores mais modernos usam uma variação deste mecanismo. A maior parte dos sistemas operacionais usam um único endereço no vetor de interrupção (no linux dox86era o endereço0x80), e por isso é comum alocar um registrador só para contê-lo. Este registrador sofre uma atribuição nobootdo sistema operacional e depois disto, só pode ser lido.
Um programa no “modo supervisor” tem acesso a todos os recursos do hardware, e pode acessar qualquer posição de me- mória ou periféricos.
6 Procure a posição deste evento no vetor de interrupções no arquivo/proc/interrupts. 7 trap
Quando a requisição for atendida (ou enviada para o dispositivo apropriado), o sistema operacional executa a instruçãoIRET
para retornar para o programa que solicitou a chamada ao sistema, em “modo usuário”.