No dejes que el endianismo te dé la vuelta

La mayoría de las arquitecturas de procesador con las que estamos en contacto hoy en día son pocos sistemas endian, lo que significa que almacenan y procesan bytes en un byte menos significativo (LSB). A diferencia del pasado, cuando las arquitecturas de gama alta, incluidas Motorola 68000 y PowerPC, eran más comunes, a menudo se puede asumir simplemente que todos los datos binarios leídos de archivos y mediante protocolos de comunicación están en un pequeño orden indio. Esto a menudo funcionará bien.

El problema proviene, por ejemplo, de los formatos de imagen que utilizan números enteros formateados de gran tamaño, incluidos TIFF y PNG. Al procesar directamente los protocolos en un llamado "orden de red", también se procesan datos de gran tamaño. Tratar de utilizar estos formatos y datos de protocolo literalmente en un sistema un tanto indio obviamente no funcionará.

Afortunadamente, es muy fácil intercambiar la finitud de algunos datos con los que estamos tratando.

Manteniendo el orden

Si los bits se pueden empaquetar en ambas secuencias, tiene sentido marcar los datos con un marcador de antemano. Por ejemplo, en imágenes TIFF (Tagged Image File Format), los primeros dos bytes del archivo indican el orden de bytes: si leen 'II' (de 'Intel'), el archivo está en formato de gama baja (LE), si leen "MM" (de "Motorola") y luego los datos están en formato grand-indian (BE). Debido a que el texto Unicode también puede ser multibyte en el caso de UTF-16 y UTF-32, su endianidad se puede codificar opcionalmente con la marca de comando de byte (BOM) al principio del archivo.

Aunque se podría argumentar sobre la necesidad de preocuparse por el endianismo en la mayoría de los códigos, es un recordatorio de que muchas arquitecturas de procesador que se utilizan hoy en día, de hecho, no son LE, sino dual-endian (BiE), lo que les permite operar en modo LE o BE. Estas arquitecturas incluyen ARM, SPARC, MIPS y derivados como RISC-V, SuperH y PowerPC. En estos sistemas, no se puede simplemente asumir que funciona en modo LE. Aún más divertido es que algunas de estas arquitecturas le permiten cambiar el carácter indio mediante un proceso, sin reiniciar el sistema.

Estudio de caso: Tratamiento de la natividad americana

Recientemente implementé un protocolo de descubrimiento de servicios simple (NyanSD) que usa un protocolo binario. Para operarlo independientemente del carácter indio del sistema de alojamiento, utilicé otro de mi proyecto llamado "ByteBauble", que contiene una serie de características para convertir fácilmente entre indios. Esta herramienta fue escrita originalmente para la biblioteca NymphMQTT MQTT, para permitir que también se ejecute en cualquier sistema.

El uso de las características indias de ByteBauble es bastante simple. Primero debe crear un ejemplo de la clase ByteBauble, después de lo cual se puede usar, por ejemplo, para componer un encabezado de mensaje binario (NyanSD):

ByteBauble bb;
BBEndianness he = bb.getHostEndian();
std::string msg = "NYANSD";
uint16_t len = 0;
uint8_t type = (uint8_t) NYSD_MESSAGE_TYPE_BROADCAST;

Una vez definido el cuerpo del mensaje, la longitud del mensaje (len) es la única parte del encabezado del mensaje que tiene más de un byte. Dado que el protocolo NyanSD se define como bit-endian, debemos asegurarnos de que siempre se escriba en el flujo de bytes en orden LE:

len = bb.toGlobal(len, he);
msg += std::string((char*) &len, 2);

El endianity global (objetivo) se define en ByteBauble como small-endian de forma predeterminada. La toGlobal() El método de plantilla toma la variable a convertir y su endianidad actual aquí desde el host. El valor resultante se puede agregar al mensaje, como se muestra. Si el endianity de entrada y el endianity de salida difieren, el valor se convierte; de ​​lo contrario, no se realiza ninguna acción.

La inversa mientras se lee la corriente de bytes es muy similar, con el conocido endianity del flujo de bytes utilizado junto con el toHost() Método de plantilla ByteBauble para asegurarnos de que obtenemos el valor objetivo en lugar del valor invertido.

Transformando las inter-endianidades

Afortunadamente, las arquitecturas de procesadores no nos permiten aferrarnos a estos regímenes indios. La mayoría de ellos también tienen funciones de dispositivo convenientes para realizar la operación de intercambio de bytes necesaria durante la conversión entre LE y BE o viceversa. Aunque las llamadas requeridas podrían usarse dependiendo de la arquitectura del procesador, es más conveniente usar el compilador internamente.

Así también se implementan las rutinas de intercambio de bytes de ByteBauble. Actualmente se dirige a los GCC y MSVC internos. Para GCC, el procedimiento básico se ve así:

std::size_t bytesize = sizeof(in);
if (bytesize == 2) {
	return __builtin_bswap16(in);
}
else if (bytesize == 4) {
	return __builtin_bswap32(in);
}
else if (bytesize == 8) {
	return __builtin_bswap64(in);
}

Como podemos ver en el código anterior, el primer paso es determinar con cuántos bytes estamos tratando, luego llamando al interno apropiado. La implementación del compilador interno depende de lo que ofrece la arquitectura de destino en términos de características de hardware para este proceso. En el peor de los casos, se puede implementar en software limpio utilizando un algoritmo inverso local.

Determinación del anfitrión

Como hemos visto antes, para convertir correctamente entre endianidad de anfitrión y objetivo, debemos saber cuál es la endianidad del primero para saber si es necesaria alguna conversión. Aquí nos encontramos con el problema de que rara vez hay una función del sistema operativo fácilmente disponible o que podamos llamar para obtener esta información.

Afortunadamente, es muy fácil averiguar el final del host (o proceso), como se muestra en ByteBauble:

uint16_t bytes = 1;
if (*((uint8_t*) &bytes) == 1) {
	std::cout << "Detected Host Little Endian." << std::endl;
	hostEndian = BB_LE;
}
else {
	std::cout << "Detected Host Big Endian." << std::endl;
	hostEndian = BB_BE;
}

La idea detrás de este control es un experimento simple. Debido a que necesitamos saber dónde se encuentran MSB y LSB en una variable de varios bytes, creamos una nueva uint16_t variable, establezca el primer bit del LSB alto y luego verifique el valor del primer byte. Si este primer byte tiene un valor de 1, sabemos que es el LSB y que estamos trabajando en un entorno de gama baja. Sin embargo, si el primer byte es 0, sabemos que es el MSB y, por lo tanto, este es un entorno de endian grande.

Lo bueno de este enfoque es que no depende de suposiciones como el control de la arquitectura de alojamiento, sino que controla directamente lo que sucede con las operaciones de varios bytes.

Envolvente

Probablemente nunca veremos el final de tener que lidiar con estas diferencias en el orden de los bytes. Esto se debe tanto al legado de los formatos de archivo existentes y las arquitecturas de procesador, como también al hecho de que algunas operaciones son más eficientes durante la ejecución en una secuencia a gran escala (como las que se encuentran a menudo para los equipos de interconexión).

Afortunadamente, como vimos en este artículo, tratar con diferentes indios no es nada complicado. El primer paso es estar siempre consciente de qué indianidad se está abordando en el flujo de bytes que se va a procesar o escribir. El segundo paso es utilizar eficazmente el host con funciones fácilmente disponibles proporcionadas por un compilador interno o bibliotecas que lo rodean.

Con estos sencillos pasos, la indigenidad es solo una leve molestia en lugar de un detalle que debe ignorarse hasta que algo se encienda.

  • Julian Skidmore dice:

    Siempre me parece extraño que la convención normal para manejar código independiente de endian sea probar la endiabilidad del host y luego intercambiar bytes cuando es fácil escribir código independiente de endian sin referencia al host, cambiando los datos directamente.

    Por tanto, si b[4] es una matriz de bytes leídos por una secuencia.

    uint32_t esti = (b[0]

    Si el valor grand-endian es independiente de la arquitectura del host y debido a que el código no involucra ramas será casi tan rápido como intercambiar bytes condicionalmente.

    • Palmadita dice:

      "Y porque el código no incluye ramas"

      La prueba de endian es constante: a menos que el compilador sea completamente estúpido, simplemente seleccionará la rama correcta durante la compilación e ingresará el código correcto, punto. El compilador debe saber que solo puede omitir las matemáticas en su ejemplo, pero una expresión "si / si no" con un resultado constante debe ocurrir incluso con la optimización más básica en un compilador.

      Esta es la razón por la que utiliza las funciones de alojamiento web en las bibliotecas: no hacen nada si Indianness ya es compatible.

    • ziew dice:

      Vine aquí para escribir exactamente esto.

      Si está utilizando GCC nuevo o haga clic, esto será incluso más rápido que intercambiar bytes condicionalmente (durante el tiempo de ejecución) porque los compiladores lo están optimizando actualmente para mov o mov + bswap en x86 y ldr o ldr + rev en AArch64. Y le ahorra problemas de alineación de palabras si escribe código que funcione en algunas arquitecturas antiguas.

      • Palmadita dice:

        "Si usa GCC nuevo o hace clic, será incluso más rápido que intercambiar bytes condicionalmente (durante el tiempo de ejecución)"

        ¿Entonces este compilador es lo suficientemente inteligente como para indicar que puede usar instrucciones más rápidas debido a lo que hace, pero no lo suficientemente inteligente como para afirmar que la prueba que está haciendo es consistente?

        • ziew dice:

          Depende de lo que haga con el resultado de la prueba. El código del artículo realiza la prueba dentro de una unidad de compilación en particular, por lo que no importa si está optimizado o no. El compilador no conocerá el resultado al llamar a los métodos de conversión endian. Si todo el código se pusiera en un archivo de encabezado _y_ todos los dichos con efectos secundarios eliminados (por ejemplo, Std :: cout), entonces sí, el compilador embellecería todo. Lo que IMO demuestra el punto: el ejemplo anterior de Julian mejorará independientemente de cómo se organice, incluya y cree el código.

  • Steve Toner dice:

    Para cualquiera que no esté familiarizado con cómo se originó la terminología, proviene del IEN 137 de Danny Cohen de ISI:
    https://www.ietf.org/rfc/ien/ien137.txt

    • Joseph Eoff dice:

      La fuente final de los términos es "Los viajes de Gulliver" de Jonathan Swift.

      La elección de los términos (así como el comienzo de IEN137) fue un juego deliberado sobre la estupidez de las guerras liliputienses sobre si romper un huevo desde el extremo grande o el extremo pequeño, que en sí mismo fue un juego sobre la estupidez de muchos políticos. disputas.

      La elección de un extremo grande o un extremo pequeño en las computadoras era un tema bastante polémico en el momento en que se escribió IEN 137, y parece que el Sr. Cohen tuvo una gran disputa.

  • bosque eterno dice:

    ¿Podemos simplemente dejar de usar el orden de bytes en línea para fines no heredados? Casi todos los chips nuevos son LE, ¿por qué usaríamos BE para un nuevo protocolo?

    • Palmadita dice:

      Debe descomprimir los valores sin importar, ya que nunca se asignarán para alinearse después de que se encapsulan repetidamente. Un pedido en línea que es un indio grande significa que los datos, cuando llegan, se pueden cambiar y procesar parcialmente más rápido, ya que las partes más importantes son lo primero. Además también tiene la ventaja de que en cable real captura los datos legibles.

      También podría argumentar "¿podemos simplemente dejar de usar transferencias DMA de direcciones incrementales para propósitos no heredados? Casi todos los diseños de chips nuevos se dirigen en ambas direcciones, ¿por qué no usar transferencias DMA de direcciones decrecientes para un nuevo dispositivo?"

      Si pierde la referencia, si recupera una captura en un búfer por una dirección decreciente, los datos ahora se recuerdan un poco. Parte de la razón por la que odio llamar a un orden de bytes "big endian" es que en realidad es "el byte más significativo * primero *", mientras que "big endian" es en realidad "el byte más significativo * la dirección más baja *". Mapear la "primera dirección" a la "dirección más baja" es la causa real del problema, y ​​no es necesario hacerlo. que.

      • bosque eterno dice:

        Prácticamente, lo único que se me ocurre que permite procesar bits parciales de datos en la red es el cambio real de paquetes de bajo nivel. No puedo imaginar que los desarrolladores de juegos estén muy interesados ​​en los dos primeros bytes de posición o munición antes de que llegue el resto, y dudo que alguien en IoT use el byte de temperatura de orden superior sin el resto, para ahorrar unos cuantos dólares.

        La conveniencia supera a la limpieza, por lo que los encabezados de bajo nivel tienen que usar todo lo que brinde la mejor latencia y ancho de banda con el costo más económico, pero un orden de bytes a veces ingresa a la capa de aplicación, incluso para cosas con las que básicamente nadie se ocupa en el nivel de DMA: parachoques. . (por lo que sé).

        No creo que haya visto direcciones en declive en un búfer de software obteniendo bytes de una API de socket, parece que sería bastante confuso.

        • Palmadita dice:

          "Prácticamente, lo único que se me ocurre que permite procesar bits parciales de datos en la red es el cambio real de paquetes de bajo nivel".

          Para ser claros, la publicación original decía "por qué usaríamos BE para un nuevo protocolo". "Nuevo protocolo" no significa necesariamente "enlace de más de 100 GbE", donde unos pocos bytes son microsegundos. También podría significar un protocolo extremadamente lento para redes inalámbricas de baja potencia, por ejemplo, donde la velocidad de datos podría ser de decenas de bytes / segundo. También podría significar algo como I2C, por ejemplo.

          ¿Y sobre "Dudo que alguien en IoT esté usando el byte alto ..."? ¿Está seguro? Eche un vistazo a todos los sensores de temperatura I2C que existen. Todos emiten grand-endian: la parte entera primero, la fracción al final. ¿Por qué? Porque si no necesitas la facción, no te molestes en leer el resto.

          “La conveniencia supera la limpieza, por lo que los encabezados de bajo nivel tienen que usar todo lo que brinde la mejor latencia y ancho de banda al menor costo, pero una orden de bytes en línea a veces ingresa a la capa de la aplicación, incluso para cosas con las que básicamente nadie se ocupa en el nivel de DMA. Búfer (que yo sepa) ".

          Exactamente lo que significa terminar con una mezcla de indianidad, sea lo que sea. Ese era mi punto, que aparentemente no estaba claro. No es que * todo * sea gran indio. Las cosas tienen que ser cualquier tipo de indianidad que tenga sentido.

          "No creo haber visto nunca una dirección en declive en un búfer de software obteniendo bytes de una API de socket. Parece que sería bastante confuso".

          Lo siento, supongo que el sarcasmo no funcionó. Toda la confusión con el orden de bytes en línea / el orden de bytes del host se debe a que la gente quiere una función sencilla "construir un número entero de bytes". Las redes tenderán a querer que los bytes más importantes sean lo primero para poder actuar rápidamente contra ellos para cambiar los objetivos, y posiblemente por otras razones.

          Podríamos * tener una función fácil "construir un número entero de bytes" si en su lugar tuviéramos operaciones "incremento de red" e "incremento de host" - un byte restado, un byte agregado (y el correspondiente 'acceso indexado a la red' y el 'acceso indexado al host' ').

          Claro, esto es en realidad más rápido, fíjate, pero como señaló (¡y como pensé que era obvio!), Probablemente sería confuso. Aunque quizás no si se llevó a cabo al principio, no lo sé.

          Pero como está ahora: solo aprenda sobre la endianidad de los datos y maneje correctamente.

    • X dice:

      Arm64 es un big endian, entre Raspberry Pi y las nuevas Mac y la mayoría de los teléfonos, yo diría que un pequeño endian es el que cae.

      • bosque eterno dice:

        Tuve la impresión de que casi todos los chips ARM grandes son bi-end y se usan principalmente en modo LE.

        • Erik Johnson dice:

          ¿Yo también pensé que eran biendianos? Mirándolo es, incluso comenzó exclusivamente LE y notó principalmente que el seno LE se convirtió en bi

  • Joseph Eoff dice:

    Fuente de gran final contra pequeña final:

    https://eo.wikipedia.org/wiki/Lilliput_kaj_Blefuscu#Satiraj_interpretoj

  • Martin Moene dice:

    Puede ser interesante observar que el estándar C ++ 20 solo está comenzando a admitirse ligeramente aquí, consulte la propuesta P0463: endian, Just endian de Howard E. Hinnant [1] y la documentación del encabezado en Cppreference [2].

    [1] https://wg21.link/P0463
    [2] https://en.cppreference.com/w/cpp/header/bit

  • Anton Kovalenko dice:

    Me pregunto si hay una manera de eludir las protecciones de la CPU cambiando endianity

  • sdspivey dice:

    Todos los números deben ser pocos. ¿Por qué seguimos usando el método inferior en nuestras vidas? (Respuesta parcial: es una permanencia de derecha-izquierda del árabe).

    Empecemos a enseñar a los niños a escribir los números en la dirección correcta. A medida que comencemos a sumar, restar y multiplicar el dígito menos significativo, será más fácil para ellos aprender.

    Una ventaja adicional es que no necesitamos una justificación adecuada solo para mostrar un problema de matemáticas cuando se escribe.

    Los grandes indios simplemente permanecen en el pasado. ¡Viva para el futuro!

  • Alexander Wikström dice:

    Honestamente, un big endian o un small endian no importa mucho.
    Lo que es importante, por otro lado, es que sabemos en qué orden se escriben los bytes de un valor multibyte dado.

    Desde un punto de vista arquitectónico, realmente no hay gran diferencia entre los dos.
    Y hacer una arquitectura de dos endian es muy trivial en la gran mayoría de los casos.

    Las únicas cosas que agregan complejidad serían cosas como funciones de administración de dispositivos, punteros de dirección y otras cosas similares. Aunque aquí podemos simplemente lanzar una moneda por uno u otro y luego continuar como si nada hubiera pasado ...

    Hacer estas funciones de dos extremos también es bastante trivial, pero agrega un poco más de control lógico y esto puede afectar la velocidad máxima del reloj, y también aumenta ligeramente el consumo de energía. (Porque hay algunos transistores más en la implementación).

    En última instancia, es importante saber si un valor dado es grande o pequeño indio, aparte de eso, no importa.

    Y en muchos casos, saber si el anfitrión es un endian pequeño o grande realmente no importa.

  • AndyPanda dice:

    Un pequeño orden indio era / es una necesidad de la arquitectura de 8/16 bits del día ... primero tome el byte de orden inferior ... todas las matemáticas comienzan con el byte inferior ...
    ¿Algún diseñador de Intel puede tocar el timbre?

  • J dice:

    resolvió un problema muy similar en el trabajo hoy con htons () y htonl ()

  • Gregg Eshelman dice:

    ¿Qué pasa con el bus PCI? ¿Es su propio pequeño indio, nacido en PC Land y luego adoptado en el gran mundo indio Macintosh?

  • las vidas endian importan dice:

    Necesitamos ser uni-endianos. Valores legibles en ambos sentidos.

    • Elliot Williams dice:

      ¿No es así? El espacio de la memoria es tan barato hoy en día. Mantenlos palíndromos.

  • Biotronics dice:

    Ningún artículo sobre endianismo está completo sin mencionar la locura de los valores de 32 bits de gama media del PDP-11, donde las palabras de gama baja de 16 bits se mantuvieron en grande (2143), y Honeywell Series 16, que tenía 16 bits .Un poco de palabras en endian grandes almacenadas en endian de bits para hacer valores de 32 bits (3412).

  • Ren dice:

    Uno pequeño, dos pequeños, tres pequeños indios,
    cuatro pequeños, cinco pequeños, seis pequeños indios.,.

Isabella Ortiz
Isabella Ortiz

Deja una respuesta

Tu dirección de correo electrónico no será publicada.