¿Por qué hay una política de kernel de Linux para nunca romper el espacio del usuario?

38

Empecé a pensar en este tema en el contexto de etiqueta en la lista de correo del kernel de Linux. Como el proyecto de software libre más conocido e indiscutiblemente más exitoso e importante del mundo, el núcleo de Linux recibe mucha prensa. Y el fundador y líder del proyecto, Linus Torvalds, claramente no necesita presentación aquí.

Linus ocasionalmente atrae controversia con sus llamas en el LKML. Estas llamas son frecuentemente, por su propia admisión, que ver con romper el espacio del usuario. Lo cual me lleva a mi pregunta.

¿Puedo tener una perspectiva histórica de por qué romper el espacio del usuario es algo tan malo? Según tengo entendido, romper el espacio del usuario requeriría correcciones en el nivel de la aplicación, pero ¿es esto algo malo si mejora el código del núcleo?

Según tengo entendido, la política declarada de Linus es que no romper el espacio del usuario supera todo lo demás, incluida la calidad del código. ¿Por qué es esto tan importante y cuáles son los pros y los contras de dicha política?

(Claramente, hay algunas desventajas de tal política, aplicada de manera consistente, ya que Linus ocasionalmente tiene "desacuerdos" con sus principales lugartenientes en la LKML sobre exactamente este tema. Hasta donde puedo decir, siempre se sale con la suya en el asunto).

Faheem Mitha
fuente
1
Has escrito mal el nombre de Linus en la introducción.
Ismael Miguel
No era yo seguro, pero olvidé votar y di mi voto ahora.
Ismael Miguel
1
Relacionado: stackoverflow.com/q/25954270/350713
Faheem Mitha

Respuestas:

38

La razón no es histórica sino práctica. Hay muchos, muchos, muchos programas que se ejecutan sobre el kernel de Linux; Si una interfaz del núcleo rompe esos programas, entonces todos tendrían que actualizar esos programas.

Ahora es cierto que la mayoría de los programas no dependen de las interfaces del kernel directamente (las llamadas del sistema ), sino solo de las interfaces de la biblioteca estándar C (las envolturas de C alrededor de las llamadas del sistema). Ah, pero ¿qué biblioteca estándar? Glibc? uClibC? Dietlibc? ¿Biónico? Musl? etc.

Pero también hay muchos programas que implementan servicios específicos del sistema operativo y dependen de las interfaces del núcleo que no están expuestas por la biblioteca estándar. (En Linux, muchos de estos se ofrecen a través de /procy /sys.)

Y luego hay binarios compilados estáticamente. Si una actualización del núcleo rompe uno de estos, la única solución sería recompilarlos. Si tiene la fuente: Linux también admite software propietario.

Incluso cuando la fuente está disponible, reunirlo todo puede ser una molestia. Especialmente cuando está actualizando su kernel para corregir un error con su hardware. Las personas a menudo actualizan su kernel independientemente del resto de su sistema porque necesitan el soporte de hardware. En palabras de Linus Torvalds :

Romper programas de usuario simplemente no es aceptable. (...) Sabemos que las personas usan viejos binarios durante años y años, y que hacer un nuevo lanzamiento no significa que puedas descartarlo. Puedes confiar en nosotros.

También explica que una razón para hacer de esto una regla fuerte es evitar el infierno de la dependencia, donde no solo tendría que actualizar otro programa para que funcione un núcleo más nuevo, sino que también tenga que actualizar otro programa, y ​​otro, y otro , porque todo depende de una cierta versión de todo.

Está algo bien tener una dependencia unidireccional bien definida. Es triste, pero inevitable a veces. (...) Lo que NO está bien es tener una dependencia bidireccional. Si el código HAL del espacio de usuario depende de un nuevo núcleo, está bien, aunque sospecho que los usuarios esperarían que no fuera el "núcleo de la semana", sino más bien un "núcleo de los últimos meses".

Pero si tienes una dependencia de DOS VÍAS, estás jodido. Eso significa que debe actualizar en el paso de bloqueo, y eso NO ES ACEPTABLE. Es horrible para el usuario, pero aún más importante, es horrible para los desarrolladores, porque significa que no se puede decir "ocurrió un error" y hacer cosas como tratar de reducirlo con bisección o similar.

En el espacio de usuario, esas dependencias mutuas generalmente se resuelven manteniendo diferentes versiones de la biblioteca; pero solo puede ejecutar un núcleo, por lo que tiene que soportar todo lo que la gente quiera hacer con él.

oficialmente ,

La compatibilidad con versiones anteriores para [llamadas del sistema declaradas estables] estará garantizada durante al menos 2 años.

En la práctica, sin embargo,

Se espera que la mayoría de las interfaces (como las llamadas al sistema) nunca cambien y siempre estén disponibles.

Lo que cambia con más frecuencia son las interfaces que solo están destinadas a ser utilizadas por programas relacionados con el hardware, en /sys. ( /proc, por otro lado, que desde la introducción de /sysse ha reservado para servicios no relacionados con el hardware, casi nunca se rompe de manera incompatible).

En resumen,

romper el espacio del usuario requeriría soluciones en el nivel de la aplicación

y eso es malo porque solo hay un kernel, que la gente quiere actualizar independientemente del resto de su sistema, pero hay muchas aplicaciones con interdependencias complejas. Es más fácil mantener estable el kernel que mantener miles de aplicaciones actualizadas en millones de configuraciones diferentes.

Gilles 'SO- deja de ser malvado'
fuente
1
Gracias por la respuesta. Entonces, ¿las interfaces que se declaran estables son un superconjunto de las llamadas al sistema POSIX? Mi pregunta sobre la historia es cómo evolucionó esta práctica. Presumiblemente, las versiones originales del kernel de Linux no se preocuparon por la ruptura del espacio del usuario, al menos inicialmente.
Faheem Mitha
3
@FaheemMitha Sí, lo hicieron, desde 1991 . No creo que el enfoque de Linus haya evolucionado, siempre ha sido "las interfaces para aplicaciones normales no cambian, las interfaces para software que están muy vinculadas al kernel cambian muy raramente".
Gilles 'SO- deja de ser malvado'
24

En cualquier sistema interdependiente hay básicamente dos opciones. Abstracción e integración. (A propósito no estoy usando términos técnicos). Con Abstracción, estás diciendo que cuando haces una llamada a una API que, si bien el código detrás de la API puede cambiar, el resultado siempre será el mismo. Por ejemplo, cuando llamamos fs.open()no nos importa si se trata de una unidad de red, un SSD o un disco duro, siempre obtendremos un descriptor de archivo abierto con el que podamos hacer cosas. Con la "integración", el objetivo es proporcionar la "mejor" forma de hacer algo, incluso si la forma cambia. Por ejemplo, abrir un archivo puede ser diferente para un recurso compartido de red que para un archivo en el disco. Ambas formas se usan ampliamente en el escritorio moderno de Linux.

Desde el punto de vista de los desarrolladores, se trata de "funciona con cualquier versión" o "funciona con una versión específica". Un gran ejemplo de esto es OpenGL. La mayoría de los juegos están configurados para funcionar con una versión específica de OpenGL. No importa si estás compilando desde la fuente. Si el juego fue escrito para usar OpenGL 1.1 y estás intentando que se ejecute en 3.x, no lo vas a pasar bien. En el otro extremo del espectro, se espera que algunas llamadas funcionen sin importar qué. Por ejemplo, quiero llamar fs.open()No quiero importarme en qué versión del kernel estoy. Solo quiero un descriptor de archivo.

Hay beneficios en cada sentido. La integración proporciona características "más nuevas" a costa de la compatibilidad con versiones anteriores. Mientras que la abstracción proporciona estabilidad sobre las llamadas "más nuevas". Aunque es importante tener en cuenta que es una cuestión de prioridad, no de posibilidad.

Desde un punto de vista comunitario, sin una muy buena razón, la abstracción siempre es mejor en un sistema complejo. Por ejemplo, imagine si fs.open()funcionó de manera diferente dependiendo de la versión del kernel. Entonces, una simple biblioteca de interacción del sistema de archivos necesitaría mantener varios cientos de métodos diferentes de "archivo abierto" (o bloques probablemente). Cuando salió una nueva versión del kernel, no podría "actualizarse", tendría que probar cada pieza de software que utilizó. Kernel 6.2.2 (falso) puede simplemente romper su editor de texto.

Para algunos ejemplos del mundo real, OSX tiende a no preocuparse por romper el espacio del usuario. Apuntan a la "integración" sobre la "abstracción" con mayor frecuencia. Y en cada actualización importante del sistema operativo, las cosas se rompen. Eso no quiere decir que una forma es mejor que la otra. Es una decisión de elección y diseño.

Lo más importante es que el ecosistema de Linux está lleno de increíbles proyectos de código abierto, donde las personas o grupos trabajan en el proyecto en su tiempo libre, o porque la herramienta es útil. Con eso en mente, en el momento en que deja de ser divertido y comienza a ser un PIA, esos desarrolladores irán a otro lugar.

Por ejemplo, envié un parche a BuildNotify.py. No porque sea altruista, sino porque uso la herramienta y quería una función. Fue fácil, así que aquí tienes un parche. Si fuera complicado o engorroso, no lo usaría BuildNotify.pyy encontraría algo más. Si cada vez que salía una actualización del núcleo, mi editor de texto se rompía, simplemente usaría un sistema operativo diferente. Mis contribuciones a la comunidad (por pequeñas que sean) no continuarían ni existirían, y así sucesivamente.

Entonces, la decisión de diseño se tomó para abstraer las llamadas al sistema, de modo que cuando lo hago fs.open(), simplemente funciona. Eso significa mantener fs.openmucho tiempo después de fs.open2()ganar popularidad.

Históricamente, este es el objetivo de los sistemas POSIX en general. "Aquí hay un conjunto de llamadas y valores de retorno esperados, usted se da cuenta del medio". De nuevo por razones de portabilidad. El motivo por el cual Linus elige usar esa metodología es interno de su cerebro, y usted tendría que pedirle que sepa exactamente por qué. Sin embargo, si fuera yo, elegiría la abstracción sobre la integración en un sistema complejo.

coteyr
fuente
1
La API para el espacio de usuario, la API 'syscall', está bien definida (especialmente el subconjunto POSIX) y estable, porque la eliminación de cualquier parte romperá el software que las personas pueden haber instalado. Lo que no tiene es una API de controlador estable .
pjc50
44
@FaheemMitha, es al revés. Los desarrolladores del kernel son libres de romper la API del controlador cuando lo deseen, siempre que reparen todos los controladores del kernel antes de la próxima versión. Está rompiendo la API del espacio de usuario, o incluso haciendo cosas que no son de la API que podrían romper el espacio de usuario, lo que produce reacciones épicas de Linus.
Mark
44
Por ejemplo, si alguien decide cambiarlo devolviendo un código de error diferente de ioctl () en algunas circunstancias: lkml.org/lkml/2012/12/23/75 (contiene palabrotas y ataques personales contra el desarrollador responsable). Ese parche fue rechazado porque habría roto PulseAudio y, por lo tanto, todo el audio en los sistemas GNOME.
pjc50
1
@FaheemMitha, básicamente, def add (a, b); devuelve a + b; end --- def add (a, b); c = a + b; volver c; end --- def add (a, b); c = a + b +10; retorno c - 10; end - son todas la "misma" implementación de add. Lo que lo molesta tanto es cuando la gente def suma (a, b); retorno (a + b) * -1; En esencia, cambiar cómo funcionan las cosas "internas" para el núcleo está bien. Cambiar lo que se devuelve a una llamada API definida y "pública" no lo es. Hay dos tipos de llamadas API "privadas" y "públicas". Él siente que las llamadas API públicas nunca deberían cambiar sin una buena razón.
coteyr
3
Un ejemplo sin código; Vas a la tienda, compras 87 Octanos de gas. A usted, como consumidor, no le "importa" de dónde vino el gas o cómo se procesó. Solo te importa que consigas gasolina. Si el gas pasó por un proceso de refinación diferente, no le importa. Seguro que el proceso de refinación puede cambiar. Incluso hay diferentes fuentes de petróleo. Pero lo que le importa es obtener 87 octanos de gas. Entonces, su posición es cambiar las fuentes, cambiar las refinerías, cambiar lo que sea, siempre que lo que sale en la bomba sea 87 Octane gas. Todas las cosas "detrás de escena" no importan. Mientras haya 87 octanos de gas.
coteyr
8

Es una decisión de diseño y elección. Linus quiere poder garantizar a los desarrolladores de espacio de usuario que, excepto en circunstancias extremadamente raras y excepcionales (por ejemplo, relacionadas con la seguridad), los cambios en el núcleo no interrumpirán sus aplicaciones.

Las ventajas son que los desarrolladores de espacio de usuario no encontrarán que su código se rompa repentinamente en nuevos núcleos por razones arbitrarias y caprichosas.

Las desventajas son que el núcleo tiene que mantener el código antiguo y las llamadas de sistema antiguas, etc. para siempre (o, al menos, más allá de las fechas de caducidad).

cas
fuente
Gracias por la respuesta. ¿Conoces la historia de cómo evolucionó esta decisión? Soy consciente de proyectos que tienen una perspectiva algo diferente. Por ejemplo, el proyecto Mercurial no tiene una API fija, y puede y romper el código que se basa en él.
Faheem Mitha
No, lo siento, no recuerdo cómo surgió. Puede enviar un correo electrónico a Linus o LKML y preguntarle.
cas
2
Mercurial no es un sistema operativo. El objetivo de un sistema operativo es permitir la ejecución de otro software encima de él, y romper ese otro software es muy impopular. En comparación, Windows también ha mantenido la compatibilidad con versiones anteriores durante mucho tiempo; El código de Windows de 16 bits fue recientemente obsoleto.
pjc50
@ pjc50 Es cierto que Mercurial no es un sistema operativo, pero sin tener en cuenta, no es otro software, incluso si sólo scripts, que dependen de él. Y potencialmente puede romperse por los cambios.
Faheem Mitha