Notas de estudio del sistema operativo Linux (dos) operación del kernel

Prefacio

  En lo anterior, analizamos el proceso desde que se presiona el botón de encendido hasta que se completa la carga del BootLoader. Una vez finalizada la carga, el kernel de Linux debe iniciarse oficialmente y, antes de eso, debe completarse primero el cambio del modo real al modo protegido. Este artículo analiza principalmente las siguientes partes

  • Alternancia de interrupciones nuevas y antiguas
  • Abrir A20
  • Entrar en la función principal
  • Inicialización del kernel

  De hecho, todavía hay mucho contenido en todo el proceso, como la verificación de varios dispositivos de hardware, etc., que se omiten aquí. Ahora comience a sumergirse en el océano del código fuente de Linux.

Alternancia de interrupciones nuevas y antiguas

  Obviamente, la interrupción en modo real no puede ser la misma que la interrupción en modo protegido, por lo que debemos cerrar la interrupción anterior (cli) y establecer una nueva interrupción (sti). mainEl sistema de servicio de interrupción cuya función puede adaptarse al modo protegido se reconstruye antes de que se active la interrupción. En ese momento, el programa de servicio que responde a la interrupción ya no será el programa de servicio de interrupción proporcionado por el BIOS, sino el programa de servicio de interrupción proporcionado por el propio sistema.

  cli y sti siempre aparecen en ambos extremos de un proceso de operación completo, el propósito es no interrumpir la intervención durante este período. El siguiente código preparará el sistema operativo para ingresar al modo protegido. Aquí se realizará el traspaso de la tabla de vectores de interrupciones en modo real y la tabla de descriptores de interrupciones (IDT) en modo protegido . Imagínense, si no hay cli, y ocurre una interrupción, si el usuario toca accidentalmente el teclado, la interrupción será interrumpida. Debe enfrentar la situación embarazosa de que el mecanismo de interrupción del modo real ha sido abolido y el mecanismo de interrupción del modo protegido no se ha completado. El resultado es un bloqueo del sistema. cli y sti aseguran que IDT se pueda crear completamente durante este proceso para evitar la creación incompleta de IDT o el uso mixto de mecanismos de interrupción nuevos y antiguos debido a una entrada de interrupción impredecible.

#boot/setup.s
……
do _move:
mov es,ax!destination segment
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax!source segment
sub di,di
sub si,si
mov cx,#0x8000
rep
movsw
jmp do_move

  El código anterior completa principalmente una tarea: copie el programa del núcleo ubicado en 0x10000 en la ubicación inicial de la dirección de memoria en 0x00000. En la sección anterior, analizamos el mapa de memoria en modo real, donde se almacenaron originalmente la tabla de vectores de interrupción y el área de datos de la BIOS creada por la BIOS. Esta acción de copia cubre completamente la tabla de vectores de interrupción del BIOS y el área de datos del BIOS, por lo que ya no existen. Los beneficios de esto son los siguientes:

  1. La supresión de la tabla de vectores de interrupciones del BIOS es equivalente a la supresión de la rutina del servicio de interrupciones en modo real proporcionada por el BIOS.
  2. Recupere el espacio de memoria ocupado por el programa que acaba de terminar su vida útil.
  3. Deje que el código del kernel ocupe la primera, natural y ventajosa posición de la dirección de memoria física.

  En este momento, están a punto de aparecer roles importantes, son la tabla de descriptores de interrupciones IDT y la tabla de descriptores globales GDT .

  • GDT (Global Descriptor Table, Global Descriptor Table), la única matriz en el sistema para almacenar el contenido del registro de segmento (descriptor de segmento), coopera con el programa para realizar el direccionamiento de segmento en modo protegido. Es de gran importancia en la conmutación de procesos del sistema operativo. Puede entenderse como una tabla de directorio total de todos los procesos, en la que cada tarea (tarea) tabla de descriptor local (LDT, tabla de descriptor local) dirección y segmento de estado de la tarea (TSS, Task Segmento de estructura), complete el direccionamiento, protección de campo y recuperación de campo de cada segmento del proceso. GDTR es el registro de direcciones base de GDT, cuando el programa hace referencia a un descriptor de segmento a través del registro de segmento, necesita obtener la entrada GDT, y la entrada identificada por GDTR es esta entrada. Una vez que el sistema operativo inicializa el GDT, se puede utilizar la instrucción LGDT (Load GDT) para cargar la dirección base del GDT al GDTR.

  • IDT (Tabla de descriptores de interrupciones), guarda las direcciones de entrada de todas las rutinas del servicio de interrupciones en modo protegido, similar a la tabla de vectores de interrupciones en modo real. IDTR (registro de direcciones base IDT), guarde la dirección inicial de IDT.

  El mecanismo de interrupción de 32 bits y el mecanismo de interrupción de 16 bits son bastante diferentes en principio. El más obvio es que el mecanismo de interrupción de 16 bits usa la tabla de vectores de interrupción. La posición inicial de la tabla de vectores de interrupciones es 0x00000, que es fija; el mecanismo de interrupciones de 32 bits usa la tabla de descriptores de interrupciones (IDT). No es fijo, el diseñador del sistema operativo puede organizarlo de manera flexible de acuerdo con los requisitos de diseño y su posición está bloqueada por IDTR. GDT es una estructura de datos que gestiona descriptores de segmento en modo protegido y es de gran importancia para el funcionamiento del propio sistema operativo, así como para los procesos de gestión y programación.

  En este momento, el kernel no se está ejecutando realmente y todavía no hay proceso, por lo que el primer elemento del GDT creado está vacío, el segundo elemento es el descriptor del segmento de código del kernel, el tercer elemento es el descriptor del segmento de datos del kernel y el resto está vacío. Aunque se ha establecido IDT, en realidad es una tabla vacía, porque la interrupción está cerrada actualmente y no hay necesidad de llamar a la rutina del servicio de interrupciones. Lo que se refleja aquí es la idea de "obtener suficientes datos".

El proceso de creación de estas dos tablas se puede entender como un proceso de dos pasos:

  1. Al diseñar el código del kernel, se han escrito las dos tablas y también se han escrito los datos necesarios. El área de datos aquí es un área de datos formada en el código fuente del kernel, compilada y cargada directamente en la memoria. El apuntado del registro especial se completa con las instrucciones lidt y lgdt en el programa
  2. Apunte los registros especiales (IDTR, GDTR) a la mesa.

A20

  Habilitar A20 es una acción icónica, lzma_decompress.img invocada por el real_to_protinicio antes mencionado . Activar A20 significa que la CPU puede realizar direccionamiento de 32 bits y el espacio máximo de direccionamiento es de 4 GB. Observe el cambio en el rango de memoria en la Figura 1-19: de 5 F a 8 F, es decir, 0xFFFFFFFF (4 GB).

  En modo real, cuando la dirección del programa es superior a 0xFFFFF, la CPU "retrocederá" a la dirección de memoria de la dirección de inicio (tenga en cuenta que la tira solo 20 líneas de dirección del
miembro inferior, 0xFFFFF + 1 = 0x00000, el desbordamiento más alto) . Por ejemplo, la dirección máxima permitida del registro de segmento del sistema (como CS) es 0xFFFF, y el desplazamiento máximo permitido del puntero de instrucción (IP) también es 0xFFFF. La dirección absoluta máxima determinada por los dos es 0x10FFEF, lo que significa que el programa puede ser El rango de direccionamiento resultante en modo real es de casi 64 KB más que 1 MB (algunos programas con requisitos especiales de direccionamiento aprovechan esta función). De esta forma, la habilitación de la línea de dirección A20 aquí equivale a apagar el mecanismo de "reversión" del direccionamiento de la CPU en modo real. Lo siguiente es utilizar esta función para verificar si la línea de dirección A20 está realmente abierta. Tenga en cuenta que el código aquí no se ejecuta en este momento, pero se utiliza en el proceso de ejecución del cabezal posterior para detectar si está en modo protegido.

#boot/head.s
……
xorl %eax,%eax
1:incl%eax#check that A20 really IS enabled
movl %eax,0x000000#loop forever if it isn't
cmpl %eax,0x100000
je 1b
……

  Si A20 no está encendido, la computadora está en un modo de direccionamiento de 20 bits y el direccionamiento más allá de 0xFFFFF debe "retroceder". Un caso especial es que 0x100000 retrocederá a 0x000000, es decir, el valor almacenado en la dirección 0x100000 debe ser exactamente el mismo que el valor almacenado en la dirección 0x000000. Al escribir un dato en la ubicación de memoria 0x000000 y luego comparar los datos aquí y 1 MB (0x100000, tenga en cuenta que ha excedido el rango de direccionamiento del modo real) es el mismo, puede verificar si la línea de dirección A20 se ha abierto.

Entrar en la función principal

  Aquí se trata de un conocimiento del hardware: en el sistema X86, el chip de control de terminal utilizado se llama 8259A. Este chip es un controlador de interrupciones que puede ser controlado por un programa. Un solo 8259A puede administrar interrupciones de prioridad vectorial de 8 niveles y puede conectarse en cascada a un sistema de interrupción de prioridad vectorial de 64 niveles como máximo sin agregar otros circuitos. En el modo de protección de la CPU, Intel reserva int 0x00 ~ int 0x1F como interrupciones internas (no enmascarables) e interrupciones anormales. Si el 8259A no se reprograma, se sobrescribirá la interrupción int 0x00 ~ int 0x1F. Por ejemplo, IRQ0 (interrupción del reloj) es la octava interrupción (int 0x08), pero en el modo protegido, este número de interrupción es el "Doble Fallo" reservado por Intel. Por lo tanto, los números de interrupción correspondientes al IRQ0x00 ~ IRQ0x0F original deben ser redistribuidos a través de la programación 8259A, es decir, en el modo de protección, los números de interrupción de IRQ0x00 ~ IRQ0x0F son int 0x20 ~ int 0x2F.

  El programa de instalación utiliza el siguiente código para establecer el modo de trabajo de la CPU en modo de protección. Aquí implica un registro CR0: No. 0 registro de control de 32 bits, ponga la bandera de control del sistema. El bit 0 es el indicador PE (habilitación del modo protegido) Cuando se establece en 1, la CPU está funcionando en modo protegido, y cuando se establece en 0, está en modo real. Configure el bit 0 (PE) del registro CR0 en 1, es decir, configure el modo de trabajo del procesador en modo de protección. Cuando el modo de trabajo de la CPU se cambia a un modo protegido, una característica importante es decidir dónde ejecutar el programa de acuerdo con el GDT. Como se mencionó anteriormente, GDT ya ha escrito los datos inicialmente, estos se utilizarán para completar el salto del programa de instalación al programa principal.

#boot/setup.s
mov ax,#0x0001!protected mode(PE)bit
lmsw ax!This is it!
jmpi 0,8!jmp offset 0 of segment 8(cs)

  El programa principal es el último paso antes de entrar en principal. El encabezado crea un mecanismo de paginación del kernel en el espacio, es decir, crea la tabla de directorio de páginas, la tabla de páginas, el búfer, GDT, IDT en la ubicación de 0x000000 y sobrescribe el espacio de memoria ocupado por el código que ha sido ejecutado por el programa principal. Esto significa que el programa principal se descartará y la función principal comenzará a ejecutarse. El mecanismo de paginación específico es más complicado, por lo que se planea introducirlo por separado en la parte posterior de administración de memoria .

  Head construye IDT, de modo que la estructura general del mecanismo de interrupción se configura primero (la rutina de servicio de interrupción real está conectada a la función principal), y todas las rutinas de servicio de interrupción apuntan al mismo segmento de la rutina de servicio que solo muestra una línea de información y retornos. En términos de tecnología de programación, este tipo de operación de inicialización no solo puede evitar la confusión lógica causada por la sobrescritura involuntaria de código o datos, sino que también proporciona avisos oportunos sobre errores de operación en el proceso de desarrollo. IDT tiene 256 entradas, de las cuales solo se utilizan unas pocas docenas. En caso de uso indebido de descriptores de interrupción no utilizados, estos mensajes de aviso pueden recordar a los desarrolladores que presten atención a los errores.

  Además, el programa principal debe abolir el GDT existente y volver a crear el GDT en la nueva ubicación del kernel. La ubicación original de GDT es el conjunto de datos en setup.s al diseñar el código. En el futuro, la ubicación de memoria de este módulo de configuración se sobrescribirá al diseñar el búfer. Si no se cambia la ubicación, el contenido del GDT definitivamente será sobrescrito por el búfer en el futuro, lo que afectará el funcionamiento del sistema. De esta manera, el único lugar seguro en toda la memoria en el futuro es donde está head.s ahora.

  Los siguientes pasos incluyen principalmente

  1. Inicializar registros de segmento y apilar
  2. Borre el registro eflag y el área de datos no inicializados del kernel
  3. Llame para decompress_kernel()descomprimir la imagen del kernel y saltar a 0X00100000.
  4. El registro de segmento se inicializa al valor final y el campo BSS se rellena con 0
  5. Inicializar la tabla de la página del kernel temporal

  Después de que finalmente se inicializa el mecanismo de paginación, la bandera PG (Paginación) se establecerá en 1, lo que indica que el modo de mapeo de direcciones adopta el mecanismo de paginación, y finalmente salta a la función principal y el kernel comienza a inicializarse.

Inicialización del kernel

  Tenga en cuenta que hasta ahora no hemos abierto interrupciones, y se debe completar una serie de inicializaciones a través de la función principal antes de abrir una nueva interrupción, para que el kernel se esté ejecutando oficialmente. Esta parte incluye principalmente:

  1. Compilar la pila en modo kernel para el proceso 0
  2. Borrar el registro eflags
  3. Llamar setup_idt()para llenar IDT con manejador de interrupciones vacío
  4. Pasar los parámetros obtenidos en la BIOS al marco de la primera página
  5. Llenar registros con tablas GDT e IDT

  Una vez completados, el kernel se está ejecutando oficialmente y se crea el proceso 0.

para resumir

  Este artículo presenta todo el proceso de conmutación del modo real al modo protegido, completa la carga del kernel y comienza a prepararse formalmente para crear el proceso 0. El seguimiento continuará analizando todo el proceso de inicio del kernel para crear los procesos 0, 1 y 2. Durante la introducción de este artículo, se ignora una gran cantidad de código ensamblador y algunos conocimientos que son muy importantes pero que no forman parte del proceso básico. Aquellos que estén interesados ​​en comprender pueden hacer un estudio e investigación más profundos basados ​​en los enlaces del artículo, el código fuente al final del artículo y los materiales de referencia.

Información de origen

[1] GURB 2

[2] syslinux

Referencia

[1] Interior de Linux

[2] Conocimiento profundo del código fuente del kernel de Linux

[3] El arte del diseño del kernel de Linux

[4] Geek Time habla sobre el sistema operativo Linux

Supongo que te gusta

Origin blog.csdn.net/u013354486/article/details/105828471
Recomendado
Clasificación