Revisión en base a preguntas, del modo de trabajo protegido... x86 1. ¿En qué se distingue el modo Protegido del...

Anuncio
Revisión en base a preguntas, del modo de trabajo protegido en los procesadores
x86
1. ¿En qué se distingue el modo Protegido del modo Real?
En que el modo Protegido ofrece mecanismos de gestión de la protección de
memoria, mientras que el modo Real no.
2. ¿Qué significa protección de la memoria?
Significa que la memoria va a tener acceso limitado, de manera que no cualquier
proceso va a poder acceder a cualquier zona de la memoria.
3. ¿Cómo se realiza esa protección?
En primer lugar la memoria se divide en zonas, que llamaremos Segmentos. Cada
segmento estará “descrito” por un Descriptor. En el descriptor de un segmento se
indicará el Privilegio que tiene que tener un proceso para acceder a esa zona de
memoria y cuales son las restricciones de ese acceso (lectura, modificación, lecturaescritura)
4. ¿Qué es el privilegio de los procesos?
En el modo protegido los procesos se ejecutan con un “nivel de privilegio”. El
procesador considera 4 niveles de privilegio [0,1,2,3] donde el mayor nivel se le
asigna a 0 y el menor nivel se le asigna a 3. Un proceso con nivel de privilegio 0
tiene virtualmente todos los derechos de ejecución y acceso sobre cualquier zona de
memoria. Un proceso con nivel de privilegio 3 sólo tendrá derechos de acceso sobre
aquellas zonas que lo permitan. Algunas instrucciones – sobre todo relacionadas con
la gestión del procesador, como modificar determinados registros críticos – sólo es
posible ejecutarlas en el modo de mayor privilegio.
5. ¿Cómo se sabe cual es el nivel de privilegio de un proceso?
Cuando un proceso se ejecuta, sus instrucciones se extraen de una zona de memoria
catalogada como zona de código en su descriptor. Un apuntador al descriptor de esa
zona de memoria está en el registro de segmento de código CS. En este mismo
registro de segmento, los dos bits menos significativos indican el nivel de privilegio
con que se ejecuta este proceso.
6. ¿Cómo accede un proceso a una zona de memoria determinada?
En primer lugar el proceso debe disponer de un Selector que apunta al descriptor de
segmentos de la zona a la que quiere acceder. Debe cargar ese selector en uno de los
registros de segmentos de datos DS,ES,FS,GS. Y a continuación invocará alguna de
las instrucciones de acceso a memoria en la que esté implicada ese registro de
segmento.
7. ¿Y ya accedemos a la zona de memoria, es decir al dato dentro de esa zona de
memoria?
No directamente. Primero se realizarán las comprobaciones y chequeos necesarios
para saber si el proceso tiene el nivel de privilegio suficiente para acceder a esa zona
de memoria.
8. ¿Qué chequeos son esos?
Pues lo primero es comprobar que el nivel de privilegio del descriptor de segmentos
DPL es compatible con el nivel de privilegio del proceso que pretende acceder a su
contenido.
9. ¿Y cómo se hace eso?
Se considera el Selector del segmento a acceder y su nivel de privilegio RPL y se
compara con el nivel de privilegio del proceso en ejecución CPL. El sistema se
queda con el mayor de estos dos valores. Max(RPL, CPL) y a continuación se
compara con el nivel de privilegio del descriptor de segmento de manera que solo si
se cumple DPL >= Max (RPL, CPL) se permitirá el acceso a contenido del
segmento.
10. ¿Pero entonces el Selector también tiene un nivel de privilegio?
En efecto. Y además si el nivel de privilegio del selector es menor que el nivel de
privilegio del proceso que lo va a utilizar, el que prima en el acceso a la zona de
memoria apuntada por el selector es el nivel de privilegio del selector.
11. Entonces, ¿aunque un proceso tuviera el máximo nivel de privilegio, si utilizara
un selector con el mínimo nivel de privilegio podría no tener acceso a una zona
de memoria?
En efecto, así podría ocurrir.
12. Pero ¿Qué es exactamente un selector, cómo se crea?
En cuanto a cómo se crea: un selector es creado por un proceso usando unas
instrucciones especiales. Cuando un proceso crea un selector puede asignarle un
nivel de privilegio inferior al suyo propio, en caso contrario el selector mantiene el
nivel de privilegio del proceso que lo crea. En cuanto a la razón por la que lo crea es
para permitir el acceso a otros procesos a una zona de memoria propia en las
condiciones que el proceso considere oportunas.
13. ¿Pero qué es exactamente un Selector?, insisto.
Un selector es un índice dentro de una tabla cuyos elementos son descriptores de
segmentos. En tiempo de compilación o usando instrucciones especiales, se crea una
entrada dentro de esa tabla de descriptores de segmentos conteniendo todas las
características necesarias para acceder a un segmento de memoria: dirección base,
longitud, tipos de datos, derechos de accesos, etc. Y luego se crea un selector que es
un índice, dentro de esa tabla, a la posición donde está el descriptor. El selector es lo
que deben cargar los procesos en sus registros de segmento para poder acceder al
descriptor del segmento.
14. ¿Tablas de descriptores? esto es nuevo.
Cada proceso en ejecución tiene a su disposición dos tablas de descriptores de
segmentos, que vienen a ser todos los recursos a los que “potencialmente” puede
acceder un proceso en ejecución. (“recursos” aquí es en un sinónimo de zonas de
memoria a las que podría acceder si cumpliese los requisitos). Estas dos tablas se
llaman GDT (Global Descriptor Table) y LDT (Local Descriptor Table).
15. ¿Dónde están estas dos tablas?
En memoria y son apuntadas por dos registros del procesador GDTR y LDTR.
16. ¿Cómo se inicializan estos registros?
El GDTR se inicializa desde el arranque del sistema operativo. LDTR se inicializa
cada vez que se realiza un cambio de contexto.
17. ¿Y esa diferencia por qué?
Porque GDT es la llamada tabla del sistema. Contiene descriptores útiles para el
sistema operativo algunos de los cuales son puestos a disposición de las
aplicaciones, otros son exclusivos del sistema.
18. ¿Cómo por ejemplo?
Como por ejemplo los descriptores de las tareas que están en ejecución, o los
descriptores del código del propio sistema operativo.
19. ¿Y LDT, qué contiene?
LDT contiene los descriptores particulares de cada tarea. Cada tarea necesita
manejar como mínimo el descriptor de su segmento de código y posiblemente el
descriptor de su segmento de datos. Esos descriptores estarán contenidos en la LDT.
20. ¿Y entonces cada tarea o proceso tendrá su propia LDT?
Correcto.
21. Entonces, recapitulo, los Selectores contienen un índice a una tabla que puede
ser la LDT o la GDT. ¿Cómo sabemos a qué tabla está apuntando?.
Un selector tiene tres campos: el índice (con 13 bits), el indicador de a qué tabla
apunta, con un bit, y el RPL o nivel de privilegio del selector, con dos bits. El
indicador de a qué tabla apunta puede ser 0 o 1. 0 indica que el selector apunta a la
GDT y 1 indica que la tabla apuntada es la LDT.
22. Sigo recapitulando. Cuando cargo un registro de segmento con un selector y
luego hago un acceso a memoria, el procesador va a memoria, a la dirección
apuntada por el registro de la tabla que esté apuntando el selector, trae los datos
del descriptor de segmento: dirección base del segmento, límite del segmento,
tipo del segmento, nivel de privilegio del segmento. Con esos datos realiza los
chequeos de acceso y si todo se cumple hace el acceso a memoria. ¿Tiene que
hacer todo esto cada vez que accede a memoria?
No todo. La primera vez que se accede a memoria con ese selector de segmento, sí
hay que hacerlo todo. Luego esos datos quedan almacenados en una sección oculta
del registro de segmento y ya no hay que acceder más a la tabla de descriptores
hasta que se cambia el Selector. El chequeo del privilegio de acceso sólo es
necesario realizarlo esa primera vez. En cambio el chequeo de los límites y el
chequeo del tipo de acceso lectura-escritura se realiza con cada instrucción.
23. Si el proceso quiere invocar a una rutina que está en otro segmento ¿cómo lo
hace.?
Para saltar a una rutina que está en otro segmento se invoca a una instrucción Call, o
a una instrucción Jmp con el formato “far”.Esto quiere decir que en la
especificación de la dirección de destino se le suministra un Selector (apuntando a
descriptor de segmento) y un desplazamiento.
24. ¿Aquí también se chequean privilegios?
Por supuesto. Para poder saltar a otra sección de código que está en otro segmento
hay que cargar un nuevo selector de segmento en el registro CS y por lo tanto se
realizan todos los chequeos. Pero hay una diferencia en el chequeo cuando se trata
de segmentos de código.
25. ¿Qué diferencia?
En primer lugar hay dos tipos de segmentos de código: Conforming y
NonConforming. Los segmentos de código NonConforming están previstos para que
un programa pueda dividirse en módulo. Para que desde un segmento de código
pueda saltarse a otro segmento de código etiquetado como NonConforming, ambos
deben tener el mismo nivel de privilegio CPL=DPL.
26. ¿Qué pasa con los segmentos Conforming?.
Estos están previstos para poner secciones de código a disposición de códigos de
menor nivel de privilegio. Para poder acceder a un segmento Conforming el CPL
del proceso de origen debe tener menor o igual nivel de privilegio que el DPL del
segmento de destino. Pero aunque el código de destino tenga un nivel de privilegio
superior se ejecutará con el nivel de privilegio igual al del proceso que invocó la
llamada.
27. ¿Esto quiere decir que un proceso que empieza a ejecutarse en un nivel de
privilegio nunca cambia ese nivel de privilegio ni aunque ejecute código del
sistema, por ejemplo?
Para permitir a un proceso cambiar de nivel de privilegio, mientras ejecuta, por
ejemplo, código del sistema, tiene que invocar a ese código de una manera especial,
usando un selector particular llamado “Puerta” (Gate).
28. ¿Qué es una Puerta?
Una puerta es un selector especial que se usa para invocar código que está en otro
segmento de memoria distinto al del proceso invocador y que le permite,
temporalmente, cambiar de nivel de privilegio.
29. Entonces si se ejecuta un Call usando un selector de puerta el proceso podría
cambiar a un nivel de ejecución más privilegiado.
Exacto. Si se cumplen con los chequeos necesario.
30. ¿Qué chequeos son esos?
Pues los mismos que para cualquier otro acceso: que el DPL del descriptor de puerta
sea mayor o igual al máximo entre el CPL(privilegio del proceso actual) y el
RPL(nivel de privilegio del selector de puerta). Pero esto sólo nos permitiría usar el
descriptor de puerta.
31. ¿Qué más hace falta?
El descriptor de puerta es un apuntador al descriptor de segmento de código de
destino. Para poder ejecutar ese código el CPL del proceso actual debe ser mayor o
igual que el DPL del descriptor de segmento de destino.
32. Todo esto es un poco rollo. En resumen: para que un proceso cambie de nivel de
privilegio, por ejemplo invocando a una rutina del sistema operativo, debe
hacerlo vía una puerta. Si no, aunque pueda invocar a un código con mayor nivel
de privilegio, se ejecutará con el nivel de privilegio del proceso invocante. ¿Vale
así?
Sí, siempre y cuando el código de destino esté en un segmento etiquetado como
conforming
33. Un asunto importante es la entrada salida. ¿Desde cualquier nivel de privilegio
se puede acceder a los puertos de entrada salida?
En principio sí, siempre y cuando el proceso en ejecución tenga un bit específico de
su registro de flags activado: el IOPL
34. ¿Entonces basta con activar ese bit para tener acceso a las instrucciones de
entrada salida?
Sí, solo que un proceso no puede modificar su propio IOPL a no ser que se esté
ejecutando en el nivel de privilegio máximo.
35. Entonces para que un proceso pueda ejecutar instrucciones de entrada salida,
digamos que tiene que nacer ya con ese bit activado o que un proceso de sistema
lo habilite.
Exactamente.
36. ¿Y entonces ya puede acceder a cualquier instrucción de entrada salida?
No. Entonces se le permite utilizar las instrucciones de entrada salida. Luego por
cada proceso hay una lista de direcciones de entrada salida a las que se le permite
acceder y otras a las que no se le permite acceder.
37. ¿Y dónde está esa lista de direcciones?
Esa lista de direcciones está en una estructura de datos que representa a un proceso
en ejecución y que se llama Task Stack Segment. Esta estructura de datos está
naturalmente descrita por un descriptor: Task Stack Descriptor que reside en la
GDT.
38. Hablemos un poco de las interrupciones.
La única diferencia frente al modo x86 es que el vector de interrupciones no está en
el primer kbyte de memoria, sino que está en una estructura llamada IDT, Interrupt
Descriptor Table.
39. ¿Es otra tabla de descriptores de segmento como la GDT y la LDT?
Correcto. Y está apuntada por un registro llamada IDTR. Sólo que los descriptores
que hay en la IDT no son simples descriptores de segmento, sino que son
descriptores de puerta. Porque se utilizan para invocar a códigos que podría
ejecutarse en un nivel de privilegio diferente al código que se está ejecutando
cuando se produce la interrupción.
Descargar