使用 Linux 内核的 API 进行数据字节序的可移植转换
Portable conversion of data endianness using the Linux kernel's API
我如何改进以下代码,即使用 Linux 内核 API 中的函数和宏,使其在类型安全和字节顺序方面更加健壮?例如,在下面的示例中,src_data
是两个 16 位有符号整数的数组(通常以小端顺序存储),将以大端字节顺序通过 UART 发送出去。
s16 src_data[2] = {...}; /* note: this is signed data! */
u8 tx_data[4];
u8* src_data_u8 = (u8*)src_data;
tx_data[0] = src_data_u8[1];
tx_data[1] = src_data_u8[0];
tx_data[2] = src_data_u8[3];
tx_data[3] = src_data_u8[2];
我认为函数 cpu_to_be16 and cpu_to_be16p 应该在进行这种转换中发挥作用。尽管我不确定如何以安全且稳健的方式使用它们。
如果 Linux 机器永远是小端,协议永远是大端,那么代码工作正常,你不需要改变任何东西。
如果您出于某种原因需要使 Linux 代码与字节序无关,那么您可以使用:
tx_data[0] = ((unsigned int)src_data[0] >> 8) & 0xFF;
tx_data[1] = ((unsigned int)src_data[0] >> 0) & 0xFF;
tx_data[2] = ((unsigned int)src_data[1] >> 8) & 0xFF;
tx_data[3] = ((unsigned int)src_data[1] >> 0) & 0xFF;
强制转换是为了确保不对签名类型执行右移,这将调用不可移植的实现定义的行为。
与任何其他版本相比,移位的优势在于它们在硬件和字节序之上的抽象级别上工作,让特定的编译器生成底层内存访问的指令。 u16 >> 8
之类的代码始终表示 "give me the least significant byte",无论该字节存储在内存中的何处。
您的安全问题似乎是 htons(x)
function/macro 需要一个无符号整数,但您拥有一个有符号整数。不是问题:
union {
int16_t signed_repr;
uint16_t unsigned_repr;
} data;
data.signed_repr = ...;
u16 unsigned_big_endian_data = htons(data.unsigned_repr);
memcpy(tx_data, &unsigned_big_endian_data,
min(sizeof tx_data, sizeof unsigned_big_endian_data));
PS。通过联合进行类型双关是 perfectly well-defined.
我认为以下是对我的问题的最佳回答之一。我使用了@0andriy 提供的链接到内核源代码中的现有示例。
正在转换带符号的 16 位值以进行传输
s16 src = -5;
u8 dst[2];
__be16 tx_buf;
*(__be16*)dst = cpu_to_be16(src);
转换多个带符号的 16 位值以进行传输
s16 src[2] = {-5,-2};
u8 dst[4];
s16* psrc = src;
u8* pdst = dst;
int len = sizeof(src);
for ( ; len > 1; len -= 2) {
*(__be16 *)pdst = cpu_to_be16p(psrc++);
pdst += 2;
}
快速免责声明,我仍然需要检查此代码是否正确/编译。
总的来说,我对复制和转换多个值的字节序的解决方案有点不满意,因为它容易出现错别字并且可以很容易地实现到宏中。
据我了解,两个16位的字,在转换成bigendian格式后,一个接一个发送。
我觉得下面应该可以吧
s16 src_data[2] = {...}; /* note: this is signed data! */
s16 tx_data[2];
tx_data[0] = cpu_to_be16(src_data_u8[0]);
tx_data[1] = cpu_to_be16(src_data_u8[1]);
我如何改进以下代码,即使用 Linux 内核 API 中的函数和宏,使其在类型安全和字节顺序方面更加健壮?例如,在下面的示例中,src_data
是两个 16 位有符号整数的数组(通常以小端顺序存储),将以大端字节顺序通过 UART 发送出去。
s16 src_data[2] = {...}; /* note: this is signed data! */
u8 tx_data[4];
u8* src_data_u8 = (u8*)src_data;
tx_data[0] = src_data_u8[1];
tx_data[1] = src_data_u8[0];
tx_data[2] = src_data_u8[3];
tx_data[3] = src_data_u8[2];
我认为函数 cpu_to_be16 and cpu_to_be16p 应该在进行这种转换中发挥作用。尽管我不确定如何以安全且稳健的方式使用它们。
如果 Linux 机器永远是小端,协议永远是大端,那么代码工作正常,你不需要改变任何东西。
如果您出于某种原因需要使 Linux 代码与字节序无关,那么您可以使用:
tx_data[0] = ((unsigned int)src_data[0] >> 8) & 0xFF;
tx_data[1] = ((unsigned int)src_data[0] >> 0) & 0xFF;
tx_data[2] = ((unsigned int)src_data[1] >> 8) & 0xFF;
tx_data[3] = ((unsigned int)src_data[1] >> 0) & 0xFF;
强制转换是为了确保不对签名类型执行右移,这将调用不可移植的实现定义的行为。
与任何其他版本相比,移位的优势在于它们在硬件和字节序之上的抽象级别上工作,让特定的编译器生成底层内存访问的指令。 u16 >> 8
之类的代码始终表示 "give me the least significant byte",无论该字节存储在内存中的何处。
您的安全问题似乎是 htons(x)
function/macro 需要一个无符号整数,但您拥有一个有符号整数。不是问题:
union {
int16_t signed_repr;
uint16_t unsigned_repr;
} data;
data.signed_repr = ...;
u16 unsigned_big_endian_data = htons(data.unsigned_repr);
memcpy(tx_data, &unsigned_big_endian_data,
min(sizeof tx_data, sizeof unsigned_big_endian_data));
PS。通过联合进行类型双关是 perfectly well-defined.
我认为以下是对我的问题的最佳回答之一。我使用了@0andriy 提供的链接到内核源代码中的现有示例。
正在转换带符号的 16 位值以进行传输
s16 src = -5;
u8 dst[2];
__be16 tx_buf;
*(__be16*)dst = cpu_to_be16(src);
转换多个带符号的 16 位值以进行传输
s16 src[2] = {-5,-2};
u8 dst[4];
s16* psrc = src;
u8* pdst = dst;
int len = sizeof(src);
for ( ; len > 1; len -= 2) {
*(__be16 *)pdst = cpu_to_be16p(psrc++);
pdst += 2;
}
快速免责声明,我仍然需要检查此代码是否正确/编译。
总的来说,我对复制和转换多个值的字节序的解决方案有点不满意,因为它容易出现错别字并且可以很容易地实现到宏中。
据我了解,两个16位的字,在转换成bigendian格式后,一个接一个发送。 我觉得下面应该可以吧
s16 src_data[2] = {...}; /* note: this is signed data! */
s16 tx_data[2];
tx_data[0] = cpu_to_be16(src_data_u8[0]);
tx_data[1] = cpu_to_be16(src_data_u8[1]);