Pular para o conteúdo principal

O Problema de Gestão de I/O

A palestra que dei no Sétimo Encontro do Grupo de Usuários de C e C++ do Brasil apresentou meu resultado atual na pesquisa de um framework baseado em uma forma de orientação a aspectos em C++ descrita no "Modern C++ Design" do Alexandrescu.

Minha palestra não apresentou, porém, o que é exatamente o problema a resolver, assumindo que fosse razoavelmente conhecido. Seu foco foi apresentar todos os mecanismos disponíveis para se resolver o problema no Linux 2.6, suas peculiaridades e seus aspectos comuns.

Uma certa razão me motivou, após esta apresentação, a rebobinar meu raciocínio e considerar o problema original. [1] O que há exatamente de tão problemático na gestão de I/O em um programa?

A imagem inicial é a de um programa cujo objetivo é interagir com um dos dispositivos ligados ao computador. A forma básica dessa interação é a cópia de dados do dispositivo para a memória e da memória para o dispositivo, sendo que trabalho útil por parte do programa ocorre enquanto os dados estão na memória.

O trabalho de um tal programa pode, portanto, se manifestar por uma recorrente execução desta sequência exata: copiar do dispositivo para a memória, processar, copiar da memória para o dispositivo, reiniciar.

Agora, observamos um fato simples: enquanto o dispositivo copia, o processador está ocioso; enquanto o processador computa, o dispositivo está ocioso.

Esse fato pode ser um problema para você por duas razões altamente relacionadas: seu computador é tão caro que apenas se justifica quando trabalha a uma taxa máxima e sua fila de tarefas é tão grande que há uma tarefa que poderia processar ou copiar enquanto o processador ou o dispositivo estão ociosos.

Com o objetivo de solucionar esta questão, sistemas operacionais surgiram com a missão de coreografar tarefas de modo a obter a máxima utilização de todas as partes da máquina; i.e. levando uma tarefa ao processador e outra ao dispositivo simultaneamente, se possível.

Este sistema operacional não está conforme a nossa imagem inicial. Ele deve continuamente colocar e tirar tarefas do processador, colocar e tirar tarefas do dispositivo, em uma sequência complexa determinada por mutações indeterminadas em uma máquina de estados. Tais mutações são indeterminadas porque o sistema operacional não pode computar previamente por quanto tempo cada tarefa ocupará cada elemento. O sistema operacional deve agir e depois observar e esperar por alguma ocorrência significativa, então agir e voltar a observar e esperar. [2]

Esta é então nossa segunda imagem: a de um programa cujo objetivo é interagir com múltiplos dispositivos ligados ao computador. A forma básica dessa interação é a alternância entre um estado de paciência, enquanto se observa todos os dispositivos que se pressupõe estarem fazendo algo de útil, e um estado de atividade, iniciado por uma ocorrência significativa ao programa.

O trabalho de um tal programa pode, portanto, se manifestar por uma sequência que intercala atividade em cada um dos dispositivos, de modo que o programa se ocupa em fazer o que puder enquanto um dispositivo ou outro não demanda atenção.

Atividade, individualmente, será copiar ou processar; tomando uma execução do programa e evidenciando o que este programa faz com apenas um dispositivo, veremos a mesma sequência da imagem inicial arranjada no tempo na mesma ordem, separada por períodos em que o programa não se ocupou deste dispositivo.

Considerando com cuidado a imagem de um dispositivo podemos obter um conceito complexo e sofisticado. Um teclado e um monitor são dispositivos simples onde apenas se obtém ou enfia dados, e portanto a imagem desses dispositivos é simples. Uma controladora de vídeo com aceleração 3D é mais complexa, e possui evidentemente processador e memória próprios. Uma controladora de rede por si só não é um dispositivo complexo, mas a interação com ela é complexa, já que o recebimento de dados é algo totalmente imprevisível; além disso, a pilha de protocolos de rede que se constrói sobre ela possibilita o estabelecimento do que são efetivamente dispositivos virtuais, cujo exemplo contemporâneo obrigatório é a sessão TCP/IP.

Podemos nos convencer que a própria máquina é um dispositivo, e que um dispositivo é realmente uma máquina com processador e memória. O programa que dirige dispositivos se transforma em um elemento de um par que interage; o programa neste dispositivo e outro programa em outro dispositivo.

Um exemplo onde esta imagem ocorre claramente é o servidor típico da Internet: sua missão é coreografar atividade entre milhares de dispositivos virtuais TCP/IP onde o dispositivo é, exatamente, um outro programa que executa em uma outra máquina.

A presença de dados passíveis de serem obtidos, do ponto de vista de um dos programas, é determinada pelo fato de o outro programa ter enfiado algum dado do outro lado; estabelecem-se assim protocolos de comunicação que tornam possível a coreografia entre ambas as partes e a realização de trabalho útil.

Se um dos programas enfia dados na ligação virtual, esses dados ficarão em suspenso enquanto o outro programa não recuperá-los. Um tal dispositivo virtual, enquanto está ocioso, pode portanto está disponível ou indisponível. Considerando novamente as nossas exigências de desempenho, é imprescindível para o programa atuar rapidamente sobre o dispositivo quando ele estiver disponível, e é permitido ignorá-lo enquanto estiver indisponível.

Nossa imagem final é portanto a imagem de um programa operando milhares de dispositivos onde cada um desses dispositivos entende-se como sendo uma outra máquina executando um outro programa, potencialmente conforme a mesma imagem e também operando milhares de dispositivos.

Sua atividade envolve coreografar uma dança entre si mesmo e um outro programa de modo que ambos possam trocar dados: um copia para fora enquanto o outro copia para dentro. Além disso, sua atividade envolve agendar diversas danças entre si e todos os dipositivos: cuidando dos disponíveis e ignorando os indisponíveis.

Para atender a essa necessidade, o sistema operacional tem à disposição controladoras especiais na máquina, e o programa tem à disposição serviços especiais do sistema operacional.



[1] este problema está afirmado em todo bom material escolar sobre Sistemas Operacionais, como os do Tanembaum.

[2] o mecanismo básico da máquina que viabiliza esse comportamento é a controladora de interrupções.

Comentários

Postagens mais visitadas deste blog

Análise vs. Projeto

Eu diria que a ruptura entre Análise e o Projeto acontece quando acabam as considerações sobre o problema e começam as considerações sobre a solução. Em outras palavras, o papel do Analista é considerar o problema, enquanto o papel do Projetista é o de considerar a solução. Assim, o Analista aparece quando um problema aparece, e dá lugar ao Projetista quando o problema foi devidamente analisado, restando produzir para ele uma solução. E diria que a ruptura entre a Especificação e a Implementação acontece quando acabam os artefatos para consumo por humanos e começam os artefatos para consumo por máquinas. Em outras palavras, o papel do Especificador é produzir artefatos para consumo por humanos, enquanto o papel do Implementador é produzir artefatos para consumo por máquinas. Assim, o Especificador aparece quando a atividade é produzir efeitos sobre humanos, e dá lugar ao Implementador quando os humanos estão devidamente satisfeitos e a atividade prossegue para produzir efeito...

JNDI e Plan 9

Acabei de realizar quão similar é o J2EE e o Plan 9 quando observados pela lente da arquitetura de componentes: no Plan 9, o sistema de arquivos é tão capaz quanto o JNDI no J2EE. Um componente no qual eu estou trabalhando agora depende de um componente de banco de dados e de um componente de correio eletrônico. O modo de ligação entre esses componentes se dá através de JNDI: meu componente faz referência aos nomes "dataSource" e "mailSession" no seu contexto local de nomes. Na configuração do componente em uma instalação, esses nomes locais são ligados aos nomes globais que o administrador preparou para oferecer as dependências. É claro que tanto o meu componente quanto suas dependências mantém um contrato entre si, na forma das interfaces java EntityManager e Session. No Plan 9, meu componente seria um programa que faz referência a dois arquivos no seu file system -- um arquivo que repr...