Linux kernel 使用Linux内核';s API

Linux kernel 使用Linux内核';s API,linux-kernel,endianness,memory-alignment,Linux Kernel,Endianness,Memory Alignment,如何改进以下代码,即使用Linux内核API中的函数和宏使其在类型安全性和endianness方面更加健壮?例如,在下面的示例中,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

如何改进以下代码,即使用Linux内核API中的函数和宏使其在类型安全性和endianness方面更加健壮?例如,在下面的示例中,
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];

我认为这些功能应该在这一转换中发挥作用。尽管我不确定如何以一种安全和健壮的方式使用它们。

如果Linux机器始终是little-endian,协议始终是big-endian,那么代码工作正常,您无需更改任何内容

如果出于某种原因需要使Linux代码endian独立,则应使用:

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;
其中,强制转换用于确保不对有符号类型执行正确的移位,这将调用非可移植实现定义的行为


与任何其他版本相比,位移位的优势在于它们在硬件和endianess之上的抽象级别上工作,让特定的编译器生成底层内存访问的指令。诸如u16>>8之类的代码始终意味着“给我最低有效字节”,而不管该字节存储在内存中的什么位置。

如果Linux机器将始终是little-endian,协议将始终是big-endian,那么代码工作正常,您无需更改任何内容

如果出于某种原因需要使Linux代码endian独立,则应使用:

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;
其中,强制转换用于确保不对有符号类型执行正确的移位,这将调用非可移植实现定义的行为


与任何其他版本相比,位移位的优势在于它们在硬件和endianess之上的抽象级别上工作,让特定的编译器生成底层内存访问的指令。像u16>>8这样的代码总是意味着“给我最低有效字节”,不管该字节存储在内存中的什么位置。

您的安全问题似乎是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.通过联合的类型双关是。

您的安全问题似乎是
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));

注:通过工会的双关语是。

我相信以下是对我问题的最好答案之一。我使用了@0andriy提供的内核源代码中现有示例的链接

转换有符号16位值以进行传输

s16 src = -5;
u8 dst[2];
__be16 tx_buf;
*(__be16*)dst = cpu_to_be16(src);
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位值以进行传输

s16 src = -5;
u8 dst[2];
__be16 tx_buf;
*(__be16*)dst = cpu_to_be16(src);
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;
}
快速免责声明,我仍然需要检查此代码是否正确/编译


总的来说,我对复制和转换多个值的尾数的解决方案有点不满意,因为它容易出现拼写错误,并且可以很容易地实现为宏。

我相信以下是对我的问题的最好答案之一。我使用了@0andriy提供的内核源代码中现有示例的链接

转换有符号16位值以进行传输

s16 src = -5;
u8 dst[2];
__be16 tx_buf;
*(__be16*)dst = cpu_to_be16(src);
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位值以进行传输

s16 src = -5;
u8 dst[2];
__be16 tx_buf;
*(__be16*)dst = cpu_to_be16(src);
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;
}
快速免责声明,我仍然需要检查此代码是否正确/编译


总的来说,我对复制和转换多个值的endianness的解决方案有点不满意,因为它容易出错,并且可以很容易地实现为一个宏。

据我所知,在将每个值转换为bigendian格式后,将一个接一个地发送两个16位字。 我认为下面应该可以

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]);

据我所知,两个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]);

您想要解决的问题到底是什么,而您链接的头文件中的注释对您没有帮助?让你的问题更具体。如果你写这样的代码,要小心对传入的协议进行反序列化。通过使用指针转换,您不能从
u8 rx_data[4]
转换到
s16[2]
——这将是一种严格的别名冲突。您可以在这里使用它,因为您可以从“任何类型”转换为字符类型,这是一种特殊的允许情况。@MichaelFoukarakis头文件中的函数/宏似乎不安全,因为它们只接受未签名的参数。。。此外,在将转换后的数据放入我的
u8 tx_data
数组之前,我不清楚如何安全地存储中间结果……感谢@0andriy的输入,协议固定为big-endian,目标机器(AVR)为little-endian。据我所知,在实践中,几乎所有基于Linux的机器都是little endian,但尽管如此,我还是想知道如何以干净、独立于平台的方式实现这种转换。@0andriy很明显,您一生中从未编写过一行与硬件相关的编程。对不起,TCP/IP套接字不是硬件,它是第3层和第4层左右。我每天都在为嵌入式系统编写驱动程序,在过去的15年中我一直在这样做,但感谢您分享您对从未使用过的东西的智慧。您想要解决的问题到底是什么,您链接的头文件中的注释对您没有帮助?让你的问题更具体。如果你写这样的代码,要小心对传入的协议进行反序列化。通过使用指针转换,您不能从
u8 rx_data[4]
转换到
s16[2]
——这将是一种严格的别名冲突。在这里你可以不受惩罚,因为你可以从“任何类型”改为字符类型,这是一种特殊的允许情况。@MichaelFoukarakis在标题中显示函数/宏