Javier Valcarce's Homepage

Kernel multiboot mínimo

Este artículo explica cómo cargar y ejecutar un programa en un PC sin sistema operativo, i.e, en el nivel de máquina convencional.

Lo mejor para empezar es ir al grano y examinar con calma un ejemplo sencillo paso a paso sobre como compilar, montar, cargar y ejecutar un programa en el nivel de máquina convencional. A este programa —en adelante y para entendernos— lo llamaremos "kernel", aunque no tiene porqué serlo.

Cargar y ejecutar un kernel en el nivel de máquina convencional es muy fácil usando GNU GRUB. Este ejemplo básico sirve para mostrar cómo se hace paso a paso. Todos los kernels que compiles los puedes cargar y ejecutar de la misma forma.


Compilación del kernel

Como "kernel" (es un decir) de ejemplo puedes usar el siguiente programa "Hola mundo" compuesto por estos dos ficheros:

$ gcc -c kernel1.c
$ gcc -c startup.S
$ ld -o kernel1 kernel1.o startup.o -Ttext 0x100000

La opción -c sirve para que gcc sólo compile y ensamble pero no intente montar el módulo *.o (formato ELF) generado. La opción -Ttext le dice al montador que desplace la sección de código (.text) del ejecutable ELF 0x100000 posiciones de memoria, puesto que es en esa dirección donde GRUB carga el ejecutable.

Instalación de GRUB y el kernel en el disquete

Para no tener que hacer ninguna modificación en el disco duro, es mejor y más seguro instalar GRUB y el kernel en un disquete, para posteriormente arrancar el ordenador con él. En linux:

$ mkfs -t ext2 /dev/fd0
$ mount -t ext2 /dev/fd0 /mnt/floppy
$ grub-install --root-directory=/mnt/floppy /dev/fd0

la orden grub-install instala GRUB<ref>La versión que aquí comento es GRUB Legacy, está ya en marcha GRUB 2, una nueva versión con importantes mejoras</ref> en el disquete. También puedes usar un stick USB en lugar de un disquete si tu ordenador pueda arrancar con él. Instalar GRUB en un stick no es tan fácil como en un disquete porque primero debes averiguar el número de disco que la BIOS asigna al stick para iniciar, puede ser el 0x81, 0x82... a saber.

Lo que hace exactamente grub-install, por si tienes interés, es instalar una serie de programas en distintos lugares del disquete

  1. Crea los directorios /boot y /boot/grub en el disquete. Copia los programas stage1_5 y stage2 en /boot/grub
  2. Instala en el sector de arranque<ref>Para iniciar el SO, la BIOS carga en memoria el primer sector (512 bytes) del disco de arranque llamado, lógicamente, el sector de arranque</ref> del disquete un programa llamado stage1 (512 bytes), y en los siguientes 20 sectores el programa stage1_5.

El proceso de arranque es el siguiente:

  1. Cuando stage1 arranca, carga los siguientes ~20 sectores del disquete, que contienen el programa llamado stage1_5 (~10KB).
  2. stage1_5 ya es capaz de interpretar la tabla de ficheros del disquete, que en este caso es EXT2 por lo que el stage1_5 elegido es concretamente e2fs_stage1_5. Su tarea es localizar y cargar el fichero stage2
  3. stage2 (~100KB) es el cargador GRUB mismo, que incluye un shell con unas 20 órdenes distintas y un menú de selección. Puede cargar directamente cualquier kernel que cumpla la especificación multiboot y también kernels no multiboot (por ejemplo Windows) cargando y transfiriendo el mando al cargador particular de estos (esta técnica se conoce como chainload). Puede leer también las opciones de configuración en el fichero menu.lst

Añade el kernel que acabas de compilar al directorio /boot del disquete

$ cp kernel1 /mnt/floppy/boot/

Configuración de GRUB

Si ahora arrancas con este disco, aparecerá en pantalla el shell de GRUB en el que tendrás que indicarle cómo se llama y donde está el kernel que quieres cargar y ejecutar, las órdenes son root fd0, kernel /boot/kernel1 y finalmente boot. Indicar todo esto cada vez que arrancas es un tedio así que es mejor escribir todas las opciones en el fichero de configuración menu.lst

    default         0
    timeout         2
    color cyan/blue white/blue

    title           kernel1
    root            (fd0)
    kernel          /boot/kernel1
  

Copia menu.lst al directorio /boot/grub del disquete pues es ahí donde GRUB buscará el fichero de configuración:

$ cp menu.lst /mnt/floppy/boot/grub

Y ya está. Ahora, cuando arranques con el disquete, en lugar de aparecer el feo shell de GRUB aparecerá un menú de selección en el que podremos elegir el kernel a cargar, aunque en este caso sólo tenemos uno. Si tuviéramos varios kernels: kernel1, kenrel2,... añadiríamos en menu.lst tantas entradas como kernels tuviéramos.


      title           kernel1
      root            (fd0)
      kernel          /boot/kernel1

      title           kernel2
      root            (fd0)
      kernel          /boot/kernel2
      .
      .
      .
  

Carga y ejecución

Arranca el ordenador con el disquete (puede que tengas que configurar la BIOS). Cuando GRUB cargue y ejecute kernel1 este simplemente mostrará en pantalla el clásico "Hello World" y entrará en un bucle infinito. ¡Listo!

Usar un emulador de PC

Probar nuestro kernel en un PC real es una tarea lenta y tediosa porque obliga a reiniciar la máquina constantemente. Durante la fase inicial de desarrollo es mejor usar un emulador de PC tal como Bochs (recomendado), QEMU o VMWare configurados para que arranquen con la imagen de un disquete.

Vuelca la imagen del disquete anterior a un fichero, en Windows no sé cómo puede hacerse esto, pero en Linux es

$ dd if=/dev/fd0 of=floppy.img bs=1k count=1440

asigna un dispositivo de bloques loop (/dev/loop*) al fichero imagen. Para hacer esto primero comprueba que el módulo loop.ko está insertado en el kernel de Linux, teclea

$ lsmod

y comprueba que el módulo "loop" aparece en la lista de módulos cargados, si no es así cárgalo (como root) con

$ modprobe loop

asigna un dispositivo loop al fichero imagen, por ejemplo elige /dev/loop0

$ losetup /dev/loop0 floppy.img

Ahora ya puedes acceder al disquete como si fuera un disquete físico mediante el dispositivo de bloques /dev/loop0. Cada vez que compiles un nuevo kernel cópialo al directorio /boot del disquete y desmonta la imagen

$ mount /dev/loop0 -t ext2 /mnt/floopy
$ cp new_kernel /mnt/floppy/boot
$ # actualiza /mnt/floppy/boot/grub/menu.lst si fuese necesario
$ umount /mnt/floppy

Ahora la imagen del disquete ya está actualizada con el nuevo kernel, la actualización efectiva del fichero imagen sólo se produce al desmontar la unidad porque sólo en ese momento se vacía el búfer de E/S del sistema.

Imagen del disquete: fat.img.zip
Configura el emulador asignando la imagen del disquete a la disquetera del PC virtual, arranca y comprueba que funciona. ¿Funciona? Debería, piensa que si funciona en el PC real entonces debe funcionar también en el PC virtual, aunque lo contrario no siempre es cierto.

Puedes usar un archivo Makefile similar a este para automatizar el proceso de /compilar el kernel/montar el disquete/copiar el kernel al disquete/desmontar el disquete/

Configuración de Bochs

El emulador Bochs

Resolución de problemas

Al ensamblar startup.S

Error: bad or irreducible absolute expression
GCC procesa los ficheros de código de manera diferente según su extensión y de si esta está escrita en mayúsculas o en minúsculas (no es lo mismo).

fichero.S Fichero de código ensamblador que necesita ser prepocesado por cpp antes de ser ensamblado
fichero.s Fichero de código ensamblador ya preprocesado y listo para ser ensamblado

Conclusión: la extensión tiene que ser *.S (S mayúscula).

This page forms part of website https://javiervalcarce.eu