atualização da aula 1

This commit is contained in:
Blau Araujo 2025-05-17 15:13:38 -03:00
parent 38aaa19866
commit 13353156c1

View file

@ -7,9 +7,9 @@
* Objetivos
- Compreender os principais componentes de um computador sob o modelo de von Neumann.
- Reconhecer os registradores da arquitetura x86_64.
- Entender a relação entre hardware e código Assembly.
- Executar o primeiro programa Assembly com uma chamada de sistema.
- Primeiro contato com os registradores da arquitetura x86_64.
- Entender a relação entre o hardware e código Assembly.
- Criar um primeiro programa em Assembly com uma chamada de sistema.
* Modelo de von Neumann
@ -19,22 +19,45 @@ Um computador possui:
- Memória (armazenamento de instruções e dados)
- Dispositivos de entrada/saída
O modelo de Von Neumann é uma arquitetura de computadores em que a unidade
central de processamento (CPU) e a memória compartilham um único espaço de
armazenamento, tanto para dados quanto para instruções de programa. Isso
significa que o processador acessa a memória de forma sequencial para buscar
dados e instruções, utilizando um único barramento para ambas as operações.
#+begin_example
┌───────────────────────────────────────┐
│ UNIDADE DE PROCESSAMENTO (CPU) │
│ │
│ • Unidade Lógica e Aritmética (ALU) │
│ • Unidade de Controle (CU) │
└───────────────────┬───────────────────┘
┌───────────────────┴───────────────────┐
│ BARRAMENTO │
└───────┬──────────────────────┬────────┘
│ │
┌───────┴───────┐ ┌────────┴────────┐
│ MEMÓRIA │ │ DISPOSITIVOS DE │
│ │ │ ENTRADA E SAÍDA │
└───────────────┘ └────────┬────────┘
┌────────┴────────┐
│ ARMAZENAMENTO │
└─────────────────┘
No modelo de Von Neumann:
#+end_example
A arquitetura de Von Neumann é um modelo de hardware no qual a Unidade Central
de Processamento (CPU) e a memória utilizam um espaço único de armazenamento
para guardar tanto as instruções dos programas quanto os dados. Isso significa
que a CPU acessa a mesma memória para buscar instruções e para ler ou gravar
dados, utilizando um único barramento para ambas as operações.
Resumindo, no modelo de Von Neumann:
- Instruções e dados compartilham o mesmo espaço de memória.
- A CPU executa o ciclo: busca → decodifica → executa.
- A CPU executa o ciclo: busca → decodificação → execução.
Como era antes:
Em outros modelos da época (como a arquitetura de Harvard):
- Instruções e dados armazenados em memórias separadas.
- A CPU executa o ciclo: busca → decodifica → executa, mas pode realizar a busca
de dados e instruções em paralelo.
- A CPU executa o ciclo: busca → decodificação → execução, podendo buscar
dados e instruções em paralelo.
** Influência nas arquiteturas modernas
@ -80,9 +103,9 @@ computacionais atuais. Algumas das principais influências incluem:
** Gargalo de Von Neumann
O gargalo de Von Neumann é uma limitação de desempenho causada pelo fato de que
a CPU e a memória compartilham o mesmo barramento para acessar instruções e
dados.
O gargalo de Von Neumann é uma limitação de desempenho causada pelo fato de
que a CPU e a memória compartilham o mesmo barramento para acessar instruções
e dados.
Isso significa que:
@ -107,23 +130,15 @@ mantendo um espaço de memória unificado do ponto de vista do programador.
A arquitetura x86 é uma família de conjuntos de instruções (ISA /Instruction
Set Architecture/) baseada no modelo de Von Neumann e desenvolvida originalmente
pela Intel a partir do processador 8086.
pela Intel a partir do processador 8086. Todas as CPUs x86 implementam (ou
emulam) o conjunto de instruções da Intel 8086 (16 bits), lançada em 1978.
Assim como no modelo de Von Neumann:
Como uma evolução prática do modelo de Von Neumann, a arquitetura x86 implementa
otimizações como:
- Dados e instruções compartilham o mesmo espaço de memória.
- A CPU segue o ciclo: busca → decodifica → executa.
#+begin_quote
*Nota:* A transição da arquitetura x86 de 32 para 64 bits foi liderada pela AMD
com a criação da AMD64 em 2003. Essa extensão manteve compatibilidade com o
conjunto de instruções x86 original (IA-32), mas adicionou registradores de 64
bits e suporte a endereçamento ampliado. Posteriormente, a Intel adotou essa
mesma arquitetura sob o nome Intel 64 (anteriormente chamada EM64T), e o termo
x86-64 passou a ser usado de forma genérica para se referir à arquitetura compatível
com AMD64. Assim, AMD64 é a origem técnica da arquitetura x86 de 64 bits utilizada
na maioria dos sistemas modernos.
#+end_quote
- Uso extensivo de memória cache.
- Execução fora de ordem (/out-of-order execution/).
- /Pipelines/ e paralelismo interno.
** Características
@ -136,22 +151,35 @@ na maioria dos sistemas modernos.
- Utilizada em desktops, laptops e servidores, sendo a base da maioria dos PCs atuais.
#+begin_quote
*Nota:* Embora a arquitetura x86 inclua registradores segmentados (como CS, DS, ES, SS),
o Linux não faz uso da segmentação de memória no modo protegido. Em vez disso, ele
utiliza o chamado /endereçamento plano/, tratando toda a memória como um único espaço
contínuo. A segmentação é mantida apenas em um nível mínimo para atender exigências
da arquitetura (como troca de contexto e proteção básica), mas a segmentação lógica,
como era usada no MS-DOS, é totalmente evitada.
*Nota:* Embora a arquitetura x86 inclua registradores segmentados (como CS, DS,
ES, SS), o Linux não faz uso da segmentação de memória no modo protegido. Em
vez disso, ele utiliza o chamado /endereçamento plano/, tratando toda a memória
como um único espaço contínuo. A segmentação é mantida apenas em um nível
mínimo para atender exigências da arquitetura (como troca de contexto e
proteção básica), mas a segmentação lógica, como era usada no MS-DOS, é
totalmente evitada.
#+end_quote
** Otimizações em relação ao modelo de Von Neumann
** Gerações da família x86:
A arquitetura x86 é uma evolução prática do modelo de Von Neumann, com
otimizações como:
| Processador | Ano | Registradores | Endereçamento | Modos de operação | Destaques principais |
|----------------+------+---------------+--------------------------------+--------------------------+-----------------------------------------------------------------|
| 8086 | 1978 | 16 bits | 20 bits (1 MB) | Real mode | Primeiro da linha x86, sem proteção ou multitarefa |
| 80286 | 1982 | 16 bits | 24 bits (16 MB) | Real, Protected | Introduziu o modo protegido e privilégio de acesso por segmento |
| 80386 | 1985 | 32 bits | 32 bits (4 GB) | Real, Protected, Virtual | Registradores de 32 bits, suporte à paginação, multitarefa |
| 80486 | 1989 | 32 bits | 32 bits | Idem 80386 | Pipeline simples, cache L1, primeira versão com FPU integrada |
| Pentium | 1993 | 32 bits | 32 bits | Idem 80486 | Pipeline duplo, superscalar, introdução de MMX |
| Pentium Pro | 1995 | 32 bits | 36 bits (PAE) | Idem | Execução fora de ordem, cache L2 on-die, suporte a PAE |
| x86-64 (AMD64) | 2003 | 64 bits | 48 bits virtuais (até 57 hoje) | Real, Protected, Long | ISA de 64 bits, registradores expandidos, compatível com x86 |
- Uso extensivo de memória cache.
- Execução fora de ordem (/out-of-order execution/).
- /Pipelines/ e paralelismo interno.
A transição da arquitetura x86 de 32 para 64 bits foi liderada pela AMD com a
criação da AMD64 em 2003. Essa extensão manteve compatibilidade com o conjunto
de instruções x86 original (IA-32), mas adicionou registradores de 64 bits e
suporte a endereçamento ampliado. Posteriormente, a Intel adotou essa mesma
arquitetura sob o nome Intel 64 (anteriormente chamada EM64T), e o termo x86_64
passou a ser usado de forma genérica para se referir à arquitetura compatível
com AMD64. Assim, o AMD64 é a origem técnica da arquitetura x86 de 64 bits
utilizada na maioria dos sistemas modernos.
** Comparativo com outras arquiteturas
@ -268,8 +296,59 @@ instruções. Veja o comparativo:
A arquitetura x86_64 ainda inclui oito registradores de propósito geral, de =R8=
a =R15=.
** Relação com outras arquiteturas x86
Cada um dos registradores de 64 bits pode ser dividido em partes que, de certo
modo, correspondem às capacidades de 32 e 16 bits de outras arquiteturas da
família x86. Tomando o registrador =RAX= como exemplo:
#+begin_example
┌────────────────────────────────────────────────────┐
│ RAX │ 64 bits
└────────────────────────┬───────────────────────────┤
│ EAX │ 32 bits
└────────────┬──────────────┤
│ AX │ 16 bits
├───────┬──────┤
│ AH │ AL │
└───────┴──────┘
#+end_example
#+begin_quote
*Importante:* O diagrama não mostra registradores diferentes, mas os nomes
pelos quais as subdivisões do registrador podem ser acessadas!
#+end_quote
Aplicando a mesma ideia aos principais registradores, os nomes de suas
subdivisões seriam:
| 64 bits | 32 bits | 16 bits | 8 bits "altos" | 8 bits "baixos" |
|---------+---------+---------+----------------+-----------------|
| RAX | EAX | AX | AH | AL |
| RBX | EBX | BX | BH | BL |
| RCX | ECX | CX | CH | CL |
| RDX | EDX | DX | DH | DL |
| RSI | ESI | SI | (sem acesso) | SIL |
| RDI | EDI | DI | (sem acesso) | DIL |
| RSP | ESP | SP | (sem acesso) | SPL |
| RBP | EBP | BP | (sem acesso) | BPL |
| RIP | EIP | IP | (sem acesso) | (sem acesso) |
| R8 | R8D | R8W | (sem acesso) | R8B |
| ... | ... | ... | ... | ... |
| R15 | R15D | R15W | (sem acesso) | R15B |
#+begin_quote
*Nota:* Assim como os registradores de R8 a R15, nem todas as subdivisões
existiam nas arquiteturas de 32 e 16 bits da família x86, ou seja, elas só
foram introduzidas na arquitetura x86_64.
#+end_quote
* Primeiro exemplo em Assembly x86_64
Apenas para apresentar a aparência de um código em Assembly, aqui está o
código de um programa que não faz nada além de terminar com o estado de
término =42=...
Arquivo [[exemplos/01/exit42.asm][exit42.asm]]
#+begin_src asm :tangle exemplos/01/exit42.asm
@ -284,26 +363,124 @@ _start:
syscall
#+end_src
O programa é pequeno, mas nos dá a oportunidade de conhecer muitos dos elementos
de um programa escrito com a linguagem Assembly.
** Seção do código executável
#+begin_src asm
section .text
#+end_src
A diretiva =section= não é uma instrução da CPU, mas uma orientação sobre como
o binário do programa deverá ser montado. No caso do exemplo, estamos dizendo
ao montador que tudo que vier depois de =section=, até que se encontre outra
definição de seção, deve ser montado na seção =.text= do binário. O nome =.text=
é uma convenção que se refere à seção das instruções do programa em si (o seu
código, essencialmente).
** O ponto de entrada
#+begin_src asm
global _start
#+end_src
A diretiva =global= diz ao montador que um determinado /rótulo/ (como =_start=)
deve ser visível fora do arquivo em que é escrito. Em outras palavras, o
símbolo marcado com =global= pode ser referenciado por outros arquivos durante
o processo de link-edição. No exemplo, =_start= é o rótulo que representa, por
padrão, o endereço da primeira instrução a ser executada em um programa ou,
como se costuma dizer, o seu /ponto de entrada/.
** Chamada de sistema
Nem todo programa em Assembly fará chamadas de sistema, que são funções
internas do kernel para diversas finalidades que envolvam o acesso aos
recursos do hardware ou controlados pelo sistema. No exemplo, porém, nós
queremos utilizar a chamada de sistema =exit= para terminar a execução do
programa retornando o valor =42= para o sistema operacional.
Para isso, nós temos que seguir as convenções de chamada, definidas pelo
kernel para a arquitetura x86_64. Essas convenções estabelecem, por exemplo,
quais registradores devem ser utilizados para receber a identificação da
chamada de sistema e os argumentos que serão passados para ela.
No caso da chamada de sistema =exit=:
- O registrador =rax= deve receber o valor =60= (identificação da chamada =exit=).
- O registrador =rdi= deve receber o valor que será retornado como estado de
término (=42=).
Sendo assim, nós utilizamos as instruções =mov= para carregar (mover) os
valores esperados (argumentos) nos registradores apropriados:
#+begin_src asm
mov rax, 60 ; syscall: exit
mov rdi, 42 ; código de saída
#+end_src
Em seguida, nós utilizamos a instrução =syscall= para informar à CPU que
ela deveria invocar a chamada de sistema definida:
#+begin_src asm
syscall
#+end_src
** Montagem e execução (no terminal)
#+begin_example
:~$ nasm -f elf64 -o exit42.o exit42.asm
#+end_example
O resultado desse comando é a geração de um arquivo binário que nós chamamos
de /objeto/ (com terminação =.o=). O arquivo objeto contém a tradução de todo o
código em Assembly para código de máquina e já poderia, por exemplo, ser
compilado com outros objetos para compor um programa completo, mas ainda não
é capaz de ser executado.
Para isso, é necessário submeter o objeto a diversos procedimentos finais
chamados de /link-edição/ -- no caso, com o editor de ligações =ld=:
#+begin_example
:~$ ld -o exit42 exit42.o
#+end_example
Assim, o editor de ligações =ld=:
- Resolve endereços e símbolos (como =_start=);
- Organiza o layout final do programa;
- Adiciona seções obrigatórias (como os cabeçalhos ELF);
- Produz um binário executável.
Com o binário final gerado, nós podemos executá-lo e testar seu estado
de término com:
#+begin_example
:~$ ./exit42
:~$ echo $?
42
#+end_example
Onde =$?= é a expansão do parâmetro especial do shell =?=, que registra o estado
de término do último comando executado. Então, com o valor em =?= expandido,
nós podemos imprimi-lo com o comando interno =echo=.
* Exercícios sugeridos
1. Modifique o programa Assembly para retornar "sucesso".
1. Por que eu usei o termo "montagem"?
1. Desmonte (=objdump -d=) os binários gerados e compare os códigos de máquina.
1. Use =strace ./exit42= para verificar a chamada de sistema realizada.
1. Antecipe-se e pesquise o que são "chamadas de sistema" (/syscalls/).
2. Modifique o programa Assembly para retornar "sucesso", segundo as convenções
do shell do GNU/Linux.
3. Por que eu usei o termo "montagem" para me referir à produção do arquivo
binário a partir do código-fonte?
4. Escreva um programa em qualquer linguagem de alto nível que reproduza o
funcionamento do exemplo em Assembly.
* Referências
- [[https://a.co/d/8Dm1Z93][Organização e Projeto de Computadores -- David A. Patterson e John L. Hennesy]]
- [[https://a.co/d/9QtcLPI][Organização Estruturada de Computadores -- Andrew S. Tanenbaum]]
- [[https://namazso.github.io/x86/][Intel® 64 and IA-32 Instruction Set Reference]]
- [[https://wiki.osdev.org/CPU_Registers_x86-64][OS Dev: CPU Registers x86-64]]
- [[https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/][Linux System Call Table for x86 64]]
- [[https://www.gnu.org/software/bash/manual/bash.html#Exit-Status][Manual do Bash: Estado de Saída]]
- =man 2 exit=