[Ostep] 03 Mecanismo de ejecución directo restringido por CPU virtualizado

Ejecución directa restringida

Hemos aprendido el concepto abstracto de proceso y entraremos en contacto con el mecanismo específico a continuación. Como se mencionó anteriormente, el mecanismo son los detalles de implementación de una función, y la estrategia es similar a la programación del mecanismo.

El primer mecanismo para aprender se llama ejecución directa limitada (ejecución directa limitada) .

Como excelente sistema operativo, debe tener cuidado de proporcionar instrucciones que puedan causar peligro (por ejemplo, operaciones de E / S en el disco duro, llamadas instrucciones privilegiadas ) Estas operaciones están encapsuladas como llamadas al sistema por el sistema operativo .

Pero hay otro problema aquí: después de todo, la llamada al sistema es una larga secuencia de instrucciones y parece que no es diferente del programa del usuario. Después de todo, cuando la CPU ejecuta una instrucción, no considera de quién es la instrucción. Además, este "quién" es simplemente nuestra abstracción. Los procesos (o programas) no tienen vida. No hay proceso a nivel de hardware. Son sólo datos de instrucciones para ser ejecutados en la memoria.

Entonces, ¿cómo debería proteger el sistema operativo las instrucciones privilegiadas? ¿Qué sucede cuando el programa de usuario llama a una instrucción privilegiada?

Modo kernel y modo de usuario

Las CPU modernas tienen múltiples niveles de niveles de ejecución de instrucciones, es decir, se pueden ejecutar niveles específicos de instrucciones en diferentes niveles, estos niveles se pueden dividir en dos modos: modo kernel y modo usuario .

Estos niveles de ejecución son implementados por hardware, la CPU verifica los valores en registros específicos para determinar el nivel de ejecución actual, todas estas lógicas están diseñadas en hardware.

Cuando la máquina se inicia, el programa de arranque cargará el sistema operativo. En este momento, el sistema operativo se ejecuta en modo kernel. A partir de ese momento, el sistema operativo tiene la última palabra sobre el nivel de ejecución de cualquier programa de usuario.

Al igual que la clase dominante de la sociedad humana, si no permiten que la gente use sus privilegios, la gente nunca tendrá la oportunidad de convertirse en la clase dominante. En otras palabras, cualquier programa que se ejecute en modo kernel tiene la oportunidad de convertirse en la clase dominante. Esto me recuerda el poder de veto de un voto de los cinco miembros permanentes de las Naciones Unidas. Solo cuando los cinco miembros permanentes estén de acuerdo, otros países pueden abolir el poder de veto de un voto de los cinco miembros permanentes. De lo contrario, otros países no lo harán tienen alguna posibilidad, porque los cinco miembros permanentes pueden abolir directamente la propuesta de veto de un voto. Veto de un voto ".

Este es el significado de la llamada "ejecución directa restringida".

Para responder a la segunda pregunta, ¿qué sucede cuando el programa de usuario llama a una instrucción privilegiada?

En términos generales, cuando una instrucción de un proceso no coincide con el nivel de ejecución actual, la CPU será anormal y el sistema operativo puede optar por terminar el proceso en este momento.

La comprensión aquí puede referirse a la pregunta sobre Zhihu: ¿Cómo conoce la computadora el modo de usuario y el modo de kernel?

trampa

Hablando de esto, está muy claro que los programas de usuario no pueden ejecutar directamente instrucciones privilegiadas, y las funciones que necesitan están encapsuladas como llamadas al sistema por el sistema operativo y abiertas al mundo exterior. El programa de usuario solo necesita llamar a estas llamadas al sistema para realizar las funciones correspondientes.

Sin embargo, ¿cómo funciona? ¿Qué sucede cuando un proceso de usuario llama a una llamada al sistema?

En primer lugar, debemos averiguar una cosa: ¿dónde está el código binario de estas llamadas al sistema?

Hay algo llamado tabla de trampas, que le dice al hardware qué código debe ejecutarse cuando ocurre una excepción, interrupción o llamada al sistema. La tabla de capturas la especifica el sistema operativo (con algunas instrucciones especiales privilegiadas) cuando se inicia la máquina. Estos códigos se denominan controladores de trampas .

Con la tabla de trampas, el hardware sabe dónde ejecutar el código cuando se produce una llamada al sistema. Entonces, ¿cómo activamos esta llamada?

Para ejecutar llamadas al sistema, el programa de usuario debe ejecutar instrucciones atrapadas . Al mismo tiempo, de acuerdo con las convenciones del sistema operativo, el programa coloca algunos parámetros y números de llamada necesarios (especificar qué llamada del sistema llamar) en el espacio de memoria acordado (o registrar ).) pulg. (Aquí se hace referencia a como entrar en el sistema operativo )

Después de que se ejecuta la instrucción, dado que la llamada al sistema (o el controlador de trampas) es una especie de llamada al proceso, el hardware será responsable de guardar el estado del proceso (por ejemplo, guardarlo en la pila del kernel), y luego ingresará al modo kernel y saltar Vaya a un controlador de trampa específico para iniciar la ejecución.

Una vez que finaliza el controlador de trampas, el sistema operativo es responsable de ejecutar una instrucción de retorno de trampa , restaurar el estado del proceso, reducir el nivel de ejecución de la instrucción, ingresar al modo de usuario y ejecutar el código del siguiente proceso de usuario.

Lo anterior es una llamada al sistema completa.

Cambio de contexto

Cambio de contexto

Enfoque cooperativo

El sistema operativo puede programar procesos para determinar si un proceso debe ejecutarse en ese momento. El programa que logra esta estrategia se denomina planificador .

Pero debe tenerse en cuenta que cuando se está ejecutando un programa de usuario, el sistema operativo no se puede ejecutar. Imagine que todas las instrucciones del programa de usuario se ejecutan en la CPU. Esto obviamente no tiene nada que ver con el sistema operativo ni un centavo, lo que significa que si el sistema operativo no tiene derechos de control, entonces la CPU no puede ejecutar el planificador del sistema operativo Una clase gobernante sin capacidad de ejecución no tiene sentido.

¿En qué circunstancias se ejecutará el sistema operativo?

Supongamos que, con base en lo que acabamos de aprender, se puede decir que cuando la máquina se inicia, el sistema operativo se carga y realizará un trabajo de inicialización, incluida la tabla de trampa de inicialización que acabamos de mencionar. En este momento, el sistema operativo debe estar corriendo. de.

Luego, cuando el programa de usuario entra en el sistema operativo (por ejemplo, se genera una excepción o se produce una llamada al sistema), el hardware activa los controladores de capturas. Los códigos de estos controladores de capturas son parte del sistema operativo, por lo que el sistema operativo es también se está ejecutando en este momento.

Sin embargo, si el programa de usuario se comporta muy bien, no se genera ninguna excepción y no se realizan operaciones privilegiadas.

Tome el ejemplo más simple, cayendo en un bucle infinito:

int main(){
    
    
    while(1);
    return 0;
}

De acuerdo con la suposición que acabamos de hacer, el sistema operativo nunca podrá obtener el control, lo que significa que el sistema operativo no podrá controlar una máquina que está atrapada en un bucle infinito, o que funciona bien y no hace que el sistema funcione. llamadas.

Esto se denomina enfoque cooperativo , es decir , el sistema operativo asume que cada proceso es amigable y cederá la CPU periódicamente. Generalmente, este sistema proporcionará una llamada al sistema para ceder la CPU, y esta llamada no hace nada. solo caerá en el sistema operativo.

Esta forma de recuperar el control no funciona bien, si el proceso se niega a realizar llamadas al sistema y no sale mal, el sistema operativo nunca se ejecutará y no podrá hacer nada.

Manera no cooperativa

Así que tenemos una forma no cooperativa , utilizando un dispositivo de reloj de hardware adicional para emitir periódicamente una interrupción del temporizador para atrapar el sistema operativo. Deje que la CPU ejecute el código de un sistema operativo específico y permita que el sistema operativo tome el control para hacer lo que quiera. Cabe señalar aquí que cuando se produce una interrupción, el hardware debe guardar el estado actual del proceso para que pueda restaurarse al regresar.

Aquí, el "código específico del sistema operativo" ejecutado por la CPU debido a la interrupción del reloj es el manejador de trampas. Este método no cooperativo es realmente equivalente a permitir que un "programa" llame periódicamente a la llamada al sistema utilizada para ceder la CPU.

Guardar y restaurar contexto

A continuación, describiremos un cambio de contexto por completo.

Cuando ocurre una interrupción del reloj, el hardware guardará el estado del proceso actual del proceso A en la pila del kernel , y luego el sistema operativo recuperará el control.

El sistema operativo hará algo, por ejemplo, de acuerdo con la estrategia del planificador, hemos decidido cambiar al proceso B.

Lo siguiente ejecutará un segmento de código utilizado para cambiar de contexto, por ejemplo, llamarlo switch().

switch()Se guardará el estado del proceso del proceso A como una estructura para mantener una lista (vuelve a la siguiente recuperación), y luego ubicar la estructura del proceso se devuelve al registro B de esta lista.

Luego regresa de la trampa El proceso B restaurará los registros de su pila de kernel, y luego comenzará a ejecutar su código, como si fuera el proceso B el que acaba de ingresar al kernel.

Este es un proceso de cambio de contexto completo.

Preste especial atención al proceso de guardar el estado del proceso dos veces, la primera vez es para la ejecución del manejador de trampas, el proceso de guardar el estado del proceso en la pila del kernel. La segunda vez es el proceso en el que el sistema operativo guarda el estado del proceso como una estructura en la lista mantenida por el sistema operativo para el cambio de contexto. La segunda operación cambia directamente la posición en la memoria real del proceso que se ejecuta a continuación.

cambiar de rutina

# void swtch(struct context **old, struct context *new);
#
# Save current register context in old
# and then load register context from new.
.globl swtch
swtch:
# Save old registers
movl 4(%esp), %eax # put old ptr into eax
popl 0(%eax) # save the old IP
movl %esp, 4(%eax) # and stack
movl %ebx, 8(%eax) # and other registers
movl %ecx, 12(%eax)
movl %edx, 16(%eax)
movl %esi, 20(%eax)
movl %edi, 24(%eax)
movl %ebp, 28(%eax)

# Load new registers
movl 4(%esp), %eax # put new ptr into eax
movl 28(%eax), %ebp # restore other registers
movl 24(%eax), %edi
movl 20(%eax), %esi
movl 16(%eax), %edx
movl 12(%eax), %ecx
movl 8(%eax), %ebx
movl 4(%eax), %esp # stack is switched here
pushl 0(%eax) # return addr put in place
ret # finally return into new ctxt

Supongo que te gusta

Origin blog.csdn.net/qq_16181837/article/details/112295679
Recomendado
Clasificación