“Donor Migration” traz o Linux um passo mais perto da Linux scheduler proxy execution

Escrito por
Emanuel Negromonte
Emanuel Negromonte é Jornalista, Mestre em Tecnologia da Informação e atualmente cursa a segunda graduação em Engenharia de Software. Com 14 anos de experiência escrevendo sobre...

Menos jank, mais fluidez: Linux scheduler proxy execution contra a inversão de prioridade multicore.

Imagine que você está na fila do café do trabalho. Você (prioridade alta) precisa de um café agora para entrar numa reunião, mas o colega à sua frente (prioridade baixa) está parado porque o cartão dele ainda está processando — e a maquininha está com ele. O que seria mais esperto: deixar você esperar, ou “emprestar” sua urgência para o colega terminar logo o pagamento (talvez até chamar ele para o caixa mais rápido) e liberar a máquina? Essa é a intuição por trás de proxy execution no escalonador do Linux — e a série de patchesDonor Migration for Proxy Execution”, hoje no v21, dá um salto importante rumo a tornar isso prático em sistemas reais, como Android, cheios de CPUs e locks compartilhados.

O problema: inversão de prioridade em vários núcleos

Inversão de prioridade acontece quando uma tarefa de alta prioridade fica presa esperando uma de baixa prioridade que segura um lock. Em máquinas multicore, isso se complica: a dona do lock pode estar em outro CPU, dormindo, migrando, ou “presa” a um conjunto de CPUs onde ela ainda não foi acordada. Em telefones Android, por exemplo, esse tipo de cadeia é fonte clássica de travadinhas (jank) e latências que aparecem “do nada”. A ideia de proxy execution é antiga em tempo real: quando A (alta) espera por B (baixa) que segura o lock, A “empresta” sua urgência para B — e, se preciso, o sistema migra A até a CPU onde B pode efetivamente correr, fazendo B rodar como se fosse A, até soltar o lock. Isso reduz stalls e melhora a responsividade percebida.

O que este v21 entrega

BWzL1WxV image 1
“Donor Migration” traz o Linux um passo mais perto da Linux scheduler proxy execution 3

John Stultz (Google) reenvia no começo de setembro a “rodada v21” focada justamente no miolo multicore: doar/migrar o contexto do donor (o bloqueado) para onde o owner (o dono do lock) pode progredir e depois lidar com a migração de retorno. A submissão explica que o envio está fatiado em peças “digestíveis”, com mudanças nas primitivas de locking e em estruturas do task_struct necessárias para não quebrar invariantes do escalonador. Há também histórico, créditos e TODOs (ex.: interação com sched_ext e avaliar regressões observadas em testes antigos).

Em termos de engenharia do kernel, os seis patches desta tranche fazem:

  • task::blocked_lock: um spinlock por tarefa para serializar o estado “estou bloqueado em X”, sem depender de contextos onde o mutex::wait_lock não está disponível. Isso evita interações perigosas e anotações de lockdep complicadas.
  • Tri-state blocked_on_state: além do “runnable” vs “blocked”, entra o estado BO_WAKING. Por quê? Porque numa proxy exec a tarefa bloqueada pode ter sido migrada para um CPU onde ela não pode rodar (afinidade). Quando o lock libera, precisamos marcar que ela está acordando, mas segurá-la até que o escalonador faça a return migration para um CPU válido. Esse terceiro estado evita acordar no lugar errado.
  • “Zapar” balance callbacks ao repick: ao detectar que vamos “escolher de novo” a próxima tarefa (por causa da migração de doador), o patch limpa callbacks de balanceamento deixados pela primeira escolha (ex.: empurrões de RT), prevenindo WARNs e estados esquisitos.
  • Migração do bloqueado e retorno: o coração da série. Se o encadeamento doador → mutex → owner cruza CPUs, migramos o doador para o runqueue do owner (respeitando afinidades do dono e tratando o doador como “contexto de agendamento móvel”). Depois, quando o lock vai embora, a série cuida de voltar o doador para onde ele realmente pode executar. Também há guardas para casos ww_mutex (morte/ferida) que marcam BO_WAKING.
  • blocked_donor para handoff inteligente: um ponteiro de quem está “dando prioridade” para o owner. Na liberação do mutex, em vez de acordar um “qualquer” da fila, o kernel passa o lock diretamente para o doador certo, reduzindo ping-pong e latência.
  • Migrar a cadeia inteira de uma vez: se há uma sequência A→B→C (A espera B, que espera C), o código pode migrar todo o encadeamento até onde o progresso realmente acontece, evitando várias idas-e-vindas.

Nada disso é “plug-and-play”: as mudanças também tocam o caminho de wakeup, a limpeza de callbacks de balance, e pequenos ajustes na classe FAIR/RT para não tentar migrar classes enquanto a tarefa está explicitamente no modo “bloqueado por mutex com proxy”. O texto do envio explica motivações e perigos (por exemplo, evitar deadlocks estilo ABBA em ww_mutex), com revisões entre v20 e v21 para segurar locks no lugar certo.

O que muda para quem usa (e por que importa em Android)

Para o usuário, a promessa é simples: menos travadinhas quando apps brigam por locks em pipelines comuns (ex.: binder/graphics, subsistemas de I/O, caches). Em vez de a tarefa interativa de alta prioridade “ficar roxa” esperando um serviço de fundo que por acaso detém o lock, o sistema empurra o trabalho certo para o CPU certo — até que o lock seja liberado — e depois traz de volta quem precisa rodar onde pode rodar. Isso é particularmente valioso em SoCs com muitos núcleos heterogêneos e políticas de afinidade/energia agressivas. A cobertura técnica recente da LWN sobre o tema contextualiza bem essa evolução e por que “donor migration” era o pedaço que faltava no quebra-cabeça multicore.

Não é “mágica”: limites, pendências e ciência por trás

Stultz deixa claro que ainda há pontos a resolver: integração com sched_ext, reavaliação de possíveis regressões de ~3–5% observadas em versões antigas sem a otimização de evitar migrações inúteis, e validação das invariantes de balanceamento em cadeias mais longas (RT/DL) — um cuidado extra enquanto o ecossistema testa com workloads reais. Pesquisadores e mantenedores de tempo real (como Juri Lelli) já estão avaliando comportamentos com SCHED_DEADLINE e herança de largura de banda multiprocessador (M-BWI), pois detalhes como spin_on_owner podem confundir expectativas “ideais” de doação imediata em RT/DL. Em resumo: o caminho está certo, mas está sendo medido com régua de laboratório.

Vale lembrar: proxy execution não nasceu no vácuo. A ideia foi descrita em trabalhos acadêmicos (Watkins, Straub, Niehaus) e depois ganhou encarnações no Linux via Peter Zijlstra, com extensões de Juri Lelli, Valentin Schneider, Connor O’Brien, entre outros — o que a série reconhece explicitamente. É bom ver a ponte entre teoria e prática fechar o ciclo no kernel mainline.

Em uma frase

O v21 de “Donor Migration for Proxy Execution” aproxima o kernel de um futuro onde o Linux scheduler proxy execution resolve inversões de prioridade entre CPUs de forma direta: a urgência de quem importa “puxa” quem segura o lock para o CPU certo, libera o caminho, e volta tudo ao lugar — menos jank, mais fluidez.

Compartilhe este artigo