En la arquitectura x86 existen 4 niveles de privilegio, siendo el anillo 0 el más privilegiado y el anillo 3 el menos. En un sistema operativo tradicional el nivel de mayor privilegio es del kernel y el de menor nivel es el del usuario.
Al usar un SO de uso general, el código y datos del kernel utilizan descriptores de anillo 0, mientras que la aplicación de usuario utiliza el anillo 3. La capacidad de la GDT es de 8192 descriptores pero estos no se utilizan en general, usándose solo 4 descriptores, dos descriptores para datos y código de usuario, y dos más para datos y código del kernel. Estos descriptores se utilizan para el acceso a toda la memoria, es decir, que permiten acceder a los 4 GB (caso del direccionamiento de 32 bits).
El hecho de utilizar diferentes niveles de privilegio agrega latencias debido a que el procesador deberá corroborar que cada acceso de escritura/lectura/ejecución es válido. En un SO de uso general la utilización de diferentes niveles de privilegios es totalmente necesario, permite que se ejecuten varios procesos y que éstos no se solapen; y por otro lado que aplicaciones mal programadas no sobreescriban código del kernel.
Si suponemos que corremos una aplicación dedicada multihilo, que ésta se ejecuta sola en el sistema y que la aplicación fue escrita con suficiente cuidado como para no realizar escrituras sobre zonas prohibidas, será necesaria la protección?, suponiendo una situación así podemos simplificar bastante el SO.
Primero, tanto la aplicación de usuario como el código y datos del kernel estarían en el mismo nivel de privilegio , por lo tanto, cuando debemos realizar una llamada al sistema no habría ningún salto de nivel de privilegio y se podrían implementar simplemente con la instrucción “call”. Recordemos que actualmente para soportar las llamadas al sistema se utiliza una interrupción que permite realizar el salto de nivel de privilegio de anillo 3 a anillo 0 y es muy costosa.
Segundo, cuando los dispositivos de hardware producen interrupciones y nos encontramos ejecutando código de usuario se producirán saltos de niveles de privilegio. Ésto se debe a que dejamos de ejecutar código de anillo 3 para ejecutar una rutina de manejo de interrupción que se encuentra en anillo 0. Si la aplicación de usuario se ejecuta en el mismo nivel del kernel nos ahorramos la latencia que presenta saltar a un mayor nivel de privilegio.
En el caso de TORO, el kernel y la aplicación de usuario se ejecutan en anillo 0, por otro lado, como ambos se compilan juntos, las llamadas al sistema son implementadas como simples instrucciones “call” a funciones del kernel.
Matias E. Vara