Estou portando algum código legado de um núcleo ARM926 para o CortexA9. Esse código é baremetal e não inclui um SO ou bibliotecas padrão, todas personalizadas. Estou com uma falha que parece estar relacionada a uma condição de corrida que deve ser evitada pelo corte crítico do código.
Quero alguns comentários sobre minha abordagem para ver se minhas seções críticas podem não ser implementadas corretamente para esta CPU. Estou usando o GCC. Suspeito que haja algum erro sutil.
Além disso, existe uma biblioteca de código-fonte aberto que tenha esses tipos de primitivos para o ARM (ou mesmo uma boa biblioteca leve de spinlock / seméforo)?
#define ARM_INT_KEY_TYPE unsigned int
#define ARM_INT_LOCK(key_) \
asm volatile(\
"mrs %[key], cpsr\n\t"\
"orr r1, %[key], #0xC0\n\t"\
"msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))
O código é usado da seguinte maneira:
/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);
<access registers, shared globals, etc...>
ARM_INT_UNLOCK(key);
A idéia da "chave" é permitir seções críticas aninhadas, e elas são usadas no início e no final das funções para criar funções reentrantes.
Obrigado!
fonte
ldrex
estrex
fazê-lo corretamente. Aqui está uma página da Web que mostra como usarldrex
estrex
implementar um spinlock.Respostas:
A parte mais difícil de lidar com uma seção crítica sem um sistema operacional não é criar o mutex, mas sim descobrir o que deve acontecer se o código quiser usar um recurso que não está disponível no momento. As instruções de carregamento exclusivo e de armazenamento condicional exclusivo facilitam bastante a criação de uma função "swap" que, dada um ponteiro para um número inteiro, armazena atomicamente um novo valor, mas retorna o que o número inteiro apontado continha:
Dada uma função como a acima, é possível inserir facilmente um mutex por meio de algo como
Na ausência de um sistema operacional, a principal dificuldade geralmente está no código "não foi possível obter o mutex". Se ocorrer uma interrupção quando um recurso protegido por mutex estiver ocupado, pode ser necessário que o código de tratamento de interrupções defina um sinalizador e salve algumas informações para indicar o que ele queria fazer e, em seguida, tenha qualquer código principal que adquira o O mutex verifica sempre que ele libera o mutex para ver se uma interrupção deseja fazer alguma coisa enquanto o mutex foi mantido e, se houver, executa a ação em nome da interrupção.
Embora seja possível evitar problemas com interrupções que desejam usar recursos protegidos por mutex simplesmente desativando interrupções (e, de fato, desativar interrupções pode eliminar a necessidade de qualquer outro tipo de mutex), em geral é desejável evitar a desativação de interrupções por mais tempo do que o necessário.
Um compromisso útil pode ser usar um sinalizador como descrito acima, mas ter o código da linha principal que liberará as interrupções de desativação do mutex e verificar o sinalizador mencionado antes de fazê-lo (reative as interrupções após o lançamento do mutex). Essa abordagem não exige que as interrupções sejam desativadas por muito tempo, mas evitará a possibilidade de que, se o código da linha principal testar a sinalização da interrupção após o lançamento do mutex, exista um risco de que entre o momento em que a sinalize e a hora em que a sinalize age sobre ele, pode ser impedido por outro código que adquire e libera o mutex e age sobre o sinalizador de interrupção; se o código da linha principal não testar o sinalizador da interrupção após o lançamento do mutex,
De qualquer forma, o mais importante será ter um meio pelo qual o código que tenta usar um recurso protegido por mutex quando não estiver disponível terá um meio de repetir sua tentativa assim que o recurso for liberado.
fonte
Essa é uma maneira pesada de fazer seções críticas; desativar interrupções. Pode não funcionar se o seu sistema tiver / lida com falhas de dados. Também aumentará a latência de interrupção. O irqflags.h do Linux possui algumas macros que lidam com isso. O
cpsie
ecpsid
instruções podem ser úteis; No entanto, eles não salvam o estado e não permitem o aninhamento.cps
não usa um registro.Para a série Cortex-A , eles
ldrex/strex
são mais eficientes e podem trabalhar para formar um mutex para a seção crítica ou podem ser usados com algoritmos sem travas para se livrar da seção crítica.Em certo sentido, o
ldrex/strex
parece um ARMv5swp
. No entanto, eles são muito mais complexos de implementar na prática. Você precisa de um cache de trabalho e a memória de destinoldrex/strex
precisa estar no cache. A documentação do ARMldrex/strex
é bastante nebulosa, pois eles querem mecanismos para trabalhar em CPUs que não sejam o Cortex-A. No entanto, para o Cortex-A, o mecanismo para manter o cache da CPU local sincronizado com outras CPUs é o mesmo usado para implementar asldrex/strex
instruções. Para a série Cortex-A, a reserva granual (tamanho deldrex/strex
memória reservada) é igual a uma linha de cache; você também precisará alinhar a memória à linha de cache se pretender modificar vários valores, como em uma lista duplamente vinculada.Você precisa garantir que a sequência nunca possa ser antecipada . Caso contrário, você poderá obter duas variáveis- chave com as interrupções ativadas e a liberação do bloqueio estará incorreta. Você pode usar a
swp
instrução com a memória principal para garantir consistência no ARMv5, mas esta instrução foi preterida no Cortex-A em favor deldrex/strex
, pois funciona melhor em sistemas com várias CPUs.Tudo isso depende de que tipo de agendamento seu sistema possui. Parece que você só tem linhas principais e interrupções. Geralmente, você precisa que as primitivas da seção crítica tenham alguns ganchos no planejador, dependendo de quais níveis (sistema / espaço do usuário / etc) você deseja que a seção crítica trabalhe.
É difícil escrever de maneira portátil. Ou seja, essas bibliotecas podem existir para determinadas versões de CPUs ARM e para sistemas operacionais específicos.
fonte
Vejo vários problemas em potencial com essas seções críticas. Existem advertências e soluções para tudo isso, mas como um resumo:
Primeiro, você definitivamente precisa de algumas barreiras de memória do compilador . O GCC os implementa como clobbers . Basicamente, essa é uma maneira de informar ao compilador "Não, não é possível mover os acessos à memória por essa parte do assembly embutido, pois isso pode afetar o resultado dos acessos à memória". Especificamente, você precisa dos dois
"memory"
e dos"cc"
clobbers, nas macros inicial e final. Isso impedirá que outras coisas (como chamadas de função) também sejam reordenadas em relação ao assembly embutido, porque o compilador sabe que pode ter acesso à memória. Eu vi o GCC para ARM manter o estado nos registros de código de condição no conjunto em linha com os"memory"
clobbers, então você definitivamente precisa do"cc"
clobber.Em segundo lugar, essas seções críticas estão economizando e restaurando muito mais do que apenas se as interrupções estão ativadas. Especificamente, eles estão salvando e restaurando a maior parte do CPSR (Registro atual do status do programa) (o link é para o Cortex-R4 porque não consegui encontrar um bom diagrama para um A9, mas deve ser idêntico). Existem restrições sutis torno das quais partes do estado podem realmente ser modificadas, mas é mais do que necessário aqui.
Entre outras coisas, isso inclui os códigos de condição (onde os resultados de instruções como
cmp
são armazenados para que instruções condicionais subsequentes possam atuar no resultado). O compilador definitivamente ficará confuso com isso. Isso é facilmente solucionável usando o"cc"
triturador como mencionado acima. No entanto, isso fará com que o código falhe sempre, portanto, não parece com o que você está tendo problemas. Um pouco de uma bomba-relógio, porém, nessa modificação aleatória de outro código pode fazer com que o compilador faça algo um pouco diferente que será quebrado por isso.Isso também tentará salvar / restaurar os bits de TI, que são usados para implementar a execução condicional do Thumb . Observe que, se você nunca executar o código Thumb, isso não importa. Eu nunca descobri como o assembly embutido do GCC lida com os bits de TI, além de concluir que não, o que significa que o compilador nunca deve colocar o assembly embutido em um bloco de TI e sempre espera que o assembly termine fora de um bloco de TI. Eu nunca vi o GCC gerar código violando essas suposições e fiz um assembly interno bastante intrincado com otimização pesada, por isso tenho certeza de que eles são válidos. Isso significa que provavelmente não tentará alterar os bits de TI; nesse caso, está tudo bem. A tentativa de modificar esses bits é classificada como "arquitetonicamente imprevisível", para que ele possa fazer todos os tipos de coisas ruins, mas provavelmente não fará nada.
A última categoria de bits que serão salvos / restaurados (além dos que realmente desativam interrupções) são os bits de modo. Provavelmente, isso não muda, portanto, provavelmente não importa, mas se você tiver algum código que mude deliberadamente os modos, essas seções de interrupção podem causar problemas. Mudar entre o modo privilegiado e o usuário é o único caso que eu esperaria.
Terceiro, nada impede que uma interrupção altere outras partes do CPSR entre o
MRS
e oMSR
inARM_INT_LOCK
. Quaisquer alterações podem ser substituídas. Na maioria dos sistemas razoáveis, as interrupções assíncronas não alteram o estado do código em que são interrompidas (incluindo o CPSR). Se o fizerem, fica muito difícil argumentar sobre o que o código fará. No entanto, é possível (a alteração do bit de desativação do FIQ me parece mais provável), portanto, você deve considerar se o seu sistema faz isso.Aqui está como eu os implementaria de maneira a abordar todos os possíveis problemas que eu apontei:
Certifique-se de compilar,
-mcpu=cortex-a9
porque pelo menos algumas versões do GCC (como a minha) são padronizadas para uma CPU ARM mais antiga que não suportacpsie
ecpsid
.Eu usei em
ands
vez de apenasand
no,ARM_INT_LOCK
por isso é uma instrução de 16 bits se for usada no código Thumb. De"cc"
qualquer forma, o clobber é necessário, por isso é estritamente um benefício de desempenho / tamanho do código.0
e1
são etiquetas locais , para referência.Eles devem ser utilizados da mesma maneira que suas versões. O
ARM_INT_LOCK
é tão rápido / pequeno quanto o original. Infelizmente, não consegui encontrar uma maneira deARM_INT_UNLOCK
agir com segurança em qualquer lugar com poucas instruções.Se o seu sistema tiver restrições quando IRQs e FIQs estiverem desativados, isso poderá ser simplificado. Por exemplo, se eles estiverem sempre desativados juntos, você poderá combinar um
cbz
+cpsie if
como este:Como alternativa, se você não se importa com FIQs, é semelhante a simplesmente deixar de ativar / desativar completamente.
Se você souber que nada mais altera nenhum dos outros bits de estado no CPSR entre o bloqueio e o desbloqueio, também poderá usar continue com algo muito semelhante ao seu código original, exceto com ambos
"memory"
e"cc"
clobbers nos doisARM_INT_LOCK
eARM_INT_UNLOCK
fonte
para seções críticas relativamente simples, você pode usar as instruções LDREX e STREX.
/programming/51795537/critical-sections-in-arm http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204f/Cihbghef.html
fonte