para - dispositivos con arm
¿Diferencias entre las arquitecturas ARM desde la perspectiva de un programador en C? (5)
ARM en sí mismo es bastante compatible, siempre y cuando se adhiera al código de usuario (el código del kernel, por supuesto, es diferente). En un entorno de sistema operativo alojado, es probable que se limite a ARMv5 (procesadores ARM926).
La gran diferencia viene de:
- El comportamiento del caché es muy diferente. La memoria caché en algunas ARM incluso se aborda de manera virtual, lo que puede hacer que los cambios de proceso sean dolorosos.
- La FPU viene en varios sabores (VFP, NEON, y más). Muchos procesadores más pequeños ni siquiera tienen una FPU.
- El modo pulgar ha cambiado dramáticamente. El modo pulgar entre ARMv5 no es portátil a Thumb2 (ARMv6 +), ni compatible con versiones anteriores.
Soy bastante nuevo en la programación para ARM. He notado que hay varias arquitecturas como ARMv4, ARMv5, ARMv6, etc. ¿Cuál es la diferencia entre estas? ¿Tienen diferentes conjuntos de instrucciones o comportamientos?
Más importante aún, si compilo algún código C para ARMv6, ¿se ejecutará en ARMv5? ¿Qué pasa con el código ARMv5 que se ejecuta en ARMv6? ¿O solo tendría que preocuparme por la diferencia si estuviera escribiendo el código de ensamblaje del kernel?
El mundo de ARM es un poco desordenado.
Para los programadores de C, las cosas son simples: todas las arquitecturas ARM ofrecen un modelo de programación de direccionamiento plano de 32 bits con regularidad. Mientras se mantenga con el código fuente de C, la única diferencia que puede ver es sobre el endianness y el rendimiento. La mayoría de los procesadores ARM (incluso los modelos antiguos) pueden ser big-endian y little-endian; la elección la realiza la placa lógica y el sistema operativo. El buen código C es neutral para endian : compila y funciona correctamente, independientemente de la endianness de la plataforma (la neutralidad endian es buena para la confiabilidad y facilidad de mantenimiento, pero también para el rendimiento: el código no neutral es un código que accede a los mismos datos a través de punteros de distintos tamaños y esto causa estragos en las estrictas reglas de alias que el compilador utiliza para optimizar el código).
La situación es bastante diferente si considera la compatibilidad binaria (es decir, reutilizar el código que se ha compilado una vez):
- Hay varios conjuntos de instrucciones:
- el conjunto de instrucciones ARM original con un contador de programa de 26 bits (muy antiguo, muy poco probable que se encuentre en la actualidad)
- el conjunto de instrucciones ARM con un contador de programa de 32 bits (a menudo denominado "código ARM")
- el conjunto de instrucciones Thumb (opcodes simplificados de 16 bits)
- El conjunto de instrucciones Thumb-2 (Thumb con extensiones)
Un procesador dado puede implementar varios conjuntos de instrucciones. El procesador más nuevo que solo conoce el código ARM es StrongARM, un representante de ARMv4 que ya es bastante antiguo (15 años). El ARM7TDMI (arquitectura ARMv4T) conoce tanto ARM como Thumb, al igual que casi todos los sistemas ARM posteriores, excepto el Cortex-M. Los códigos ARM y Thumb se pueden mezclar juntos dentro de la misma aplicación, siempre y cuando se inserte el pegamento adecuado donde cambian las convenciones; Esto se denomina interfuncionamiento de pulgar y puede ser manejado automáticamente por el compilador de C.
El Cortex-M0 solo conoce las instrucciones del pulgar. Conoce algunas extensiones, porque en los procesadores ARM "normales", el sistema operativo debe usar el código ARM (para manejar las interrupciones); por lo tanto, el Cortex-M0 sabe algunas cosas de Thumb-for-OS. Esto no importa para el código de la aplicación.
Los otros Cortex-M solo saben Pulgar-2. Thumb-2 es mayormente compatible con Thumb, al menos a nivel de ensamblaje.
- Algunas arquitecturas agregan instrucciones adicionales.
Por lo tanto, si se compila algún código con un conmutador de compilador que indica que esto es para un ARMv6, entonces el compilador puede usar una de las pocas instrucciones con ARMv6 pero no ARMv5. Esta es una situación común, que se encuentra en casi todas las plataformas: por ejemplo, si compila el código C en una PC, con GCC, utilizando el indicador -march=core2
, es posible que el binario resultante no se ejecute en un procesador Pentium más antiguo.
- Hay varias convenciones de llamadas.
La convención de llamada es el conjunto de reglas que especifican cómo las funciones intercambian parámetros y devuelven valores. El procesador solo conoce sus registros y no tiene noción de pila. La convención de llamada indica en qué parámetros de registro van y cómo se codifican (por ejemplo, si hay un parámetro char
, va en los 8 bits más bajos de un registro, pero se supone que el autor de la llamada debe borrar / ampliar el signo los 24 bits superiores). , o no ?). Describe la estructura de la pila y la alineación. Normaliza las condiciones de alineación y el relleno de los campos de estructura.
Hay dos convenciones principales para ARM, llamadas ATPCS (antiguas) y AAPCS (nuevas). Son muy diferentes en el tema de los valores de punto flotante. Para los parámetros enteros, son en su mayoría idénticos (pero AAPCS requiere una alineación de pila más estricta). Por supuesto, las convenciones varían según el conjunto de instrucciones y la presencia de interfuncionamiento de pulgar.
En algunos casos, es posible tener algún código binario que se ajuste tanto a ATPCS como a AAPCS, pero eso no es confiable y no hay ninguna advertencia de discrepancia. Entonces, la conclusión es: no puede tener una verdadera compatibilidad binaria entre sistemas que usan convenciones de llamadas distintas.
- Hay coprocesadores opcionales.
La arquitectura ARM se puede ampliar con elementos opcionales, que agregan sus propias instrucciones al conjunto de instrucciones principales. La FPU es un coprocesador opcional (y rara vez se encuentra en la práctica). Otro coprocesador es NEON, un conjunto de instrucciones SIMD que se encuentra en algunos de los procesadores ARM más nuevos.
El código que usa un coprocesador no se ejecutará en un procesador que no tenga ese coprocesador, a menos que el sistema operativo atrape los códigos de operación correspondientes y emule al coprocesador en el software (esto es más o menos lo que sucede con los argumentos de punto flotante cuando se usa la llamada ATPCS) convención, y es lento ).
Para resumir, si tiene código C, entonces vuelva a compilarlo. No intente reutilizar el código compilado para otra arquitectura o sistema.
Lista muy rápida y sucia de áreas que se deben verificar al migrar entre arquitecturas en general:
- Endianness : uso sindical, conversión de tipos de datos, campos de bits, intercambio de datos
- Alineación : requisitos de alineación, pero también características de rendimiento de posible acceso no alineado
- Modelo de memoria : débil vs fuerte?
- Multi-core : ¿cómo funciona la coherencia?
- Varios : tipos de datos firmados y no firmados, empaquetado de la estructura de datos, uso de pila, tipo de datos enum ...
Piense en esta cosa de ARM vs ARM como una computadora de Wintel vs una Intel Mac. Supongamos que incluso tiene el mismo chip de inteligencia (familia) en ambas computadoras, de modo que algunas partes de su código C podrían compilarse una vez y ejecutarse en ambos procesadores sin problemas. Dónde y por qué varían sus programas no tiene nada que ver con el procesador Intel, sino todo lo relacionado con los chips y la placa base que lo rodean, más el sistema operativo en este caso.
Con ARM vs ARM, la mayoría de las diferencias no son el núcleo, sino la lógica específica del proveedor que rodea al núcleo. así que es una pregunta cargada, si su código C es una aplicación que llama a las llamadas de API estándar, debe compilarse en arm o intel o powerpc o lo que sea. Si su aplicación entra en hablar con periféricos de chip o en placa, entonces no importa cuál sea el tipo de procesador, una placa, un chip variará y, como resultado, su código C debe escribirse para ese chip o placa base. Si compila un binario para ARMv6 puede y tendrá instrucciones que se considerarán indefinidas en un ARMv4 y causará una execepción. Si compilas para ARMv4, ARMv6 debería ejecutarlo bien.
En el mejor de los casos, si se encuentra en este espacio de aplicación, lo que probablemente verá es solo diferencias de rendimiento. Algunos de los cuales tienen que ver con su elección en las opciones del compilador. Y a veces puedes ayudar con tu código. Recomiendo evitar divisiones y punto flotante siempre que sea posible. No me gustan las multiplicaciones, pero se multiplicarán en lugar de dividir si se presionan. x86 nos ha echado a perder con los accesos no alineados, si comienza ahora con la E / S alineada, le ahorrará en el camino a medida que ingresa en otros chips que también prefieren accesos alineados, o se ve afectado por los diversos sistemas operativos y los cargadores de arranque configuran el ARM para reaccionar, ninguno de los cuales es lo que estaba acostumbrado en un x86. Igualmente mantén este hábito y tu código x86 se ejecutará mucho más rápido.
Obtenga una copia del ARM ARM (google: ARM Architectural Reference Manual, puede descargarlo gratis en muchos lugares, no sé cuál es la versión actual, rev I o algo así). Examine el conjunto de instrucciones ARM y vea que la mayoría de las instrucciones son compatibles con todos los núcleos, y algunas se agregaron a lo largo del tiempo como divide y byteswap y similares. Verás que no hay nada que temer entre los núcleos.
Piensa desde una perspectiva de sistemas, el wintel vs el intel mac. ARM no hace chips, ellos hacen y licencian núcleos. La mayoría de los vendedores que usan un ARM en su chip tienen su propia salsa especial a su alrededor. Así que es como el wintel contra el mac con el mismo procesador en el medio, pero completamente diferente cuando se trata de todas las cosas que el procesador toca y tiene que usar. No se detiene con el núcleo ARM, ARM vende periféricos, unidades de punto flotante, cachés, etc. Tan pocos ARMv4, si hay alguno, son iguales, por ejemplo. Si su código toca las diferencias, tendrá problemas si no lo hace.
Para las partes del brazo del chip, además de la ARM ARM, hay TRM (manuales de referencia técnica). pero si obtiene la prueba incorrecta para el componente que está usando, puede causarle dolores de cabeza. El TRM puede tener descripciones de registro y otras cosas similares que el ARM ARM no tiene, pero si está viviendo en un espacio de aplicación, es probable que no necesite ninguno de ellos, ni el ARM ARM. El ARM ARM es bueno para fines educativos, si nada más. Comprender por qué es posible que no desee dividir o usar accesos no alineados.
Si la diferencia es realmente tan importante para usted, debería poder averiguarlo a partir de la documentación pública de ARM.
Pero el objetivo principal de escribir en un lenguaje de nivel superior (incluso si es tan "alto" como C) es no preocuparse por eso . Todo lo que haces es recompilar . Incluso dentro del núcleo, no es necesario escribir mucho en ensamblaje; y cuando tiene que escribir algo en el ensamblaje (es decir, no solo para obtener el máximo rendimiento), generalmente se debe a algo más que la elección de la CPU (por ejemplo, ¿qué se ha asignado directamente a la memoria?).