0001 .. include:: ../disclaimer-zh_CN.rst
0002
0003 :Original: Documentation/core-api/unaligned-memory-access.rst
0004
0005 :翻译:
0006
0007 司延腾 Yanteng Si <siyanteng@loongson.cn>
0008
0009 :校译:
0010
0011 时奎亮 <alexs@kernel.org>
0012
0013 .. _cn_core-api_unaligned-memory-access:
0014
0015 ==============
0016 非对齐内存访问
0017 ==============
0018
0019 :作者: Daniel Drake <dsd@gentoo.org>,
0020 :作者: Johannes Berg <johannes@sipsolutions.net>
0021
0022 :感谢他们的帮助: Alan Cox, Avuton Olrich, Heikki Orsila, Jan Engelhardt,
0023 Kyle McMartin, Kyle Moffett, Randy Dunlap, Robert Hancock, Uli Kunitz,
0024 Vadim Lobanov
0025
0026
0027 Linux运行在各种各样的架构上,这些架构在内存访问方面有不同的表现。本文介绍了一些
0028 关于不对齐访问的细节,为什么你需要编写不引起不对齐访问的代码,以及如何编写这样的
0029 代码
0030
0031
0032 非对齐访问的定义
0033 ================
0034
0035 当你试图从一个不被N偶数整除的地址(即addr % N != 0)开始读取N字节的数据时,就
0036 会发生无对齐内存访问。例如,从地址0x10004读取4个字节的数据是可以的,但从地址
0037 0x10005读取4个字节的数据将是一个不对齐的内存访问。
0038
0039 上述内容可能看起来有点模糊,因为内存访问可以以不同的方式发生。这里的背景是在机器
0040 码层面上:某些指令在内存中读取或写入一些字节(例如x86汇编中的movb、movw、movl)。
0041 正如将变得清晰的那样,相对容易发现那些将编译为多字节内存访问指令的C语句,即在处理
0042 u16、u32和u64等类型时。
0043
0044
0045 自然对齐
0046 ========
0047
0048 上面提到的规则构成了我们所说的自然对齐。当访问N个字节的内存时,基础内存地址必须被
0049 N平均分割,即addr % N == 0。
0050
0051 在编写代码时,假设目标架构有自然对齐的要求。
0052
0053 在现实中,只有少数架构在所有大小的内存访问上都要求自然对齐。然而,我们必须考虑所
0054 有支持的架构;编写满足自然对齐要求的代码是实现完全可移植性的最简单方法。
0055
0056
0057 为什么非对齐访问时坏事
0058 ======================
0059
0060 执行非对齐内存访问的效果因架构不同而不同。在这里写一整篇关于这些差异的文档是很容
0061 易的;下面是对常见情况的总结:
0062
0063 - 一些架构能够透明地执行非对齐内存访问,但通常会有很大的性能代价。
0064 - 当不对齐的访问发生时,一些架构会引发处理器异常。异常处理程序能够纠正不对齐的
0065 访问,但要付出很大的性能代价。
0066 - 一些架构在发生不对齐访问时,会引发处理器异常,但异常中并没有包含足够的信息来
0067 纠正不对齐访问。
0068 - 有些架构不能进行无对齐内存访问,但会默默地执行与请求不同的内存访问,从而导致
0069 难以发现的微妙的代码错误!
0070
0071 从上文可以看出,如果你的代码导致不对齐的内存访问发生,那么你的代码在某些平台上将无
0072 法正常工作,在其他平台上将导致性能问题。
0073
0074 不会导致非对齐访问的代码
0075 ========================
0076
0077 起初,上面的概念似乎有点难以与实际编码实践联系起来。毕竟,你对某些变量的内存地址没
0078 有很大的控制权,等等。
0079
0080 幸运的是事情并不复杂,因为在大多数情况下,编译器会确保代码工作正常。例如,以下面的
0081 结构体为例::
0082
0083 struct foo {
0084 u16 field1;
0085 u32 field2;
0086 u8 field3;
0087 };
0088
0089 让我们假设上述结构体的一个实例驻留在从地址0x10000开始的内存中。根据基本的理解,访问
0090 field2会导致非对齐访问,这并不是不合理的。你会期望field2位于该结构体的2个字节的偏移
0091 量,即地址0x10002,但该地址不能被4平均整除(注意,我们在这里读一个4字节的值)。
0092
0093 幸运的是,编译器理解对齐约束,所以在上述情况下,它会在field1和field2之间插入2个字节
0094 的填充。因此,对于标准的结构体类型,你总是可以依靠编译器来填充结构体,以便对字段的访
0095 问可以适当地对齐(假设你没有将字段定义不同长度的类型)。
0096
0097 同样,你也可以依靠编译器根据变量类型的大小,将变量和函数参数对齐到一个自然对齐的方案。
0098
0099 在这一点上,应该很清楚,访问单个字节(u8或char)永远不会导致无对齐访问,因为所有的内
0100 存地址都可以被1均匀地整除。
0101
0102 在一个相关的话题上,考虑到上述因素,你可以观察到,你可以对结构体中的字段进行重新排序,
0103 以便将字段放在不重排就会插入填充物的地方,从而减少结构体实例的整体常驻内存大小。上述
0104 例子的最佳布局是::
0105
0106 struct foo {
0107 u32 field2;
0108 u16 field1;
0109 u8 field3;
0110 };
0111
0112 对于一个自然对齐方案,编译器只需要在结构的末尾添加一个字节的填充。添加这种填充是为了满
0113 足这些结构的数组的对齐约束。
0114
0115 另一点值得一提的是在结构体类型上使用__attribute__((packed))。这个GCC特有的属性告诉编
0116 译器永远不要在结构体中插入任何填充,当你想用C结构体来表示一些“off the wire”的固定排列
0117 的数据时,这个属性很有用。
0118
0119 你可能会倾向于认为,在访问不满足架构对齐要求的字段时,使用这个属性很容易导致不对齐的访
0120 问。然而,编译器也意识到了对齐的限制,并且会产生额外的指令来执行内存访问,以避免造成不
0121 对齐的访问。当然,与non-packed的情况相比,额外的指令显然会造成性能上的损失,所以packed
0122 属性应该只在避免结构填充很重要的时候使用。
0123
0124
0125 导致非对齐访问的代码
0126 ====================
0127
0128 考虑到上述情况,让我们来看看一个现实生活中可能导致非对齐内存访问的函数的例子。下面这个
0129 函数取自include/linux/etherdevice.h,是一个优化的例程,用于比较两个以太网MAC地址是否
0130 相等::
0131
0132 bool ether_addr_equal(const u8 *addr1, const u8 *addr2)
0133 {
0134 #ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
0135 u32 fold = ((*(const u32 *)addr1) ^ (*(const u32 *)addr2)) |
0136 ((*(const u16 *)(addr1 + 4)) ^ (*(const u16 *)(addr2 + 4)));
0137
0138 return fold == 0;
0139 #else
0140 const u16 *a = (const u16 *)addr1;
0141 const u16 *b = (const u16 *)addr2;
0142 return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) == 0;
0143 #endif
0144 }
0145
0146 在上述函数中,当硬件具有高效的非对齐访问能力时,这段代码没有问题。但是当硬件不能在任意
0147 边界上访问内存时,对a[0]的引用导致从地址addr1开始的2个字节(16位)被读取。
0148
0149 想一想,如果addr1是一个奇怪的地址,如0x10003,会发生什么?(提示:这将是一个非对齐访
0150 问。)
0151
0152 尽管上述函数存在潜在的非对齐访问问题,但它还是被包含在内核中,但被理解为只在16位对齐
0153 的地址上正常工作。调用者应该确保这种对齐方式或者根本不使用这个函数。这个不对齐的函数
0154 仍然是有用的,因为它是在你能确保对齐的情况下的一个很好的优化,这在以太网网络环境中几
0155 乎是一直如此。
0156
0157
0158 下面是另一个可能导致非对齐访问的代码的例子::
0159
0160 void myfunc(u8 *data, u32 value)
0161 {
0162 [...]
0163 *((u32 *) data) = cpu_to_le32(value);
0164 [...]
0165 }
0166
0167 每当数据参数指向的地址不被4均匀整除时,这段代码就会导致非对齐访问。
0168
0169 综上所述,你可能遇到非对齐访问问题的两种主要情况包括:
0170
0171 1. 将变量定义不同长度的类型
0172 2. 指针运算后访问至少2个字节的数据
0173
0174
0175 避免非对齐访问
0176 ==============
0177
0178 避免非对齐访问的最简单方法是使用<asm/unaligned.h>头文件提供的get_unaligned()和
0179 put_unaligned()宏。
0180
0181 回到前面的一个可能导致非对齐访问的代码例子::
0182
0183 void myfunc(u8 *data, u32 value)
0184 {
0185 [...]
0186 *((u32 *) data) = cpu_to_le32(value);
0187 [...]
0188 }
0189
0190 为了避免非对齐的内存访问,你可以将其改写如下::
0191
0192 void myfunc(u8 *data, u32 value)
0193 {
0194 [...]
0195 value = cpu_to_le32(value);
0196 put_unaligned(value, (u32 *) data);
0197 [...]
0198 }
0199
0200 get_unaligned()宏的工作原理与此类似。假设'data'是一个指向内存的指针,并且你希望避免
0201 非对齐访问,其用法如下::
0202
0203 u32 value = get_unaligned((u32 *) data);
0204
0205 这些宏适用于任何长度的内存访问(不仅仅是上面例子中的32位)。请注意,与标准的对齐内存
0206 访问相比,使用这些宏来访问非对齐内存可能会在性能上付出代价。
0207
0208 如果使用这些宏不方便,另一个选择是使用memcpy(),其中源或目标(或两者)的类型为u8*或
0209 非对齐char*。由于这种操作的字节性质,避免了非对齐访问。
0210
0211
0212 对齐 vs. 网络
0213 =============
0214
0215 在需要对齐负载的架构上,网络要求IP头在四字节边界上对齐,以优化IP栈。对于普通的以太网
0216 硬件,常数NET_IP_ALIGN被使用。在大多数架构上,这个常数的值是2,因为正常的以太网头是
0217 14个字节,所以为了获得适当的对齐,需要DMA到一个可以表示为4*n+2的地址。一个值得注意的
0218 例外是powerpc,它将NET_IP_ALIGN定义为0,因为DMA到未对齐的地址可能非常昂贵,与未对齐
0219 的负载的成本相比相形见绌。
0220
0221 对于一些不能DMA到未对齐地址的以太网硬件,如4*n+2或非以太网硬件,这可能是一个问题,这
0222 时需要将传入的帧复制到一个对齐的缓冲区。因为这在可以进行非对齐访问的架构上是不必要的,
0223 所以可以使代码依赖于CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS,像这样::
0224
0225 #ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
0226 skb = original skb
0227 #else
0228 skb = copy skb
0229 #endif