make how c pointers data-structures linux-kernel linux-device-driver

how - Kernel de Linux: ¿por qué las estructuras ''subclase'' ponen la información de la clase base al final?



linux kernel driver (4)

El sistema operativo Amiga usa este truco de "encabezado común" en muchos lugares y parecía una buena idea en ese momento: Subclasificar simplemente lanzando el tipo de puntero. Pero hay inconvenientes.

Pro:

  • Puede ampliar las estructuras de datos existentes.
  • Puede usar el mismo puntero en todos los lugares donde se espera el tipo de base, no se necesita aritmética de punteros, ahorrando ciclos preciosos
  • Se siente natural

Estafa:

  • Los diferentes compiladores tienden a alinear las estructuras de datos de manera diferente. Si la estructura base terminara con char a; , entonces podría tener 0, 1 o 3 bytes de relleno posteriormente antes de que comience el siguiente campo de la subclase. Esto dio lugar a errores bastante desagradables, especialmente cuando tenía que mantener la compatibilidad con versiones anteriores (es decir, por algún motivo, debe tener un cierto relleno porque una versión antigua del compilador tenía un error y ahora, hay un montón de código que espera el relleno con errores) .
  • No te das cuenta rápidamente cuando pasas la estructura equivocada. Con el código en su pregunta, los campos se eliminan rápidamente si la aritmética del puntero es incorrecta. Eso es bueno, ya que aumenta las posibilidades de que un error se descubra más pronto.
  • Conduce a una actitud "mi compilador lo arreglará por mí" (que a veces no lo hará) y todos los lanzamientos llevan a una actitud de "Sé mejor que el compilador". El último le haría insertar casts automáticamente antes de comprender el mensaje de error, lo que llevaría a todo tipo de problemas extraños.

El kernel de Linux está poniendo la estructura común en otro lugar; Puede ser pero no tiene que ser al final.

Pro:

  • Los insectos se mostrarán temprano
  • Tendrás que hacer aritmética de punteros para cada estructura, así que estás acostumbrado.
  • No necesitas moldes

Estafa:

  • No es obvio
  • El código es más complejo

Estaba leyendo el capítulo en Beautiful Code en el kernel de Linux y el autor explica cómo el kernel de Linux implementa la herencia en el lenguaje C (entre otros temas). En pocas palabras, se define una estructura ''base'' y para heredar de ella la estructura ''subclase'' coloca una copia de la base al final de la definición de la estructura subclase. Luego, el autor pasa un par de páginas explicando una macro inteligente y complicada para averiguar cuántos bytes respaldar para convertir de la parte base del objeto a la parte de subclase del objeto.

Mi pregunta: dentro de la estructura de subclase, ¿por qué no declarar la estructura base como la primera cosa en la estructura, en lugar de la última ?

La principal ventaja de poner las cosas de estructura base primero es que al lanzar desde la base a la subclase no necesitaría mover el puntero en absoluto. campos que la estructura de subclase ha colocado después de lo que define la base.

Solo para aclarar un poco mi pregunta, permítame lanzar un código:

struct device { // this is the ''base class'' struct int a; int b; //etc } struct usb_device { // this is the ''subclass'' struct int usb_a; int usb_b; struct device dev; // This is what confuses me - // why put this here, rather than before usb_a? }

Si resulta que uno tiene un puntero al campo "dev" dentro de un objeto usb_device, para volver a ese objeto usb_device, debe restar 8 de ese puntero. Pero si "dev" fue lo primero en un dispositivo usb_device, el puntero no tendría que mover el puntero en absoluto.

Cualquier ayuda en esto sería apreciada grandemente Se agradecería incluso el consejo sobre dónde encontrar una respuesta. No estoy realmente seguro de cómo buscar en Google por la razón arquitectónica detrás de una decisión como esta. Lo más cercano que pude encontrar aquí en StackOverflow es: por qué usar estas extrañas estructuras de anidamiento

Y, para que quede claro, entiendo que muchas personas brillantes han trabajado en el kernel de Linux durante mucho tiempo, así que claramente hay una buena razón para hacerlo de esta manera, simplemente no puedo entender qué es.


Es por herencia múltiple. struct dev no es la única interfaz que puede aplicar a una estructura en el kernel de Linux, y si tiene más de una, simplemente no sería correcto convertir la subclase en una clase base. Por ejemplo:

struct device { int a; int b; // etc... }; struct asdf { int asdf_a; }; struct usb_device { int usb_a; int usb_b; struct device dev; struct asdf asdf; };


No tengo experiencia nueva en el kernel de Linux, sino en otros kernels. Yo diría que esto no importa en absoluto.

Se supone que no debes lanzar de uno a otro. Permitir lanzamientos de ese tipo solo debe hacerse en situaciones muy específicas. En la mayoría de los casos, reduce la robustez y flexibilidad del código y se considera bastante descuidado. Entonces, la "razón arquitectónica" más profunda que estás buscando podría ser "porque ese es el orden en el que alguien escribió". O alternativamente, eso es lo que mostraron los puntos de referencia que serían los mejores para el rendimiento de algunas rutas de código importantes en ese código. O alternativamente, la persona que lo escribió piensa que se ve bonita (siempre construyo pirámides invertidas en mis declaraciones y estructuras variables si no tengo otras restricciones). O alguien por casualidad lo escribió de esta manera hace 20 años y desde entonces todos los demás lo han estado copiando.

Puede haber un diseño más profundo detrás de esto, pero lo dudo. Simplemente no hay razón para diseñar esas cosas en absoluto. Si desea averiguar de una fuente autorizada por qué se hace de esta manera, simplemente envíe un parche a Linux que lo cambie y vea quién le grita.


Soy nuevo en el código del kernel de Linux, así que tome mis divisiones aquí con un grano de sal. Por lo que puedo decir, no hay ningún requisito en cuanto a dónde colocar la estructura "subclase". Eso es exactamente lo que proporcionan las macros: puede convertir a la estructura de "subclase", independientemente de su diseño. Esto proporciona robustez a su código (el diseño de una estructura puede cambiarse sin tener que cambiar su código. Tal vez haya una convención de colocar la estructura de "clase base" al final, pero no estoy al tanto de ello. He visto muchos códigos en los controladores, donde se utilizan diferentes estructuras de "clase base" para volver a la misma estructura de "subclase" (desde campos diferentes en la "subclase", por supuesto).