Emulando a Linus Torvalds: Crea tu propio sistema operativo desde 0 (V)

Este artículo lo escribí para el blog en español DesdeLinux el 11 de enero de 2014 y ahora lo dejo aquí, en mi blog personal. El artículo está tal cual, sin ninguna modificación desde aquella fecha.

En esta quinta entrega veremos una tabla bastante parecida a la GDT tanto en teoría como en uso, nos referimos a la IDT. Las siglas de IDT hacen referencia a Interrupts Description Table y es una tabla que se usa para manejar las interrupciones que se produzcan. Por ejemplo, alguien hace una división entre 0, se llama a la función encargada de procesar. Estas funciones son los ISR (Interrupt Service Routines). Así pues vamos a crear la IDT y añadir algunos ISR.

Lo primero vamos a declarar las estructuras correspondientes a la IDT:

Como se observa si comparáis con la GDT la estructura Ptr es idéntica y la Entry es bastante parecida. Por consiguiente las funciones de poner una entrada (SetGate) e instalar (Install) son muy parecidas.

Instalar:

Si nos fijamos veremos que la función de instalar usa la función ND::Memory::Set que habíamos declarado en el otro post. También podemos apreciar como no hacemos ninguna llamada a SetGate todavía y llamamos a ND::IDT::Flush, para esta función usamos otra vez la sentencia asm volatile:

Si todo va bien y hacemos un arreglo estético debería quedar así:

NextDivel-IDT

Bien, ahora vamos a empezar a rellenar la IDT con interrupciones. Aquí voy a crear solo una pero para el resto se haría igual. Voy a hacer la interrupción de división por cero. Como bien sabrán en matemáticas no se puede dividir un número entre 0. Si esto ocurre en el procesador se genera una excepción ya que no puede continuar. En la IDT la primera interrupción en la lista (0) corresponde a este suceso.

Añadimos esto entre el seteo de memoria y el flush dentro de la función Install de la IDT:

La función de callback va a ser ND::ISR::ISR1 que es bastante simple aunque debemos usar ASM:

NDISRCommon lo definiremos como una función en lenguaje C. Para ahorrar ficheros y mejorar legibilidad podemos usar extern “C”{}:

Este código en ASM puede ser un poco difícil de entender pero esto es así porque vamos a declarar una estructura en C para acceder a los datos que genere la interrupción. Obviamente si no quisieras eso podrías llamar simplemente en ND::ISR::ISR1 al Kernel Panic o algo por el estilo. La estructura tiene una forma tal que así:

Y por último hacemos la función NDISRHandler (también con link del C) en que mostramos un kernel panic y una pequeña descripción del error según el que tenemos en una lista de errores.

Bien y con esto ya somos capaces de manejar esta interrupción. Con el resto de interrupciones pasaría parecido salvo que hay algunas que devuelven parámetros y usaríamos la estructura reg para obtenerlo. Sin embargo te preguntarás que como sabemos si funciona de verdad. Para probar si funciona vamos a introducir una sencilla línea después del ND::IDT::Install():

Si compilamos nos dará un warning y si tratamos de ejecutarlo nos saldrá una bonita pantalla:

NextDivel-ISR

Y con esto termina este post, creo que es uno de los más extensos pero bastante funcional.

Emulando a Linus Torvalds: Crea tu propio sistema operativo desde 0 (I)

Este artículo lo escribí para el blog en español DesdeLinux el 27 de diciembre de 2013 y ahora lo dejo aquí, en mi blog personal. El artículo está tal cual, sin ninguna modificación desde aquella fecha.

En esta serie vamos a emular a Linus Torvalds, vamos a crear nuestro sistema operativo desde 0. En este primer episodio vamos a ver el arranque y pondremos un texto en pantalla desde nuestro kernel.

LinusTorvalds

En mi caso el sistema operativo se llama NextDivel. La primera decisión que debemos hacer nada más plantearnos el sistema operativo es ¿cuál va a ser el bootloader?

Aquí existen múltiples variantes, e incluso podríamos crear uno nosotros; sin embargo, en este tutorial voy a usar GRUB, porque la mayoría conoce más o menos algo de él. Creamos una carpeta que será el root de nuestro sistema operativo y allí creamos la carpeta /boot/grub

Allí creamos el fichero grub.cfg de la siguiente manera:

En este fichero hemos visto como GRUB cargará nuestro kernel, en este caso, en /next/START.ELF. Ahora debemos crear nuestro kernel.

Para ello necesitaremos el GCC y GAS (el ensamblador del proyecto GNU, suele venir con el gcc). Así pues vamos a crear el kernel.

Primero hacemos un archivo llamado kernel.asm. Este archivo contendrá el punto de inicio de nuestro kernel y además definirá el multiboot (una característica de algunos bootloaders como GRUB). El contenido de kernel.asm será:

Todo lo relacionando con multiboot es simplemente seguir la especificación nada más. Todo empezará en start, llamará a multiboot_entry, habremos definido el multiboot header en los primeros 4k y lo pondremos (con movl).

Más tarde llamamos a NextKernel_Main que es nuestra función en C del kernel. En el loop hacemos un halt para parar el ordenador. Esto se compila con:

Ahora vamos a entrar a programar en C. Pensarás que ahora todo es pan comido, ponemos un printf en main y ya está, lo hemos hecho.

Pues no, ya que printf y main son funciones que define el sistema operativo, ¡pero nosotros lo estamos creando! Solo podremos usar las funciones que nosotros mismos definamos.

En capítulos posteriores hablaré de como poner nuestra propia libraría del C (glibc, bionic, newlibc) pero tiempo al tiempo. Hemos hablado que queremos poner texto en pantalla, bueno veremos como lo hacemos.

Hay dos opciones, una es llamar a la BIOS y otra es manejar la memoria de la pantalla directamente. Vamos a hacer esto último pues es más claro desde C y además nos permitirá hacerlo cuando entremos en modo protegido.

Creamos un fichero llamado NextKernel_Main.c con el siguiente contenido:

Con esto manipulamos directamente la memoria VGA y caracter a caracter lo vamos escribiendo. Compilamos desactivando la stdlib:

Si has llegado hasta aquí querrás probar ya tu nuevo y flamante sistema operativo, pero todavía no hemos terminado. Necesitamos un pequeño fichero que diga al compilador en que posición del archivo dejar cada sección. Esto se hace con un linker script. Creamos link.ld:

Con esto definimos la posición de cada sección y el punto de entrada, start, que hemos definido en kernel.asm. Ahora ya podemos unir todo este mejunje:

Ahora copiamos START.ELF al /next dentro de nuestra carpeta que simula el root de nuestro sistema operativo. Nos dirigimos a la carpeta root de nuestro sistema operativo nuevo con la consola y verificamos que hay dos archivos: uno /boot/grub/grub.cfg y otro /next/START.ELF.

Vamos al directorio superior y llamamos a una utilidad de creación ISOs con GRUB llamada grub-mkrescue

Una vez hayamos hecho esto tendremos una ISO. Esta ISO puede abrirse en ordenadores x86 (64 bits también) y máquinas virtuales. Para probarlo, voy a usar QEMU. Llamamos a QEMU desde la línea de comandos:

Arrancará SeaBIOS y más tarde tendremos GRUB. Después si todo va correcto veremos nuestra frase. Pensarás que esto es difícil, te respondo, sí lo es.

Realmente crear un sistema operativo es difícil y eso que este de aquí no hace nada útil. En próximos capítulos veremos como manejar colores en la pantalla, reservar memoria y si puedo, como obtener datos del teclado.

Si alguien no quiere copiar todo lo que hay aquí, tengo un repositorio en GitHub (más elaborado) con el sistema operativo NextDivel. Si quieres compilar NextDivel solo tienes que tener git y cmake:

Os animo a colaborar en NextDivel si tienes tiempo y ganas de crear un sistema operativo. Quizá incluso superior a Linux… el tiempo lo dirá.

Usar GNU Parallel para aumentar el rendimiento de tus scripts

La computación ha avanzado. Ha aumentado la potencia de cálculo. Y no lo ha hecho subiendo la velocidad del reloj del procesador, pues no queremos tener minitostadoras. En vez de eso se ha escogido el camino de paralelizar. Todo lo que sea susceptible de ser paralelizado deberá ser paralelizado. Desafortunadamente la programación en paralelo es compleja y requiere una planificación mucho más larga. ¡Pero no desistamos! ¡Podemos usar GNU Parallel para paralelizar algunas tareas que llevan tiempo pero son independientes las unas de las otras! ¡Usemos GNU Parallel en nuestros scripts!

GNUParallel

Ejemplos prácticos:
– Convertir una biblioteca de MP3 en OGG (con ffmpeg)
– Normalizar el audio (con sox)
– Optimizar las imágenes de un sitio web (con OptiPNG, jpegoptim, etc)

Instalar GNU Parallel

En Debian/Ubuntu:

En Fedora:

En openSUSE:

En Arch Linux:

En NetBSD/SmartOS:

Convertir una biblioteca de MP3 en OGG y normalizar el audio

Esta era la tarea que tenía que realizar. Primero realicé una versión sencilla, que funcionaba utilizando un solo core del procesador.

Con este script conseguimos el objetivo que nos habíamos propuesto, pero podemos optimizar el rendimiento. Usando GNU Parallel:

Con esta modificación GNU Parallel se encarga de poner en cola los trabajos de conversión y normalización y los reparte entre los cores disponibles del procesador. La gráfica explica claramente la diferencia de uso entre los dos scripts.

Versión básica

TradicionalCore

Versión GNU Parallel

GNUParallel

Optimizar las imágenes de un sitio web

Aquí viene otro ejemplo que usa GNU Parallel para realizar la tarea más rápidamente.

Hay muchos más usos para GNU Parallel, solo tienes que usar tu imaginación. ¿Y tú? ¿Conocías GNU Parallel? ¿Qué opinas al respecto?

¡Haz scripts!

¡Haz scripts! ¡Escribe scripts! ¡Programa scripts! ¡Ejecuta scripts! ¡Automatiza! Escribir scripts tiene muchas ventajas. Escribir scripts es la mejor manera de ser productivo, mantener el entorno actualizado y evitar el error humano. ¡Automatiza!

Automation

Se es más productivo

Escribir scripts es la mejor manera de ser productivo. Perdemos parte de nuestro tiempo inicialmente para posteriormente recuperarlo en el futuro. Escribir un script es una inversión.

Estan siempre actualizados

Puede que hoy sepas el procedimiento exacto a realizar para una determinada tarea pero en el futuro puede que no lo sepas. El script siempre está actualizado, pues en cuanto deja de funcionar se debe reparar para seguir usándose. La documentación por ejemplo

bash

Menos errores

Tener un script reduce la posibilidad de error humano. Piensa que cada vez que hacemos algo de memoria podemos fallar. ¡Y puede que no sepamos cuando ha pasado eso!

Fáciles de compartir

Hoy día es muy fácil compartir scripts con el mundo. Cualquier programa de ‘paste’ funciona y sirve. Pastebin, BitBin.it, DesdeLinux Paste, GitHub Gist, BitBucket Snippets, Mozilla Paste, Ubuntu Paste, CryptoBin, Pasted, …

Algunos incluso te pagan por las visitas

Son TUS herramientas

Eres programador. Usas herramientas diseñadas por otros programadores. ¿Por qué no hacer tú alguna de tus herramientas? Agilizan tu entorno de trabajo como solo tu sabes. El verdadero maestro conoce sus herramientas al dedillo, si las diseñas todo ya tendrás mucho trecho hecho.

powershell

¿A qué esperas?

Creo que se ha quedado demostrado que quiero que hagas scripts; que automatices. Da igual el lenguaje que uses, pero usa tus propios scripts. Puedes usar Perl, Python, JavaScript, Ruby, Tcl, Bash, Fish, PowerShell, Lua y muchos más.

Rust Essentials, reseña del libro

Dicen que a las personas importantes les pide la gente su opinión. Así que no entiendo porque tengo ahora que dar opiniones…

Hoy voy a hablar del libro Rust Essentials de Ivo Balbaert. En primer lugar quiero agradecer a la editorial Packt Publishing por haber contado conmigo a la hora de valorar este libro para que todos vosotros conozcais algo más acerca de él.

RustEssentials

Rust Essentials es un libro de introducción al lenguaje de programación Rust, lenguaje del que ya he hablado anteriormente. El libro está en inglés y asume que no conoces nada o muy poquito de Rust pero sí que has programado con anterioridad. Así pues, el libro no explica conceptos de programación tales como funciones o variables sino que directamente expone sus peculiaridades. Es recomendable haber programado C para entender algunas partes del libro al 100%.

El libro se estructura en 9 capítulos, a saber:

  • Starting with Rust
  • Using variables and types
  • Using functions and control structures
  • Structuring data and matching patterns
  • Generalizing code with high-order functions and parametrization
  • Pointers and memory safety
  • Organizing code and macros
  • Concurrency and parallelism
  • Programming at the boundaries

En estos temas se tratan desde cosas tan triviales como los comentarios (que no lo son, pues según explica el libro, puedes hacer comentarios de RustDoc, que serán compilados como documentación en HTML y tienen marcado Markdown) hasta la gestión multihilo de Rust, para aprovechar uno de los 3 apartados en los que se enfoca Rust: la concurrencia.

Veremos la magia de Rust, respetando la convención de estilo (esto es importante, no vaya a pasar como con JavaScript) y las características que hacen de Rust un gran lenguaje de programación. El libro contiene ejercicios e incluso analizarás porque en determinados lugares obtenemo un error de compilación.

Hacen falta un tiempo para que dejes de ver al compilador de Rust como un protestón sin sentido y lo empieces a ver como tu mejor amigo en la programación

El libro también se adentra a explicar las partes de programación funcional de Rust, no sin antes explicar las closures y las funciones de primer orden. Más tarde nos adentramos en las traits, que posibilitan la programación orientada a objetos pero no como se plantea desde C++, C# o Java. En Rust, es mucho más flexible y unido a las funciones genéricas podemos evitar la repetición del código en un 100%. DRY (don’t repeat yourself). El capítulo 6 es interesante y quizá algo denso para alguien que venga de lenguajes donde la gestión de memoria es administrada por una máquina virtual o intérprete. No es difícil, pero hay que saber las diferencias. Rust tiene muchos tipos de punteros y he de decir que este libro los explica mejor que mi antiguo libro de C de Anaya.

Rust

Más tarde se ve el sistema de módulos y la creación de macros en Rust. Las macros en Rust son muy potentes, más que en C donde también son bastante usadas. El capítulo 8 se dedica por completo a los hilos y la gestión de datos entre distintos hilos. El capítulo 9 nos explica cosas interesantes pero que no tienen mucha densidad y no se merecen un capítulo propio como la intercomunicación entre C y Rust o instrucciones en ensamblador directamente en el código.

Me gusta que tenga ejercicios para practicar (las soluciones están en GitHub), que use las herramientas disponibles de Cargo, que explique porque un determinado código no compilará, que hable de como desarrollar tests unitarios y de que explore todas las características del lenguaje de manera incremental, muchas veces modificando ejemplos anteriores.

No me gusta que quizá sea un libro muy rápido que presupone algunos conceptos y que casi no explora la librería estándar mas que para hablar de ciertas traits muy útiles y la gestión de hilos. No habla en ningún momento de como leer archivos por ejemplo aunque en el anexo menciona librerías para leer distintos tipos de archivos.

En definitiva es un libro que puede estar bien para todos aquellos con experiencia programando y quieran aprender un nuevo lenguaje, lleno de peculiaridades diseñadas para trabajar en: velocidad, seguridad y concurrencia. No se lo recomendaría a alguien que no hubiese programado nunca.

Actualmente existen libros mejores en el mercado y además, en Adrianistán puedes aprender con Rust 101, mi tutorial de Rust.