☰
round_up/round_down
2026/9/26 6:43:23 网站建设 项目流程

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实现位运算对齐的核心。

  1. 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的倍数,从而实现向上取整。

  2. 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的位运算实现能提供极佳的性能。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询