CoreCLR usa mlock
durante a inicialização e falha se mlock
falhar com EPERM
. Geralmente, isso não é um problema.
No entanto, muitas distribuições Linux estão começando a usar systemd-nspawn
para construir código. Isso cria um chroot onde os programas têm recursos restritos. Especificamente, eles não têm CAP_IPC_LOCK
, o que significa que eles não podem usar mlock
.
Quando mlock
não funciona, o coreclr não inicia. Isso aparece em um strace
como algo como:
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbd542bb000
mlock(0x7fbd542bb000, 4096) = -1 EPERM (Operation not permitted)
write(2, "Failed to initialize CoreCLR, HR"..., 49) = 49
Como resultado, isso torna basicamente impossível construir o coreclr em alguns sistemas de compilação de distribuição Linux.
cc @tmds @alucryd
Consulte https://github.com/dotnet/source-build/issues/285#issuecomment -399949984 e https://github.com/rpm-software-management/mock/issues/186 para alguns exemplos em que isso está atingindo alguns constrói
O mlock
é necessário para o comportamento adequado da função FlushProcessWriteBuffers PAL que é crucial para garantir a suspensão confiável do tempo de execução para o GC. Veja https://github.com/dotnet/coreclr/blob/e6ebea25bea93eb4ec07cbd5003545c4805886a8/src/pal/src/thread/process.cpp#L3095 -L3098 para descrição do motivo.
No Linux 4.3 e superior, há um syscall sys_membarrier
que podemos usar como um mecanismo alternativo para implementar FlushProcessWriteBuffers. O problema dotnet/runtime#4501 está rastreando isso. @sdmaclea tentou implementá-lo e testou no ARM64 . Ele descobriu que o desempenho foi muito ruim e que o tempo de execução de nossos ~11.000 testes coreclr foi cerca de 50% maior. No entanto, nenhum teste foi feito em outro hardware, portanto, não ficou claro se o problema de desempenho é específico do ARM64 ou um problema geral.
Curiosamente, acabei de descobrir o seguinte artigo descrevendo problemas de desempenho com o sys_membarrier: https://lttng.org/blog/2018/01/15/membarrier-system-call-performance-and-userspace-rcu/. A razão é que o syscall internamente espera até que todos os threads em execução no sistema tenham passado por uma troca de contexto, o que pode levar dezenas de milissegundos. Mas a boa notícia mencionada neste artigo é que a partir do Linux 4.14, há um novo sinalizador que pode ser passado para o sys_membarrier syscall e que o faz usar IPI para implementar a semântica de barreira de memória. E isso é muito mais rápido. Portanto, devemos experimentá-lo.
Comentários muito úteis
O
mlock
é necessário para o comportamento adequado da função FlushProcessWriteBuffers PAL que é crucial para garantir a suspensão confiável do tempo de execução para o GC. Veja https://github.com/dotnet/coreclr/blob/e6ebea25bea93eb4ec07cbd5003545c4805886a8/src/pal/src/thread/process.cpp#L3095 -L3098 para descrição do motivo.No Linux 4.3 e superior, há um syscall
sys_membarrier
que podemos usar como um mecanismo alternativo para implementar FlushProcessWriteBuffers. O problema dotnet/runtime#4501 está rastreando isso. @sdmaclea tentou implementá-lo e testou no ARM64 . Ele descobriu que o desempenho foi muito ruim e que o tempo de execução de nossos ~11.000 testes coreclr foi cerca de 50% maior. No entanto, nenhum teste foi feito em outro hardware, portanto, não ficou claro se o problema de desempenho é específico do ARM64 ou um problema geral.Curiosamente, acabei de descobrir o seguinte artigo descrevendo problemas de desempenho com o sys_membarrier: https://lttng.org/blog/2018/01/15/membarrier-system-call-performance-and-userspace-rcu/. A razão é que o syscall internamente espera até que todos os threads em execução no sistema tenham passado por uma troca de contexto, o que pode levar dezenas de milissegundos. Mas a boa notícia mencionada neste artigo é que a partir do Linux 4.14, há um novo sinalizador que pode ser passado para o sys_membarrier syscall e que o faz usar IPI para implementar a semântica de barreira de memória. E isso é muito mais rápido. Portanto, devemos experimentá-lo.