O Windows é um dos sistemas operacionais mais multifacetados e flexíveis, funciona em arquiteturas completamente diferentes e está disponível em diferentes versões. Hoje, ele suporta as arquiteturas x86, x64, ARM e ARM64. O Windows
já suportou Itanium, PowerPC, DEC Alpha e MIPS. Além disso, o Windows suporta uma ampla variedade de SKUs que operam em várias condições; De data centers, laptops, Xbox e telefones a versões incorporadas da Internet das coisas, por exemplo, em caixas eletrônicos.
O aspecto mais surpreendente é que o kernel do Windows permanece praticamente inalterado, dependendo de todas essas arquiteturas e
SKUs . O kernel é escalonado dinamicamente, dependendo da arquitetura e do processador em que trabalha, para tirar o máximo proveito do equipamento. Obviamente, o kernel possui uma certa quantidade de código associada a uma arquitetura específica, mas há uma quantidade mínima, permitindo que o Windows seja executado em uma variedade de arquiteturas.
Neste artigo, falarei sobre a evolução das principais partes do kernel do Windows que permitem escalar de forma transparente, desde o chip NVidia Tegra de baixa potência em execução no
Surface RT 2012 até os
monstros gigantes que trabalham nos data centers do Azure.
Um gerenciador de tarefas do Windows em execução em uma máquina de pré-lançamento do Windows DataCenter, com 896 núcleos que suportam processadores lógicos 1792 e 2 TB de memória
Evolução de núcleo único
Antes de discutir os detalhes do kernel do Windows, vamos nos aprofundar um pouco na
refatoração . A refatoração desempenha um papel fundamental no aumento da reutilização de componentes do SO em várias SKUs e plataformas (por exemplo, cliente, servidor e telefone). A idéia básica da refatoração é permitir que você reutilize a mesma DLL em diferentes SKUs, suportando pequenas modificações feitas especificamente para o SKU desejado, sem renomear a DLL e sem interromper o trabalho dos aplicativos.
A principal tecnologia da refatoração do Windows é uma tecnologia pouco documentada chamada
conjuntos de API . Os conjuntos de APIs são um mecanismo que permite ao sistema operacional separar a DLL e seu local de uso. Por exemplo, o conjunto de API permite que os aplicativos do win32 continuem usando o kernel32.dll, apesar de a implementação de todas as APIs estar gravada em outra DLL. Essas DLLs de implementação também podem diferir entre os SKUs. Você pode ver os conjuntos de APIs em ação executando a passagem de dependência em uma DLL tradicional do Windows, por exemplo, kernel32.dll.
Depois de concluir essa digressão sobre a estrutura do Windows, que permite ao sistema maximizar a reutilização e o compartilhamento de código, passemos às profundezas técnicas de início do kernel de acordo com o planejador, que é a chave para escalar o sistema operacional.
Componentes do Kernel
O Windows NT é, de fato, um
microkernel , no sentido de possuir seu próprio núcleo (KE) com um conjunto limitado de funções, usando a camada executável (camada Executiva, Ex) para executar todas as políticas de alto nível. O EX ainda está no modo kernel, portanto não é exatamente um microkernel. O kernel é responsável pelo agendamento de threads, sincronização entre processadores, tratamento de exceções no nível do hardware e implementação de funções dependentes de hardware de baixo nível. A camada EX contém vários subsistemas que fornecem um conjunto de funcionalidades que geralmente é considerado o núcleo - IO, Gerenciador de Objetos, Gerenciador de Memória, Subsistema de Processo etc.
Para entender melhor o tamanho dos componentes, aqui está um detalhamento aproximado do número de linhas de código em vários diretórios principais da árvore de fontes do kernel (incluindo comentários). A tabela ainda não incluiu muito de tudo relacionado ao kernel.
Subsistemas de kernel | Linhas de código |
---|
Gerenciador de memória | 501.000 |
Registo | 211.000 |
Poder | 238.000 |
Executiva | 157.000 |
Segurança | 135.000 |
Kernel | 339.000 |
Subsistema de processo | 116.000 |
Para obter mais informações sobre a arquitetura do Windows, consulte a série de livros do
Windows Internals .
Planejador
Tendo preparado o terreno dessa maneira, vamos falar um pouco sobre o agendador, sua evolução e como o kernel do Windows pode ser dimensionado para um número tão grande de arquiteturas diferentes com tantos processadores.
Um encadeamento é uma unidade básica que executa o código do programa e é precisamente seu trabalho que o planejador do Windows planeja. Ao decidir qual encadeamento iniciar, o planejador usa suas prioridades e, em teoria, o encadeamento com a prioridade mais alta deve iniciar no sistema, mesmo que isso signifique que não haverá tempo restante para encadeamentos com prioridades mais baixas.
Tendo trabalhado tempo quântico (a quantidade mínima de tempo que um encadeamento pode funcionar), o encadeamento experimenta uma diminuição na prioridade dinâmica, para que encadeamentos de alta prioridade não funcionem para sempre, a alma de todos os outros. Quando outro encadeamento acorda para o trabalho, recebe prioridade, calculada com base na importância do evento que causou a espera (por exemplo, a prioridade é muito aumentada para a interface do usuário front-end e não muito - para concluir as operações de E / S). Portanto, um thread trabalha com alta prioridade enquanto permanece interativo. Quando se torna predominantemente conectado aos cálculos (vinculado à CPU), sua prioridade diminui e eles retornam a ele depois que outros threads com alta prioridade têm seu tempo de processador. Além disso, o kernel aumenta arbitrariamente a prioridade de encadeamentos prontos que não recebem tempo de processador por um determinado período, a fim de evitar a inanição computacional e corrigir a inversão de prioridade.
O Agendador do Windows inicialmente tinha uma fila de prontidão, na qual selecionou o próximo thread de maior prioridade para execução. No entanto, com o início do suporte para um número crescente de processadores, a única fila se transformou em um gargalo, e o agendador mudou o trabalho na área de lançamento do Windows Server 2003 e organizou uma fila de prontidão por processador. Ao mudar para suportar várias solicitações de um processador, eles não fizeram um único bloqueio global protegendo todas as filas e permitiram que o planejador tomasse decisões com base nas ótimas locais. Isso significa que, a qualquer momento no sistema, há um encadeamento com a maior prioridade, mas isso não significa necessariamente que N dos encadeamentos de maior prioridade na lista (onde N é o número de processadores) funcione no sistema. Essa abordagem valeu a pena até o Windows começar a mudar para CPUs de baixo consumo de energia, como laptops e tablets. Quando o encadeamento com as prioridades mais altas não funcionava nesses sistemas (por exemplo, o encadeamento front-end da interface do usuário), isso levava a falhas visíveis na interface. Portanto, no Windows 8.1, o planejador foi transferido para um modelo híbrido, com filas para cada processador para encadeamentos associados a esse processador e uma fila compartilhada de processos prontos para todos os processadores. Isso não afetou visivelmente o desempenho devido a outras alterações na arquitetura do planejador, por exemplo, refatorando um bloqueio de banco de dados do expedidor.
O Windows 7 introduziu um agendador dinâmico de compartilhamento justo (Dynamic Fair Share Scheduler, DFSS); isso diz respeito principalmente aos servidores de terminal. Esse recurso tentou resolver o problema de que uma sessão de terminal com uma carga de CPU alta poderia afetar os threads em outras sessões de terminal. Como o planejador não levou em consideração as sessões e simplesmente usou a prioridade para distribuir fluxos, os usuários em diferentes sessões podem influenciar o trabalho dos usuários em outras sessões, estrangulando seus fluxos. Também proporcionou uma vantagem injusta para sessões (e usuários) com um grande número de threads, pois uma sessão com um grande número de threads teve mais oportunidades de obter tempo do processador. Foi feita uma tentativa de adicionar uma regra ao planejador, segundo a qual cada sessão era considerada em pé de igualdade com as outras em termos de tempo do processador. Funcionalidade semelhante existe no Linux com seu planejador completamente honesto (
Completely Fair Scheduler ). No Windows 8, esse conceito foi generalizado como um grupo de agendadores e adicionado ao agendador, como resultado do qual cada sessão se enquadrava em um grupo independente. Além das prioridades para os encadeamentos, o planejador usa os grupos do planejador como um índice de segundo nível, decidindo qual encadeamento iniciar em seguida. No servidor de terminal, todos os grupos de agendadores têm o mesmo peso; portanto, todas as sessões recebem a mesma quantidade de tempo do processador, independentemente do número ou da prioridade dos threads nos grupos de agendadores. Além disso, esses grupos também são usados para um controle mais preciso dos processos. No Windows 8, os objetos de trabalho foram aprimorados para oferecer suporte ao
gerenciamento de tempo do processador . Usando uma API especial, você pode decidir quanto tempo o processador pode usar, caso seja um limite flexível ou flexível, e receber notificações quando o processo atingir esses limites. Isso é semelhante ao gerenciamento de recursos nos
cgroups no Linux.
A partir do Windows 7, o Windows Server apresentou
suporte para mais de 64 processadores lógicos em um único computador. Para adicionar suporte a muitos processadores, uma nova categoria foi introduzida no sistema, o "grupo de processadores". Um grupo é um conjunto invariável de processadores lógicos de não mais que 64 partes, que são consideradas por um planejador como uma unidade de computação. O kernel na inicialização determina qual processador pertence a qual grupo e, para máquinas com menos de 64 núcleos, é quase impossível perceber essa abordagem. Um processo pode ser dividido em vários grupos (por exemplo, uma instância do SQL server), um único encadeamento de cada vez só pode ser executado no mesmo grupo.
Mas em máquinas em que o número de núcleos de CPU excede 64, o Windows começou a mostrar novos gargalos que não permitiam que aplicativos tão exigentes como o SQL Server escalassem linearmente com o crescente número de núcleos de processador. Portanto, mesmo com a adição de novos núcleos e memória, as medições de velocidade não mostraram um aumento significativo. Um dos principais problemas associados a isso foi a disputa pelo bloqueio da base do expedidor. Bloquear o banco de dados do expedidor protegia o acesso a objetos cujo trabalho tinha que ser planejado. Entre esses objetos estão threads, timers, portas de entrada / saída, outros objetos do kernel que estão sujeitos a espera (eventos, semáforos, mutexes). Sob pressão da necessidade de resolver esses problemas, no Windows 7, foi realizado um trabalho para eliminar o bloqueio do banco de dados do despachante e substituí-lo por ajustes mais precisos, por exemplo, bloqueio de objeto por bloco. Isso permitiu que as medições de desempenho, como o SQL
TPC-C, demonstrassem um aumento de 290% na velocidade em comparação com o esquema anterior em algumas configurações. Foi um dos maiores aprimoramentos de desempenho na história do Windows que ocorreu devido a uma alteração em um único recurso.
O Windows 10 trouxe outra inovação ao apresentar conjuntos de CPU. Os conjuntos de CPU permitem que um processo particione um sistema para que um processo possa ser distribuído por vários grupos de processadores, impedindo que outros processos os usem. O kernel do Windows nem permite interrupções do dispositivo para usar os processadores incluídos no seu conjunto. Isso garante que mesmo os dispositivos não possam executar seus códigos nos processadores emitidos para o grupo do seu aplicativo. Parece uma máquina virtual de baixa tecnologia. É claro que esse é um recurso poderoso, e muitas medidas de segurança são incorporadas a ele para que o desenvolvedor do aplicativo não cometa grandes erros ao trabalhar com a API. A funcionalidade dos conjuntos de CPU é usada no modo de jogo.
Finalmente, chegamos ao suporte ao ARM64,
que apareceu no Windows 10 . A arquitetura ARM suporta a arquitetura
big.LITTLE , de natureza heterogênea - o núcleo "grande" é rápido e consome muita energia, e o núcleo "pequeno" é lento e consome menos. A idéia é que tarefas insignificantes possam ser executadas em um núcleo pequeno, economizando bateria. Para oferecer suporte à arquitetura big.LITTLE e aumentar a vida útil da bateria ao executar o Windows 10 no ARM, um suporte de layout heterogêneo foi adicionado ao agendador, levando em consideração os desejos de um aplicativo que trabalha com a arquitetura big.LITTLE.
Por desejos, quero dizer que o Windows está tentando fornecer um serviço de qualidade para aplicativos, rastreando threads em execução em primeiro plano (ou com falta de tempo do processador) e garantindo sua execução no núcleo "grande". Todas as tarefas em segundo plano, serviços e outros threads auxiliares são executados em núcleos pequenos. Também no programa, você pode
observar à força a baixa importância do encadeamento para fazê-lo funcionar no núcleo pequeno.
Trabalho em nome de outra pessoa [Trabalho em nome]: no Windows, muito trabalho em primeiro plano é realizado por outros serviços que funcionam em segundo plano. Por exemplo, ao pesquisar no Outlook, a própria pesquisa é realizada pelo serviço de segundo plano do Indexador. Se apenas executarmos todos os serviços em um núcleo pequeno, a qualidade e a velocidade dos aplicativos em primeiro plano sofrerão. Para impedir que ele diminua a velocidade das arquiteturas big.LITTLE em tais cenários de trabalho, o Windows monitora as chamadas de aplicativos que chegam a outros processos para executar o trabalho em seu nome. Nesse caso, atribuímos prioridade prioritária ao encadeamento relacionado ao serviço e forçamos a execução no núcleo principal.
Deixe-me terminar este primeiro artigo sobre o kernel do Windows, que fornece uma visão geral do agendador. Os artigos com detalhes técnicos semelhantes sobre o funcionamento interno do sistema operacional seguirão posteriormente.