Documentation/translations/zh_CN/core-api/unaligned-memory-access.rst

Source file repositories/reference/linux-study-clean/Documentation/translations/zh_CN/core-api/unaligned-memory-access.rst

File Facts

System
Linux kernel
Corpus path
Documentation/translations/zh_CN/core-api/unaligned-memory-access.rst
Extension
.rst
Size
9714 bytes
Lines
230
Domain
Support Tooling And Documentation
Bucket
Documentation
Inferred role
Support Tooling And Documentation: documentation
Status
atlas-only

Why This File Exists

Repository support layer: documentation, build tooling, samples, user-space helper tools, generated initramfs support, licenses, and validation utilities.

Dependency Surface

Detected Declarations

Annotated Snippet

struct foo {
		u16 field1;
		u32 field2;
		u8 field3;
	};

让我们假设上述结构体的一个实例驻留在从地址0x10000开始的内存中。根据基本的理解,访问
field2会导致非对齐访问,这并不是不合理的。你会期望field2位于该结构体的2个字节的偏移
量,即地址0x10002,但该地址不能被4平均整除(注意,我们在这里读一个4字节的值)。

幸运的是,编译器理解对齐约束,所以在上述情况下,它会在field1和field2之间插入2个字节
的填充。因此,对于标准的结构体类型,你总是可以依靠编译器来填充结构体,以便对字段的访
问可以适当地对齐(假设你没有将字段定义不同长度的类型)。

同样,你也可以依靠编译器根据变量类型的大小,将变量和函数参数对齐到一个自然对齐的方案。

在这一点上,应该很清楚,访问单个字节(u8或char)永远不会导致无对齐访问,因为所有的内
存地址都可以被1均匀地整除。

在一个相关的话题上,考虑到上述因素,你可以观察到,你可以对结构体中的字段进行重新排序,
以便将字段放在不重排就会插入填充物的地方,从而减少结构体实例的整体常驻内存大小。上述
例子的最佳布局是::

	struct foo {
		u32 field2;
		u16 field1;
		u8 field3;
	};

对于一个自然对齐方案,编译器只需要在结构的末尾添加一个字节的填充。添加这种填充是为了满
足这些结构的数组的对齐约束。

另一点值得一提的是在结构体类型上使用__attribute__((packed))。这个GCC特有的属性告诉编
译器永远不要在结构体中插入任何填充,当你想用C结构体来表示一些“off the wire”的固定排列
的数据时,这个属性很有用。

你可能会倾向于认为,在访问不满足架构对齐要求的字段时,使用这个属性很容易导致不对齐的访
问。然而,编译器也意识到了对齐的限制,并且会产生额外的指令来执行内存访问,以避免造成不
对齐的访问。当然,与non-packed的情况相比,额外的指令显然会造成性能上的损失,所以packed
属性应该只在避免结构填充很重要的时候使用。


导致非对齐访问的代码
====================

考虑到上述情况,让我们来看看一个现实生活中可能导致非对齐内存访问的函数的例子。下面这个
函数取自include/linux/etherdevice.h,是一个优化的例程,用于比较两个以太网MAC地址是否
相等::

  bool ether_addr_equal(const u8 *addr1, const u8 *addr2)
  {
  #ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
	u32 fold = ((*(const u32 *)addr1) ^ (*(const u32 *)addr2)) |
		   ((*(const u16 *)(addr1 + 4)) ^ (*(const u16 *)(addr2 + 4)));

	return fold == 0;
  #else
	const u16 *a = (const u16 *)addr1;
	const u16 *b = (const u16 *)addr2;
	return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) == 0;
  #endif
  }

在上述函数中,当硬件具有高效的非对齐访问能力时,这段代码没有问题。但是当硬件不能在任意
边界上访问内存时,对a[0]的引用导致从地址addr1开始的2个字节(16位)被读取。

想一想,如果addr1是一个奇怪的地址,如0x10003,会发生什么?(提示:这将是一个非对齐访
问。)

尽管上述函数存在潜在的非对齐访问问题,但它还是被包含在内核中,但被理解为只在16位对齐

Annotation

Implementation Notes