En esta especie de "bitacotuto" os voy a explicar como he conseguido hacer que un programa arranque sobre la maquina y muestre un simple, pero a la vez complicado Hola Mundo!. Tenemos unos requisitos minimos de conocimientos, estos son:
Primero de todo vamos a aprender un poco como funcionará el "Hola Mundo!" que vamos a escribir.
Lo primero de todo es hacer que el programa arranque, para esto necesitaremos una imagen del GRUB o otro bootloader(al final se adjunta una con un kernel pre-compilado). Lo que hará el programa será lo siguiente:
Cuando arranque, el código ensamblador llama a la funcion kernel de nuestro código en C y este llamará a una función printf de nuestra creacion.
AVISO: Cuando programamos sobre Hardware no podemos usar librerías que se incluyan con el compilador, puesto que estas están diseñadas para trabajar sobre un determinado sistema y no nos serviran.
Primero, debemos de saber como funciona el codigo ensamblador que vamos a usar:
El código ensamblador, lo que simplemente ará será llamar a la funcion k_main que definimos en el codigo C, os preguntareis, ¿porque no llamarla directamente y no usar ensamblador?. Pues hay una sencilla respuesta, mas adelante necesitaremos que el arranque sea en ensamblador para preparar la lectura del teclado y más cosas necesarias.
Para escribir en la pantalla, primero de todo, necesitamos saber como "ve" el ordenador la escritura(cada numero hexadecimal es el color, por lo que 0x01 es BLANCO):
H0x01O0x01L0x01A0x01 0x01M0x01u0x01n0x01d0x01o0x01!0x01
Así es como "verá" el PC lo que escribimos en pantalla, os estareis preguntando: ¿qué quiere decir esto?, pues muy sencillo, primero terminemos de ver la teoría, como máximo podemos escribir 80 caracteres a lo largo y 25 lineas a lo alto, lo que hace un total de 1280 carácteres por pantalla. Cada carácter tiene asociado, en el número siguiente del array el colór con el cual se verá. Es decir, si yo escribo Hola Mundo! con letras blancas, el ordenador lo entenderá como lo que os he enseñado más arriba.
Ahora, sabiendo lo necesario vamos a pasar a la práctica. Como aún no tengo muy claro como funcionan los linkers de momento usaremos este Script estandar para el arranque:
ENTRY (_loader) SECTIONS{ . = 0x00100000; .text :{ *(.text) } .rodata ALIGN (0x1000) : { *(.rodata) } .data ALIGN (0x1000) : { *(.data) } .bss : { _sbss = .; *(COMMON) *(.bss) _ebss = .; } }
Este codigo le indica al ordenador que funcion debe de buscar y arrancar. Debeis de guardarlo con el nombre de link.ld y en la raíz de la carpeta de vuestro proyecto.
Una vez tenemos el Script de arranque vamos a crear una carpeta llamada src, otra llamada obj, otra llamada bin y otra llamada include.
Ahora abrimos src y creamos el archivo de nombre boot.s
Una vez creado hagamos lo siguiente:
global _loader ; esto es para el linkeo global eokl ; punto final del kernel extern k_main ; setting up the Multiboot header - see GRUB docs for details no tocar, es para que arranque el GRUB MODULEALIGN equ 1<<0 ; align loaded modules on page boundaries MEMINFO equ 1<<1 ; provide memory map FLAGS equ MODULEALIGN | MEMINFO ; this is the Multiboot 'flag' field MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header CHECKSUM equ -(MAGIC + FLAGS) ; checksum required section .text align 4 MultiBootHeader: dd MAGIC dd FLAGS dd CHECKSUM ; reservar stack STACKSIZE equ 0x4000 ; 16k _loader: mov esp, stack+STACKSIZE; set up the stack push eax ; pasamos Multiboot Magick Number push ebx ; pasamos Multiboot Info Structure call k_main hlt eokl dd STACKSIZE + stack section .bss align 32 stack: resb STACKSIZE ; reservar 16k de stack en una quadword
Explico por partes:
global _loader ; esto es para el linkeo global eokl ; punto final del kernel extern k_main
Simplemente definimos el punto de entrada del linker, el final del kernel y la funcion k_main que sera nuestro kernel.
; setting up the Multiboot header - see GRUB docs for details no tocar, es para que arranque el GRUB MODULEALIGN equ 1<<0 ; align loaded modules on page boundaries MEMINFO equ 1<<1 ; provide memory map FLAGS equ MODULEALIGN | MEMINFO ; this is the Multiboot 'flag' field MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header CHECKSUM equ -(MAGIC + FLAGS) ; checksum required
Aqui simplemente le damos informacion al grub de que nuestro programa es compatible.
section .text align 4 MultiBootHeader: dd MAGIC dd FLAGS dd CHECKSUM
Esto indica que se escriba en la seccion text, alineado a 4 y define una estructura.
; reservar stack STACKSIZE equ 0x4000 ; 16k _loader: mov esp, stack+STACKSIZE; set up the stack push eax ; pasamos Multiboot Magick Number push ebx ; pasamos Multiboot Info Structure call k_main hlt
Reservamos el STACK, enviamos unos argumentos a la funcion k_main y la llamamos, por el momento estos argumentos no los usaremos.
eokl dd STACKSIZE + stack section .bss align 32 stack: resb STACKSIZE ; reservar 16k de stack en una quadword
Fin del kernel
Ahora que ya hemos entendido mejor esto vamos a la parte en C.
Aunque esta parte es algo más sencilla, igualmente daré la explicacion para aquellos que no lo entiendan.
k_main() //funcion definida como extern en boot.s, si os dais cuenta no se usa ningun tipo, no es necesario { //Estas funciones las creamos nosotros mas abajo k_clear_screen(); printf("HolaMundo\n"); printf("Segunda linea\n"); printf("Tercera linea\n"); while(1); //Paramos el programa };
Como veis, el codigo es sencillo, pero esto no es lo complicado, si no lo que viene ahora. Como ya he dicho más arriba, no sirven las librerias ya incluidas con nuestro compilador GCC, por lo que tendremos que crearnos nosotros mismos unas funciones para escribir y vaciar la pantalla. Para esto vamos a necesitar 3 funciones(aunque en el k_main() solo se usan dos la tercera nos puede ser util):
//Esto va arriba de todo el codigo #define COLOR_NEGRO 0x00 #define COLOR_AZUL 0x01 #define COLOR_VERDE 0x02 #define COLOR_AZULCLARO 0x03 #define COLOR_ROJO 0x04 #define COLOR_ROSA 0x05 #define COLOR_NARANJA 0x06 #define COLOR_BLANCO 0x07 #define COLOR_GRIS 0x08 #define COLOR_MORADO 0x09 #define FONDO_AZUL 0x10 #define FONDO_VERDE 0x20 #define FONDO_AZULCLARO 0x30 #define FONDO_ROJO 0x40 #define FONDO_ROSA 0x50 #define FONDO_NARANJA 0x06 #define FONDO_BLANCO 0x70 #define FONDO_GRIS 0x80 #define FONDO_MORADO 0x90 void k_clear_screen(); unsigned int k_xy_printf(char *message, unsigned int x, unsigned int y, int color); int printf(char* text); char *__VIDMEM__ = (char *) 0xb8000; unsigned long __PRINTF__LINE__ = 0; //Esto va despues del k_main() int printf(char *text){ int str_long = 0; int str_tmp_long = 0; int str_lines = 0; int pos = 0; while(text[str_long] != *"\0"){ str_long++; } str_tmp_long = str_long; while(str_long >= 80){ str_long -= 80; str_lines++; } pos = (__PRINTF__LINE__*80*2); str_long = 0; while(str_long < str_tmp_long){ if(text[str_long] == *"\n"){ __PRINTF__LINE__++; pos= (__PRINTF__LINE__*80*2); str_long++; } __VIDMEM__[pos] = text[str_long]; __VIDMEM__[pos+1] = COLOR_BLANCO; str_long++; pos+=2; } } void k_clear_screen() // limpia completamente la pantalla { char *vidmem = (char *) 0xb8000; unsigned int i=0; while(i < (80*25*2)) { vidmem[i]=' '; i++; vidmem[i]=COLOR_BLANCO; i++; }; }; unsigned int k_xy_printf(char *message, unsigned int x, unsigned int y, int color) // El mensaje y su posicion X e Y { char *vidmem = (char *) 0xb8000; unsigned int i = 0; if((x == 0) && (y == 0)){ i=(0); }else if(y == 0){ i=(x*2); }else if(x == 0){ i=(y*2); }else{ i=(y*x*2); }; while(*message!=0) { if(*message=='\n') // comprobamos para el caracter especial de nueva linea { y++; x=0; if((x == 0) && (y == 0)){ i=(0); }else if(y == 0){ i=(x*2); }else if(x == 0){ i=(y*2); }else{ i=(y*x*2); }; *message++; } else { if( x >= 80){ while(x >=80){ x -= 80; }; if((x == 0) && (y == 0)){ i=(0); }else if(y == 0){ i=(x*2); }else if(x == 0){ i=(y*2); }else{ i=(y*x*2); }; }; vidmem[i]=*message; *message++; i++; vidmem[i]=color; i++; }; }; return(1); };
Esta ultima parte de codigo os dejo a vosotros entenderla, pero básicamente lo que hace es escribir texto en pantalla línea a línea sin que el usuario tenga que introducir la posicion X e Y ó el color de tal.
Una vez tenemos el código programado necesitamos compilarlo. Para esto, en la carpeta de nuestro proyecto creamos un archivo llamado Makefile.
Dentro de este escribimos:
all: nasm -f aout -i ./src/boot/ ./src/boot.s -o ./obj/boot.o gcc -B ./include/ -c ./src/main.c -o ./obj/main.o -fno-builtin ld -T link.ld -o ./bin/kernel.bin ./obj/boot.o ./obj/main.o
Luego guardamos. Ahora abrimos una terminal y vamos a la carpeta en la que tenemos el Makefile, ahora solo queda escribir make y comprobar que la compilación no de ningún error.
Si todo ha ido bien, dentro de la carpeta bin se debe de haber creado un archivo llamado kernel.bin. Ahora abrimos la imagen de disco de GRUB que podeis encontrar mas abajo con un editor de imagenes ISO. Si estais en ubuntu podeis usar MagicISO desde Wine, es el que yo uso.
Abrimos el archivo grub.img y navegamos hasta la carpeta BOOT, ahora borramos el archivo kernel.bin que ya existe y lo reemplazamos por el nuestro. Guardamos la imagen de disco y abrimos el VirtualBox. Si no tenemos ninguna máquina creada simplemente creamos una. Por el momento no necesitaremos disco duro. Arrancamos la maquina, pulsamos en Devices->Floppy devices->More floppy Images. En la ventana pulsamos ADD y buscamos nuestro archivo grub.img. Una vez añadida a la lista simplemente la seleccionamos y pulsamos Select. Si la máquina ya había arrancado la reiniciamos y deberemos de ver el arranque de GRUB. Ahora solo queda pulsar Intro y ver nuestro Hola mundo funcionando.
Archivos del tutorial:
Descargar grub.img (Solo usuarios registrados)
Comentarios
no he podido compilarlo en
no he podido compilarlo en ubuntu 10.10 a 64bits me sale este error. en probe en ubuntu de 32bit de un amigo y si lo complia, pero mi pregunta es si hay alguna opcion del comando ld para que se pueda compilar en ubuntu 64bits?
cd /Users/mike/Desktop/SOpruebamike-desktop:~ mike$ cd /Users/mike/Desktop/SOprueba
mike-desktop:SOprueba mike$ make
nasm -f aout -i ./src/boot/ ./src/boot.s -o ./obj/boot.o
gcc -B ./include/ -c ./src/main.c -o ./obj/main.o -fno-builtin
ld -t link.ld -o ./bin/kernel.bin ./obj/boot.o ./obj/main.o
ld: warning: in link.ld, file was built for unsupported file format which is not the architecture being linked (x86_64)
ld: warning: in ./obj/boot.o, file was built for unsupported file format which is not the architecture being linked (x86_64)
./obj/main.o
ld: could not find entry point "start" (perhaps missing crt1.o) for inferred architecture x86_64
make: *** [all] Error 1
mike-desktop:SOprueba mike$
Jugar futbol es simple, pero es dificil jugar simple
no entiendo ni I :s
soy un usuario promedio :S tegno 14 años y quiro ser informatico recien me estoy integrando a esto de los sistemas operativos mientras los estoy aprendiendo a instalar espero despues cuando estudie saber programarlos :/ pero todo a su tiempo
salu2
Bueno..
No eres el único con ese problema.
Yo también quiero ser desarrollador de sistemas operativos y trabajar para MS (pa que mejoren, si no entro, pos a Apple tocan... xD), primero aprende los lenguajes básicos: Visual Basic .NET, luego C#, y finalmente C, y C++. Es lo mejor, y aprenderás más fácilmente.
¡Saludos!
PD: Puedes pasarte un año mínimo con cada lenguaje.
Wii 4.3E, sobrevivió a un Full Brick gracias al Chip InFeCtuS... ^^
http://elrapdelpinguino.wordpress.com
Kyngo's System Checker - www.scenebeta.com
-----------------------------------------
Debemos aprender a aceptar a los demás por cómo son, no por quienes son.
Si seguimos siendo tan superficiales, el mundo acabarña realmente peor...
¡Como triunfa!
Uoooo, ¡esto suena genial!
Lo pruebo y os digo algo!
Mmmmm.... Interesante. Buen
Mmmmm.... Interesante.
Buen trabajo (^.^)
Buenas: En ubuntu compila,
Buenas:
En ubuntu compila, pero en windows:
Y me gustaria no tener que reiniciar el equipo cada vez que tenga que compilar. Y ya proble, pero no funciona una maquina virtual. Ayuda.
Be Libre my Friend.
Simplemente el linker para
Simplemente el linker para windows parece no permitir el a.out, así que puedes probar cambiando lo de aout en la línea del nasm por obj y probar suerte.
Sigue igual. Ayudaaa.
Sigue igual. Ayudaaa.
Si cambias obj por win32
Si cambias obj por win32 parece dar un error de que no encuentra la funcion k_main, escribe en la consola: nasm -hf y ve probando con los de la lista hasta que uno funcione.
Ninguno va.
Ninguno va. Provado 1 a 1. Y nada. Desde el bin hasta el win.
Muy bien sinceramente muy
Muy bien sinceramente muy bueno
Código muy interesante
Guau Almamu, tremendo código XD
Es tan interesante como complejo, pero lo has explicado de una forma muy clara...
Un saludo y gracias :P
Para recibir ayuda por parte de otros usuarios más rápidamente, recomendamos que pongas títulos descriptivos y no utilices abreviaturas (estilo MSN) en tus post de los foros. Recuerda que accediendo al Manual del perfecto forero y las Normas de la Comunidad aprenderas trucos para resolver tus dudas antes.
No preguntes por MP, mejor pregunta aquí.
Eso es lo que intento, que
Eso es lo que intento, que sea sencillo de entender por muy complejo que sea(que tiene tela lo que me costo dar con la estructura "Magica" para el GRUB).
Estás programando un SO?
que bestia O_o No pensaba que ibas a ir tan lejos...
el hexadecimal "Hola Mundo!" no sería: H0x01O0x01L0x01A0x01 M0x01u0x01n0x01d0x01o0x01!0x01?
Esque tal cómo lo has escrito tú: >>> H0x01O0x01L0x01A 0x01M0x01u0x01n0x01d0x01o0x01!0x01 yo entiendo que la "A" no tendría color y que en cambio definirías el espacio de color blanco... no?
SaluDTs
Mi historia, del principio al fin
Ops, es verdad me he saltado
Ops, es verdad me he saltado esa letra. Y si, el espacio también tiene color.