Pergunta

Estou trabalhando em um projeto Linux embarcado que conecta um ARM9 a um chip codificador de vídeo de hardware e grava o vídeo em um cartão SD ou pendrive.A arquitetura do software envolve um driver de kernel que lê dados em um conjunto de buffers e um aplicativo de usuário que grava os dados em um arquivo no dispositivo removível montado.

Estou descobrindo que, acima de uma determinada taxa de dados (cerca de 750 kbyte/s), começo a ver o aplicativo de gravação de vídeo do usuário travando por talvez meio segundo, aproximadamente a cada 5 segundos.Isso é suficiente para fazer com que o driver do kernel fique sem buffers - e mesmo que eu pudesse aumentar o número de buffers, os dados de vídeo teriam que ser sincronizados (idealmente dentro de 40 ms) com outras coisas que estão acontecendo em tempo real.Entre esses "picos de atraso" de 5 segundos, as gravações são concluídas em 40 ms (no que diz respeito ao aplicativo - agradeço que eles sejam armazenados em buffer pelo sistema operacional)

Acho que esse pico de atraso tem a ver com a maneira como o Linux está liberando dados para o disco - observo que o pdflush foi projetado para acordar a cada 5s, meu entendimento é que seria isso que faria a escrita.Assim que a paralisação termina, o aplicativo da área do usuário é capaz de atender e gravar rapidamente o backlog de buffers (que não estourou).

Acho que o dispositivo para o qual estou escrevendo tem um rendimento final razoável:copiar um arquivo de 15 MB de um fs de memória e aguardar a conclusão da sincronização (e a luz do pendrive parar de piscar) me proporcionou uma velocidade de gravação de cerca de 2,7 MBytes/s.

Estou procurando dois tipos de pistas:

  1. Como posso impedir que a gravação intermitente paralise meu aplicativo - talvez processar prioridades, patches em tempo real ou ajustar o código do sistema de arquivos para gravar continuamente em vez de intermitentemente?

  2. Como posso informar meus aplicativos sobre o que está acontecendo com o sistema de arquivos em termos de backlog de gravação e taxa de transferência para o cartão/stick?Tenho a capacidade de alterar a taxa de bits do vídeo no codec de hardware em tempo real, o que seria muito melhor do que descartar quadros ou impor um limite artificial na taxa de bits máxima permitida.

Mais algumas informações:este é um ARM9 de 200 MHz atualmente executando um kernel baseado em Montavista 2.6.10.

Atualizações:

  • Montar o sistema de arquivos SYNC faz com que o rendimento seja muito baixo.
  • A mídia removível é formatada em FAT/FAT32 e deve ser assim, pois o objetivo do design é que a mídia possa ser conectada a qualquer PC com Windows e lida.
  • Chamar regularmente sync() ou fsync(), digamos, cada segundo causa travamentos regulares e rendimento inaceitavelmente baixo
  • Estou usando write() e open(O_WRONLY | O_CREAT | O_TRUNC) em vez de fopen() etc.
  • Não consigo encontrar nada on-line imediatamente sobre os mencionados "sistemas de arquivos em tempo real do Linux".Ligações?

Espero que isto faça sentido.Primeira pergunta sobre Linux embarcado no stackoverflow?:)

Foi útil?

Solução

Para que conste, revelaram-se dois aspectos principais que parecem ter eliminado o problema em todos os casos, exceto nos mais extremos.Este sistema ainda está em desenvolvimento e ainda não foi completamente testado sob tortura, mas está funcionando razoavelmente bem (toque na madeira).

A grande vitória veio de tornar o aplicativo userland Writer multithread.São as chamadas para write() que bloqueiam às vezes:outros processos e threads ainda são executados.Contanto que eu tenha um thread atendendo o driver do dispositivo e atualizando contagens de quadros e outros dados para sincronizar com outros aplicativos em execução, os dados podem ser armazenados em buffer e gravados alguns segundos depois, sem quebrar nenhum prazo.Tentei primeiro um buffer duplo simples de pingue-pongue, mas não foi suficiente;buffers pequenos ficariam sobrecarregados e os grandes apenas causariam pausas maiores enquanto o sistema de arquivos digeria as gravações.Um pool de 10 buffers de 1 MB enfileirados entre threads está funcionando bem agora.

O outro aspecto é ficar de olho no rendimento final de gravação em mídia física.Para isso estou de olho na estatística Dirty:relatado por /proc/meminfo.Eu tenho um código básico e pronto para limitar o codificador se estiver sujo:sobe acima de um certo limite, parece funcionar vagamente.Mais testes e ajustes serão necessários posteriormente.Felizmente, tenho muita RAM (128M) para brincar, o que me dá alguns segundos para ver meu backlog aumentando e diminuindo suavemente.

Tentarei lembrar de voltar e atualizar esta resposta se achar que preciso fazer mais alguma coisa para lidar com esse problema.Obrigado aos outros respondentes.

Outras dicas

Vou dar algumas sugestões, conselhos são baratos.

  • certifique-se de usar uma API de nível inferior para gravar no disco, não use funções de cache no modo de usuário como fopen, fread, fwrite use as funções de nível inferior open, read, write.
  • passe o O_SYNC flag quando você abre o arquivo, isso fará com que cada operação de gravação seja bloqueada até ser gravada no disco, o que removerá o comportamento intermitente de suas gravações... com a despesa de cada gravação ser mais lenta.
  • Se você estiver fazendo leituras/ioctls de um dispositivo para obter um pedaço de dados de vídeo, você pode considerar alocar uma região de memória compartilhada entre o aplicativo e o kernel, caso contrário, você será atingido por um monte de copy_to_user chamadas ao transferir buffers de dados de vídeo do espaço do kernel para o espaço do usuário.
  • Talvez seja necessário validar se o seu dispositivo flash USB é rápido o suficiente com transferências sustentadas para gravar os dados.

Apenas algumas reflexões, espero que isso ajude.

Aqui há algumas informações sobre como ajustar o pdflush para operações com muita gravação.

Parece que você está procurando sistemas de arquivos Linux em tempo real.Certifique-se de pesquisar no Google e outros por isso.

O XFS tem uma opção em tempo real, embora eu não tenha brincado com ela.

hdparm pode permitir que você desligue completamente o cache.

Ajustar as opções do sistema de arquivos (desativar todos os atributos extras de arquivo desnecessários) pode reduzir o que você precisa liberar, acelerando assim a liberação.Duvido que isso ajude muito, no entanto.

Mas minha sugestão seria evitar usar o stick como sistema de arquivos e, em vez disso, usá-lo como um dispositivo bruto.Coloque dados nele como faria usando 'dd'.Em seguida, leia os dados brutos em outro lugar e escreva-os após o cozimento.

Claro, não sei se isso é uma opção para você.

Tem um auxílio de depuração, você pode usar strace para ver quais operações estão demorando.Pode haver algo surpreendente com o FAT/FAT32.

Você escreve em um único arquivo ou em vários arquivos?

Você pode criar um thread de leitura, que manterá um pool de buffer de vídeo pronto para ser gravado em uma fila.Quando um quadro é recebido, ele é adicionado à fila e o thread de escrita é sinalizado

Dados compartilhados

empty_buffer_queue
ready_buffer_queue
video_data_ready_semaphore

Tópico de leitura:

buf=get_buffer()
bufer_to_write = buf_dequeue(empty_buffer_queue)
memcpy(bufer_to_write, buf)
buf_enqueue(bufer_to_write, ready_buffer_queue)
sem_post(video_data_ready_semaphore)

Tópico de escrita

sem_wait(vido_data_ready_semaphore)
bufer_to_write = buf_dequeue(ready_buffer_queue)
write_buffer
buf_enqueue(bufer_to_write, empty_buffer_queue)

Se a sua escrita threaded estiver bloqueada aguardando o kernel, isso pode funcionar.No entanto, se você estiver bloqueado dentro do espaço do kernel, não há muito o que fazer, exceto procurar um kernel mais recente que o 2.6.10.

Sem saber mais sobre suas circunstâncias específicas, só posso oferecer as seguintes suposições:

Tente usar fsync()/sync() para forçar o kernel a liberar dados para o dispositivo de armazenamento com mais frequência.Parece que o kernel armazena em buffer todas as suas gravações e, em seguida, bloqueia o barramento ou paralisa o sistema enquanto executa a gravação real.Com chamadas cuidadosas para fsync() você pode tentar agendar gravações no barramento do sistema de uma maneira mais detalhada.

Pode fazer sentido estruturar o aplicativo de tal forma que a tarefa de codificação/captura (você não mencionou a captura de vídeo, então estou fazendo uma suposição aqui - você pode querer adicionar mais informações) seja executada em seu próprio thread e armazena em buffer sua saída na área do usuário - então, um segundo thread pode lidar com a gravação no dispositivo.Isso fornecerá um buffer de suavização para permitir que o codificador sempre termine suas gravações sem bloqueio.

Uma coisa que parece suspeita é que você só vê esse problema em uma determinada taxa de dados - se isso realmente fosse um problema de buffer, eu esperaria que o problema acontecesse com menos frequência em taxas de dados mais baixas, mas ainda esperaria ver isso emitir.

De qualquer forma, mais informações podem ser úteis.Qual é a arquitetura do seu sistema?(Em termos muito gerais.)

Dadas as informações adicionais que você forneceu, parece que a taxa de transferência do dispositivo é bastante baixa para pequenas gravações e liberações frequentes.Se você tiver certeza de que, para gravações maiores, poderá obter taxa de transferência suficiente (e não tenho certeza se esse é o caso, mas o sistema de arquivos pode estar fazendo algo estúpido, como atualizar o FAT após cada gravação), tendo um encadeamento de codificação de dados para um thread de escrita com buffer suficiente no thread de escrita para evitar travamentos.Eu usei buffers de anel de memória compartilhada no passado para implementar esse tipo de esquema, mas qualquer mecanismo IPC que permita ao gravador gravar no processo de E/S sem parar, a menos que o buffer esteja cheio, deve resolver o problema.

Uma função útil do Linux e uma alternativa para sincronizar ou fsync é sync_file_range.Isso permite agendar dados para gravação sem esperar que o sistema de buffer do kernel faça isso.

Para evitar longas pausas, certifique-se de que sua fila de IO (por exemplo:/sys/block/hda/queue/nr_requests) é grande o suficiente.Essa fila é para onde os dados vão entre a liberação da memória e a chegada ao disco.

Observe que sync_file_range não é portátil e está disponível apenas nos kernels 2.6.17 e posteriores.

Disseram-me que depois que o host envia um comando, os cartões MMC e SD "devem responder entre 0 e 8 bytes".

No entanto, a especificação permite que esses cartões respondam com "ocupado" até que a operação seja concluída e, aparentemente, não há limite de quanto tempo um cartão pode alegar estar ocupado (por favor, diga-me se existe tal limite).

Vejo que alguns chips flash de baixo custo, como o M25P80, têm um "tempo máximo de apagamento de setor único" garantido de 3 segundos, embora normalmente "apenas" exija 0,6 segundos.

Esses 0,6 segundos soam suspeitamente semelhantes ao seu "paralisar por talvez meio segundo".

Suspeito que a compensação entre chips flash baratos e lentos e chips flash caros e rápidos tenha algo a ver com a grande variação nos resultados da unidade flash USB:

Ouvi rumores de que toda vez que um setor flash é apagado e reprogramado, demora um pouco mais do que da última vez.

Portanto, se você tiver um aplicativo com tempo crítico, pode ser necessário (a) testar seus cartões SD e pendrives para garantir que eles atendam à latência, largura de banda mínima, etc.exigido pela sua aplicação e (b) testar novamente periodicamente ou substituir preventivamente esses dispositivos de memória.

Bem, óbvio primeiro, você tentou dizer explicitamente para o arquivo ser liberado?Também acho que pode haver algum ioctl que você possa usar para fazer isso, mas honestamente não fiz muita programação de arquivos C/POSIX.

Vendo que você está em um kernel Linux, você poderá ajustar e reconstruir o kernel para algo que atenda melhor às suas necessidades, por exemplo.descargas muito mais frequentes, mas também menores, para o armazenamento permanente.


Uma verificação rápida em minhas páginas de manual revela o seguinte:

SYNC(2)                    Linux Programmer’s Manual                   SYNC(2)

NAME
       sync - commit buffer cache to disk

SYNOPSIS
       #include <unistd.h>

       void sync(void);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       sync(): _BSD_SOURCE || _XOPEN_SOURCE >= 500

DESCRIPTION
       sync() first commits inodes to buffers, and then buffers to disk.

ERRORS
       This function is always successful.

Fazer seu próprio flush() parece certo para mim - você quer estar no controle, não deixá-lo aos caprichos da camada de buffer genérica.

Isso pode ser óbvio, mas certifique-se de não chamar write() com muita frequência - certifique-se de que cada write() tenha dados suficientes para serem gravados para que a sobrecarga do syscall valha a pena.Além disso, na outra direção, não ligue muito raramente, ou ele bloqueará por tempo suficiente para causar um problema.

Em uma faixa mais difícil de reimplementar, você tentou mudar para E/S assíncrona?Usando o aio, você pode disparar uma gravação e entregar a ela um conjunto de buffers enquanto suga dados de vídeo para o outro conjunto e, quando a gravação terminar, você alterna os conjuntos de buffers.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top