Mejorada reserva de memoria en las pilas de las CPUs

Hace unos días, tras una revisión atenta del código, nos dimos cuenta de que la técnica usada para la reserva de memoria de las cpus podría provocar que la pila de una cpu sobreescribiese a la de otra, así que decidimos mejorarla. Para ello, nos hemos basado en la técnica utilizada en las pilas de interrupción, en la función ​`​interrupt_stack_alloc()` basada en un array de punteros a pilas, con una posición por cpu.

Utilizando esta técnica, hemos vuelto a conseguir que Hurd SMP funcione en VirtualBox, arrancando las cpus de forma correcta.

Los próximos pasos se encargarán de mejorar la copia en memoria de la rutina ensamblador de la startup IPI, y de activar la paginación dentro de los AP.

 

Anuncios

Resueltas algunas erratas

Tras revisar el código de cpuboot.S añadido ayer, hemos corregido algunas erratas en el mismo, eliminando código innecesario y corrigiendo algunas situaciones anómalas.

  • En primer lugar, hemos eliminado el código referente al iplt, que ya estaba siendo inicializado en boothdr.S, y por tanto era innecesario.
  • También hemos eliminado la carga de la pila de interrupciones, dado que estaba sobreescribiendo la carga anterior de la pila principal de la cpu; aunque hemos dejado su declaración.
  • Y hemos corregido una pequeña errata en el cálculo del desplazamiento en la pila principal.

Con estos cambios, el código ya no funciona en VirtualBox (se puede solucionar temporalmente cambiado la carga de la pila principal stack_ptr, por la de la pila de interrupciones, _intstack, aunque no es una solución definitiva)

En lo que respecta al funcionamiento, estas correcciones no han alterado el funcionamiento actual del sistema, que sigue mostrando el número real de cpus existentes en la máquina, con el comando nproc

nproc_hurd2

Avances en la integración de las cpus

Esta semana, hemos estado experimentando con integración de las cpus dentro del sistema, y la configuración de las mismas.

En primer lugar, tras añadir una llamada a cpu_up() después de activar las cpus, hemos logrado que el sistema registre todas las cpus existentes en la máquina, y que el comando nproc muestre el número real de cpus del equipo.

También hemos estado cacharreando para añadir una pila de interrupciones a las cpus, para permitir que puedan ejecutar sus Rutinas de Atención a la Interrupción. Para conseguirlo, hemos seguido varios pasos:

  • En primer lugar, nos hemos basado en la rutina boothdr.S, para copiar los bloques de código referentes a la pila de interrupción, _intstack, en la rutina cpuboot.S que configura las cpus en modo protegido.
  • Posteriormente, hemos añadido una llamada a la función interrupt_stack_alloc(), que inicializa el array con la pila de interrupción de cada cpu. Esta llamada se ha colocado justo antes de invocar a mp_desc_init() (que inicializa los descriptores de la cpu) dado que esta función hace uso de ese mismo array para funcionar.

Este último cambio no ha producido ninguna mejora aparente, con lo cual habrá que esperar algún tiempo para comprobar si éste ha sido correcto.

Tras probar estos cambios en VirtualBox, hemos encontrado varias diferencias respecto a Qemu:

  • La anterior versión publicada en la rama smp, sin reservar la pila de interrupción, falla al arrancar. La última versión, reservando dicha pila, arranca correctamente
  • El servidor de red arranca correctamente y establece la conexión (en Qemu ese servidor fallaba al arrancar)

Hemos grabado un vídeo con el arranque de Hurd SMP en VirtualBox. En este caso, la máquina virtual solo dispone de 2 cpus.

Refactorizada función cpu_number()

En espera a comenzar la siguiente fase de desarrollo, hemos realizado algunas refactorizaciones dentro del código relativo a la función cpu_number(), que se encarga de obtener el kernel ID de la cpu actualmente activa.

Para ello, hemos añadido un nuevo array, apic2kernel[], que guarda la relación entre el APIC ID y el kernel ID, pero indexado por APIC ID. Con este nuevo vector, ya podemos obtener el Kernel ID de la cpu utilizando un simple acceso, en lugar de la búsqueda secuencial anteriormente utilizada.

Ya de paso, hemos podido implementar la versión ensamblador de esta misma función, CPU_NUMBER(), cuya implementación previa no era funcional.

En el proceso, también hemos podido detectar un problema en el acceso a la Local APIC de las cpus AP durante la función cpu_setup(), que impedía el acceso a dicho registro desde el puntero *lapic y, con esto, provocaba un error al actualizar el APIC ID de las cpus AP recientemente iniciadas. Tras generar un puntero temporal a partir de la dirección guardada en lapic_addr, hemos logrado acceder a la Local APIC de la CPU y obtener el nuevo APIC ID correctamente.

Estos cambios no han provocado ningún avance significativo en el arranque del sistema, pero hemos logrado ciertas optimizaciones y detectar algunos posibles problemas potenciales en la activación de las cpus.

Ahora estamos investigando sobre la configuración de los registros de la Local APIC, y la forma de añadir las nuevas cpus al scheduler, y hacerlas disponibles al sistema operativo.

También hemos mejorado la documentación en el README del proyecto, detallando los problemas a los que nos hemos enfrentado durante el desarrollo. Podéis acceder a la documentación en este enlace:

https://github.com/AlmuHS/GNUMach_SMP/blob/smp/README.md

 

 

 

Conseguida la activación de las cpus durante el arranque de Hurd

Continuando con el trabajo anterior, hemos implementado la activación de las cpus durante la secuencia de arranque de gnumach.

Inspirándonos en el trabajo realizando en el modelo de pruebas, Min_SMP, hemos modificado las funciones del fichero mp_desc.c para que se encarguen de activar las cpus mediante el envío de la startup IPI a cada una de ellas.
La startup IPI se ha asociado a una rutina en ensamblador que se encarga de pasar la cpu de modo real a modo protegido, a través de la carga de las tablas GDT e IDT. Una vez cargadas las tablas, y ya con el procesador en modo protegido, la rutina ensamblador llama a la función cpu_ap_main(), que se encarga de inicializar las estructuras del sistema, para indicar que la cpu ya está activa.

Tras probarlo en Qemu, podemos comprobar como las cpus se activan correctamente, aunque todavía no se integran dentro del sistema.

 

Con esto completado, y a falta de ciertas revisiones, podemos dar por completada la primera fase del desarrollo del proyecto.

Las siguientes fases del proyecto consistirán en configurar las Local APIC de los procesadores, e integrar los mismos dentro del kernel, para que esten disponibles para su uso por el resto de procesos del sistema.

 

 

Testeado arranque sin enumeración de cpus

Con algunos pequeños cambios, hemos testeado el código desactivando la lectura de las tablas ACPI (sin puntero a lapic y sin enumeración de cpus).

La prueba ha sido exitosa, demostrando que el microkernel es capaz de arrancar en el caso de que el número de cpus y la Local APIC estén sin definir.

Este aspecto es clave, en vistas a una posible implementación futura que leyera las tablas ACPI desde Hurd, arrancando las cpus después del arranque del sistema.

 

 

El arranque es ligeramente mas rápido que el anterior, demostrando cierta ralentización en el proceso de enumeración de las cpus.

Los errores en el arranque se mantienen igual que en el caso anterior, aislando la causa de nuestra implementación

 

 

 

 

 

Conseguido arranque de Hurd en modo SMP (con una sola cpu)

Tras varios días de trabajo, hemos logrado que gnumach sea capaz de arrancar en modo multiprocesador, con una sola cpu, bypaseando aquellas funciones que requieren datos de las estructuras de multiprocesador.

Con esto, Hurd consigue finalizar su arranque, aunque con algunos errores y cierta lentitud.

 

También hemos logrado resolver algunos problemas con la reserva de memoria, consiguiendo con esto generar un puntero a la zona de memoria donde está mapeada la Local APIC del procesador actual.  Y, con esto, hemos logrado implementar la función cpu_number(), que devuelve el Kernel ID de la cpu actualmente activa.

Haciendo algunas pruebas, cuyos mensajes se ven en el vídeo, hemos comprobado que el acceso a la Local APIC con dicho puntero funciona, y cpu_number()  muestra el kernel ID de la cpu sin errores.

Aparte de esto, hemos logrado realizar con éxito la enumeración de las ioapic, cambiando la lista enlazada inicialmente utilizada, por un array estático.

Con estos cambios, ya podemos saber cuantas y cuales cpus existen en la máquina, saber su APIC ID, y tener acceso a la Local APIC del procesador actual; pasos previos para poder envíar la Startup IPI a los procesadores, para poder activarlos.

La siguiente fase tratará de arrancar las cpus mediante el envío de la startup IPI, y de configurar los registros de sus Local APIC.