Joao Brietzke Blog

← voltar

Como a CPU conversa com o mundo: barramento e I/O por dentro

Uma viagem do barramento até o usuário

Quando a gente abre um computador ou um servidor, a primeira impressão é de uma confusão de peças ligadas por fios. Processador aqui, memória ali, discos, placa de rede, tudo conectado. A pergunta natural é: no meio disso tudo, quem está no comando?

Este artigo responde a essa pergunta de um jeito diferente. Em vez de despejar definições, vamos subir pelo sistema camada por camada, começando pelo componente mais "burro" de todos e perguntando, a cada passo, "mas então quem manda nisto?". A resposta nunca é o componente que estamos olhando, e sim o próximo, mais acima. E a viagem só termina quando chegamos em alguém que talvez você não esperasse.

Vamos começar pela estrada.


1. A estrada por onde tudo passa: o barramento

Barramento (ou bus) é o conjunto de fios e trilhas que transporta informação entre os componentes do computador. É o sistema de estradas que liga CPU, memória e dispositivos.

Sem ele, cada peça estaria isolada, incapaz de falar com as outras. O barramento é o caminho comum por onde tudo trafega.

Pense numa rua de entregas de uma cidade: é por ela que os caminhões circulam levando encomendas de um endereço a outro. Guarde essa imagem, porque ela vai nos acompanhar até o fim.

E já deixo no ar a pergunta que move este texto: a estrada existe, mas quem decide o que trafega por ela?


2. O que viaja na estrada: dados, endereços e controle

Antes de responder, precisamos saber o que circula. Um barramento costuma ser dividido em três partes, conforme o tipo de sinal que carrega:

  • Barramento de dados: carrega a informação em si, o conteúdo. Sua largura (8, 16, 32, 64 bits) diz quantos bits passam de uma vez.
  • Barramento de endereços: diz para onde o dado vai ou de onde vem. Quanto mais largo, mais posições o sistema consegue endereçar.
  • Barramento de controle: carrega os comandos, como ler, escrever e sincronizar. É quem coordena "agora é leitura", "agora é escrita".

Voltando à rua de entregas:

  • os dados são a encomenda dentro do caminhão;
  • o endereço é o número da casa de destino;
  • o controle é o semáforo e as placas, dizendo quando andar e em que direção.

Esse vocabulário (dado, endereço, controle) vai reaparecer o tempo todo. Fixe bem.

Os três barramentos: dados, endereços e controle conectando CPU, memória e controlador I/O

3. Primeira virada: a estrada é burra de propósito

Aqui mora a primeira confusão comum. É tentador achar que o barramento, por carregar o sinal de "controle", controla alguma coisa. Não controla.

O barramento não decide nada. Ele só transporta. São fios de metal. Não interpretam, não escolhem destino, não tomam iniciativa. Apenas conduzem os sinais elétricos que colocam neles.

Quem comanda são os dispositivos ligados ao barramento. Em barramentos clássicos, existe sempre um iniciador (geralmente a CPU), que começa a operação, e um alvo (por exemplo, a memória), que responde. Uma leitura acontece assim: a CPU coloca um endereço no barramento, sinaliza "isto é leitura" no controle, e a memória, reconhecendo seu próprio endereço, devolve o dado. O barramento foi só o meio.

E quando vários dispositivos querem usar a estrada ao mesmo tempo? Aí entra o árbitro de barramento, um circuito à parte que decide de quem é a vez, como um guarda num cruzamento. Repare: o guarda organiza o trânsito, mas não é a rua.

A lição desta seção: o "controle" é apenas um sinal que passa pela estrada, não a estrada decidindo nada. A estrada é burra de propósito.

Então, se quem manda são os dispositivos, que dispositivos são esses?


4. Os usuários da estrada: os dispositivos de I/O

I/O significa Input/Output, entrada e saída. São os dispositivos que fazem o computador se comunicar com o mundo fora da CPU e da memória. O núcleo (CPU mais memória) processa e guarda informação, mas sozinho é cego e mudo; os dispositivos de I/O são as portas para o exterior.

Eles se dividem pela direção da informação:

  • Entrada: trazem dados de fora para dentro, como teclado, mouse e sensores.
  • Saída: levam dados de dentro para fora, como monitor, impressora e alto-falante.
  • Ambos: fazem os dois sentidos, como disco/SSD e placa de rede.

Num servidor, os que mais pesam são os de armazenamento (SSDs NVMe, discos) e os de rede, porque é por aí que passa o trabalho de verdade.

Mas a CPU não fala diretamente com o teclado ou o disco. Entre eles existe um intermediário: o controlador do dispositivo. A CPU conversa com o controlador, e o controlador conversa com o dispositivo físico, porque dispositivos são lentos, variados e eletricamente "estranhos", e o controlador padroniza essa conversa.

E como a CPU acessa o controlador? Por meio de registradores dentro dele, geralmente três:

  • registrador de dados: onde o dado entra ou sai;
  • registrador de status: informa o estado ("ocupado", "terminei", "deu erro");
  • registrador de controle/comando: onde a CPU manda a ordem ("leia", "escreva").

Repare como isso espelha o barramento: dados, status, controle. A mesma lógica se repete em escala menor.


5. Duas formas de a CPU enxergar esses registradores

Para a CPU conseguir falar com esses registradores, eles precisam ser "alcançáveis" de alguma maneira. E aqui existem duas formas, uma distinção que importa bastante:

  • I/O mapeado em memória (memory-mapped I/O): os registradores do controlador recebem endereços de memória. Para a CPU, falar com o disco é igual a escrever na memória, com o mesmo barramento e as mesmas instruções. É o modelo dominante hoje.
  • I/O isolado (port-mapped I/O): existe um espaço de endereços separado só para I/O, com instruções próprias (como IN e OUT no x86). Mais antigo e limitado.

Apresentei as duas como fato. Mas por que diabos existem duas formas para fazer a mesma coisa? Essa pergunta tem uma resposta bonita, e ela é o coração técnico deste artigo.


6. O problema que deu origem a essas duas formas

Essas duas formas não foram capricho de design. São duas respostas para um mesmo problema concreto.

Lembre que a CPU se comunica por um único barramento de endereços. Junte isso a dois fatos:

  1. Tanto a memória quanto os controladores de I/O precisam de endereços. O registrador do disco precisa "ter um número", senão a CPU não consegue apontar para ele.
  2. O espaço de endereços é finito, limitado pela largura do barramento. Uma CPU com 16 bits de endereço endereça no máximo 64 KB de "coisas". Ponto.

Daí nasce o conflito: se memória e I/O dividem o mesmo barramento, como saber se um endereço se refere à RAM ou ao registrador de um dispositivo? E todo endereço gasto com I/O é um endereço que se deixa de ter para RAM. São dois sub-problemas grudados: ambiguidade (quem responde a este endereço?) e escassez (cada endereço de I/O é RAM perdida).

As duas formas são escolhas diferentes sobre qual preço pagar.

O I/O isolado resolve a escassez: cria um espaço separado só para I/O, ativado por um sinal de controle extra no barramento (no x86, o pino M/IO#). O mesmo número pode significar coisas diferentes conforme esse fio, como duas ruas com a mesma numeração e uma placa dizendo em qual bairro você está. Para acioná-lo, a CPU precisa de instruções especiais (IN e OUT). Nenhum byte de RAM é sacrificado, mas a CPU fica mais complexa e o I/O vira um "cidadão de segunda classe".

O I/O mapeado em memória resolve a ambiguidade: não cria espaço novo, apenas reserva um pedaço do espaço de memória que já existe. Um decodificador de endereços roteia esses endereços para o controlador em vez da RAM. Não precisa de fio extra nem instrução especial, e falar com o disco é literalmente ler/escrever na memória. A CPU fica mais simples e uniforme, mas paga-se com escassez: aquele pedaço some do total de RAM.

Historicamente, a divergência ficou famosa em duas filosofias. A Intel (8080, 8086) escolheu I/O isolado, porque nos anos 70 e 80 o espaço de endereços era minúsculo e precioso. A Motorola (6800, 68000) escolheu só memory-mapped, priorizando simplicidade e uniformidade.

E o desfecho: conforme os barramentos de endereço cresceram (32 bits dão 4 GB, 64 bits dão um espaço gigantesco), a escassez praticamente desapareceu. "Perder" alguns endereços para I/O virou irrelevante. Com isso, a vantagem do isolado evaporou, e a simplicidade do memory-mapped venceu, pois ele combina bem com memória virtual, MMU e cache. Hoje o modelo dominante é o memory-mapped; o isolado sobrevive como herança histórica.

I/O isolado versus memory-mapped I/O: comparação dos espaços de endereços

7. Como a CPU dá uma ordem, de fato

Já sabemos onde a CPU fala com o dispositivo (no registrador de comando) e como ela alcança esse registrador (por um endereço). Falta entender o que acontece quando ela dá a ordem.

A intuição comum é: a CPU "salva um bit na memória do device". Quase. A CPU de fato escreve um conjunto de bits no registrador de comando, mas isso não é guardar no sentido da RAM.

Aqui está o pulo do gato. Numa posição normal de RAM, se você escreve o número 5, nada acontece: o 5 fica lá, quietinho, até alguém ler de novo. No registrador de comando, o próprio ato de escrever dispara uma ação no hardware. A lógica do controlador está constantemente olhando para aquele registrador; quando certos bits mudam, o circuito interpreta aquilo como uma ordem e age.

Ou seja: não é "salvar um bit e depois alguém lê". É "escrever o bit é a ordem". O efeito colateral da escrita é a coisa toda.

Cada bit tem um significado fixado pelo fabricante, um contrato. Num controlador de disco simplificado, por exemplo, o bit 0 pode ser "iniciar", o bit 1 a direção (leitura ou escrita), e outros bits a unidade. A CPU monta o número com os bits arrumados e escreve de uma vez. Ela não "explica" nada; o significado já está cravado no circuito.

E como o disco é lento, a escrita no comando dispara um trabalho que leva tempo. Por isso existe o registrador de status separado: depois de mandar a ordem, a CPU fica lendo o status para saber "já terminou?", "deu erro?". O comando vai num registrador; a resposta vem de outro.

Registradores do controlador: Dados, Status e Comando, e como a CPU interage com cada um

Mas isso levanta uma pergunta incômoda: se a CPU está sempre mandando ordens e checando respostas, quem está mandando na CPU?


8. Segunda virada: a CPU não é passiva, ela comanda

É tentador, depois de tudo isso, achar que a CPU também é passiva, que ela só recebe ações de fora e repassa para os lugares certos. É exatamente o contrário.

O barramento é passivo. A CPU é a parte ativa do sistema: é ela quem comanda, inicia e decide. Os outros componentes em boa parte só respondem ao que ela pede.

Mas se ninguém manda na CPU, o que faz ela saber o que enviar? A resposta é: ela está executando um programa, uma sequência de instruções guardada na memória. Essa é a ideia mais importante da arquitetura de computadores, o conceito de programa armazenado, de von Neumann: a CPU não improvisa, segue uma lista de ordens.

O coração disso é um laço repetido bilhões de vezes por segundo, o ciclo de busca-decodificação-execução:

  1. Busca a próxima instrução da memória.
  2. Decodifica o que ela manda fazer.
  3. Executa, seja uma soma, uma comparação, ou "escreva tal valor em tal endereço".
  4. Avança e repete.

Quando um programa quer mandar a letra "A" para a tela, em algum ponto existe uma instrução como "escreva o valor 65 no endereço Y", onde Y é o registrador de dados do controlador de vídeo. A CPU não decidiu sozinha enviar o "A"; a instrução do programa mandou. A CPU sabe o quê e para onde porque estava escrito na instrução.

Há um pedaço de verdade na ideia de que a CPU "reage a coisas de fora": quando você aperta uma tecla, gera-se uma interrupção que faz a CPU pausar e tratar o evento. Mas mesmo essa reação é a CPU executando outro trecho de programa. O evento dispara a resposta; qual resposta já estava programada. A CPU nunca é um fio que repassa: está sempre executando instruções.

Então o programa é a fonte do "o que enviar". E isso nos leva à última pergunta, a mais interessante de todas.


9. Clímax: então quem dá as ordens?

É tentador concluir que quem dá as ordens é o usuário. Não é, pelo menos não no sentido que a palavra "ordem" parece ter. O segredo é separar dois significados que vivem se misturando.

Sentido 1, as instruções de máquina. Aquelas do ciclo busca-decodifica-executa. Essas não vêm do usuário: foram escritas por um programador, antes, e viraram código de máquina guardado na memória. O usuário comum nunca escreve uma dessas.

Sentido 2, as ações do usuário. Clicar, digitar, abrir um programa. Isso o usuário faz, mas não é uma instrução para a CPU. É um evento de entrada, um dado que chega de fora.

A confusão é achar que os dois são a mesma coisa. Pensando em camadas, de baixo para cima: o hardware só executa instruções; o programador escreveu o programa que virou essas instruções (é a fonte original das ordens); o sistema operacional, que é outro programa, organiza qual roda quando; e o usuário fica no topo, interagindo com programas prontos.

O papel do usuário, então, é duplo, e nenhum deles é "dar ordens à CPU": ele escolhe qual programa executar e fornece dados de entrada que influenciam o caminho do programa.

A analogia que fecha tudo: o programa é uma receita de bolo já escrita. O programador é o autor da receita. A CPU é o cozinheiro que segue cada passo sem questionar. E o usuário é quem escolhe qual receita fazer e traz os ingredientes (os dados). Ele não escreve a receita nem cozinha, mas sem ele talvez nada começasse.

Voltando ao "A" na tela: a lógica ("quando uma tecla for pressionada, mostre-a") veio do programador; o dado (qual tecla) veio de você. A CPU só costurou as duas coisas executando o programa.

A resposta final da nossa investigação, portanto, é em camadas. O barramento transporta. Os dispositivos respondem. A CPU comanda a execução. O programa dita o que fazer. O programador é o autor das ordens. E o usuário dá intenções e dados que moldam como tudo isso se comporta.

Camadas de comando: Usuário, Programa, Sistema Operacional, CPU e Hardware

10. Esperando o trabalho ficar pronto: polling, interrupção e DMA

Tem uma pergunta que ficou no ar a viagem inteira. Na seção 7 vimos a CPU mandar uma ordem para o disco escrevendo no registrador de comando. Mas o disco é lento, lentíssimo na escala da CPU, e o trabalho leva tempo. Enquanto ele não termina, a CPU poderia executar milhões de outras instruções. A questão é: como ela descobre quando ficou pronto?

Existem três estratégias, e o bonito é que elas formam uma evolução: cada uma resolve a falha que a anterior deixou.

Polling: a CPU fica perguntando

A ideia mais ingênua. A CPU manda a ordem e entra num laço apertado lendo o registrador de status sem parar: "terminou? terminou? terminou?", até o bit virar para "pronto".

É simples e funciona, mas tem um desperdício gritante: a CPU não faz mais nada enquanto espera. Fica girando em falso, queimando ciclos só para perguntar.

É como ficar parado na frente do micro-ondas, olhando o visor e repetindo "já?", sem fazer mais nada. O resíduo de problema é claro: CPU ociosa desperdiçada.

Interrupção: o dispositivo avisa

Aqui a lógica se inverte. Em vez de a CPU perguntar, o dispositivo avisa quando termina. A CPU manda a ordem e vai embora fazer outra coisa, executar outro programa.

Quando o disco acaba, ele dispara um sinal por uma linha do barramento de controle, a interrupção. Esse sinal faz a CPU pausar o que estava fazendo, rodar uma rotina curta que trata o evento, e depois voltar de onde parou. É a mesma interrupção da seção 8, a do teclado.

A analogia agora é programar o micro-ondas e ir cuidar da vida; o "bip" chama você de volta só quando está pronto.

Isso resolve o desperdício do polling, mas sobra um problema mais sério. Para uma leitura de, digamos, 4 KB, quem move esses dados do controlador para a memória, palavra por palavra, ainda é a própria CPU. Mesmo livre da espera, ela vira uma "carregadora de caixas", ocupada com trabalho burro de cópia. E interrupções em altíssima frequência têm um custo próprio de troca de contexto.

DMA: um ajudante move os dados sozinho

O DMA (Direct Memory Access, acesso direto à memória) ataca exatamente esse último resíduo: tirar da CPU o trabalho de mover os dados. Um circuito dedicado, o controlador de DMA, transfere os dados diretamente entre o dispositivo e a memória, sem a CPU no meio.

O fluxo fica assim: a CPU configura a transferência ("pegue tantos bytes, da origem X, e ponha no endereço Y") e vai embora fazer outra coisa. O controlador de DMA faz o bloco inteiro sozinho e, só ao final, dispara uma única interrupção para avisar.

A CPU é incomodada apenas duas vezes, no começo e no fim; todo o trabalho pesado aconteceu sem ela. É como contratar uma transportadora: você diz o que levar e para onde, cuida da sua vida, e recebe um toque só quando a mudança inteira já está no lugar.

Repare numa conexão com a seção 3: para mexer no barramento sozinho, o controlador de DMA precisa virar um iniciador, aquele papel que antes era só da CPU. Agora há mais de um candidato a usar a estrada, e é por isso que existe o árbitro de barramento. O DMA costuma usar o "roubo de ciclos", aproveitando os instantes em que a CPU não está usando o barramento.

Vale um detalhe que parece contraditório, especialmente num contexto de servidor: em sistemas de altíssimo desempenho, o polling às vezes vence de novo. Quando um dispositivo é absurdamente rápido (um SSD NVMe moderno, uma placa de rede de 100 Gbps), ele termina tão depressa e com tanta frequência que as interrupções chegariam numa cadência insuportável, e o custo de cada troca de contexto viraria o gargalo. Nesses casos, é mais eficiente a CPU ficar em polling ativo, porque o dado quase sempre já está pronto quando ela pergunta. Ou seja, a escolha entre os três não é "qual é o melhor", e sim "qual encaixa na velocidade e na frequência deste dispositivo", o mesmo tipo de troca que vimos na seção 6 entre memory-mapped e isolado.

Polling, interrupção e DMA: três estratégias para a CPU esperar o dispositivo terminar

Conclusão

Começamos olhando para dentro de um servidor e vendo uma confusão de peças e fios. Terminamos com algo bem diferente: uma cadeia de comando clara, em que cada parte sabe exatamente o seu papel. A estrada só transporta, os dispositivos respondem, a CPU executa um programa, o programador escreveu esse programa, e o usuário, no topo, fornece intenções e dados.

A ideia para levar é esta: um computador não tem um único "chefe" que faz tudo. Ele é uma divisão de trabalho bem ensaiada, em que a inteligência está espalhada e cada componente resolve um pedaço específico do problema, dentro de trocas e limites bem definidos. A próxima vez que você olhar para dentro de uma máquina, talvez veja não um amontoado de fios, mas essa coreografia silenciosa, em que a estrada, no fim das contas, é só uma estrada.


Referências