mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 11:41:29 -04:00
docs: pt_BR: process: translate botching-up-ioctls guide
Translate the 'botching-up-ioctls' documentation into Brazilian Portuguese, ensuring precise technical alignment with the upstream source guidelines. The translation covers critical driver-private API design concepts, including fixed-sized integers, structure padding, error path validation rules, and handling asynchronous hardware timeouts. Signed-off-by: Daniel Pereira <danielmaraboo@gmail.com> Signed-off-by: Jonathan Corbet <corbet@lwn.net> Message-ID: <20260703170552.174764-6-danielmaraboo@gmail.com>
This commit is contained in:
committed by
Jonathan Corbet
parent
4f98d97c5a
commit
84278d5735
@@ -70,6 +70,7 @@ kernel e sobre como ver seu trabalho integrado.
|
||||
Guia do Processo de Desenvolvimento <process/development-process>
|
||||
Como aplicar patches <process/applying-patches>
|
||||
Backporting e resolução de conflitos <process/backporting>
|
||||
Como não Deixar as ioctls malfeitas <process/botching-up-ioctls>
|
||||
Index de documentos do Kernel <process/kernel-docs>
|
||||
Regras de licenciamento <process/license-rules>
|
||||
Como começar <process/howto>
|
||||
|
||||
256
Documentation/translations/pt_BR/process/botching-up-ioctls.rst
Normal file
256
Documentation/translations/pt_BR/process/botching-up-ioctls.rst
Normal file
@@ -0,0 +1,256 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
============================================
|
||||
(Como evitar) Deixar as ioctls malfeitas
|
||||
============================================
|
||||
|
||||
De: https://blog.ffwll.ch/2013/11/botching-up-ioctls.html
|
||||
|
||||
Por: Daniel Vetter, Copyright © 2013 Intel Corporation
|
||||
|
||||
Uma percepção clara que os hackers de gráficos do kernel tiveram nos últimos
|
||||
anos é que tentar criar uma interface unificada para gerenciar as unidades de
|
||||
execução e a memória em GPUs completamente diferentes é um esforço inútil.
|
||||
Portanto, hoje em dia, cada driver tem seu próprio conjunto de ioctls para
|
||||
alocar memória e enviar trabalho para a GPU. O que é bom, já que não há mais a
|
||||
insanidade na forma de interfaces falsamente genéricas, mas que na verdade só
|
||||
são usadas uma vez. No entanto, a desvantagem clara é que há muito mais
|
||||
potencial para estragar as coisas.
|
||||
|
||||
Para evitar repetir todos os mesmos erros novamente, escrevi algumas das lições
|
||||
aprendidas enquanto fazia um trabalho malfeito para o driver drm/i915. A maioria
|
||||
delas aborda apenas tecnicalidades e não os problemas macro (big-picture), como
|
||||
deveria ser exatamente a aparência da ioctl de envio de comando. Aprender essas
|
||||
lições é provavelmente algo que cada driver de GPU tem que fazer por conta
|
||||
própria.
|
||||
|
||||
|
||||
Pré-requisitos
|
||||
--------------
|
||||
|
||||
Primeiro, os pré-requisitos. Sem estes você já falhou, porque precisará
|
||||
adicionar uma camada de compatibilidade de 32 bits (compat layer):
|
||||
|
||||
* Use apenas inteiros de tamanho fixo. Para evitar conflitos com typedefs no
|
||||
espaço de usuário (userspace), o kernel possui tipos especiais como __u32 e
|
||||
__s64. Use-os.
|
||||
|
||||
* Alinhe tudo ao tamanho natural e use preenchimento (padding) explícito.
|
||||
Plataformas de 32 bits não alinham necessariamente valores de 64 bits a
|
||||
limites (boundaries) de 64 bits, mas plataformas de 64 bits o fazem. Portanto,
|
||||
sempre precisamos de padding para o tamanho natural para acertar isso.
|
||||
|
||||
* Preencha a struct inteira para um múltiplo de 64 bits se a estrutura contiver
|
||||
tipos de 64 bits -- caso contrário, o tamanho da estrutura diferirá entre
|
||||
32 bits e 64 bits. Ter um tamanho de estrutura diferente prejudica ao passar
|
||||
matrizes (arrays) de estruturas para o kernel, ou se o kernel verificar o
|
||||
tamanho da estrutura, o que o core do drm, por exemplo, faz.
|
||||
|
||||
* Ponteiros são __u64, convertidos de/para um uintptr_t no lado do espaço de
|
||||
usuário e de/para um void __user * no kernel. Tente de verdade não atrasar
|
||||
essa conversão ou, pior ainda, manipular o __u64 bruto pelo seu código, pois
|
||||
isso diminui a verificação que ferramentas como o sparse podem fornecer. A
|
||||
macro u64_to_user_ptr pode ser usada no kernel para evitar avisos sobre
|
||||
inteiros e ponteiros de tamanhos diferentes.
|
||||
|
||||
|
||||
Conceitos básicos
|
||||
-----------------
|
||||
|
||||
Evitadas as alegrias de escrever uma camada de compatibilidade (compat layer),
|
||||
podemos dar uma olhada nos deslizes básicos. Negligenciar estes pontos tornará a
|
||||
compatibilidade retroativa e futura uma verdadeira dor de cabeça. E, como errar
|
||||
na primeira tentativa é garantido, você certamente terá uma segunda iteração ou,
|
||||
pelo menos, uma extensão para qualquer interface fornecida.
|
||||
|
||||
* Tenha uma maneira clara para o espaço de usuário descobrir se a sua nova
|
||||
ioctl ou extensão de ioctl é suportada em um determinado kernel. Se você não
|
||||
puder confiar que os kernels antigos rejeitarão as novas flags/modos ou
|
||||
ioctls (já que fazer isso foi deixado de lado no passado), então você
|
||||
precisará de uma flag de recurso (feature flag) do driver ou de um número de
|
||||
revisão em algum lugar.
|
||||
|
||||
* Tenha um plano para estender as ioctls com novas flags ou novos campos no
|
||||
final da estrutura. O core do drm verifica o tamanho passado para cada
|
||||
chamada de ioctl e preenche com zero (zero-extends) quaisquer divergências
|
||||
entre o kernel e o espaço de usuário. Isso ajuda, mas não é uma solução
|
||||
completa, já que um espaço de usuário mais novo em um kernel mais antigo não
|
||||
notará que os campos recém-adicionados no final estão sendo ignorados.
|
||||
Portanto, isso ainda exige novas flags de recurso do driver.
|
||||
|
||||
* Verifique todos os campos e flags não utilizados, além de todo o preenchimento
|
||||
(padding), para garantir que estejam em 0, e rejeite a ioctl se esse não for
|
||||
o caso. Caso contrário, seu excelente plano para extensões futuras irá por
|
||||
água abaixo, pois alguém enviará uma struct de ioctl com lixo de pilha
|
||||
(stack garbage) aleatório nas partes ainda não utilizadas. O que, então,
|
||||
consolida na ABI que esses campos nunca poderão ser usados para nada além de
|
||||
lixo. Esta também é a razão pela qual você deve preencher explicitamente todas
|
||||
as estruturas, mesmo que nunca as use em uma matriz (array) -- o padding que
|
||||
o compilador possa inserir poderia conter lixo.
|
||||
|
||||
* Tenha casos de teste simples para tudo o que foi mencionado acima.
|
||||
|
||||
|
||||
Diversão com caminhos de erro (Error Paths)
|
||||
-------------------------------------------
|
||||
|
||||
Hoje em dia, não temos mais nenhuma desculpa para que os drivers drm sejam pequenos
|
||||
exploits de root disfarçados. Isso significa que precisamos tanto de uma
|
||||
validação completa de entrada quanto de caminhos sólidos de tratamento de erros
|
||||
-- as GPUs eventualmente vão parar de funcionar (die) nos casos mais bizarros
|
||||
de qualquer maneira:
|
||||
|
||||
* A ioctl deve verificar se há estouros de matriz (array overflows). Ela também
|
||||
precisa verificar estouros superiores/inferiores (over/underflows) e problemas
|
||||
de limitação (clamping) de valores inteiros em geral. O exemplo usual são os
|
||||
valores de posicionamento de sprite alimentados diretamente no hardware, onde
|
||||
o hardware possui apenas 12 bits ou algo assim. Funciona perfeitamente até que
|
||||
algum servidor de exibição bizarro não se preocupe em fazer o clamping por si
|
||||
mesmo e o cursor dê a volta (wrap around) na tela.
|
||||
|
||||
* Tenha casos de teste simples para cada caso de falha de validação de entrada
|
||||
na sua ioctl. Verifique se o código de erro corresponde às suas expectativas.
|
||||
E, finalmente, certifique-se de testar apenas um único caminho de erro em
|
||||
cada subteste, enviando dados que, de outra forma, seriam perfeitamente
|
||||
válidos. Sem isso, uma verificação anterior já poderia rejeitar a ioctl e
|
||||
ofuscar (shadow) o caminho de código que você realmente deseja testar,
|
||||
ocultando bugs e regressões.
|
||||
|
||||
* Torne todas as suas ioctls reiniciáveis (restartable). Primeiro, o X (X11)
|
||||
realmente ama sinais (signals) e, segundo, isso permitirá que você teste 90%
|
||||
de todos os caminhos de tratamento de erro apenas interrompendo sua suíte de
|
||||
testes principal constantemente com sinais. Graças ao amor do X por sinais,
|
||||
você obterá uma excelente cobertura de base de todos os seus caminhos de erro
|
||||
praticamente de graça para drivers de gráficos. Além disso, seja consistente
|
||||
na forma como você lida com a reinicialização de ioctls -- por exemplo, o drm
|
||||
possui um pequeno helper drmIoctl em sua biblioteca de espaço de usuário. O
|
||||
driver i915 estragou isso com a ioctl set_tiling; agora estamos presos para
|
||||
sempre com algumas semânticas arcanas tanto no kernel quanto no espaço de
|
||||
usuário.
|
||||
|
||||
* Se você não puder tornar um determinado caminho de código reiniciável, torne
|
||||
uma tarefa travada pelo menos finalizável (killable). As GPUs simplesmente
|
||||
morrem, e seus usuários não vão gostar mais de você se você travar a máquina
|
||||
inteira deles (por meio de um processo do X impossível de matar). Se a
|
||||
recuperação de estado ainda for muito complicada, tenha um timeout ou uma
|
||||
rede de segurança de verificação de travamento (hangcheck) como um esforço de
|
||||
última hora (last-ditch) caso o hardware enlouqueça (gone bananas).
|
||||
|
||||
* Tenha casos de teste para os cenários mais complexos (corner cases) no seu
|
||||
código de recuperação de erros -- é fácil demais criar um deadlock entre seu
|
||||
código de hangcheck e os processos que estão aguardando (waiters).
|
||||
|
||||
|
||||
Tempo, Espera e a Perda de Prazos
|
||||
---------------------------------
|
||||
|
||||
As GPUs fazem quase tudo de forma assíncrona, portanto, temos a necessidade de
|
||||
cronometrar operações e aguardar pelas que estão pendentes. Esse é um negócio
|
||||
realmente complicado; no momento, nenhuma das ioctls suportadas pelo drm/i915
|
||||
acerta isso completamente, o que significa que ainda há toneladas de lições para
|
||||
aprender aqui.
|
||||
|
||||
* Use CLOCK_MONOTONIC como seu tempo de referência, sempre. É o que o alsa, o
|
||||
drm e o v4l usam por padrão hoje em dia. Mas informe ao espaço de usuário
|
||||
quais carimbos de data/hora (timestamps) são derivados de domínios de relógio
|
||||
diferentes, como o relógio principal do seu sistema (fornecido pelo kernel)
|
||||
ou algum contador de hardware independente em outro lugar. Os relógios vão
|
||||
divergir se você olhar de perto o suficiente, mas se as ferramentas de
|
||||
medição de desempenho tiverem essa informação, elas poderão ao menos compensar.
|
||||
Se o seu espaço de usuário puder obter os valores brutos de alguns relógios
|
||||
(por exemplo, por meio de instruções de amostragem de contador de desempenho
|
||||
no fluxo de comandos), considere expor esses também.
|
||||
|
||||
* Use __s64 para segundos mais __u64 para nanossegundos para especificar o
|
||||
tempo. Não é a especificação de tempo mais conveniente, mas é praticamente o
|
||||
padrão.
|
||||
|
||||
* Verifique se os valores de tempo de entrada estão normalizados e rejeite-os
|
||||
caso contrário. Note que a struct nativa do kernel, ktime, possui um inteiro
|
||||
sinalizado tanto para segundos quanto para nanossegundos, portanto, cuidado
|
||||
aqui.
|
||||
|
||||
* Para timeouts, use tempos absolutos. Se você for um bom sujeito e tiver
|
||||
tornado a sua ioctl reiniciável, os timeouts relativos tendem a ser muito
|
||||
imprecisos (coarse) e podem estender indefinidamente o seu tempo de espera
|
||||
devido ao arredondamento a cada reinicialização. Especialmente se o seu relógio
|
||||
de referência for algo realmente lento, como o contador de quadros da tela
|
||||
(display frame counter). Vestindo o chapéu de advogado de especificações, isso
|
||||
não é um bug, já que os timeouts sempre podem ser estendidos -- mas os usuários
|
||||
com certeza vão odiar você se as belas animações deles começarem a gaguejar
|
||||
(stutter) devido a isso.
|
||||
|
||||
* Considere descartar quaisquer ioctls de espera síncrona com timeouts e apenas
|
||||
entregue um evento assíncrono em um descritor de arquivo passível de poll
|
||||
(pollable file descriptor). Isso se encaixa muito melhor no loop principal de
|
||||
aplicações orientadas a eventos.
|
||||
|
||||
* Tenha casos de teste para cenários complexos (corner-cases), especialmente se
|
||||
os valores de retorno para eventos já concluídos, esperas bem-sucedidas e
|
||||
esperas que estouraram o tempo (timed-out) são todos sãos e adequados às suas
|
||||
necessidades.
|
||||
|
||||
|
||||
Evitando o vazamento de recursos (Leaking Resources, Not)
|
||||
---------------------------------------------------------
|
||||
|
||||
Um driver drm completo essencialmente implementa um pequeno SO, mas especializado
|
||||
para as plataformas de GPU fornecidas. Isso significa que um driver precisa
|
||||
expor toneladas de handles (identificadores) para diferentes objetos e outros
|
||||
recursos para o espaço de usuário. Fazer isso corretamente traz seu próprio
|
||||
pequeno conjunto de armadilhas:
|
||||
|
||||
* Sempre vincule o tempo de vida (lifetime) de seus recursos criados
|
||||
dinamicamente ao tempo de vida de um descritor de arquivo (file descriptor -
|
||||
fd). Considere usar um mapeamento 1:1 se o seu recurso precisar ser
|
||||
compartilhado entre processos -- a passagem de fds sobre unix domain sockets
|
||||
também simplifica o gerenciamento do tempo de vida para o espaço de usuário.
|
||||
|
||||
* Sempre tenha suporte a O_CLOEXEC.
|
||||
|
||||
* Certifique-se de que você tem isolamento suficiente entre os diferentes
|
||||
clientes. Por padrão, escolha um namespace privado por fd, o que força
|
||||
qualquer compartilhamento a ser feito de forma explícita. Só adote um
|
||||
namespace mais global por dispositivo se os objetos forem verdadeiramente
|
||||
únicos do dispositivo. Um contraexemplo nas interfaces de modeset do drm é
|
||||
que os objetos de modeset por dispositivo, como conectores, compartilham um
|
||||
namespace com objetos de framebuffer, que na maioria das vezes não são
|
||||
compartilhados de forma alguma. Um namespace separado, privado por padrão,
|
||||
para os framebuffers teria sido mais adequado.
|
||||
|
||||
* Pense sobre os requisitos de unicidade para os handles do espaço de usuário.
|
||||
Por exemplo, para a maioria dos drivers drm, é um bug do espaço de usuário
|
||||
enviar o mesmo objeto duas vezes na mesma ioctl de envio de comando. Mas,
|
||||
se os objetos forem compartilháveis, o espaço de usuário precisa saber se
|
||||
já viu um objeto importado de outro processo ou não. Eu ainda não tentei isso
|
||||
sozinho devido à falta de uma nova classe de objetos, mas considere usar
|
||||
números de inode em seus descritores de arquivo compartilhados como
|
||||
identificadores únicos -- é assim que arquivos reais também são diferenciados.
|
||||
Infelizmente, isso requer um sistema de arquivos virtual completo no kernel.
|
||||
|
||||
|
||||
Por último, mas não menos importante
|
||||
------------------------------------
|
||||
|
||||
Nem todo problema precisa de uma nova ioctl:
|
||||
|
||||
* Pense bem se você realmente quer uma interface privada do driver. Claro que
|
||||
é muito mais rápido aprovar uma interface privada do driver do que se envolver
|
||||
em discussões longas por uma solução mais genérica. E, ocasionalmente, criar
|
||||
uma interface privada para liderar um novo conceito é o que se exige. Mas,
|
||||
no final, assim que a interface genérica surgir, você acabará mantendo duas
|
||||
interfaces. Indefinidamente.
|
||||
|
||||
* Considere outras interfaces além de ioctls. Um atributo sysfs é muito melhor
|
||||
para configurações por dispositivo ou para objetos filhos com tempos de vida
|
||||
razoavelmente estáticos (como conectores de saída no drm com todos os seus
|
||||
atributos de sobreposição de detecção). Ou talvez apenas a sua suíte de
|
||||
testes precise dessa interface e, nesse caso, o debugfs, com seu aviso de
|
||||
isenção de responsabilidade por não ter uma ABI estável, seria melhor.
|
||||
|
||||
Finalmente, o objetivo principal é acertar na primeira tentativa, pois se o seu
|
||||
driver se provar popular e suas plataformas de hardware forem duradouras, você
|
||||
ficará preso a uma determinada ioctl essencialmente para sempre. Você pode
|
||||
tentar depreciar ioctls horríveis em iterações mais novas do seu hardware, mas
|
||||
geralmente leva anos para conseguir isso. E depois mais anos até que o último
|
||||
usuário capaz de reclamar sobre regressões desapareça também.
|
||||
Reference in New Issue
Block a user