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

Bienvenidos de nuevo a esta serie de posts titulada “Emulando a Linus Torvalds”. Hoy veremos la GDT. Primero tenemos que ver que es la GDT. Según Wikipedia:

The Global Descriptor Table or GDT is a data structure used by Intel x86-family processors starting with the 80286 in order to define the characteristics of the various memory areas used during program execution, including the base address, the size and access privileges like executability and writability

Que traducido sería una Tabla de Descriptores Global, una estructura de datos usada en los procesadores Intel x86 desde el 80286 para definir las características de varias áreas de memoria usadas durante la ejecución del programa.

Resumiendo, si estamos en un procesador Intel x86 deberemos definir una GDT para un correcto uso de la memoria. Nosotros no vamos a hacer mucha complicación y vamos a definir 3 entradas en la tabla:

  • Una entrada NULL, obligatoria para todas las tablas.
  • Una entrada para la sección data, usaremos el máximo, que en 32 bits son 4 GB.
  • Una entrada para la sección code, usaremos el máximo, que en 32 bits son 4 GB.

Como veis data y code usarán el mismo espacio. Bien, ahora vamos a implementarlo. Para ello usaremos dos estructuras, la primera se encargará de contener un puntero hacia los datos reales de nuestra GDT. Y la segunda será un array con las entradas de la GDT. Primero vamos a definirlas

struct Entry{
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
} __attribute__((packed));
struct Ptr{
uint16_t limit;
uint32_t base;
} __attribute__((packed));

Habrán observado un curioso __attribute__((packed)) al final de las estructuras. Esto le dice al GCC que no optimice las estructuras porque lo que queremos es pasar los datos tal cual al procesador. Ahora vamos a hacer una función para instalar la GDT. Antes deberemos haber declarado las estructuras, ahora vamos a inicializarlas.

struct ND::GDT::Entry gdt[3];
struct ND::GDT::Ptr gp;
void ND::GDT::Install()
{
gp.limit=(sizeof(struct ND::GDT::Entry)*3)-1;
gp.base=(uint32_t)&gdt;
}

Así conseguimos el construir el puntero que va hacia nuestra tabla de 3 entradas.

Si compilas usando 64 bits lo más probable es que falle aquí. Esto se debe a que los punteros en sistemas de 64 bits son de , obviamente, 64 bits y aquí usamos tipos de 32 bits. Usando la opción -m32 puede ayudar de momento
Ahora definimos una función común para poner los datos en las entradas

void ND::GDT::SetGate(int num, uint32_t base, uint32_t limit, uint8_t access,uint8_t gran)
{
gdt[num].base_low=(base & 0xFFFF);
gdt[num].base_middle=(base >> 16) & 0xFF;
gdt[num].base_high=(base >> 24) & 0xFF;
gdt[num].limit_low=(limit & 0xFFFF);
gdt[num].granularity=(limit >> 16) & 0x0F;
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access=access;
}

Y la llamamos 3 veces desde la función de instalar

ND::GDT::SetGate(0,0,0,0,0); /* NULL segmente entry */
ND::GDT::SetGate(1,0,0xFFFFFFFF,0x9A,0xCF); /* 4 GiB for Code Segment */
ND::GDT::SetGate(2,0,0xFFFFFFFF,0x92,0xCF); /* 4 GiB for Data segment */

Por último debemos decirle al procesador que tenemos una GDT, para que la cargue, y en nuestro caso al cargar el kernel con GRUB, sobreescribir la GDT de GRUB. Para cargar la GDT existe una instrucción en asm llamada lgdt (o lgdtl dependiendo de la sintaxis), vamos a usarla.

asm volatile("lgdtl (gp)");
asm volatile(
"movw $0x10, %ax \n"
"movw %ax, %ds \n"
"movw %ax, %es \n"
"movw %ax, %fs \n"
"movw %ax, %gs \n"
"movw %ax, %ss \n"
"ljmp $0x08, $next \n"
"next: \n"
);

Bien una vez hayamos terminado esto nuestro sistema ya contará con GDT. En el siguiente capítulo veremos la IDT, una tabla muy parecida a la GDT pero con interrupciones. Yo he puesto unos mensajes de estado y confirmación con la GDT así que NextDivel ahora luce así:


5 comentarios

  1.   Saeron dijo

    Quizás una estructura de 64 bits sea mas adecuada para los tiempos que corren, es un atraso seguir usando el 8086.

    1.    AdrianArroyoCalle dijo

      He estado buscando información sobre la GDT en x86_64 y creo que sigue el modelo antiguo con una flag especial. Se sigue usando una dirección de 32 bits. Ahora bien no sé exactamente como realizarlo correctamente. Unos links:
      http://wiki.osdev.org/Entering_Long_Mode_Directly
      http://f.osdev.org/viewtopic.php?f=1&t=16275

  2.   geronimo dijo

    Lo primero muy buenos tus aportes ,, pero creo que el titulo deveria ser
    “emulando a Richard Stallman ” o por lo menos eso creo ,,,
    Saludos

    1.    abimaelmartell dijo

      Linus creo el nucleo de Linux, Stallman creo GNU que son las herramientas y comandos Unix .

      El titulo es adecuado porque estan creando un nucleo.

      Un saludo!

  3.   Ruby dijo

    Muchas gracias por responder a todas mis preguntas y tenerme paciencia, de ensamblador solo se lo básico y de C casi nada, pero me gusta mucho, ahora estoy un poco confundido con el GDT, a ver si entendí.

    El GDT va a tener las los ‘descriptores’ globales a los cuales se puede acceder siempre y cualquier programa, y estos descriptores apuntan a la secciones donde esta (el programa) que se va a ejecutar? o es de otra forma.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*

*

  1. Responsable de los datos: Miguel Ángel Gatón
  2. Finalidad de los datos: Controlar el SPAM, gestión de comentarios.
  3. Legitimación: Tu consentimiento
  4. Comunicación de los datos: No se comunicarán los datos a terceros salvo por obligación legal.
  5. Almacenamiento de los datos: Base de datos alojada en Occentus Networks (UE)
  6. Derechos: En cualquier momento puedes limitar, recuperar y borrar tu información.