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

35

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

Linus ocasionalmente atrai controvérsia com suas chamas no LKML. Essas chamas são freqüentemente, por sua própria admissão, relacionadas com a quebra do espaço do usuário. O que me leva à minha pergunta.

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

Pelo que entendi, a política declarada da 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 contras de tal política?

(Há claramente alguns contras para tal política, consistentemente aplicados, já que Linus ocasionalmente tem "desentendimentos" com seus principais tenentes sobre o LKML exatamente sobre esse tópico. Até onde eu sei, ele sempre consegue o que quer importa.)

    
por Faheem Mitha 11.10.2015 / 05:51

3 respostas

35

A razão não é histórica, mas prática. Existem muitos e muitos programas que são executados no kernel do Linux; se uma interface do kernel quebrar esses programas, todos precisarão atualizar esses programas.

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

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

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

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

Breaking user programs simply isn't acceptable. (…) We know that people use old binaries for years and years, and that making a new release doesn't mean that you can just throw that out. You can trust us.

Ele também explica que uma das razões para tornar isso uma regra strong é evitar a dependência onde você Não apenas temos que atualizar outro programa para fazer com que um novo kernel funcione, mas também atualizar outro programa, e outro, e outro, porque tudo depende de uma certa versão de tudo.

It's somewhat ok to have a well-defined one-way dependency. It's sad, but inevitable sometimes. (…) What is NOT ok is to have a two-way dependency. If user-space HAL code depends on a new kernel, that's ok, although I suspect users would hope that it wouldn't be "kernel of the week", but more a "kernel of the last few months" thing.

But if you have a TWO-WAY dependency, you're screwed. That means that you have to upgrade in lock-step, and that just IS NOT ACCEPTABLE. It's horrible for the user, but even more importantly, it's horrible for developers, because it means that you can't say "a bug happened" and do things like try to narrow it down with bisection or similar.

No userspace, essas dependências mútuas geralmente são resolvidas mantendo-se diferentes versões de bibliotecas; mas você só precisa rodar um kernel, então ele tem que suportar tudo que as pessoas possam querer fazer com ele.

Oficialmente ,

backward compatibility for [system calls declared stable] will be guaranteed for at least 2 years.

Na prática,

Most interfaces (like syscalls) are expected to never change and always be available.

O que muda com mais frequência são as interfaces que só devem ser usadas por programas relacionados a hardware, em /sys . ( /proc , por outro lado, que desde a introdução de /sys foi reservada para serviços não relacionados a hardware, praticamente nunca quebra de maneiras incompatíveis.)

Em resumo,

breaking user space would require fixes on the application level

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

    
por 12.10.2015 / 10:11
23

Em qualquer sistema interdependente, existem basicamente duas opções. Abstração e integração. (Eu não estou propositadamente 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 chamamos fs.open() , não nos importamos se é uma unidade de rede, um SSD ou um disco rígido, sempre obteremos um descritor de arquivo aberto com o qual possamos fazer coisas. Com a "integração", o objetivo é fornecer a "melhor" maneira de fazer uma coisa, mesmo que o caminho mude. Por exemplo, a abertura de um arquivo pode ser diferente para um compartilhamento de rede do que para um arquivo no disco. Ambas as formas são amplamente utilizadas no ambiente de trabalho moderno do Linux.

Do ponto de vista de um desenvolvedor, é 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 da fonte. Se o jogo foi escrito para usar o OpenGL 1.1 e você está tentando executá-lo no 3.x, você não vai se divertir. No outro extremo do espectro, espera-se que algumas chamadas funcionem, não importa o quê. Por exemplo, quero chamar fs.open() e não quero me preocupar com a versão do kernel em que estou. Eu só quero um descritor de arquivos.

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

De um ponto de vista comunal, 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 simples biblioteca de interação 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 fosse lançada, você não seria capaz de "atualizar", você teria que testar cada parte do software que você usou. 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 em quebrar o Espaço do Usuário. Eles buscam "integração" sobre "abstração" com mais frequência. E em todas as principais atualizações do sistema operacional, as coisas quebram. Isso não quer dizer que um caminho é melhor que o outro. É uma decisão de escolha e design.

O mais importante, 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 segundo em que ele deixa de ser divertido e passa a ser um PIA, esses desenvolvedores irão para outro lugar.

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

Assim, a decisão de design foi feita para abstrair as chamadas do sistema, de modo que, quando eu faço fs.open() , ele simplesmente funciona. Isso significa manter fs.open depois que fs.open2() ganhou 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 razões de portabilidade. Por que Linus escolhe usar essa metodologia é interno ao seu cérebro, e você teria que perguntar a ele para saber exatamente o porquê. Se fosse eu, no entanto, escolheria abstração em vez de integração em um sistema complexo.

    
por 11.10.2015 / 06:30
8

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

Os profissionais são que os desenvolvedores de espaço do usuário não encontrarão seu código quebrando de repente em novos kernels por razões arbitrárias e caprichosas.

Os contras são que o kernel tem que manter o código antigo e o syscalls antigo, etc., para sempre (ou, pelo menos, muito além de suas datas de uso).

    
por 11.10.2015 / 05:55