A saudação Hello World
(thecoder08.github.io)-
Explorando o mundo de abstrações escondido por trás de um programa Hello World moderno
- Este texto trata de um programa Hello World escrito em C. Entre as linguagens de alto nível em que não é preciso se preocupar com o que a linguagem faz antes de o programa realmente rodar em um interpretador/compilador/JIT, C é a de nível mais baixo.
- A intenção original era escrever algo que qualquer pessoa com experiência em programação pudesse entender, mas ter pelo menos algum conhecimento de C ou assembly provavelmente ajuda.
-
Começando com o programa Hello World
- Todo mundo provavelmente já está familiarizado com o programa Hello World. Em Python, talvez o primeiro programa que você escreveu tenha sido algo como
print('Hello World!'). - Neste texto, vamos olhar para um Hello World escrito na linguagem de programação C. Em C, não dá para executar um programa chamando um interpretador. Primeiro é preciso rodar um compilador para convertê-lo em código de máquina que o processador do computador possa executar diretamente.
- Todo mundo provavelmente já está familiarizado com o programa Hello World. Em Python, talvez o primeiro programa que você escreveu tenha sido algo como
-
Analisando nosso programa
- Ao analisar o arquivo do programa compilado, dá para ver que ele é um executável ELF e destinado ao conjunto de instruções x86-64.
- Um executável ELF é o equivalente, no Linux, a um arquivo .exe do Windows.
- x86-64 é a arquitetura de CPU usada em PCs desde a introdução do IBM PC em 1981.
- Esse arquivo contém código de máquina, a única linguagem que a CPU consegue entender.
-
Analisando o código assembly
- Encontramos o entry point, o endereço inicial do programa, e analisamos o código assembly.
- Assembly é uma representação legível por humanos do código de máquina.
- Dá para ver o código de inicialização adicionado automaticamente pelo compilador, mais precisamente pelo linker, e notar que ele chama a função
__libc_start_main. - Mas esse código não está definido no nosso programa; ele está em outro lugar.
-
Biblioteca padrão de C
- A função
__libc_start_mainestá definida emlibc.so.6, a biblioteca padrão de C do nosso sistema. - A biblioteca padrão de C é um conjunto de rotinas e funções usado por quase todos os programas do computador.
- A biblioteca C faz o trabalho de inicialização e chama a função
main()que escrevemos. Quandomain()retorna, ela encerra o programa com o código de saída fornecido.
- A função
-
Análise da função main()
- Na função
main(), é criado o stack frame, o endereço da string Hello World é configurado como argumento da chamada de função e, em seguida,puts()é chamada. puts()aparece no lugar deprintf()porque o compilador fez essa otimização.printf()é mais complexa, enquantoputs()apenas imprime uma string sem formatação.
- Na função
-
A string Hello World
- A string tem a forma
"Hello World!"seguida por um terminador NULL. - Em C, não há informação de tamanho associada à string, então o terminador NULL marca o fim dela. Sem ele, o programa continuaria lendo memória não permitida e acabaria morrendo com um Segmentation Fault.
- Por causa da otimização do compilador, a quebra de linha (
\n) usada emprintf()foi removida.puts()já adiciona uma quebra de linha depois de imprimir a string.
- A string tem a forma
-
A função puts()
- A função
puts()por sua vez acaba chamando código dentro da biblioteca padrão. - Observando o código da glibc, é possível ver as chamadas
_IO_puts -> _IO_new_file_xsputn, mas o código é complexo demais para explicar com facilidade. - No caso da musl libc, é um pouco mais simples. A sequência é
puts -> fputs -> fwrite -> __fwritex -> __stdio_write -> syscall.
- A função
-
Chamada de sistema
- Por maior que a biblioteca C seja, ela não consegue se comunicar diretamente com o hardware. Só o kernel pode fazer isso.
- Portanto, a chamada de
puts()termina pedindo ao sistema operacional que faça algo. Aqui, trata-se de escrever a string em um fluxo de saída. - A musl libc usa a chamada de sistema
writev, que permite escrever vários buffers de uma vez. - A chamada de sistema acontece configurando os parâmetros em registradores e executando a instrução
syscall. Então o controle passa para o kernel, que lê os parâmetros e executa a chamada de sistema.
-
Kernel
- O kernel Linux precisa executar a ação solicitada pela chamada de sistema. A chamada
writeinstrui o kernel a escrever em um arquivo ou fluxo aberto no sistema de arquivos. writerecebe três parâmetros: o descritor de arquivo para escrita, o buffer a ser escrito e a quantidade de bytes a gravar.- Onde exatamente será escrito depende da situação. No caso de um emulador de terminal, aparece como um terminal virtual (pty); em um login remoto, é encaminhado ao
sshd; em um terminal físico, vai para um adaptador serial-USB. No caso de um console em frame buffer, o kernel renderiza o texto e o mostra na tela.
- O kernel Linux precisa executar a ação solicitada pela chamada de sistema. A chamada
-
Conclusão
- Os sistemas de software modernos funcionam de forma extremamente complexa e sofisticada sobre o hardware, então tentar compreender completamente como o computador realiza uma pequena tarefa chega a ser inútil.
- Para explicar tudo, foi inevitável omitir muita coisa.
- Enviar a mensagem Hello World é apenas uma entre inúmeras chamadas de sistema e programas em execução neste computador neste momento.
Opinião do GN⁺
- É um texto que mostra como cada camada de um sistema computacional esconde a complexidade da camada inferior por meio de abstrações, permitindo que desenvolvedores criem aplicações com mais conveniência.
- Ao mesmo tempo, ele faz perceber quanta coisa acontece por baixo para que uma única linha da aplicação seja executada, e também por que depurar é tão difícil.
- Acho que todo programador deveria conhecer bem ao menos o sistema que está abaixo da linguagem que usa no dia a dia. Não é preciso saber tudo, mas é importante entender como as partes abstraídas realmente funcionam.
- Mesmo usando linguagens de alto nível, estudar conceitos de programação de sistemas como estrutura de memória, stack e heap, e chamadas de sistema pode ajudar muito em depuração e otimização de desempenho.
- Desenvolvedores de aplicações quase nunca precisarão mexer diretamente no compilador ou na biblioteca C, mas entender de que forma o programa que escreveram acaba usando o sistema é, na minha opinião, essencial para se tornar um bom programador.
Ainda não há comentários.