reflexiones sobre los sistemas operativos modernos

Durante mucho tiempo he estado jugando con el código de bajo nivel en una variedad de hardware embebido.
Mientras que generalmente encuentro código del kernel linux un ejemplo excepcional de arquitectura de software
y uno de los proyectos más limpios implementadas en el lenguaje C, hay
todavía mucho por hacer.

Voy a tratar de resumir mis impresiones sobre el desarrollo de kernel de Linux (y otros software />
de una implementación del núcleo del sistema operativo: arquitectónico emitida (que son en lengua
agnóstico) y la elección de un lenguaje de programación

Una. punto de discusión es cómo modular debe ser el kernel. Los dos modelos principales son el núcleo monolítico (donde todos los controladores se ejecutan en el mismo espacio de memoria virtual y por lo tanto no hay aislamiento entre el acceso a los recursos entre los conductores) y el enfoque microkernel donde
cada conductor se está ejecutando como un proceso en un espacio de direcciones independiente en modo de usuario.

Los problemas de los núcleos monolíticos

  • Todos los conductores tienen el mismo acceso a todos los recursos. Lo que significa que, si no confiamos en el código del controlador (por ejemplo, un controlador de código cerrado), todo nuestro sistema se ve comprometida. Por otra parte, cualquier error en el código del controlador es una raíz potencial exploit porque si logramos romper pila o forzar al código del kernel para saltar a un ayudante usermode, tenemos todos los privilegios posibles [ KernelExploits ].
  • Si un código de controlador cuelga, no podemos fácilmente reiniciarlo porque todo el código se ejecuta dentro del proceso individual y las acciones de las variables globales. Es decir, incluso si quitamos e insertamos el módulo, éste no garantiza que empezamos de nuevo con el estado limpio. Por otra parte, los bloqueos compartidos, en la mayoría de los casos prohibirnos hacerlo cuando las cosas van mal.

Por otro parte, microkernels tratan de resolver el problema con las siguientes medidas de seguridad:

  • Operando cada proceso en el modo de usuario para evitar que mira a escondidas en otros datos de los procesos mediante la explotación de modo supervisor
  • El principio de mínimos privilegios: sólo permitir el acceso a los recursos necesarios por parte del conductor (IO, GPIO, IRQ)
  • Reducir TCB (base informática de confianza) para la cantidad mínima de código
  • Usando las capacidades para controlar el acceso a los recursos. Las capacidades son, como, punteros al recurso deseado, con la propiedad controlada por el núcleo (o, en realidad, por el administrador de políticas que puede comprender múltiples entidades dentro y fuera del núcleo)
No existen varios micronúcleos. La mayor éxito comercial son los, por supuesto, los granos de QNX y Mach. Mientras QNX y Mach se pueden utilizar en un «puro» sentido microkernel – es decir, cuando nada se ejecuta en el kernel en modo suprevisor y tiene privilegios finales, las aplicaciones más prácticas incluyen rutinas y conductores en kernel (QNX permite interrumpir, y Max OS X kernel, XNU, depende en gran medida de esta característica)
Uno de los proyectos más interesantes son la L4 Fiasco.OC [ FO ] kernel de la TU Dresden y el Marco Genode de los laboratorios Genode

Fiasco.OC <. / span> no es particularmente interesante en sí (hay proyectos mucho más interesantes como l4.verified de NICTA que es formalmente prooven), pero tiene bastante conjunto de buenas propiedades

  • Es opensource. Bueno, algo así. El desarrollo se lleva a cabo a puerta cerrada y que es un poco de dolor de empujar un parche a aguas arriba, pero al menos podemos descargar el código fuente y adaptarlo para nuestros propósitos
  • Tiene L4Re (L4 Runtime Environment), que es un conjunto de bibliotecas para facilitar el llevar software de los entornos compatibles con POSIX convencionales
  • Puede ser utilizado para paravirtualize otros granos. En particular, existe la [ L4Linux ] proyecto que permite una para ejecutar Linux en la parte superior de l4 para permitir la reutilización de código sin problemas.

Genode Marco se un paso a un lado de una multitud de intentos de escribir un microkernel éxito capaz de sobrevivir en el mundo cruel . Se puede ejecutar en la parte superior de muchos núcleos (L4 de TU Dresden, OKL4, linux) e incluso directamente en el hardware de metal desnudo y proporciona un modelo de seguridad basado en la capacidad unificada en la parte superior de cualquier núcleo que utiliza. Tiene algunas características interesantes.

  • Soporta arquitectura ARM y reciente SoC (OMAP4). Esto significa que podemos utilizar para paravirtualizing, por ejemplo, Android en CPU ARMv7 sin soporte de virtualización de hardware. Eso es importante porque ARM no tiene una selección de soluciones de virtualización como, por ejemplo, x86 tal.
  • implementado en C + + utilizando plantillas. La aplicación de los controles de tipo en tiempo de compilación reduce la cantidad de errores de programación
  • tiene una implementación agradable RPC / IDL en C + +. Programación de software multi-servidor nunca ha sido tan fácil.

Desafortunadamente, hay mucho trabajo por delante heckuva si queremos utilizar Genode para dominar el mundo . Aquí están algunas ideas que tengo sobre la mejora de la situación actual del marco Genode y espero que voy a tener la oportunidad de trabajar en algunas de estas cosas con el tiempo. La mayoría de los problemas son causados ​​por el mal soporte de hardware.

  • La falta de controladores de dispositivos. ¿Qué se debe hacer es escribir abstracciones (interfaces e implementaciones de muestra) para diversas clases de hardware – reguladores de voltaje, las tarjetas MMC, controladores GPIO etcétera. Al igual que en linux, pero utilizando kernelization (descomposición del software en servidores independientes se comunican a través de una RPC de confianza custodiado por los tokens de seguridad – capacidades) para mejorar la seguridad y C + + o DSL a medida para hacer cumplir los controles de tipo y la prevención de algunos errores de programación. Idealmente, me gustaría que pudiéramos hacer cumplir las políticas de acceso de grano grueso para cada recurso – ser que un pin GPIO o un registro de dispositivo I2C
  • No administración de energía.. Quiero decir, en absoluto. Ni siquiera DVFS (Dynamic Voltage / Frecuencia Scaling). No suspensión hacia ram apoyo. Qué bien limita el uso del marco en los dispositivos móviles.
  • apoyo a la gestión Poor pantalla. Propongo que nitpicker o algún otro servidor de pantalla debería ser ampliado para que hotplug, cambio de resolución en tiempo de ejecución y l4linux/genode controlador framebuffer debe ser completado para permitir el uso de todas las complejas características de los adaptadores de pantalla modernos – blitting, rotación, superposiciones y lo que no .
  • manejo adecuado culpa. Creo que algún tipo de sistema (lo llaman un paradigma, un modelo de diseño o lo que sea) debe ser implementado para reiniciar los servicios con gracia en el caso de fallo (de hecho es muy difícil decidir qué hacer cuando falla un controlador). Probablemente debería haber alguna clase política abstracta con diversos sistemas de aplicación. Una aplicación (por ejemplo, para el escritorio del usuario) podría intentar reiniciar las tareas que fallan. La otra (por ejemplo, para entornos informáticos de confianza) mataría el mundo si un solo proceso falla.
  • Algunos agradable interfaz de usuario debe ser escrito para cambiar con facilidad y la creación de nuevas instancias de Linux virtualizados.

Un enorme agujero de seguridad vale la pena mencionar es el hardware . No sólo es muy difícil de verificar la ausencia de puertas traseras en el hardware (un smartphone moderno en realidad tiene más de 20 CPUs de diferentes arquitecturas que ejecutan código de código cerrado), pero algunas de las características de hardware pueden utilizarse para eludir las medidas de seguridad de software.

El más famoso tipo de ataques asistidas por hardware son los ataques basados ​​en DMA . De hecho, en los sistemas más antiguos, un controlador de DMA típicamente tenía un acceso sin restricciones a toda la memoria física por lo que es posible que un conductor malicioso para controlar todo el sistema. Por otra parte, algunos protocolos, como IEEE1394 (FireWire) y PCI por diseño permiten dispositivo arbitraria para acceder a toda la memoria del sistema. Ha habido varios exploits para eludir la protección de los sistemas operativos de escritorio (como, [ WinLockPwn ]). Por otra parte, las mismas técnicas se han utilizado con éxito para romper la seguridad y los hipervisores de varias consolas de juegos como se muestra en [ consola ] y [ StateOfTheHack ]. Una solución a esto es usar IOMMU que es una memoria intermedia de traducción de memoria virtual entre el dispositivo y el bus IO. Además de la capacidad para definir dominios de protección en función de cada página también permite el uso de regiones de memoria contiguas con los dispositivos que no lo soportan (como la mayoría de los ISP de la cámara (procesador de señal de imagen) o GPU) y la reasignación de memoria de hardware en arbitraria proceso (como ejemplo, esto le permite pasar a través de su tarjeta VGA en una instancia paravirtualizado de linux en Xen)

La otra área de la llama que provoca es la elección de un lenguaje de programación para la aplicación
el sistema operativo. Históricamente, la mayoría de los granos, incluyendo Linux, BSD * y hasta cierto punto
Windows NT, fueron escritos en C plano, mientras que la mayoría de los otros. />

Estoy harto de la programación en el lenguaje C . Mientras que C es a menudo llamada la «asamblea multiplataforma ‘y siempre se puede entender lo que su código se traducen en, que ofrece una multitud de maneras de tiro a sí mismo en los pies y tengo algunas ideas sobre lo que es un lenguaje de programación a nivel de kernel debe ser similar. Voy a dividir las ideas en dos bandos: los que se centrarán en mejorar el lenguaje de una manera no intrusiva, sin romper la semántica y la eficiencia del código. Los otros se discutirán los enfoques alternativos para la construcción de software del sistema.

En primer lugar, vamos a hablar de los problemas comunes de programación en C y las ideas en la mejora. Esto es lo que me ha estado molestando a diario desde que comencé mi viaje en el mundo de desarrollo integrado.

  • memoria sin inicializar. En serio, esta es la causa de 90% de mal comportamiento de software. Solamente es necesario activar la bandera correspondiente si su compilador lo soporta. Me gustaría que esto siempre se hace cumplir por el compilador de C. La pérdida de rendimiento es insignificante im mayoría de los casos. Cuando uno necesita datos sin inicializar o es imposible para almacenar los valores de los datos iniciales en el BSS o borrar el búfer en tiempo de ejecución, una construcción del lenguaje por separado se debe utilizar de modo que sea posible identificar a todos esos lugares en tiempo de compilación.
  • desbordamientos de búfer. Esta es una gran problema de seguridad. En la mayoría de los casos es causado por los errores en el manejo de cadenas terminadas en nulo (el que piensa que son una buena idea es, probablemente, una cruel genialidad con la esperanza de tener una puerta trasera en todas partes). Una forma posible de evitar sería utilizar envoltorios con tipo de matrices – una estructura de datos que contiene el puntero array y recuento de elementos de acceso y de modificación de comprobación de errores fuera de la gama. Al igual que en Java, pero esto puede ser perfectamente simulada utilizando plantillas de C + + o menos cómodamente con macros C.
  • desreferencias puntero nulo. Todos los indicadores deben ser revisados ​​para su validez por el compilador. Además, de fundición punteros a enteros deben ser prohibidos y tampones IO también deben aplicarse en el compilador para que un programador no pierde el manual con la memoria.
  • Falta de ambas clases de programación orientada a objetos, clases de tipos de Haskell y funcional- coincidencia de patrones de estilo. Mientras que la combinación de todas estas características por lo general hace que el lenguaje muy complicado (como Ocaml), la falta de cualquiera de ellos hace que la programación de un ejercicio constante de reinventar el compilador con la ayuda de fugly (o bellos) como macros container_of
  • (C + +) que sería agradable ser capaz de definir abstracto implementaciones de clase en tiempo de compilación sin vtbl. Hasta cierto punto, usted puede conseguir lejos con el uso de plantillas para la sustitución de los tipos de definición de clase. Plantillas de C + + en general, es un poderoso sistema similar a Hindley-Millner sistemas de inferencia de tipo en ML o Haskell, úselo sabiamente 🙂
  • Stupid romper el código en la cabecera y la« ejecución ». Esto es especialmente agotador en C + +, ya que hay que saltar entre la cabecera y la fuente de cualquier cambio. Yo ni siquiera voy profundo en la discusión de otras cuestiones. Al igual que, la mayoría de los compiladores de C + + no poder manejar plantillas externas.

Ahora , vamos a considerar enfoques alternativos, sus puntos fuertes y débiles. Aquí están algunas ideas que podríamos pedir prestado al mundo userland.

  • Garbage Collection. Esto por sí solo puede ahorrar una gran cantidad de pérdidas de memoria y los esfuerzos de depuración. Si bien es cierto que la actividad de GC es bastante difícil de predecir y no ha habido una (falsa) creencia de que los sistemas de GC y de tiempo real son incompatibles, hay que señalar que la gestión de memoria manual también sufre de VM paliza y malloc () las llamadas pueden consumir cantidad arbitraria de tiempo. Por lo tanto, en algunos casos en los que el rendimiento y la fiabilidad son la cuestión de la vida y la muerte, la asignación dinámica de memoria no se utiliza en absoluto y todos los buffers de memoria se define estáticamente en tiempo de compilación.
  • tipo Runtime introspección (reflexión). Es muy útil poder consultar el estado del objeto sin necesidad de escribir toneladas de macro repetitivo. Si bien podemos poner la tarea de comprobación de tipos del compilador, a veces tenemos que interactuar con el código de mando a distancia y el espacio de usuario (por ejemplo, sysfs) y luego RTTI es muy útil. Por ejemplo, la mayoría de userlands utilizan algún tipo de un bus de mensaje escrito (dbus, p9fs) para IPC (comunicación entre procesos) que podríamos conectar directamente a la interfaz pública kernel
  • Monitorea primitiva para las secciones atómicas / crítico para evitar el control de bloqueo manual. De hecho, yo quiero algo así como el «sincronizado» primitivos en Java o «bloqueo» C #. Además, sería muy útil si el monitor conocía el contexto de ejecución (interrumpir, manipuladores parte inferior) y utilizar el dormir o no dormir bloqueo en consecuencia (spinlocks vs mutex). Como: « atómica [x <- readBus (); waitBus (x y ~ 1)] «

Existen varios proyectos destinados a aprovechar las ventajas . de propios lenguajes de programación de alto nivel para mejorar la calidad del software del sistema Para nombrar sólo algunos más interesante para mí:

  • Net Micro marco por Microsoft [. DotNetMicro ], que se ejecuta en hardware desnudo. Exige a la vuelta de 64K RAM. Si bien esta es una enorme pérdida de potencia de cálculo para un pequeño controlador como ATmega 48, es aún más pequeña que la mayoría de los núcleos con todas las funciones-como Linux, Windows NT o hipervisores como Xen o L4. Lo bueno de este proyecto es que es de código abierto y es compatible con una gran cantidad de hardware común (perdón por mi ignorancia, pero la última vez que lo comprobé hace 5 años apoyó la Intel / Marvell PXA SoC que todavía era muy popular en ese entonces)
  • Singularity OS de Microsoft. De hecho, es similar a. Net Micro ya que utiliza el dialecto de los lenguajes de programación C #, pero también se basa en la (AOT) precompilación Ahead-Of-Time a código nativo.
  • House es un sistema operativo implementado en el lenguaje de programación Haskell [ HouseOs ]. Esto es muy interesante porque Haskell es un lenguaje fuertemente tipado con la inferencia de tipos que lo hace ideal para la escritura de los sistemas complejos a gran escala. Lamentablemente parece que el ritmo de desarrollo se ha ralentizado últimamente y soporte de hardware está limitado a un subconjunto de los ordenadores compatibles con x86
  • [ SqueakNOS ] es un sistema operativo en torno al medio ambiente Squeak Smalltalk ejecuta en el metal desnudo, con todo, hasta manejo de interrupciones hace en Smalltalk. Smalltalk es un muy buen lenguaje orientado a objetos (a pesar de la mala OOP fama ha ganado recientemente debido a la naturaleza pervertida de C + + y Java), construida alrededor del mecanismo de señal de llamada de la ranura.


Más información:
[ KernelExploits ] kernel Escritura explota http://ugcs.net/ ~ keegan / charlas / kernel-exploit / talk.pdf

[ FO ] Sitio web kernel TU Dresden Fiasco.OC L4 http://os.inf.tu-dresden.de/fiasco/overview.html
[ L4Linux ] Sitio web de TU Dresden L4Linux http://os.inf.tu-dresden.de/L4/LinuxOnL4/
[ Genode ] Sitio web oficial Genode Marco http://genode.org/

[ WinLockPwn ] Las Aventuras de Daisy en Thunderbolt-DMA- tierra: Piratería Macs a través de la interfaz Thunderbolt http://www.breaknenter.org/tag/winlockpwn/
[ consola ] la consola del juego Modern Exploitation http://www.cs.arizona.edu/~collberg/Teaching/466-566/2012/Resources/presentations/2012/topic1-final/report.pdf
[ StateOfTheHack ] Todas las consolas son pertenecen a nosotros http://delroth.net/state-of-the-hack.pdf
<. br /> [ DotNetMicro ] Microsoft NET Micro Framework http://www.netmf.com/
[ Singularity ] Microsoft Singularity OS http://research.microsoft.com/en-us/projects/singularity/
[ HouseOs ] Sitio web Casa Sistema Operativo http://programatica.cs.pdx.edu/House/
[ SqueakNos ] SqueakNos – Smalltalk en hardware desnudo ; http://squeaknos.blogspot.com/

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *