Anuncios Google

Creando un SO - Primeros pasos

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:

  • Conocimiento básico de las funciones de ensamblador MOV, PUSH, entre otras y los registros.
  • Conocimiento medio-alto de C. Este tutorial está echo para C, pero podeis portarlo a C++.
  • Tener conocimientos basicos-medios de Hardware, como minimo entender como funciona y arranca un ordenador.
  • Tener GCC y NASM instalado en nuestro equipo.
  • Tener instalado VirtualBox

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)


4.682925
Tu voto: Ninguno Votos totales: 4.7 (41 votos)

Anuncios Google

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de miguel28

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

Imagen de xCO 3DS

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

Imagen de Kyngo

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...

Imagen de Kyngo

¡Como triunfa!

Uoooo, ¡esto suena genial!

Lo pruebo y os digo algo!

Imagen de duhowpi

Mmmmm.... Interesante. Buen

Mmmmm.... Interesante.

Buen trabajo (^.^)

Imagen de OdnetninI

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.

Imagen de Almamu

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.


Imagen de OdnetninI

Sigue igual. Ayudaaa.

Sigue igual. Ayudaaa.

Imagen de Almamu

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.


Imagen de OdnetninI

Ninguno va.

Ninguno va. Provado 1 a 1. Y nada. Desde el bin hasta el win.

Imagen de krl1995

Muy bien sinceramente muy

Muy bien sinceramente muy bueno

Imagen de moikop

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í.

Imagen de Almamu

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).


Imagen de _-eDu69-_

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

Imagen de Almamu

Ops, es verdad me he saltado

Ops, es verdad me he saltado esa letra. Y si, el espacio también tiene color.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.