【问题标题】:Portable conversion of data endianness using the Linux kernel's API使用 Linux 内核 API 进行数据字节序的可移植转换
【发布时间】:2018-05-28 09:25:31
【问题描述】:

如何改进以下代码,即使用 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_be16cpu_to_be16p 应该在这种转换中发挥作用。虽然我不确定如何以一种安全且对字节顺序稳健的方式使用它们。

【问题讨论】:

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

标签: linux-kernel endianness memory-alignment


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    【解决方案3】:

    如果 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 之类的代码始终意味着“给我最低有效字节”,无论该字节存储在内存中的什么位置。

    【讨论】:

    • 这几乎是一个完整的答案。如果您可以添加一个简短的解释,说明为什么这个解决方案是字节序无关的,并确认(或推测为什么)Linux 内核的 API 中还没有一个函数来执行或协助这个转换,我会接受答案.
    • 更具体地说,为什么tx_data[0] 会包含src_data[0] 的MSB?在小端机器上,我会认为 ((unsigned int)src_data[0] >> 8) 会给你 LSB,因为 LSB 位于较低的地址......
    • @allsey87 我添加了一些解释。基本上,C 位移位不知道也不关心字节在内存中的存储位置。可能有一些库函数,但这是基本的东西,我认为没有理由使用函数。
    • 我明白了!所以本质上,C 中的任何数据类型都可以可视化为一系列相邻的字节,MSB 位于左侧,LSB 位于右侧,而与字节顺序无关。
    • @allsey87 仅限整数数据类型,因为我们只能转换这些数据类型。
    【解决方案4】:

    我相信以下是我的问题的最佳答案之一。我使用了@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;
    }
    

    一个快速的免责声明,我仍然需要检查这段代码是否正确/编译。

    总的来说,我对复制和转换多个值的字节顺序的解决方案有点不满意,因为它容易出现拼写错误并且很容易实现到宏中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多