如何使用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和cpu_to_be16p应该在执行此转换时发挥作用。虽然我不确定如何以对字节序安全且稳健的方式使用它们。
答案 0 :(得分:0)
据我了解,在将每个16位字转换成Bigendian格式后,将一个接一个地发送两个字。 我认为以下做法应该可以。
s16 src_data[2] = {...}; /* note: this is signed data! */
s16 tx_data[2];
tx_data[0] = cpu_tp_be16(src_data_u8[0]);
tx_data[1] = cpu_to_be16(src_data_u8[1]);
s16 src_data[2] = {...}; /* note: this is signed data! */
s16 tx_data[2];
tx_data[0] = cpu_tp_be16(src_data_u8[0]);
tx_data[1] = cpu_to_be16(src_data_u8[1]);
答案 1 :(得分:-1)
您的安全问题似乎是htons(x)
函数/宏需要无符号整数,但您拥有一个已签名的整数。不是问题:
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。
答案 2 :(得分:-1)
我相信以下是我的问题的最佳答案之一。我已经将@ 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;
}
快速免责声明,我仍然需要检查此代码是否正确/编译。
总的来说,我对复制和转换多个值的字节顺序的解决方案有点不满意,因为它很容易出现拼写错误并且很容易被实现到宏中。
答案 3 :(得分:-2)
如果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
之类的代码总是意味着"给我最不重要的字节"无论该字节存储在内存中的哪个位置。