Ponteiro perdido no Kernel pode corromper arquivos?

9 de June de 2010 - Fernando Roberto

A nova versão de um driver pode implementar aquela funcionalidade nova que você tanto esperava. Afinal de contas, o time de desenvolvimento de drivers anda sempre muito ocupado, e para conseguir alguma coisa nova é sempre um parto. O único problema é que de vez em outra uma tela azul acontece. Os mais desesperados podem até querer usar o novo driver a qualquer custo, mesmo que uma telinha azul apareça com uma frequência aceitável. Aí surge a perguntinha:

Fernando, existe algum problema se eu for usando esse driver novo até que uma correção para essa tela azul saia?

O principal problema aqui é que não temos a menor idéia do que está causando a tela azul. Esse tipo de classificação (sem a menor idéia) inclui um ponteiro que pode sair escrevendo onde não se deve. Além de sair atropelando estruturas vitais do sistema operacional, esse ponteiro bomba pode também corromper arquivos. Então, mais uma perguntinha:

Mas Fernando, esse driver nem faz manipulação de arquivos. Como é que um ponteiro perdido pode abrir um arquivo e ainda gerar uma escrita em disco para corrompê-lo?

Na verdade, isso não é assim tão difícil. Você já ouviu falar do Cache Manager?

Quem é esse tal de Cache Manager?

Vi uma definição simples de Cache Manager em uma palestra do Plugfest. Vamos ver se consigo reproduzí-la aqui. Vocês desenvolvedores provavelmente já fizeram cache do conteúdo de algum arquivo em uma aplicação para não ter que acessar o tal arquivo toda vez que precisar da informação contida nele, certo? Errado? Tá tá, então do começo hoje.

Lembra lá no prézinho quando a tia ensinou que se você fizer acesso com frequência a um arquivo, você pode manter uma cópia dele em memória e evitar de fazer tanto I/O para ganhar performance? Nesse caso uma área de memória, também conhecida como cache, é carregada com o conteúdo do arquivo. Depois disso, os vários acessos de leitura ao arquivo são substituídos por leituras no cache. Quando uma escrita acontece, o cache é atualizado e a escrita também vai para o arquivo. Em casos onde a escrita é frequente, o cache é atualizado a cada escrita enquanto o arquivo recebe várias modificações de uma vez em intervalos definidos.

O cache implementado na aplicação perde o sentido se um determinado arquivo é compartilhado por mais de uma aplicação, o conteúdo do cache da aplicação “A” tem que ser o mesmo do cache da aplicação “B”, caso contrário, as alterações feitas pela aplicação “A” não seriam vistas pela aplicação “B” e vice versa, sem falar que as atualizações seriam perdidas sem o correto sincronismo que isso exigiria.

Por essa razão é que existe um cache centralizado no sistema. Um módulo no Kernel que mantém páginas de memória contendo o conteúdo dos arquivos recentemente manipulados. Quando um arquivo é aberto, ele é registrado pelo seu respectivo driver de File System no Cache Manager. Mas o Cache Manager não faz tudo sozinho. Na verdade ele faz parte de uma gangue nas “quebrada” que garante a otimização de acessos a arquivos no sistema. Para isso o Cache Manager conta com a ajuda de seus fiéis companheiros, o Vitual Memory Manager e os File System Drivers.

Pulando algumas toneladas de detalhes, digamos que quando uma solicitação de leitura chega a um driver de file system, este a encaminha ao Cache Manager, este então vai satisfazer tal operação apenas copiando o conteúdo desejado do arquivo que já estaria em páginas de memória. Copiar os dados da memória é muito mais rápido que fazer todo o ritual para obter os mesmos dados do disco, mas para isso os dados já deveriam estar carregados na memória.

Fernando, o Cache Manager coloca todo o arquivo na memória?

Quer uma resposta tosca? Sim e Não ;-). O Cache Manager na verdade mapeia o arquivo aberto em memória, e para isso ele conta com as características mais básicas de memória virtual discutidas neste outro post. Um intervalo de endereços é reservado no sistema, mas tais endereços ainda não se refletem em espaços nos chips de memória, ou seja, existe um endereço de memória, mas seu contetúdo ainda está em disco. O Memory Manager protege esses endereços contra acessos que podem ocorrem a eles. Quando um acesso de leitura é realizado nestes endereços, um page fault ocorre e o Memory Manager então precisa recuperar os dados do arquivo que estão no disco e colocá-os em memória. Para fazer isso o Memory Manager vai criar uma solicitação de leitura no I/O Manager para que uma IRP possa ser entregue ao respectivo driver de file system do arquivo em questão.

Pára tudo “perlamor” de Deus! Fernando, você disse logo alí em cima que quando uma solicitação de leitura chega ao driver de file system, esta é encaminhada ao Cache Manager que vai copiar os dados já contidos na memória a fim de antender a solicitação. Mas agora você está dizendo que para carregar tais páginas de memória o Cache Manager troca uma idéia com o Memory Manager, que por sua vêz vai criar uma solicitação de leitura para os drivers de file system. Isso não lhe parece um pouco recursivo?

Eu diria completamente recursivo, mas lembre-se que isso vai ocorrer apenas quando o arquivo ainda não foi lido por nenhum processo, e portanto ainda não está no cache do sistema. Para diferenciar uma solicitação da outra, drivers de file system precisam verificar a flag IRP_NOCACHE nas solicitações que recebem. Quando as solicitações vêm de uma aplicação, estas não carregam a flag IRP_NOCACHE, e desta forma podem ser atendidas pelo Cache Manager, por outro lado quando o Memory Manager precisa suprir as páginas de memória do Cache Manager, tais solicitações precisam ignorar o conteúdo do cache, e por isso carregam a flag IRP_NOCACHE. Paga facilitar o entendimento de toda essa  máquina, observem os passos enumerados de uma leitura de arquivo que ainda não está no cache.

  1. Uma aplicação faz uma solicitação de leitura de um arquivo.
  2. I/O Manager cria uma IRP e a encaminha ao seu respectivo driver de file system.
  3. O driver de file system verifica a ausência da flag IRP_NOCACHE e solicita a cópia dos dados desse arquivo do cache para o buffer da aplicação.
  4. O Cache Manager tenta fazer a cópia acessando as páginas que foram mapeadas do arquivo. Com isso um page fault é gerado por esse acesso e é atendido pelo Memory Manager.
  5. O Memory Manager cria uma nova IRP para atender a necessidade de abastecer o Cache Manager. Essa solicitação é encaminhada recursivamente ao driver de file system.
  6. Dessa vez o driver verifica a presença da flag IRP_NOCACHE e então cria as solicitações que serão atendidas pelos drivers de disco ou de rede.
  7. As solicitações de leitura de mídia são atendidas.
  8. As páginas de memória são abastecidas e o page fault é satisteito.
  9. Memory Manager re-executa a tentativa de leitura do Cache Manager que gerou o page fault, mas desta vez a o acesso de leitura será bem sucedido, pois os dados agora estão no endereço de memória mapeado.
  10. O Cache Manager completa a cópia dos dados para o buffer da aplicação.
  11. O driver de file system completa a solicitação de leitura.
  12. Os dados são retornados para a aplicação que fez a solicitação inicial.

Atenção agora meninos e meninas: A sequência descrita acima ilustra o caso onde o Cache Manager ainda precisa carregar o arquivo em memória. As próximas tentativas de leituras são satisfeitas diretamente pelo Cache Manager, que não vai gerar um page fault. Não vão me matar de vergonha dizendo por aí que o sistema operacional sempre faz toda a sequência para cada leitura de arquivo.

Mais uma vez as regras básicas de memória virtual são aplicadas aqui para que conforme as páginas de memória vão deixando de ser acessadas com tanta frequência, elas perdem lugar nos chips de memória, e assim, se mais tarde forem acessadas novamente, um novo page fault será gerado.

Interessante ver como esses componentes, o I/O Manager, Cache Manager, Virtual Memory Manager, File System Drivers, sem falar dos filtros que ainda podem existir, todos trabalhando juntos como caixas pretas, cada um com seu papel e sem conhecer o funcionamento interno do outro, interagindo entre si apenas através de suas interfaces públicas. Óbviamente que para a felicidade de alguns e talvêz tristeza de outros, não coloquei todos os detalhes aqui, mas podem ser encontrados no conhecido livro da galinha preta.

Mas voltando ao assunto…

De maneira análoga, as escritas também utilizam essa mesma mecânica que envolve mapeamento de arquivos. O simples fato de escrever no intervalo de endereços que é mantido pelo Cache Manager vai fazer com que tal página seja marcada como modificada, e mais tarde o Memory Manager vai querer atualizar essa página em disco. Dessa forma podemos resumir que solicitações de escrita chegam aos drivers de File System e são encaminhadas ao Cache Manager, que vai simplesmente escrever nas páginas referentes ao conteúdo do arquivo e completar a solicitação. Page faults e threads de sistema vão se encarregar de atualizar o que for preciso no momento mais adequado. O importante a notar aqui é pensarmos no Cache Manager como um simples consumidor dos serviços do Memory Manager, tudo que ele precisa fazer é ler ou escrever em páginas de memória, e é aqui que o título do post começa a fazer sentido.

Nada impede um ponteiro retardado de escrever em páginas de memória que fazem referência ao conteúdo de arquivos. Se isso acontece, o restante do sistema vai se encarregar de atualizar as barbaridades desse ponteiro em disco corrompendo o arquivo. É fácil notar que nem é necessário tantos passos para isso acontecer.

  1. Um driver inexperiente come aquele pedaço de pizza que ficou esquecido no micro-ondas e fica bem loco. Depois de dar vexame, falar o que não devia, chorar e dizer que te considera pra caramba, o driver escreve em páginas de memória referente a um arquivo de dados. Tipo um daqueles do SQL que eu nem imagino a extensão.
  2. O coitado do Memory Manager faz seu trabalho para garantir o leitinho das crianças como se nada de errado tivesse acontecido.
  3. O driver de file system vai de embalo e consolida a completa falta de noção do driver, que numa hora dessas já está abraçado com o vaso sanitário.
  4. Esse passo não é ilustrado na sequência acima mas pode ser explicado nesse site.

Nessa hora você vai torcer para que seu driver novinho em folha escreva sobre alguma estrutura vital do sistema para que uma tela azul possa conter a atividade desse inconsequente. Por esse motivo é que o sistema está cheio de testes e verificações para garantir que os dados do usuário não sejam perdidos. Melhor ver uma tela azul do que ter consequências muito piores. Lembre-se do sábio Morphy:

Nada é tão ruim que não possa ser piorado

Ainda existem muitas outras características interessantes sobre o Cache Manager que eu gostaria de descrever aqui, como por exemplo a “Falha de escrita retardada”, mas esse post já está ficando muito grande.


Até mais… 😉

2 Responses to “Ponteiro perdido no Kernel pode corromper arquivos?”

  1. Leonardo D`Angelo says:

    Parabéns pelo Blog !!!!

  2. Matheus Garbelini says:

    Muito bom seu blog, é realmente muito difício encontrar assuntos relacionados a drivers no windows. Assuntos a respeito de desenvolvimento de drivers a linux podem ser encontrados em qualquer esquina. Fico feliz que seu blog da uma luz para aqueles que pretendem entrar nesse ramo. Desejo mesmo que no futuro as comunidades de kernels possa ficar tão aberta e de fácil acesso quanto as comunidades que temos referentes à programações em user mode. Talvez um dia seja criado um stack Overflow só para kernels xD.

Deixe um comentário