Por que existe uma política de kernel Linux para nunca quebrar o espaço do usuário?

38

Comecei a pensar sobre esse problema no contexto de etiqueta na lista de discussão do Kernel do Linux. Como o projeto de software livre mais conhecido e sem dúvida o mais bem-sucedido e importante do mundo, o kernel Linux recebe bastante destaque. E o fundador e líder do projeto, Linus Torvalds, claramente não precisa de introdução aqui.

Linus ocasionalmente atrai polêmica com suas chamas no LKML. Essas chamas são frequentemente, por sua própria admissão, relacionadas à quebra do espaço do usuário. O que me leva à minha pergunta.

Posso ter uma perspectiva histórica de por que quebrar o espaço do usuário é uma coisa tão ruim? Pelo que entendi, a quebra do espaço do usuário exigiria correções no nível do aplicativo, mas isso é uma coisa tão ruim se melhorar o código do kernel?

Pelo que entendi, a política declarada de Linus é que não quebrar o espaço do usuário supera todo o resto, incluindo a qualidade do código. Por que isso é tão importante e quais são os prós e os contras dessa política?

(Há claramente alguns contras a essa política, aplicada de forma consistente, uma vez que Linus ocasionalmente tem "discordâncias" com seus principais tenentes no LKML sobre exatamente esse tópico. Até onde eu sei, ele sempre consegue entender o assunto.)

Faheem Mitha
fonte
1
Você digitou incorretamente o nome de Linus na introdução.
Ismael Miguel
Não era eu, com certeza, mas eu esqueci de votar e dei meu voto agora.
Ismael Miguel
1
Relacionados: stackoverflow.com/q/25954270/350713
Faheem Mitha

Respostas:

38

O motivo não é histórico, mas prático. Existem muitos programas que rodam no kernel do Linux; se uma interface do kernel interromper esses programas, todos precisariam atualizar esses programas.

Agora é verdade que a maioria dos programas de fato não depende diretamente das interfaces do kernel (as chamadas do sistema ), mas apenas das interfaces da biblioteca padrão C ( wrappers C nas chamadas do sistema). Ah, mas qual biblioteca padrão? Glibc? uClibC? Dietlibc? Biônico? Musl? etc.

Mas também existem muitos programas que implementam serviços específicos do SO e dependem de interfaces do kernel que não são expostas pela biblioteca padrão. (No Linux, muitos deles são oferecidos por meio de /proce /sys.)

E existem binários estaticamente compilados. Se uma atualização do kernel quebrar uma delas, a única solução seria recompilá-las. Se você possui a fonte: o Linux também suporta software proprietário.

Mesmo quando a fonte está disponível, reunir tudo isso pode ser uma dor. Especialmente quando você está atualizando seu kernel para corrigir um erro no seu hardware. As pessoas geralmente atualizam seu kernel independentemente do resto do sistema porque precisam do suporte de hardware. Nas palavras de Linus Torvalds :

Quebrar programas de usuários simplesmente não é aceitável. (…) Sabemos que as pessoas usam binários antigos por anos e anos, e que fazer um novo lançamento não significa que você pode simplesmente jogar isso fora. Você pode confiar em nós.

Ele também explica que uma razão para tornar isso uma regra forte é evitar o inferno das dependências, onde você não apenas precisa atualizar outro programa para que algum kernel mais recente funcione, mas também precisa atualizar outro programa e outro e outro , porque tudo depende de uma determinada versão de tudo.

É um pouco ok para ter uma dependência de uma forma bem definida. É triste, mas inevitável às vezes. (…) O que NÃO está ok é ter uma dependência bidirecional. Se o código HAL no espaço do usuário depende de um novo kernel, tudo bem, embora eu suspeite que os usuários esperem que não seja o "kernel da semana", mas mais o "kernel dos últimos meses".

Mas se você tem uma dependência de duas vias, está ferrado. Isso significa que você precisa atualizar na etapa de bloqueio e isso NÃO É ACEITÁVEL. É horrível para o usuário, mas ainda mais importante, é horrível para os desenvolvedores, porque significa que você não pode dizer "um bug aconteceu" e fazer coisas como tentar reduzi-lo com bissecção ou similar.

No espaço do usuário, essas dependências mútuas geralmente são resolvidas mantendo diferentes versões da biblioteca; mas você só pode rodar um kernel, então ele precisa suportar tudo o que as pessoas possam querer fazer com ele.

Oficialmente ,

a compatibilidade com versões anteriores para [chamadas de sistema declaradas estáveis] será garantida por pelo menos 2 anos.

Na prática, porém,

Espera-se que a maioria das interfaces (como syscalls) nunca mude e esteja sempre disponível.

O que muda com mais freqüência são as interfaces que devem ser usadas apenas por programas relacionados a hardware /sys. ( /procpor outro lado, que desde a introdução de /sysfoi reservado para serviços não relacionados a hardware, praticamente nunca quebra de maneiras incompatíveis.)

Em suma,

quebrar o espaço do usuário exigiria correções no nível do aplicativo

e isso é ruim porque existe apenas um kernel, que as pessoas desejam atualizar independentemente do restante do sistema, mas existem muitos aplicativos por aí com interdependências complexas. É mais fácil manter o kernel estável do que manter milhares de aplicativos atualizados em milhões de configurações diferentes.

Gilles 'SO- parar de ser mau'
fonte
1
Obrigado pela resposta. Portanto, as interfaces declaradas estáveis ​​são um superconjunto das chamadas do sistema POSIX? Minha pergunta sobre a história é como essa prática evoluiu. Presumivelmente, as versões originais do kernel Linux não se preocupavam com a quebra do espaço do usuário, pelo menos inicialmente.
Faheem Mitha 12/10
3
@FaheemMitha Sim, eles fizeram desde 1991 . Eu não acho que a abordagem de Linus tenha evoluído, sempre foram “interfaces para aplicativos normais não mudam, interfaces para software que estão muito fortemente vinculadas ao kernel mudam muito raramente”.
Gilles 'SO- stop be evil'
24

Em qualquer sistema interdependente, existem basicamente duas opções. Abstração e integração. (De propósito, não estou usando termos técnicos). Com o Abstraction, você está dizendo que quando você faz uma chamada para uma API que, embora o código por trás da API possa mudar, o resultado será sempre o mesmo. Por exemplo, quando ligamos para fs.open()nós, não nos importamos se é uma unidade de rede, um SSD ou um disco rígido, sempre teremos um descritor de arquivo aberto com o qual podemos fazer coisas. Com a "integração", o objetivo é fornecer a "melhor" maneira de fazer uma coisa, mesmo que a maneira mude. Por exemplo, abrir um arquivo pode ser diferente para um compartilhamento de rede e para um arquivo em disco. Ambas as formas são usadas amplamente na área de trabalho moderna do Linux.

Do ponto de vista dos desenvolvedores, é uma questão de "funciona com qualquer versão" ou "funciona com uma versão específica". Um ótimo exemplo disso é o OpenGL. A maioria dos jogos está configurada para funcionar com uma versão específica do OpenGL. Não importa se você está compilando a partir do código-fonte. Se o jogo foi escrito para usar o OpenGL 1.1 e você está tentando executá-lo no 3.x, não vai se divertir. Do outro lado do espectro, espera-se que algumas ligações funcionem, não importa o quê. Por exemplo, quero ligar fs.open()Não quero me importar com a versão do kernel em que estou. Eu só quero um descritor de arquivo.

Existem benefícios para cada caminho. A integração fornece recursos "mais recentes" ao custo da compatibilidade com versões anteriores. Enquanto a abstração fornece estabilidade em relação às chamadas "mais recentes". Embora seja importante notar que é uma questão de prioridade, não de possibilidade.

Do ponto de vista comunitário, sem uma razão realmente boa, a abstração é sempre melhor em um sistema complexo. Por exemplo, imagine se fs.open()funcionasse de maneira diferente, dependendo da versão do kernel. Então, uma biblioteca de interação simples do sistema de arquivos precisaria manter várias centenas de métodos diferentes de "arquivo aberto" (ou blocos provavelmente). Quando uma nova versão do kernel foi lançada, você não seria capaz de "atualizar", teria que testar cada software usado. O kernel 6.2.2 (falso) pode apenas quebrar o seu editor de texto.

Para alguns exemplos do mundo real, o OSX tende a não se importar com a quebra do espaço do usuário. Eles visam "integração" sobre "abstração" com mais frequência. E a cada atualização importante do sistema operacional, as coisas acontecem. Isso não quer dizer que uma maneira seja melhor que a outra. É uma decisão de escolha e design.

Mais importante ainda, o ecossistema Linux é repleto de projetos incríveis de código aberto, onde pessoas ou grupos trabalham no projeto em seu tempo livre ou porque a ferramenta é útil. Com isso em mente, no momento em que deixa de ser divertido e passa a ser uma PIA, esses desenvolvedores irão para outro lugar.

Por exemplo, enviei um patch para BuildNotify.py. Não porque sou altruísta, mas porque uso a ferramenta e queria um recurso. Foi fácil, então aqui, tem um patch. Se fosse complicado ou complicado, eu não usaria BuildNotify.pye encontraria outra coisa. Se toda vez que uma atualização do kernel fosse lançada, meu editor de texto falhasse, eu usaria apenas um sistema operacional diferente. Minhas contribuições para a comunidade (por menores que sejam) não continuariam ou existiriam, e assim por diante.

Portanto, a decisão de design foi tomada para abstrair as chamadas do sistema, para que, quando eu fizer fs.open(), funcione. Isso significa manter fs.openmuito tempo depois de fs.open2()ganhar popularidade.

Historicamente, esse é o objetivo dos sistemas POSIX em geral. "Aqui estão um conjunto de chamadas e valores de retorno esperados, você descobre o meio". Novamente por motivos de portabilidade. Por que Linus escolhe usar essa metodologia é interno ao cérebro dele, e você teria que pedir que ele soubesse exatamente o porquê. No entanto, se fosse eu, escolheria a abstração ao invés da integração em um sistema complexo.

coteyr
fonte
1
A API para o espaço do usuário, a API 'syscall', é bem definida (especialmente o subconjunto POSIX) e estável, porque a remoção de qualquer parte dela quebrará o software que as pessoas possam ter instalado. O que não tem é uma API de driver estável .
Pjc50
4
@FaheemMitha, é o contrário. Os desenvolvedores do kernel são livres para interromper a API do driver sempre que desejarem, desde que consertem todos os drivers do kernel antes do próximo lançamento. É quebrar a API do espaço do usuário, ou mesmo fazer coisas que não são da API que poderiam quebrar o espaço do usuário, que produz reações épicas de Linus.
Mark
4
Por exemplo, se alguém decidir alterá-lo retornando um código de erro diferente de ioctl () em algumas circunstâncias: lkml.org/lkml/2012/12/23/75 (contém ataques de palavrões e pessoais ao desenvolvedor responsável). Esse patch foi rejeitado porque teria quebrado o PulseAudio e, portanto, todo o áudio nos sistemas GNOME.
Pjc50
1
@FaheemMitha, basicamente, def add (a, b); retornar a + b; fim --- def add (a, b); c = a + b; retornar c; fim --- def add (a, b); c = a + b + 10; retorno c - 10; end - são todas as "mesmas" implementações do add. O que o deixa tão chateado é quando as pessoas def add (a, b); retorno (a + b) * -1; end Em essência, mudar como as coisas "internas" do kernel funcionam é bom. Alterar o que é retornado para uma chamada de API definida e "pública" não é. Existem dois tipos de chamadas de API "privadas" e "públicas". Ele acha que as chamadas públicas à API nunca devem mudar sem uma boa razão.
coteyr
3
Um exemplo não de código; Você vai à loja e compra 87 octanas. Você, como consumidor, não "se importa" de onde o gás veio ou como foi processado. Você só se importa com o seu gás. Se o gás passou por um processo de refino diferente, você não se importa. Claro que o processo de refino pode mudar. Existem até fontes diferentes de petróleo. Mas o que importa é obter 87 octanas. Portanto, a posição dele é mudar as fontes, mudar as refinarias, mudar o que for, desde que o que sai da bomba seja 87 octanas. Todo o material "nos bastidores" não importa. Enquanto houver 87 octanas.
coteyr
8

É uma decisão e escolha de design. A Linus deseja garantir aos desenvolvedores do espaço do usuário que, exceto em circunstâncias extremamente raras e excepcionais (por exemplo, relacionadas à segurança), as mudanças no kernel não quebrarão seus aplicativos.

Os profissionais são que os desenvolvedores do userspace não encontrarão seu código subitamente quebrado em novos kernels por motivos arbitrários e caprichosos.

Os contras são que o kernel precisa manter o código antigo e os syscalls antigos por toda a eternidade (ou, pelo menos, muito tempo depois das datas de validade).

cas
fonte
Obrigado pela resposta. Você está ciente da história de como essa decisão evoluiu? Estou ciente de projetos que têm uma perspectiva um pouco diferente. Por exemplo, o projeto Mercurial não possui uma API fixa e pode e pode quebrar o código que depende dele.
Faheem Mitha
Não, desculpe, não me lembro como isso aconteceu. Você pode enviar um e-mail para Linus ou LKML e perguntar a ele.
cas
2
Mercurial não é um sistema operacional. O objetivo de um sistema operacional é permitir a execução de outro software em cima dele, e quebrar esse outro software é muito impopular. Por comparação, o Windows também manteve a compatibilidade com versões anteriores por um período muito longo; O código do Windows de 16 bits só foi obsoleto recentemente.
Pjc50
@ pjc50 É verdade que o Mercurial não é um sistema operacional, mas independentemente disso, não é outro software, mesmo que apenas scripts, que dependem dele. E pode potencialmente ser quebrado por mudanças.
Faheem Mitha