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.