Pequeños arreglos y refactorizaciones

Mientras intentamos localizar la causa del fallo con mas de 2 cpus, hemos realizado algunas pequeñas reparaciones y refactorizaciones en el código.

  • En primer lugar, hemos movido el código que activa la paginación a una función en C, recuperando las llamadas originales provenientes de model_dep.c

    El código resultante se ha añadido a una nueva función, llamada paging_setup(), que se invoca desde cpuboot.S justo antes de llamar a cpu_ap_main(), quien se encarga de iniciar el resto de configuraciones de la cpu.

  • En segundo lugar, hemos retirado una instrucción pushl innecesaria, que provocaba que todos los AP compartieran el mismo valor en EIP, la pila del procesador. Tras esta modificación, cada cpu tiene su propio EIP, asegurando así que cada cpu tenga su propia pila.

Estas modificaciones no han provocado ninguna variación significativa en el comportamiento del sistema, que sigue manteniendo los problemas anteriores de concurrencia y problemas al usar ncpus <> 2.

Próximamente iniciaremos la depuración exhaustiva del código, para intentar encontrar la causa que provoca el fallo del arranque con mas de 2 cpus.

Anuncios

Integradas cpus en el microkernel

Tras varios meses de trabajo, hemos logrado incorporar las cpus al microkernel, y que empiecen a trabajar junto a la cpu principal.

Para ello, hemos tenido que activar la paginación en los procesadores AP, para posibilitar que la cpu pueda acceder al puntero de la Local APIC, y así ejecutar la función cpu_number(), necesaria para que la función slave_main() añada el AP al microkernel.

Para activar la paginación, es necesario inicializar los registros CR0, CR3 y CR4; indicando la dirección del directorio de páginas (CR3), las opciones especiales utilizadas en Mach (CR4), y activando el uso de la paginación (CR0).

Este proceso se ha realizado utilizando como base el código indicado en model_dep.c, que realiza la configuración de la paginación en el procesador principal (BSP); y la rutina cpuboot.S, que se lanza justo después de la startup IPI, y ya estábamos usando para pasar la cpu de modo real a modo protegido.

El código resultante se ha incorporado cpuboot.S. Debido a problemas con la inicialización del registro CR0, hemos tenido que hardcodear sus valores.

Debido a que Mach usa segmentación con base en 0xC0000000, es necesario hacer uso de un mapeado temporal, que remapea las direcciones a partir de 0xC0000000 en las direcciones 0x00000000 en adelante. Para conseguirlo, hemos mantenido el mapeado temporal ya realizado en model_dep.c (para el procesador principal), desplazando su desactivación hasta el final del arranque de las cpus, en start_other_cpus().

Tras esto, la paginación ya se activa correctamente, y es capaz de acceder correctamente a las posiciones indicadas por los punteros.

Pero el procesador todavía conserva las GDT e IDT temporales, con lo cual no puede cargar las LDT necesarias para incorporarse al kernel. Para resolverlo, hemos añadido dos llamadas, a gdt_init() e idt_init(), en la función cpu_setup(), ejecutada por el AP justo después de la rutina cpuboot.S.

Tras completar estos pasos, ya hemos podido reactivar la llamada a la función slave_main(), dentro de la misma función cpu_setup(), esta vez de forma exitosa.

Finalmente, después de todo este trabajo, las cpus parecen haberse incorporado correctamente al kernel, y funcionar correctamente. Pero, debido a la falta de control de la concurrencia en varias cpus, varios servicios fallan durante el arranque.

Con una tarjeta de red incompatible con Mach, el netdde falla y nos permite arrancar, aunque la pantalla de bienvenida no responde a las ordenes del teclado.

Con una NIC compatible, el proceso DHCP se cuelga, bloqueando el arranque. Configurando IP fija para evitar el DHCP, el arranque avanza un poco mas, pero se termina bloqueando al cargar el ssh

Durante el desarrollo también se han generado dos nuevos problemas: el arranque se detiene al usar una única cpu, y al usar mas de 2 cpus.

Los siguientes pasos tratarán de controlar la concurrencia de los hilos, introduciendo exclusión mutua; hacer algunas mejoras y refactorizaciones en el código para mejorar su funcionamiento; y resolver los problemas en ncpu = 1 o ncpu > 2.

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.

 

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.