Múltiplos Processadores
Autor: Hermano | Publicado: 2013-11-21T04:43:00-08:00


O sistema FESO tem suporte a processadores multicore. Um computador multicore
combina dois ou mais processadores em uma única peça de silício [1]. Este tipo de sistema permite que mais de um programa seja executado
simultaneamente em um computador, ou mesmo, que um programa seja dividido em
partes e executado por diversas CPUs em simultâneo.





Durante o processo de inicialização, o SO faz a detecção da existência
de múltiplos processadores através das tabelas ACPI encontradas na memória. O
padrão ACPI (Advanced Configuration and
Power Intefacer) envolve, entre outras funções, o fornecimento das Multiprocessor Specification Tables
(tabelas para especificação de múltiplos processadores) [2]. Através da
busca a análise dessas tabelas, o sistema operacional consegue descobrir
quantos processadores existem no sistema, e como eles podem ser acessados.



Uma vez que o SO tem acessos às informações contidas nas
tabelas, ele pode inicializar os processadores através do envio de IPIs ou
interrupções entre processadores. Sempre que um computador com múltiplos
processadores é iniciado, um dos processadores é selecionado para inicializar o
computador, este processador é chamado de BSP (boot strap processor), enquanto os outros processadores são
chamados de AP (application processors).
As IPIs são enviadas a partir do BSP para realizar a inicialização de todos os
APs presentes no sistema.



O sistema operacional carrega previamente em um espaço no inicio
da memória um pequeno programa responsável por configurar corretamente os
processadores. É o chamado código trampolim. O endereço do ponto de entrada
desse programa é enviado junto com as IPIs para cada um dos APs. Ao receber a
IPI o application processor executa a partir da posição
recebida junto com a interrupção, ficando corretamente configurado após a
execução do código trampolim. Em seguida, os APs entram em estado de espera por uma interrupção, para só então começarem
a executar algum programa.



Enquanto processadores com apenas um núcleo possuem um par de
PICs ligados a eles, sistemas com múltiplos núcleos precisam de outro tipo de chip para funcionar corretamente. Este
chip é o Advanced Programmable Interrupt
Controller (APIC). Sistema com múltiplos núcleos possuem uma estrutura como
mostrada na figura [3].



Conectado a cada processador existe um Local APIC ou LAPIC responsável por receber e enviar as
interrupções. Cada Local APIC está
conectado ao barramento ICC, bem como o I/O APIC. O I/O APIC é responsável por
receber os sinais das interrupções de hardware
e repassá-los aos outro LAPICs, permitindo que mais de um processador possa
tratar um pedido de interrupção de hardware.

Cada LAPIC possui um TIMER, que pode ser configurado para
disparar uma interrupção a cada intervalo de tempo predefinido. Esse TIMER é
configurando pelo sistema operacional, para que cada núcleo interrompa a
execução do processo em execução e permita que um novo processo tenha sua fatia
de tempo na CPU.



Como cada processador pode estar executando um programa
diferente, e como cada um deles tem seu próprio TIMER, gerando interrupções a
todo o momento, é altamente provável que dois ou mais processadores tentem
acessar as rotinas do kernel simultaneamente.
Caso dois ou mais processadores estejam executando o código do núcleo
inadvertidamente ao mesmo tempo, é possível que ambos acessem alguma variável
compartilhada e que acabem por deixar as estruturas do núcleo em um estado
inconsistente.









Para evitar esse problema, o núcleo do sistema FESO tem uma
grande trava. Essa trava é implementada através de uma variável que é lida por
todos os processadores antes que eles comecem a executar alguma rotina do
núcleo. Uma variável de trava que usa a espera ocupada é chamada de trava
giratória (spin lock) [4]. Caso o valor da variável esteja em 1, significa que há um outro
processador executando o código do núcleo, e que é necessário aguardar. Os
processadores ficam em um laço de repetição testando o valor da variável de
trava, até que este esteja em 0. Quando isso ocorre, o processador altera o
valor da variável para 1, e passa a executar o código do núcleo. Após terminar
de executar o código do núcleo, o processador coloca novamente o valor da trava
em 0. 



A operação de leitura e atualização da variável de trava deve
ser indivisível. Isto é, enquanto um processador estiver lendo e possivelmente
atualizando a variável de trava, os outros processadores não podem ter acesso a
ela. Caso fosse possível que dois processadores lessem o valor 0 na variável de
trava e tentassem atualizá-la ao mesmo tempo, ambos começariam a executar o
código do núcleo em simultâneo, o que não é desejável.




Para evitar esse cenário, o hardware
do processador oferece a possibilidade de se utilizar uma instrução que
realizar o teste e atualização da variável de trava de maneira atômica. Os
processadores da família INTEL de 32 bits suportam operações atômicas em locais
da memória do sistema [5]. Essas instruções são utilizadas para
garantir a atomicidade das operações que fazem o acesso a variável de trava. A
instrução recebe um prefixo lock, que
faz com que o barramento da memória, que é compartilhado pelos processadores,
seja travado, garantindo que apenas um processador acesse a variável em um dado
instante de tempo.



[1]&nbsp;STALLINGS, William. Arquitetura e Organização de Computadores. 8. ed. São Paulo: Pearson Prentice Hall, 2010

[2]&nbsp;Advanced configuration &amp; power interface.

[3]&nbsp;INTEL. Multiprocessor Specification. 1997. 97 p.

[4]&nbsp;TANEBAUM, Andrew S. Sistemas Operacionais Modernos. 3. ed. São Paulo: Pearson Education, 2010.

[5]&nbsp;INTEL. Intel 64 and IA-32 Architectures Software Developer’s Manual.2011. 4161 p.