ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux 内核非对齐内存访问完全指南:从原理到 get_unaligned 实践

Linux 内核非对齐内存访问完全指南:从原理到 get_unaligned 实践 Linux 内核非对齐内存访问完全指南从原理到 get_unaligned 实践【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux导读Linux 内核运行在行为各异、对内存访问约束不尽相同的众多架构之上非对齐内存访问Unaligned Memory Access是内核开发中极易踩坑、却又难以定位的一类问题。本文以内核官方文档 unaligned-memory-access.rst 为主线结合内核源码系统讲解非对齐访问的定义、不同架构下的危害、编译器对齐机制以及get_unaligned()/put_unaligned()等标准规避手段并延伸探讨网络子系统中的NET_IP_ALIGN与CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS实践。读完本文你将能够识别代码中的非对齐隐患写出跨架构安全且高效的内核代码。什么是非对齐访问非对齐内存访问发生在这样的场景当你试图从某个地址读取 N 字节数据而该地址不能被 N 整除即addr % N ! 0时。例如从地址0x10004读取 4 字节数据没有问题因为0x10004 % 4 0但从地址0x10005读取 4 字节数据就会构成一次非对齐内存访问。这里的访问语境需要放在机器码层面理解某些指令从内存读取或写入若干字节例如 x86 汇编中的movb、movw、movl。在 C 语言层面处理u16、u32、u64等类型的语句通常很容易编译成多字节内存访问指令因此最容易暴露非对齐问题。自然对齐一条普适的规则上述规则形成了所谓的自然对齐Natural Alignment访问 N 字节内存时基地址必须能被 N 整除即addr % N 0。写代码时应当假设目标架构存在自然对齐要求。事实上现实中只有少数架构对所有尺寸的内存访问都强制自然对齐但内核必须考虑所有受支持的架构——编写满足自然对齐要求的代码是达成完全可移植性最简单的方式。为什么非对齐访问是糟糕的非对齐访问在不同架构上的表现差异极大常见场景可归纳为四类部分架构可以透明地完成非对齐访问但通常伴随显著的性能开销部分架构在发生非对齐访问时触发处理器异常异常处理程序虽能纠正该访问但代价高昂部分架构触发处理器异常但异常包含的信息不足以纠正该访问直接导致程序失败部分架构根本不支持非对齐访问却会静默执行一个与请求不同的内存访问产生难以察觉的隐蔽代码缺陷。由此可见如果代码引发了非对齐访问它将在某些平台上无法正确工作并在另一些平台上引发性能问题。不会导致非对齐访问的代码初看这些概念你可能觉得与日常编码实践相距甚远——毕竟你对某些变量的内存地址没有太多控制权。幸运的是在大多数情况下编译器会替你处理好一切。例如下面这个结构体struct foo { u16 field1; u32 field2; u8 field3; };假设该结构体实例从地址0x10000开始存放。直觉上field2位于结构体偏移 2 字节处地址0x10002而0x10002不能被 4 整除似乎访问field2就会产生非对齐访问。但实际上编译器理解对齐约束会在field1与field2之间插入 2 字节填充padding因此对于标准结构体类型你始终可以信赖编译器通过填充保证字段访问的适当对齐前提是你没有把字段强制转换成不同长度的类型。类似地编译器也会根据变量类型的大小将变量和函数参数按自然对齐方案对齐。由此可以得出一个推论访问单字节数据u8或char永远不会造成非对齐访问因为所有内存地址都能被 1 整除。利用填充优化结构体布局基于上述对齐规则你可以重排结构体字段把字段放到本来要插入填充的位置从而减小结构体实例占用的内存。上文示例的最优布局是struct foo { u32 field2; u16 field1; u8 field3; };在自然对齐方案下编译器只需在结构体末尾追加 1 字节填充这是为了满足结构体数组的对齐约束即可完成布局。attribute((packed))压缩布局与隐性成本__attribute__((packed))是 GCC 专有属性它告诉编译器绝不在结构体内部插入任何填充常用于用 C 结构体表示线上固定排布如网络协议报文的数据。你可能担心使用该属性访问不满足架构对齐要求的字段会轻易引发非对齐访问。但同样地编译器感知对齐约束会生成额外指令以不造成非对齐访问的方式完成内存访问。当然这些额外指令显然会带来性能损失因此 packed 属性只应在必须避免结构体填充时使用。内核中的 packed 实现打包结构体宏内核在 include/linux/unaligned/packed_struct.h 中正是利用__packed属性实现了通用的非对齐访问基础层struct __una_u16 { u16 x; } __packed; struct __una_u32 { u32 x; } __packed; struct __una_u64 { u64 x; } __packed; static inline u16 __get_unaligned_cpu16(const void *p) { const struct __una_u16 *ptr (const struct __una_u16 *)p; return ptr-x; }这里通过把任意指针转换为 packed 结构体指针再访问字段编译器会为每次访问自动生成安全的逐字节或架构友好的存取指令这是内核实现get_unaligned()的核心机制之一。会导致非对齐访问的代码示例一ether_addr_equal来看一个真实存在于内核中的函数——取自 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 }当硬件具备高效非对齐访问能力时这段代码没有问题但当硬件无法在任意边界访问内存时#else分支中对a[0]的引用会从addr1起始地址读取 2 字节16 位。试想如果addr1是0x10003这样的奇数地址就会发生非对齐访问。尽管存在潜在的非对齐访问问题该函数仍被保留在内核中但约定它只在 16 位对齐的地址上正常工作由调用者负责保证对齐或干脆不使用该函数。这个对齐不安全的函数依然有用在以太网上下文中几乎总能保证对齐此时它是一个不错的优化。内核还提供了配套的ether_addr_equal_64bits()、ether_addr_equal_unaligned()等变体同文件第 382、406 行附近其中ether_addr_equal_unaligned()专门用于比较未按 u16 对齐的地址便于在无法保证对齐的场合安全使用。示例二直接指针强转再看一个可能导致非对齐访问的典型写法void myfunc(u8 *data, u32 value) { [...] *((u32 *) data) cpu_to_le32(value); [...] }只要data指向的地址不能被 4 整除这段代码每次都会造成非对齐访问。两大高危场景总结综合来看最容易遇到非对齐访问问题的两类场景是把变量强制转换成不同长度的类型如u8 *转u32 *后解引用指针算术之后访问至少 2 字节的数据。避免非对齐访问get_unaligned 与 put_unaligned避免非对齐访问最简单的方式是使用linux/unaligned.h头文件提供的get_unaligned()与put_unaligned()宏。回到前面会引发非对齐访问的示例void myfunc(u8 *data, u32 value) { [...] *((u32 *) data) cpu_to_le32(value); [...] }改写为非对齐安全版本void myfunc(u8 *data, u32 value) { [...] value cpu_to_le32(value); put_unaligned(value, (u32 *) data); [...] }get_unaligned()用法类似假设data是指向内存的指针希望避免非对齐访问u32 value get_unaligned((u32 *) data);这些宏适用于任意长度的内存访问不仅限于示例中的 32 位。需要注意的是与对齐内存上的标准访问相比用这些宏访问非对齐内存会产生可观的性能开销。内核中的完整 API 家族查看 include/linux/unaligned.h 可以看到除了通用宏外内核还提供了一整套针对特定长度和字节序的便捷接口通用宏get_unaligned(ptr)、put_unaligned(val, ptr)通过typeof(*(ptr))自动推导类型小端little-endian接口get_unaligned_le16/le32/le64、put_unaligned_le16/le32/le64大端big-endian接口get_unaligned_be16/be32/be64、put_unaligned_be16/be32/be6424 位接口get_unaligned_be24/le24、put_unaligned_be24/le24常见于 WiFi、以太网速率/长度字段等场景48 位接口get_unaligned_be48、put_unaligned_be48例如以太网 MAC 地址等 6 字节字段。以 24 位为例其实现是纯粹的逐字节移位拼接天然避免非对齐访问static inline u32 __get_unaligned_be24(const u8 *p) { return p[0] 16 | p[1] 8 | p[2]; } static inline void __put_unaligned_le24(const u32 val, u8 *p) { *p val 0xff; *p (val 8) 0xff; *p (val 16) 0xff; }从源码结构看这些接口最终都路由到__get_unaligned_t/__put_unaligned_t底层宏并由架构相关的实现如 packed 结构体方案提供支撑。备选方案memcpy如果使用这些宏不方便另一个选择是memcpy()且源或目标或两者为u8 *或unsigned char *类型。由于该操作本质上是逐字节进行的可以避免非对齐访问。对齐与网络子系统在要求对齐加载的架构上网络子系统要求 IP 头按 4 字节边界对齐以优化 IP 协议栈。对于常规以太网硬件使用常量NET_IP_ALIGN。在大多数架构上该常量取值为 2因为标准以太网头是 14 字节为了获得正确对齐需要 DMA 到可表示为4*n 2的地址。一个显著例外是 powerpc它将NET_IP_ALIGN定义为 0因为向非对齐地址 DMA 可能非常昂贵其开销甚至超过非对齐加载本身。对于某些无法 DMA 到4*n2这类非对齐地址的以太网硬件或非以太网硬件就需要把收到的帧拷贝到对齐缓冲区。由于在支持非对齐访问的架构上这一拷贝毫无必要代码可以依赖CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS来条件化处理#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS skb original skb #else skb copy skb #endif配置项的真实分布CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS是一个由架构通过 Kconfig 的select声明的能力标志。以本仓库为例x86 在 arch/x86/Kconfig 中select HAVE_EFFICIENT_UNALIGNED_ACCESSarm64 在 arch/arm64/Kconfig 中同样select HAVE_EFFICIENT_UNALIGNED_ACCESS。这意味着在这类平台上编译器与硬件能够高效地执行非对齐访问内核可以放心采用ether_addr_equal()的u32/u16快速折叠路径而无需回退到逐u16的保守实现。反之在不具备该能力的架构上#else分支与拷贝 skb路径才会生效。总结与编码建议默认假设目标架构强制自然对齐这是达成全架构可移植性的最稳妥策略结构体访问、变量与参数对齐交给编译器但避免将字段强转为不同长度类型需要紧凑布局时使用__attribute__((packed))并接受随之而来的性能开销在可能非对齐的地址上读写多字节数据优先使用 include/linux/unaligned.h 提供的get_unaligned()/put_unaligned()及其字节序专用变体网络驱动中善用CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS做条件化优化并结合NET_IP_ALIGN设计 DMA 缓冲布局编写可移植代码时可借助内核配置CONFIG_DEBUG_FS之外的对齐检测/调试手段如UBSAN_ALIGNMENT相关配置在开发期提前暴露非对齐访问避免在运行时踩中难以定位的架构差异。理解非对齐访问的成因与规避手段是写出高质量、跨架构 Linux 内核代码的基本功也是排查内核中偶发异常与性能问题的有力工具。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进