Windows es uno de los sistemas operativos más multifacéticos y flexibles, funciona en arquitecturas completamente diferentes y está disponible en diferentes versiones. Hoy es compatible con arquitecturas x86, x64, ARM y ARM64. Windows
a la vez era compatible con Itanium, PowerPC, DEC Alpha y MIPS. Además, Windows admite una amplia gama de SKU que funcionan en diversas condiciones; Desde centros de datos, computadoras portátiles, Xbox y teléfonos hasta versiones integradas de Internet de cosas, por ejemplo, en cajeros automáticos.
El aspecto más sorprendente es que el kernel de Windows permanece prácticamente sin cambios dependiendo de todas estas arquitecturas y
SKU . El kernel se escala dinámicamente dependiendo de la arquitectura y el procesador en el que trabaja, para aprovechar al máximo el equipo. Por supuesto, el núcleo tiene una cierta cantidad de código asociado con una arquitectura específica, pero hay una cantidad mínima allí, lo que permite que Windows se ejecute en una variedad de arquitecturas.
En este artículo, hablaré sobre la evolución de las partes clave del núcleo de Windows que le permiten escalar de forma transparente desde el chip NVidia Tegra de baja potencia que se ejecuta en
Surface RT 2012 hasta los
monstruos gigantes que trabajan en los centros de datos de Azure.
Un administrador de tareas de Windows que se ejecuta en una máquina de prelanzamiento de Windows DataCenter, con 896 núcleos que admiten 1792 procesadores lógicos y 2 TB de memoria
Evolución de un solo núcleo
Antes de discutir los detalles del kernel de Windows, pasemos un poco hacia la
refactorización . La refactorización desempeña un papel clave para aumentar la reutilización de los componentes del sistema operativo en varias SKU y plataformas (por ejemplo, cliente, servidor y teléfono). La idea básica de refactorizar es permitirle reutilizar la misma DLL en diferentes SKU, admitiendo pequeñas modificaciones hechas específicamente para la SKU deseada, sin renombrar la DLL y sin interrumpir el trabajo de las aplicaciones.
La tecnología central de la refactorización de Windows es una tecnología poco documentada llamada
conjuntos de API . Las suites API son un mecanismo que permite al sistema operativo separar la DLL y su lugar de uso. Por ejemplo, el conjunto de API permite que las aplicaciones para win32 sigan usando kernel32.dll, a pesar de que la implementación de todas las API está escrita en otra DLL. Estas DLL de implementación también pueden diferir entre las SKU. Puede ver los conjuntos de API en acción ejecutando el recorrido de dependencia en una DLL tradicional de Windows, por ejemplo, kernel32.dll.
Después de terminar esta digresión sobre la estructura de Windows, que permite que el sistema maximice la reutilización y el uso compartido del código, pasemos a las profundidades técnicas de iniciar el núcleo de acuerdo con el planificador, que es la clave para escalar el sistema operativo.
Componentes del kernel
Windows NT es, de hecho, un
microkernel , en el sentido de que tiene su propio núcleo Kernel (KE) con un conjunto limitado de funciones, utilizando la capa ejecutable (capa ejecutiva, Ex) para ejecutar todas las políticas de alto nivel. EX todavía está en modo kernel, por lo que no es exactamente un microkernel. El kernel es responsable de programar subprocesos, sincronizar entre procesadores, manejar excepciones de nivel de hardware e implementar funciones dependientes de hardware de bajo nivel. La capa EX contiene varios subsistemas que proporcionan un conjunto de funcionalidades que generalmente se consideran el núcleo: IO, Administrador de objetos, Administrador de memoria, Subsistema de procesos, etc.
Para comprender mejor el tamaño de los componentes, aquí hay un desglose aproximado del número de líneas de código en varios directorios clave del árbol de fuentes del núcleo (incluidos los comentarios). La tabla aún no ha incluido mucho de todo lo relacionado con el núcleo.
Subsistemas del núcleo | Lineas de codigo |
---|
Administrador de memoria | 501,000 |
Registro | 211,000 |
Poder | 238,000 |
Ejecutiva | 157,000 |
Seguridad | 135,000 |
Kernel | 339,000 |
Subsistema de proceso | 116,000 |
Para obtener más información sobre la arquitectura de Windows, consulte la serie de libros de
Windows Internals .
Planificador
Habiendo preparado el terreno de esta manera, hablemos un poco sobre el planificador, su evolución y cómo el kernel de Windows puede escalar a tantas arquitecturas diferentes con tantos procesadores.
Un hilo es una unidad básica que ejecuta el código del programa, y es precisamente su trabajo lo que el planificador de Windows planea. Al decidir qué subproceso iniciar, el planificador utiliza sus prioridades y, en teoría, el subproceso con la prioridad más alta debe comenzar en el sistema, incluso si esto significa que no habrá tiempo para subprocesos con prioridades más bajas.
Después de haber trabajado el tiempo cuántico (la cantidad mínima de tiempo que un subproceso puede funcionar), el subproceso experimenta una disminución en la prioridad dinámica, por lo que los subprocesos de alta prioridad no pueden funcionar para siempre, el alma de todos los demás. Cuando otro subproceso se despierta para trabajar, se le da prioridad, calculado sobre la base de la importancia del evento que causó la espera (por ejemplo, la prioridad aumenta considerablemente para la interfaz de usuario front-end, y no mucho, para completar las operaciones de E / S). Por lo tanto, un hilo funciona con alta prioridad mientras permanece interactivo. Cuando se conecta predominantemente con los cálculos (vinculados a la CPU), su prioridad disminuye y vuelven a ella después de que otros subprocesos con alta prioridad tengan su tiempo de procesador. Además, el núcleo aumenta arbitrariamente la prioridad de los subprocesos preparados que no han recibido tiempo de procesador durante un cierto período para evitar su inanición computacional y corregir la inversión de prioridad.
El Programador de Windows inicialmente tenía una cola de preparación, desde la cual seleccionó el siguiente subproceso de mayor prioridad para ejecutar. Sin embargo, con el comienzo del soporte para un número creciente de procesadores, la única cola se convirtió en un cuello de botella, y el programador cambió el trabajo en torno al área de lanzamiento de Windows Server 2003 y organizó una única cola de preparación para el procesador. Cuando cambiaron para admitir varias solicitudes de un procesador, no hicieron un solo bloqueo global que protegía todas las colas y permitieron que el planificador tomara decisiones basadas en los óptimos locales. Esto significa que en cualquier momento un subproceso con la mayor prioridad está funcionando en el sistema, pero no necesariamente significa que N de los subprocesos de mayor prioridad en la lista (donde N es el número de procesadores) funcione en el sistema. Este enfoque valió la pena hasta que Windows comenzó a cambiar a CPU de bajo consumo, como computadoras portátiles y tabletas. Cuando el hilo con las prioridades más altas no funcionaba en tales sistemas (por ejemplo, el hilo frontal de la interfaz de usuario), esto condujo a fallos notables en la interfaz. Por lo tanto, en Windows 8.1, el programador se transfirió a un modelo híbrido, con colas para cada procesador para subprocesos asociados con este procesador, y una cola compartida de procesos listos para todos los procesadores. Esto no afectó notablemente el rendimiento debido a otros cambios en la arquitectura del planificador, por ejemplo, refactorizando un bloqueo de la base de datos del despachador.
Windows 7 introdujo un programador dinámico de reparto justo (Dynamic Fair Share Scheduler, DFSS); esto se refería principalmente a los servidores de terminal. Esta característica trató de resolver el problema de que una sesión de terminal con una alta carga de CPU podría afectar los hilos en otras sesiones de terminal. Como el planificador no tuvo en cuenta las sesiones y simplemente utilizó la prioridad para distribuir flujos, los usuarios en diferentes sesiones podrían influir en el trabajo de los usuarios en otras sesiones estrangulando sus flujos. También dio una ventaja injusta a las sesiones (y usuarios) con una gran cantidad de subprocesos, ya que una sesión con una gran cantidad de subprocesos tenía más oportunidades de obtener tiempo de procesador. Se intentó agregar una regla al planificador, según la cual cada sesión se consideraba en pie de igualdad con las demás en términos de tiempo de procesador. Funcionalidad similar existe en Linux con su programador completamente honesto (
Programador completamente justo ). En Windows 8, este concepto se generalizó como un grupo de planificador y se agregó al planificador, como resultado de lo cual cada sesión cayó en un grupo independiente. Además de las prioridades para los subprocesos, el planificador utiliza los grupos del planificador como un índice de segundo nivel, decidiendo qué subproceso comenzará a continuación. En el servidor de terminal, todos los grupos del planificador tienen el mismo peso, por lo que todas las sesiones reciben la misma cantidad de tiempo de procesador, independientemente del número o la prioridad de los subprocesos dentro de los grupos del planificador. Además, estos grupos también se utilizan para un control más preciso sobre los procesos. En Windows 8, los objetos de trabajo se han mejorado para admitir la
gestión del tiempo del procesador . Usando una API especial, puede decidir cuánto tiempo de procesador puede usar un proceso, en caso de que sea un límite suave o duro, y recibir notificaciones cuando el proceso alcance estos límites. Esto es similar a la gestión de recursos en
cgroups en Linux.
Comenzando con Windows 7, Windows Server introdujo
soporte para más de 64 procesadores lógicos en una sola computadora. Para agregar soporte a tantos procesadores, se ha introducido una nueva categoría en el sistema, el "grupo de procesadores". Un grupo es un conjunto invariable de procesadores lógicos de no más de 64 piezas, que un planificador considera como una unidad informática. El núcleo en el arranque determina qué procesador pertenece a qué grupo, y para máquinas con menos de 64 núcleos de procesador, este enfoque es casi imposible de notar. Un proceso se puede dividir en varios grupos (por ejemplo, una instancia del servidor SQL), un solo hilo a la vez solo se puede realizar dentro del mismo grupo.
Pero en las máquinas donde el número de núcleos de CPU supera los 64, Windows comenzó a mostrar nuevos cuellos de botella que impedían que las aplicaciones exigentes, como el servidor SQL, se escalaran linealmente con el creciente número de núcleos de procesador. Por lo tanto, incluso con la adición de nuevos núcleos y memoria, las mediciones de velocidad no mostraron un aumento significativo. Uno de los principales problemas asociados con esto fue la disputa sobre el bloqueo de la base de despachadores. El bloqueo de la base de datos del despachador protegía el acceso a los objetos cuyo trabajo tenía que planificarse. Entre estos objetos hay hilos, temporizadores, puertos de entrada / salida, otros objetos del núcleo que están sujetos a espera (eventos, semáforos, mutexes). Bajo la presión de la necesidad de resolver tales problemas, en Windows 7, se trabajó para eliminar el bloqueo de la base de datos del despachador y reemplazarlo con ajustes más precisos, por ejemplo, bloqueo de objeto por bloque. Esto permitió mediciones de rendimiento como SQL
TPC-C para demostrar un aumento del 290% en la velocidad en comparación con el esquema anterior en algunas configuraciones. Fue una de las mayores mejoras de rendimiento en la historia de Windows que ocurrió debido a un cambio en una sola función.
Windows 10 trajo otra innovación al presentar conjuntos de CPU. Los conjuntos de CPU permiten que un proceso particione un sistema para que un proceso pueda distribuirse entre varios grupos de procesadores, evitando que otros procesos los usen. El kernel de Windows ni siquiera permite que las interrupciones del dispositivo utilicen los procesadores incluidos en su conjunto. Esto garantiza que incluso los dispositivos no puedan ejecutar su código en procesadores emitidos para el grupo de su aplicación. Parece una máquina virtual de baja tecnología. Está claro que esta es una característica poderosa, hay tantas medidas de seguridad incorporadas para que el desarrollador de la aplicación no cometa grandes errores al trabajar con la API. La funcionalidad de los conjuntos de CPU se utiliza en el modo Juego.
Finalmente, llegamos al soporte para ARM64,
que apareció en Windows 10 . La arquitectura ARM admite la arquitectura
big.LITTLE , que es de naturaleza heterogénea: el núcleo "grande" es rápido y consume mucha energía, y el núcleo "pequeño" es lento y consume menos. La idea es que se puedan realizar tareas insignificantes en un núcleo pequeño, ahorrando así batería. Para admitir la arquitectura big.LITTLE y aumentar la duración de la batería al ejecutar Windows 10 en ARM, se agregó un soporte de diseño heterogéneo al planificador, teniendo en cuenta los deseos de una aplicación que funcione con la arquitectura big.LITTLE.
Por deseos, quiero decir que Windows está tratando de proporcionar un servicio de calidad para las aplicaciones, rastreando los hilos que se ejecutan en primer plano (o aquellos que carecen de tiempo de procesador) y garantizando su ejecución en el núcleo "grande". Todas las tareas en segundo plano, servicios y otros subprocesos auxiliares se ejecutan en núcleos pequeños. También en el programa, puede
notar a la fuerza la baja importancia del hilo para que funcione en el núcleo pequeño.
Trabajar en nombre de otra persona [Work on Behalf]: en Windows, otros servicios que trabajan en segundo plano realizan mucho trabajo en primer plano. Por ejemplo, al buscar en Outlook, la búsqueda en sí es realizada por el servicio en segundo plano Indexer. Si solo ejecutamos todos los servicios en un núcleo pequeño, la calidad y la velocidad de las aplicaciones en primer plano se verán afectadas. Para evitar que se ralentice en arquitecturas big.LITTLE bajo tales escenarios de trabajo, Windows monitorea las llamadas de aplicaciones que llegan a otros procesos para realizar el trabajo en su nombre. En este caso, le damos prioridad al subproceso relacionado con el servicio y lo forzamos a ejecutarse en el núcleo grande.
Permítanme terminar este primer artículo sobre el núcleo de Windows, que ofrece una descripción general del planificador. Los artículos con detalles técnicos similares sobre el funcionamiento interno del sistema operativo seguirán más adelante.