通用位字段打包和解包函数 【ChatGPT】

发布时间 2023-12-09 20:34:58作者: 摩斯电码

通用位字段打包和解包函数

问题陈述

在处理硬件时,人们必须在几种接口方式之间进行选择。可以将指针内存映射到硬件设备的内存区域上,并将其字段作为结构体成员(可能声明为位字段)进行访问。但是以这种方式编写代码会使其不太可移植,因为CPU和硬件设备之间可能存在字节序不匹配的问题。此外,在将硬件文档中的寄存器定义转换为结构体的位字段索引时,人们必须特别注意。此外,一些硬件(通常是网络设备)倾向于以违反任何合理字边界的方式对其寄存器字段进行分组(有时甚至是64位的)。这会带来不便,需要在结构体内定义寄存器字段的“高”和“低”部分。相对于结构体字段定义,更健壮的替代方案是通过移位适当数量的位来提取所需的字段。但是,这仍然无法防止字节序不匹配,除非所有内存访问都是逐字节执行的。此外,代码很容易变得混乱,并且高层次的思想可能会在许多所需的位移中丢失。许多驱动程序采用位移方法,然后尝试使用定制的宏来减少混乱,但往往这些宏采用了仍然阻止代码真正可移植的捷径。

解决方案

此API涉及两个基本操作:

  • 将CPU可用的数字打包到内存缓冲区中(具有硬件约束/怪癖)

  • 将内存缓冲区(具有硬件约束/怪癖)解包为CPU可用的数字。

该API提供了对所述硬件约束和怪癖的抽象,对CPU字节序,因此也对两者之间可能的不匹配进行了处理。

这些API函数的基本单位是u64。从CPU的角度来看,第63位始终表示字节7的位偏移7,尽管只是逻辑上如此。问题是:我们在内存中应该将这一位放在哪里?

以下示例涵盖了打包的u64字段的内存布局。打包缓冲区中的字节偏移始终隐含为0、1、... 7。示例显示的是逻辑字节和位的位置。

  1. 通常情况下(没有怪癖),我们会这样做:
63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32
7                       6                       5                        4
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
3                       2                       1                        0

也就是说,CPU可用的u64的最高有效字节(7)位于内存偏移0处,u64的最低有效字节(0)位于内存偏移7处。这对应于大多数人所认为的“大端”,其中第i位对应于数字2^i。代码注释中也称之为“逻辑”表示法。

  1. 如果设置了QUIRK_MSB_ON_THE_RIGHT,则会这样做:
56 57 58 59 60 61 62 63 48 49 50 51 52 53 54 55 40 41 42 43 44 45 46 47 32 33 34 35 36 37 38 39
7                       6                        5                       4
24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23  8  9 10 11 12 13 14 15  0  1  2  3  4  5  6  7
3                       2                        1                       0

也就是说,QUIRK_MSB_ON_THE_RIGHT不影响字节定位,但会倒置字节内部的位偏移。

  1. 如果设置了QUIRK_LITTLE_ENDIAN,则会这样做:
39 38 37 36 35 34 33 32 47 46 45 44 43 42 41 40 55 54 53 52 51 50 49 48 63 62 61 60 59 58 57 56
4                       5                       6                       7
7  6  5  4  3  2  1  0  15 14 13 12 11 10  9  8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24
0                       1                       2                       3

因此,QUIRK_LITTLE_ENDIAN意味着在内存区域内,每个4字节单词的每个字节都放置在与该字的边界相比的镜像位置。

  1. 如果同时设置了QUIRK_MSB_ON_THE_RIGHT和QUIRK_LITTLE_ENDIAN,则会这样做:
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
4                       5                       6                       7
0  1  2  3  4  5  6  7  8   9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0                       1                       2                       3
  1. 如果只设置了QUIRK_LSW32_IS_FIRST,则会这样做:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
3                       2                       1                        0
63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32
7                       6                       5                        4

在这种情况下,8字节的内存区域被解释如下:前4个字节对应于最低有效的4字节单词,接下来的4个字节对应于更高有效的4字节单词。

  1. 如果设置了QUIRK_LSW32_IS_FIRST和QUIRK_MSB_ON_THE_RIGHT,则会这样做:
24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23  8  9 10 11 12 13 14 15  0  1  2  3  4  5  6  7
3                       2                        1                       0
56 57 58 59 60 61 62 63 48 49 50 51 52 53 54 55 40 41 42 43 44 45 46 47 32 33 34 35 36 37 38 39
7                       6                        5                       4
  1. 如果设置了QUIRK_LSW32_IS_FIRST和QUIRK_LITTLE_ENDIAN,则会这样做:
7  6  5  4  3  2  1  0  15 14 13 12 11 10  9  8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24
0                       1                       2                       3
39 38 37 36 35 34 33 32 47 46 45 44 43 42 41 40 55 54 53 52 51 50 49 48 63 62 61 60 59 58 57 56
4                       5                       6                       7
  1. 如果设置了QUIRK_LSW32_IS_FIRST、QUIRK_LITTLE_ENDIAN和QUIRK_MSB_ON_THE_RIGHT,则会这样做:
0  1  2  3  4  5  6  7  8   9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0                       1                       2                       3
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
4                       5                       6                       7

我们始终将偏移量视为没有怪癖,并在访问内存区域之前进行转换。

预期用途

选择使用此API的驱动程序首先需要确定上述3种怪癖组合(共8种)中的哪一种与硬件文档描述相匹配。然后,他们应该包装packing()函数,创建一个新的xxx_packing(),使用适当的QUIRK_*位设置来调用它。

packing()函数返回一个int编码的错误代码,这可以保护程序员免受不正确的API使用。不希望在运行时发生错误,因此xxx_packing()返回void并简单地忽略这些错误是合理的。可选地,它可以转储堆栈或打印错误描述。