Fila de entrada de pré-busca - Prefetch input queue
Buscar os opcodes de instrução da memória do programa com bastante antecedência é conhecido como pré - busca e é servido usando a fila de entrada de pré-busca (PIQ). As instruções pré-buscadas são armazenadas na estrutura de dados - ou seja, uma fila . A busca de opcodes com bastante antecedência, antes de sua necessidade de execução, aumenta a eficiência geral do processador, aumentando sua velocidade. O processador não precisa mais esperar pelas operações de acesso à memória para que o opcode de instrução subsequente seja concluído. Essa arquitetura foi usada com destaque no microprocessador Intel 8086 .
Introdução
Pipelining foi trazido para a vanguarda do design de arquitetura de computação durante a década de 1960 devido à necessidade de uma computação mais rápida e eficiente. Pipelining é o conceito mais amplo e a maioria dos processadores modernos carregam suas instruções alguns ciclos de clock antes de executá-las. Isso é obtido através do pré-carregamento do código de máquina da memória em uma fila de entrada de pré - busca .
Esse comportamento se aplica apenas a computadores von Neumann (ou seja, não aos computadores da arquitetura Harvard ) que podem executar código de automodificação e ter algum tipo de pipelining de instruções . Quase todos os computadores modernos de alto desempenho atendem a esses três requisitos.
Normalmente, o comportamento de pré-busca do PIQ é invisível para o modelo de programação da CPU. No entanto, existem algumas circunstâncias em que o comportamento do PIQ é visível e precisa ser levado em consideração pelo programador.
Quando o processador x86 muda o modo de realmode para o modo protegido e vice-versa, o PIQ deve ser esvaziado, ou então a CPU continuará a traduzir o código de máquina como se ele tivesse sido escrito em seu último modo. Se o PIQ não for liberado, o processador pode traduzir seus códigos incorretamente e gerar uma exceção de instrução inválida .
Ao executar o código de auto-modificação , uma alteração no código do processador imediatamente antes do local atual de execução pode não alterar a forma como o processador interpreta o código, pois ele já está carregado em seu PIQ. Ele simplesmente executa sua cópia antiga já carregada no PIQ em vez da versão nova e alterada do código em sua RAM e / ou cache .
Este comportamento do PIQ pode ser usado para determinar se o código está sendo executado dentro de um emulador ou diretamente no hardware de uma CPU real. A maioria dos emuladores provavelmente nunca simulará esse comportamento. Se o tamanho do PIQ for zero (alterações no código sempre afetam o estado do processador imediatamente), pode-se deduzir que o código está sendo executado em um emulador ou o processador invalida o PIQ ao gravar em endereços carregados no PIQ .
Avaliação de desempenho com base na teoria das filas
Foi AK Erlang (1878-1929) quem primeiro concebeu uma fila como uma solução para o congestionamento no tráfego telefônico. Diferentes modelos de filas são propostos para simular aproximadamente os sistemas de filas em tempo real para que possam ser analisados matematicamente para diferentes especificações de desempenho.
Modelos de enfileiramento podem ser representados usando a notação de Kendall :
- A1 / A2 / A3 / A4
Onde:
- A1 é a distribuição de tempo entre duas chegadas
- A2 é a distribuição do tempo de serviço
- A3 é o número total de servidores
- A4 é a capacidade do sistema
- Modelo M / M / 1 (Servidor Único de Fila Única / Markoviano ): Neste modelo, os elementos da fila são atendidos por ordem de chegada. Dadas as taxas médias de chegada e serviço, as taxas reais variam em torno desses valores médios aleatoriamente e, portanto, devem ser determinadas usando uma função de distribuição de probabilidade cumulativa .
- Modelo M / M / r : Este modelo é uma generalização do modelo M / M / 1 básico em que vários servidores operam em paralelo. Esse tipo de modelo também pode modelar cenários com usuários impacientes que deixam a fila imediatamente se não estiverem recebendo serviço. Isso também pode ser modelado usando um processo de Bernoulli com apenas dois estados, sucesso e falha. O melhor exemplo desse modelo são nossos sistemas regulares de telefonia fixa.
- Modelo M / G / 1 ( modelo de entrada finita de Takacs): Este modelo é usado para analisar casos avançados. Aqui, a distribuição do tempo de serviço não é mais um processo de Markov . Este modelo considera o caso de mais de uma máquina com falha sendo reparada por um único reparador. O tempo de serviço para qualquer usuário vai aumentar neste caso.
Geralmente em aplicativos como fila de entrada de pré-busca, o modelo M / M / 1 é popularmente usado devido ao uso limitado de recursos de fila. Neste modelo de acordo com microprocessadores, o usuário assume o papel de unidade de execução e o servidor é a unidade de interface de barramento.
Fila de instrução
O processador executa um programa obtendo as instruções da memória e executando-as. Normalmente, a velocidade de execução do processador é muito mais rápida do que a velocidade de acesso à memória. A fila de instruções é usada para pré-buscar as próximas instruções em um buffer separado enquanto o processador está executando a instrução atual.
Com um pipeline de quatro estágios , a taxa na qual as instruções são executadas pode ser até quatro vezes maior que a da execução sequencial.
O processador geralmente possui duas unidades separadas para buscar as instruções e para executar as instruções.
A implementação de uma arquitetura de pipeline só é possível se a unidade de interface de barramento e a unidade de execução forem independentes. Enquanto a unidade de execução está decodificando ou executando uma instrução que não requer o uso de barramentos de dados e endereços , a unidade de interface de barramento busca opcodes de instrução da memória.
Este processo é muito mais rápido do que enviar um endereço, ler o opcode e então decodificá-lo e executá-lo. Buscar a próxima instrução enquanto a instrução atual está sendo decodificada ou executada é chamado de pipelining.
A arquitetura 8086 tem um pipeline de instrução de pré-busca de seis bytes, enquanto a 8088 tem uma pré-busca de quatro bytes. Enquanto a Unidade de Execução está executando a instrução atual, a unidade de interface de barramento lê até seis (ou quatro) bytes de opcodes antecipadamente da memória. Os comprimentos da fila foram escolhidos com base em estudos de simulação.
Uma exceção é encontrado quando a unidade de execução encontra um ramo ou seja, a instrução seja um salto ou uma instrução de chamada. Nesse caso, toda a fila deve ser despejada e o conteúdo apontado pelo ponteiro de instrução deve ser obtido da memória.
Inconvenientes
Os processadores que implementam o algoritmo de pré-busca da fila de instruções são bastante avançados do ponto de vista técnico. A complexidade do nível de design da CPU de tais processadores é muito maior do que para processadores regulares. Isso se deve principalmente à necessidade de implementar duas unidades separadas, a BIU e a UE , operando separadamente.
Conforme a complexidade desses chips aumenta, o custo também aumenta. Esses processadores são relativamente mais caros do que seus equivalentes sem a fila de entrada de pré-busca.
No entanto, essas desvantagens são amplamente compensadas pela melhoria no tempo de execução do processador. Após a introdução da fila de instrução de pré-busca no processador 8086, todos os processadores sucessivos incorporaram esse recurso.
código de exemplo x86
code_starts_here:
mov bx, ahead
mov word ptr cs:[bx], 9090h
ahead:
jmp near to_the_end
; Some other code
to_the_end:
Este programa de auto-modificação substituirá o jmp to_the_end com dois NOPs (que é codificado como 0x9090 ). O jmp de salto próximo a to_the_end é montado em dois bytes de código de máquina, então os dois NOPs irão apenas sobrescrever este salto e nada mais. (Ou seja, o salto é substituído por um código de não fazer nada.)
Como o código de máquina do salto já foi lido no PIQ e provavelmente também já foi executado pelo processador (processadores superescalares executam várias instruções ao mesmo tempo, mas "fingem" que não, devido à necessidade de compatibilidade com versões anteriores ), a mudança do código não acarretará em nenhuma alteração do fluxo de execução.
Programa de exemplo para detectar o tamanho
Este é um exemplo de NASM - sintaxe auto-modificadora x86 - algoritmo de linguagem assembly que determina o tamanho do PIQ:
code_starts_here:
xor bx, bx ; zero register bx
xor ax, ax ; zero register ax
mov dx, cs
mov [code_segment], dx ; "calculate" codeseg in the far jump below (edx here too)
around:
cmp ax, 1 ; check if ax has been altered
je found_size
; 0x90 = opcode "nop" (NO oPeration)
mov byte [nop_field+bx], 0x90
inc bx
db 0xEA ; 0xEA = opcode "far jump"
dw flush_queue ; should be followed by offset (rm = "dw", pm = "dd")
code_segment:
dw 0 ; and then the code segment (calculated above)
flush_queue:
; 0x40 = opcode "inc ax" (INCrease ax)
mov byte [nop_field+bx], 0x40
nop_field:
times 256 nop
jmp around
found_size:
;
; register bx now contains the size of the PIQ
; this code is for [[real mode]] and [[16-bit protected mode]], but it could easily be changed into
; running for [[32-bit protected mode]] as well. just change the "dw" for
; the offset to "dd". you need also change dx to edx at the top as
; well. (dw and dx = 16 bit addressing, dd and edx = 32 bit addressing)
;
O que esse código faz é basicamente alterar o fluxo de execução e determinar por força bruta o tamanho do PIQ. "A que distância devo alterar o código à minha frente para que ele me afete?" Se estiver muito próximo (já está no PIQ), a atualização não terá nenhum efeito. Se estiver longe o suficiente, a mudança do código afetará o programa e o programa então encontrou o tamanho do PIQ do processador. Se este código estiver sendo executado no sistema operacional multitarefa, a troca de contexto pode levar ao valor errado.