Eu tive ótimos professores na faculdade, e de tudo que eu aprendi de computação, o que eu mais gostava sem dúvida eram matérias de programação de baixo nível. Meu Professor de Linguagem de Montagem na UFMS, Prof. Ronaldo, lecionava também matérias de Redes de Computadores e até mesmo Sistemas Operacionais, igualmente interessantes.
Haviam outros professores igualmente habilidosos, e me recordo como se fosse hoje de aprender quaternions1 na disciplina de Computação Gráfica com o Prof. Pagliosa. O motor gráfico que fizemos (entenda o “nós” aqui como ele - o professor, no fim das contas era um boilerplate onde precisávamos completar trechos específicos) resultou em um dos trabalhos mais interessantes, e ao mostrar o resultado para a minha namorada à época - hoje, esposa - ela não pareceu tão impressionada, e me lembra até hoje de ter ficado feliz de ter desenhado uma hemácia em 3D.
Deixando as digressões de lado, os trabalhos de Linguagem de Montagem eram fascinantes, para quem gostava obviamente. Nem todos os alunos compartilhavam da minha opinião de que eram divertidos. É o tipo de matéria que se você não tem interesse ou perdeu alguma aula, pode ficar sem acompanhar o restante da disciplina. Principalmente para um estudante no segundo ano do curso de Ciência da Computação, tais trabalhos eram difíceis de entender, assimilar e codificar. Ainda são, na real.
Lembro dos trabalhos que tivemos que fazer até hoje. O primeiro era um dissassembler: dado um executável em formato raw binary (isso porque existem outros tipos de executáveis binários, como o ELF2 ou COFF3, para citar alguns), a missão era ler arquivo binário, identificar as operações e operandos, e imprimir o código equivalente em assembly no formato NASM4. Para isso foi necessário ler o manual do processador x865, descobrir o opcode de cada uma das instruções, identificar o tamanho da instrução na memória (quantos bytes ocupava), os operandos, etc. Pois é, para a surpresa de ninguém, a arquitetura CISC dos processadores x86 adicionam uma complexidade para a decodificação de suas instruções. Este trabalho todo feito em C, e pela primeira vez, tivemos contato com ponteiros de função, com o opcode servindo de índice para chamarmos a função que decodifica a instrução.
O segundo foi implementar o tipo BigInteger6, que existe em Java, em assembly. Na época, não havíamos tido a disciplina de Java. E o problema em si era, dado um número inteiro de tamanho qualquer, possivelmente maior que os 32bit dos registradores que possuíamos, e fazer as operações básicas de adição, subtração, multiplicação e divisão com tais dados.
O terceiro foi implementar uma lista ligada em assembly.
O quarto foi o mais interessante. O professor fazia uma mini bootloader e nossa missão era salvar o endereço da interrupção da BIOS que fica no vetor de interrupções (primeiros endereços da memória), substituíamos por uma função nossa, que era encarregada de mostrar um relógio no canto inferior esquerdo da tela. Depois que nossa função terminava, nós invocávamos a função original que havia sido salva. Dessa forma, a cada tick do relógio, nosso relógio era atualizado na tela. Trabalhávamos em conjunto com a interrupção do teclado, para que pudéssemos escrever coisas na tela e ver o relógio funcionando simultaneamente.
No fim das contas, aqueles eram tempos mais fáceis. Muito tempo depois, o Prof. Ronaldo ministrou a disciplina de Sistemas Operacionais para a pós-graduação. Eu há muito tinha terminado o mestrado, e já tinha cumprido os créditos do Doutorado. Mas como ele sabia do meu interesse no tema, eu assistia às aulas como ouvinte, fazendo os trabalhos também.
Para a minha surpresa, o prof. aproveitou o curso COS 318 de Princeton7, e portanto faríamos um kernel de verdade!
Fizemos todos os 6 trabalhos, e nunca aprendi tanto sobre SO como nessa ocasião. E percebi o quanto que eu achava que sabia, mas que de fato não era verdade, ou das coisas que tinha uma ideia de como as coisas funcionavam mas não sabia exatamente como era implementado na prática.
Chegou a hora de revisitar essas páginas e fazer um kernel do zero (não falei em quanto tempo). Já batizei o rapaz, e ele tem o nome Boring OS por um motivo muito simples: ele não fará nada impressionante, a diversão está na construção. Se você notar os 6 projetos de Princeton, notará por exemplo que não há nada de redes, ou interfaces gráficas. E como até chegar lá é um loooooongo caminho, provavelmente aqui também não terá nada do tipo.
Um dos desafios auto impostos por mim a mim mesmo é justamente não usar nem consultar nada do que fiz na época. Isso é um trabalhão, já que entre os projetos, há muito boilerplate de códigos já feitos pela galera da universidade americana. Nada de lá será usado aqui, até por questões de plágio etc. Portanto, vaguearemos no escuro apenas com as instruções da página.
No próximo artigo começaremos a por a mão na massa, no Projeto 1: Bootloader8! Nos vemos lá.
https://link.springer.com/book/10.1007/978-1-4471-7509-4
https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
https://en.wikipedia.org/wiki/COFF
https://nasm.us/
Não foi esse o manual que usei na época, mas para fins didáticos, esse aqui serve: https://cdrdv2.intel.com/v1/dl/getContent/671200
https://docs.oracle.com/javase/8/docs/api/java/math/BigInteger.html
https://www.cs.princeton.edu/courses/archive/fall15/cos318/projects.html
https://www.cs.princeton.edu/courses/archive/fall15/cos318/projects/project1/p1.html