round_up/round_down

发布时间:2026/8/27 20:43:31
round_up/round_down round_up是 Linux 内核中一个非常常用的宏它的功能是将一个给定的数值x向上取整到y的下一个整数倍。举个例子round_up(10, 8)会返回16因为 16 是 8 的最小倍数且大于等于 10。实现细节两种不同的round_up在内核中round_up这个词其实指代了两种不同的实现它们的区别非常关键这和内存对齐场景直接相关round_up要求y必须是 2 的幂次方。它的实现利用了位运算按位或和加法因此速度更快。在init_mem_mapping()这类启动流程中对地址进行对齐比如对齐到 2MB、1GB 这样的内存页大小时使用的就是这个版本。// include/linux/kernel.h #define round_up(x, y) ((((x)-1) | __round_mask(x, y)) 1)这里的__round_mask(x, y)会生成一个(y-1)的掩码通过位运算高效地完成向上取整。roundup支持任意的y值。它的实现基于乘除法因此更通用但速度较慢。// 源码来自 include/linux/math.h #define roundup(x, y) ((((x) (y) - 1) / (y)) * (y))这个宏的数学原理非常直观先将x加上(y - 1)再除以y并向下取整最后乘以y。加(y - 1)这一步是关键它相当于给x补上一个“余数补偿”确保只要x不是y的整数倍除法结果就会进位到下一个整数从而在乘法后得到大于等于x的最小y的倍数。举个例子roundup(10, 8)的计算过程是((10 7) / 8) * 8 (17 / 8) * 8 2 * 8 16结果与round_up(10, 8)一致。但它的优势在于y可以是任意正整数比如roundup(10, 3)会返回12而位运算版本的round_up在这种情况下就无法正确工作。由于涉及整数除法和乘法roundup的执行开销比位运算版本高。因此在内核中它通常用于那些对齐单位不是 2 的幂次方的场景例如按结构体大小、缓存行大小或某些硬件寄存器要求的非 2 的幂次方边界进行对齐时。为什么区分两种实现内核开发者区分这两种宏主要是为了性能。在split_mem_range这类对内存地址进行划分的底层函数中y通常就是PMD_SIZE(2MB) 或PUD_SIZE(1GB) 这样的常量它们正好都是 2 的幂次方。这时使用round_up能获得最高的执行效率。round_up在地址对齐中的应用回忆一下我们在分析split_mem_range()实现原理时提到的“分段计算”过程。为了确定一个区间能否使用 2MB 大页函数需要检查地址是否与PMD_SIZE对齐。例如计算一个start地址之后第一个PMD_SIZE对齐的地址其本质就是进行round_up(start, PMD_SIZE)运算。round_up宏正是实现这种高效计算的基础工具。round_down是 Linux 内核中一个与round_up相对应的宏它的功能是将给定的数值x向下取整到y的整数倍。举个例子round_down(10, 8)会返回8因为 8 是小于等于 10 的最大的 8 的倍数。与round_up类似内核中同样存在两种实现区分标准也完全相同一种是快速位运算版本要求y是 2 的幂次方另一种是通用版本支持任意的y值。round_down的实现位运算版本 (round_down)要求y必须为 2 的幂次方。它通过对x进行按位与操作清除其低位的比特从而将数值向下对齐到y的边界。// 定义在 include/linux/kernel.h #define round_down(x, y) ((x) ~__round_mask(x, y))这里的__round_mask(x, y)会生成一个(y-1)的掩码~取反后与x进行按位与操作实现向下取整。通用版本 (rounddown)支持任意的y值。它通过除法 (/)、取整和乘法 (*) 来实现逻辑简单直接但效率相对较低。// 定义在 include/linux/math.h #define rounddown(x, y) ( ( (x) / (y) ) * (y) )如何区分和使用内核中这四个宏的命名遵循着一套清晰的规则round_up/round_down使用位运算速度快但要求y必须是 2 的幂次方。在split_mem_range()这类对性能敏感的启动代码中y通常是PMD_SIZE或PUD_SIZE这类常量正好满足此要求因此优先使用它们。roundup/rounddown使用乘除法速度较慢但支持任意的y值。当对齐单位不是 2 的幂次方时例如通用的结构体对齐时才会使用它们。round_down在地址对齐中的应用回到split_mem_range()的分段计算过程中round_down与round_up共同配合确定一个区间的起始和结束边界。例如为了计算一个内存区间中最大的一块能够被PMD_SIZE对齐的起始地址其本质就是在进行round_down(end, PMD_SIZE)运算。这些宏提供了最基础的数学工具使split_mem_range()能够精确地完成区间的划分。__round_mask是 Linux 内核include/linux/math.h文件中定义的一个辅助宏它本身不独立使用而是作为底层工具为round_up()和round_down()这两个常用的对齐宏提供高效实现。核心定义__round_mask的定义非常简洁#define __round_mask(x, y) ((__typeof__(x))((y)-1))函数作用详解它的核心作用就像一个“模板”生成一个用于位运算的特定掩码。生成掩码对于给定的参数x和y它返回y-1这个值。这里的y必须是 2 的幂次方。例如如果y是 8二进制1000则__round_mask返回 7二进制0111。这个返回值(y-1)的二进制形式就是一个“低位全为1”的掩码。确保类型安全代码中的__typeof__(x)是一个关键设计。它保证生成的掩码的数据类型与x完全一致。这主要是为了解决在不同整数类型如int和long之间进行位运算时可能导致的符号扩展或高位信息丢失等问题。在round_up/round_down中的工作原理__round_mask是round_up和round_down实现位运算对齐的核心。round_up(x, y)向上取整#define round_up(x, y) ((((x)-1) | __round_mask(x, y)) 1)它的逻辑是先将x减1然后与掩码(y-1)进行按位或|操作最后加1。这会将x的“低位”部分强制置为1加1后“进位”到下一个y的倍数从而实现向上取整。round_down(x, y)向下取整#define round_down(x, y) ((x) ~__round_mask(x, y))它的逻辑是将x与掩码(y-1)的按位取反~进行按位与操作。这会直接将x的低位清零从而得到小于等于x的最大y的倍数实现向下取整。在split_mem_range()这类需要高效计算地址对齐的场景中y通常是PMD_SIZE这类常量正好满足 2 的幂次方这一要求因此round_up/round_down的位运算实现能提供极佳的性能。