• Nenhum resultado encontrado

na primeira camada. Existem dois tipos de probes, passivas e ativas. Cada probe pode ser ligada a um grupo de analisadores locais, para os quais ela envia informação. Probes ativas são regularmente acionadas por um temporizador do sistema, enquanto probes passivas são acionadas por um evento do sistema. Existem dois diferentes eventos do sistema: eventos do kernel do Linux e eventos do scheduler global, esse último coleta informações locais. Quando uma probe passiva é acionada por um evento do sistema, a probe envia a informação coletada para o analisador local com o qual ela está ligada.

Por exemplo, uma probe ativa pode ser usada para verificar a utilização de CPU (a CPU é periodicamente verificada), enquanto que se utiliza uma probe passiva para detectar um “ping-pong” de páginas de memória entre duas threads de uma aplicação de memória compartilhada (quando uma página chega ao nó, a probe é acionada). O Kerrighed provê um conjunto de probes na sua implementação, podendo novas probes ser desenvolvidas por programadores de sistemas operacionais.

Analisadores locais recebem as informações das probes, as analisa e filtra, detectando algum estado anormal do sistema local. Essa camada também tem a função de enviar as informações das probes para gerenciador global de escalonamento. Um grupo de analisadores locais é executado em cada nó.

Cada analisador local pode estar ligado a um conjunto de probes. Por exemplo, podemos ter uma probe para o consumo de CPU e outra para a temperatura da mesma. O analisador local ligado a essas duas probes pode detectar uma alta contenção local de CPU, assim como problemas locais de temperatura. Se um problema de CPU é detectado, o analisador local envia uma requisição para o gerenciador global de escalonamento (o analisador local não tem uma visão geral do estado do cluster e, portanto, não pode tomar uma decisão nesse nível).

155

Um gerenciador global de escalonamento é executado em cada nó, sendo ligado a um grupo de analisadores locais. Os gerenciadores globais de escalonamento executados em diferentes nós comunicam-se entre si para trocar informação do estado de cada nó que compõe o cluster. Essa camada é a única que tem uma visão global do mesmo. Essa visão global é construída com as informações das probes (por exemplo, probes de CPU), habilitando a detecção global de problemas de escalonamento. Para esse fim, cada gerenciador global de escalonamento implementa uma política global de escalonamento ( por exemplo, o balanceamento de CPU). Quando um problema de escalonamento é detectado (por exemplo, CPU’s locais com mais carga do que a média do cluster), o gerenciador global de escalonamento pode decidir migrar alguns processos ou acionar um checkpointing para alguma aplicação, de acordo com a política de escalonamento, a fim de obter um uso eficiente dos recursos do cluster. Essas três camadas que compõe o escalonamento global modular do Kerrighed podem ser configuradas usando arquivos XML. Diferentes probes, analisadores locais e gerenciadores globais de escalonamento podem ser dinamicamente ativados e desativados sem a necessidade de se encerrar nem o sistema operacional nem as aplicações. Além disso, cada camada provê uma estrutura de desenvolvimento para facilitar a programação de novos componentes, permitindo, de maneira simples, a criação de novas políticas de escalonamento global. Finalmente, o scheduler do kernel do Linux não é modificado. O Kerrighed apenas adiciona ou remove processos no scheduler local.

156

Gerenciamento do estado dos processos

Os schedulers do Kerrighed são baseados em três mecanismos: estabelecimento, migração e check-point/restart de processos. Para o estabelecimento, o Kerrighed provê dois mecanismos: criação remota de processos e duplicação remota de processos. A criação remota de processos utiliza uma interface dedicada semanticamente equivalente a um fork(), imediatamente seguido por um execv() no processo filho.

A duplicação de processos é utilizada na execução de aplicações quando um novo processo (usando fork) ou threads (usando pthread create) são criados, necessitando herdar o contexto da aplicação. Para estabelecer um novo processo, o sistema necessita extrair uma imagem do processo criador e transferi-lo para um nó remoto, criando um clone que será executado no mesmo. Similarmente para uma migração remota de processo, essa migração precisa extrair uma imagem do processo e transferi-la para o nó remoto, criando um clone do mesmo, mas o processo original é finalizado. O checkpointing de processo também necessita extrair uma imagem do mesmo e armazená-la no disco ou numa memória remota.

A criação, duplicação e checkpoint/restart de um processo Kerrighed utilizam o mesmo mecanismo fundamental de extração de processo.

A extração de um processo consiste na criação de um processo ghost (virtualização de processo) composto dos seguintes componentes: espaço de endereçamento, arquivos abertos, Identificador de Processo (PID), registradores do processador e os próprios dados.

Gerenciamento de espaço de endereçamento e arquivos abertos:

De modo similar a um kernel padrão do Linux, que necessita extrair todas as informações de memória e arquivos abertos a fim de criar um processo ghost coerente, no Kerrighed o espaço de endereçamento e os arquivos abertos de um K- process são globalmente gerenciados. O espaço de memória e arquivos convencionais abertos são tratados pelos containers e os arquivos de stream por mecanismos de dynamic streams (fluxos dinâmicos). Através dos containers tanto arquivos abertos como páginas de memória podem ser acessados por qualquer nó do cluster, que viabiliza consideravelmente a migração de processos.

157

Gerenciamento do Identificador de Processo (PID):

No Linux padrão as threads são implementadas por processos utilizando a biblioteca pthread. Então, os processos são identificados por um Process Identifier (PID). As threads são distinguidas por identificadores internos na biblioteca pthread. O Kerrighed adiciona uma camada ao sistema. Para cada processo é designado um Kerrighed Process Identifier (KPID), como o seu PID. Em nível de kernel, o PID é usado, enquanto que em nível de usuário, apenas o KPID é visualizado. Dessa forma um único KPID é designado para um processo em todo cluster. Para garantir a unicidade, um KPID é composto do PID original e o identificador do nó corrente. A biblioteca de thread do Kerrighed gerencia o identificador adicional de thread.

Gerenciamento Global de Memória

O gerenciamento global de memória num cluster abrange muitos serviços. Primeiro, a fim de suportar a execução de aplicações multi-thread e modelos usuais de programação em ambiente de memória compartilhada, um sistema de DSM (Distributed Shared Memory) é necessário, permitindo que processos e threads compartilhem segmentos de dados em qualquer nó do cluster. Segundo, é muito desejável num cluster explorar a memória que está distribuída entre os nós, para aumentar a eficiência dos serviços do sistema operacional. Existem duas áreas chaves no gerenciamento de memória num cluster, um mecanismo remoto de paginação e um mecanismo cooperativo de arquivos de cache.

Num sistema de DSM, os sistemas de paginação de memória e de arquivos de cache cooperativos baseiam-se em mecanismos comuns: localizar a cópia de uma página de memória no cluster, transferir páginas entre nós e gerenciar a coerência de cópias de páginas replicadas.

O Kerrighed implementa o conceito de container como um único mecanismo para gerenciar globalmente as memórias físicas do cluster. Todos os serviços do sistema operacional que utilizam páginas de memória acessam a memória física através de containers.

158

Containers

Num cluster, cada nó executa o seu próprio kernel do sistema operacional, o qual pode ser dividido de modo grosseiro em duas partes: serviços de sistema e gerenciadores de dispositivos. O Kerrighed propõe um serviço genérico inserido entre os serviços de sistema e os gerenciadores de serviços chamados de containers. Os containers são integrados ao kernel graças aos linkers que são partes de software inseridas entre os gerenciadores de dispositivos existentes, os serviços de sistema e os containers. A ideia principal do container é que ele dá a ilusão aos serviços de sistemas que a memória física é compartilhada como um computador SMP (Symmetric Multiprocessing).

Um container é um objeto que permite que o armazenamento e compartilhamento de dados em toda extensão do cluster. Ele atua em nível de kernel, sendo completamente transparente em nível de software de usuário. Os dados armazenados num container podem ser compartilhados e acessados pelo kernel de qualquer nó do cluster. As páginas de um container podem ser mapeadas num espaço de endereço de um processo, podendo assim ser utilizadas como entrada de arquivo de cache, dentre outras aplicações.

Por integrar esse mecanismo genérico de compartilhamento dentro do sistema, é possível dar a ilusão para o kernel que ele está manipulando uma memória física. Sobre essa memória virtual compartilhada é possível estender ao cluster os serviços tradicionais oferecidos por um sistema operacional padrão. Isso permite manter a interface do sistema operacional já conhecida pelos usuários, tirando vantagem do gerenciamento de recursos locais de baixo nível. O modelo oferecido pelos containers é sequencialmente consistente, implementado com um protocolo write-invalidate. Linkers

Muitos mecanismos num kernel apoiam-se no manuseio de páginas físicas. Os linkers desviam esses mecanismos para garantir o compartilhamento através dos containers. Para cada container são associados um ou mais linkers de alto nível chamados linkers de interface e linkers de baixo nível chamados linkers de input/output. A função do linker de interface é desviar o acesso aos dispositivos de serviços do sistema para os containers enquanto um linker de E/S permite o acesso a um gerenciador de dispositivo.

159

Os serviços de sistemas são conectados aos containers graças aos linkers de interface. Um linker de interface muda a interface do container para fazê-la compatível com a interface de serviços de sistema de alto nível. Essa interface precisa dar a ilusão para esses serviços que eles se comunicam com gerenciadores de dispositivos tradicionais. É possível conectar vários serviços de sistema a um mesmo container. Durante a criação de um novo container, um linker de E/S é associado ao mesmo. O container deixa de ser então um objeto genérico para tornar-se um objeto de compartilhamento de dados, vindos do dispositivo ao qual ele está ligado.

Design dos Serviços de Sistema Distribuídos

Os containers e linkers são utilizados para implementar vários serviços que compõe um cluster, dentre eles memória virtual compartilhada e serviços de mapeamento de arquivos.

Memória Virtual Compartilhada

A memória virtual compartilhada no Kerrighed estende esse serviço ao cluster por permitir que vários processos ou threads sejam executados em nós diferentes, compartilhando dados através do seu espaço de endereçamento. Para que esse serviço seja provido, são requeridas três propriedades: compartilhamento de dados entre os nós, coerência na replicação de dados e acesso simples a dados compartilhados. Os serviços do container garantem as duas primeiras propriedades, enquanto que a terceira é garantida pelo mapeamento do linker de interface. Assim, mapear um container de memória em espaços de memória virtual de vários processos via um mapeamento de linker, leva a uma memória virtual compartilhada.

Mapeamento de Arquivos

O serviço de mapeamento de arquivos de um sistema operacional convencional permite mapear um arquivo no espaço de endereço de um ou mais processo, ou no espaço de endereço compartilhado por um grupo de threads. Estendendo esse serviço a um cluster considera-se o mapeamento de um arquivo no espaço de endereço de um processo não importando onde será sua execução, seja em seu nó do cluster seja numa memória compartilhada de um grupo de threads. Isso é feito por mapear um arquivo de container num espaço de memória virtual de um processo graças a um mapeamento de um linker de interface.

160