Linux中页面写回初始化page_writeback_init函数实现
页面写回初始化函数page_writeback_init
void __init page_writeback_init(void)
{long buffer_pages = nr_free_buffer_pages();long correction;total_pages = nr_free_pagecache_pages();correction = (100 * 4 * buffer_pages) / total_pages;if (correction < 100) {dirty_background_ratio *= correction;dirty_background_ratio /= 100;vm_dirty_ratio *= correction;vm_dirty_ratio /= 100;if (dirty_background_ratio <= 0)dirty_background_ratio = 1;if (vm_dirty_ratio <= 0)vm_dirty_ratio = 1;}mod_timer(&wb_timer, jiffies + (dirty_writeback_centisecs * HZ) / 100);set_ratelimit();register_cpu_notifier(&ratelimit_nb);
}
函数整体功能
这个函数用于初始化Linux内核的页面写回子系统,根据系统内存情况调整脏页写回参数,并启动定时写回机制
代码分段详解
第一段:函数定义
void __init page_writeback_init(void)
void
: 无返回值__init
: 初始化函数宏,表示这个函数在内核初始化后会被释放page_writeback_init
: 页面写回初始化函数
第二段:变量声明和初始页面计数
long buffer_pages = nr_free_buffer_pages();long correction;total_pages = nr_free_pagecache_pages();
获取缓冲区页面数量:
long buffer_pages = nr_free_buffer_pages();
nr_free_buffer_pages()
: 内核函数,返回可用的缓冲区页面数量- 缓冲区页面用于文件系统缓存等操作
声明校正因子变量:
long correction;
- 用于存储内存调整的校正因子
获取总页面缓存数量:
total_pages = nr_free_pagecache_pages();
nr_free_pagecache_pages()
: 返回页面缓存中的总空闲页面数total_pages
: 全局变量,存储系统总页面数
第三段:计算校正因子
correction = (100 * 4 * buffer_pages) / total_pages;
correction = (100 * 4 * buffer_pages) / total_pages
: 计算内存校正因子- 公式含义:
校正因子 = (100 × 4 × 缓冲区页面) / 总页面
- 为什么要×4: 期望比例是1/4
- 为什么要×100: 为了使用整数运算避免浮点数,保持精度
- 作用: 根据缓冲区内存占总内存的比例来调整写回参数
第四段:应用校正因子
if (correction < 100) {dirty_background_ratio *= correction;dirty_background_ratio /= 100;vm_dirty_ratio *= correction;vm_dirty_ratio /= 100;
条件检查:
if (correction < 100)
- 只在校正因子小于100时进行调整
- 如果校正因子≥100,说明缓冲区内存足够,使用默认参数
调整后台脏页比率:
dirty_background_ratio *= correction;
dirty_background_ratio /= 100;
dirty_background_ratio
: 全局变量,后台脏页比率阈值- 当脏页比例超过此值时,内核开始在后台写回脏页
- 调整公式:
新比率 = 原比率 × 校正因子 / 100
调整虚拟内存脏页比率:
vm_dirty_ratio *= correction;
vm_dirty_ratio /= 100;
vm_dirty_ratio
: 全局变量,系统脏页比率阈值- 当脏页比例超过此值时,应用程序可能会被阻塞以等待写回完成
- 调整公式同上
第五段:边界检查
if (dirty_background_ratio <= 0)dirty_background_ratio = 1;if (vm_dirty_ratio <= 0)vm_dirty_ratio = 1;}
检查后台脏页比率:
if (dirty_background_ratio <= 0)dirty_background_ratio = 1;
- 如果调整后的比率小于等于0,设置为最小值1
- 防止除零错误和无效参数
检查系统脏页比率:
if (vm_dirty_ratio <= 0)vm_dirty_ratio = 1;
- 同样的边界检查,确保比率至少为1%
第六段:启动写回定时器
mod_timer(&wb_timer, jiffies + (dirty_writeback_centisecs * HZ) / 100);
mod_timer(&wb_timer, ...)
: 修改定时器的到期时间&wb_timer
: 写回定时器指针jiffies
: 内核时间单位,系统启动后的时钟滴答数dirty_writeback_centisecs
: 全局变量,写回间隔(百分之一秒)HZ
: 系统时钟频率(每秒滴答数)- 计算公式:
到期时间 = 当前时间 + (写回间隔 × HZ) / 100
- 作用: 启动周期性脏页写回定时器
第七段:设置速率限制
set_ratelimit();
set_ratelimit()
: 设置写回速率限制函数- 初始化写回操作的速率控制参数
- 防止写回操作消耗过多系统资源
第八段:注册CPU通知器
register_cpu_notifier(&ratelimit_nb);
}
register_cpu_notifier(&ratelimit_nb)
: 注册CPU热插拔通知器&ratelimit_nb
: 速率限制通知块指针- 作用: 当CPU被热添加或移除时,调整写回速率限制参数
内存调整策略可视化
校正因子公式分解
correction = (100 * 4 * buffer_pages) / total_pages;
1. 基本比例计算
(buffer_pages) / total_pages
含义: 缓冲区页面占总页面缓存的比例
2. 乘以4的原因
4 * buffer_pages
为什么要乘以4?
在Linux内核的早期版本中,这个设计基于以下观察:
内存使用模式:
总内存 ≈ 缓冲区内存 + 文件缓存 + 应用程序内存
当缓冲区内存占总内存的 25% (1/4) 时,系统能达到较好的平衡。所以:
- 基准线: 25% 的缓冲区比例被认为是"理想"状态
- 校正逻辑: 如果实际比例小于25%,就需要调整脏页阈值
数学推导:
期望比例 = 25% = 1/4
校正因子 = (实际比例) / (期望比例) = (buffer_pages/total_pages) / (1/4)= 4 * (buffer_pages/total_pages)
3. 乘以100避免浮点数
100 * 4 * buffer_pages
先放大100倍,然后进行除法运算,提高精度的同时不会出现浮点运算
校正因子语义解释
校正因子的取值范围
具体计算示例:
缓冲区比例 | 计算过程 | 校正因子 |
---|---|---|
25% | (100×4×25)/100 = 100 | 100 |
12.5% | (100×4×12.5)/100 = 50 | 50 |
6.25% | (100×4×6.25)/100 = 25 | 25 |
50% | (100×4×50)/100 = 200 | 200 |
条件判断的逻辑
if (correction < 100)
为什么只在校正因子<100时调整?
设计哲学:
- 校正因子 < 100: 缓冲区内存不足,需要降低脏页阈值
- 校正因子 ≥ 100: 缓冲区内存充足,使用默认参数
向pdflush
线程池提交一个写回操作任务pdflush_operation
int pdflush_operation(void (*fn)(unsigned long), unsigned long arg0)
{unsigned long flags;int ret = 0;if (fn == NULL)BUG(); /* Hard to diagnose if it's deferred */spin_lock_irqsave(&pdflush_lock, flags);if (list_empty(&pdflush_list)) {spin_unlock_irqrestore(&pdflush_lock, flags);ret = -1;} else {struct pdflush_work *pdf;pdf = list_entry(pdflush_list.next, struct pdflush_work, list);list_del_init(&pdf->list);if (list_empty(&pdflush_list))last_empty_jifs = jiffies;pdf->fn = fn;pdf->arg0 = arg0;wake_up_process(pdf->who);spin_unlock_irqrestore(&pdflush_lock, flags);}return ret;
}
函数整体功能
这个函数用于向pdflush
(页面脏数据刷新)线程池提交一个写回操作任务,是Linux内核旧版本中页面写回机制的核心调度函数
代码分段详解
第一段:函数定义和变量声明
int pdflush_operation(void (*fn)(unsigned long), unsigned long arg0)
{unsigned long flags;int ret = 0;
int
: 返回整型状态码,0成功,-1失败void (*fn)(unsigned long)
: 函数指针参数,指向要执行的回调函数unsigned long arg0
: 回调函数的参数flags
: 用于保存中断状态的变量ret = 0
: 返回值,初始化为0(成功)
第二段:函数指针有效性检查
if (fn == NULL)BUG(); /* Hard to diagnose if it's deferred */
if (fn == NULL)
: 检查传入的函数指针是否为空BUG()
: 如果为空,触发内核致命错误- 作用: 确保传入有效的回调函数,避免后续执行空指针
第三段:获取锁并保存中断状态
spin_lock_irqsave(&pdflush_lock, flags);
spin_lock_irqsave(&pdflush_lock, flags)
:- 获取
pdflush
锁,保护共享数据结构 - 同时保存当前中断状态到flags,并禁用中断
- 防止并发访问
pdflush_list
和中断处理程序的竞争条件
- 获取
第四段:检查pdflush
线程池是否为空
if (list_empty(&pdflush_list)) {spin_unlock_irqrestore(&pdflush_lock, flags);ret = -1;}
检查空列表:
if (list_empty(&pdflush_list))
- 检查
pdflush
线程列表是否为空 pdflush_list
: 全局变量,存储所有可用的pdflush
线程
释放锁并返回错误:
spin_unlock_irqrestore(&pdflush_lock, flags);
ret = -1;
- 恢复中断状态并释放锁
- 设置返回值为-1(失败)
- 场景: 没有可用的
pdflush
线程来处理任务
第五段:有可用线程的情况
else {struct pdflush_work *pdf;
else
:pdflush
线程池非空,可以处理任务struct pdflush_work *pdf
: 声明pdflush
工作结构指针
第六段:获取第一个可用线程
pdf = list_entry(pdflush_list.next, struct pdflush_work, list);
list_entry(pdflush_list.next, struct pdflush_work, list)
:pdflush_list.next
: 获取链表第一个节点的指针struct pdflush_work
: 目标结构体类型list
: 在结构体中的链表成员名称- 作用: 通过链表节点指针获取包含它的完整结构体指针
第七段:从链表中移除线程
list_del_init(&pdf->list);
list_del_init(&pdf->list)
:- 将线程从可用列表中移除
- 并重新初始化链表节点(设置为空链表状态)
- 作用: 标记这个线程为"工作中"状态
第八段:更新最后空列表时间
if (list_empty(&pdflush_list))last_empty_jifs = jiffies;
if (list_empty(&pdflush_list))
: 检查移除后列表是否为空last_empty_jifs = jiffies
: 如果为空,记录当前时间戳jiffies
: 内核时间单位,系统启动后的时钟滴答数- 作用: 记录线程池耗尽的时间,用于监控和统计
第九段:设置任务参数
pdf->fn = fn;pdf->arg0 = arg0;
pdf->fn = fn
: 将回调函数指针保存到工作结构pdf->arg0 = arg0
: 将参数保存到工作结构- 作用: 准备任务执行所需的所有信息
第十段:唤醒pdflush
线程
wake_up_process(pdf->who);
wake_up_process(pdf->who)
:pdf->who
: 指向pdflush
线程的task_struct指针- 唤醒对应的内核线程,让它开始执行任务
- 作用: 触发实际的写回操作执行
第十一段:释放锁并返回
spin_unlock_irqrestore(&pdflush_lock, flags);}return ret;
}
spin_unlock_irqrestore(&pdflush_lock, flags)
: 恢复中断状态并释放锁return ret
: 返回操作结果(0成功,-1失败)
数据结构关系图
定义写回定时器wb_timer
static struct timer_list wb_timer =TIMER_INITIALIZER(wb_timer_fn, 0, 0);
static void wb_timer_fn(unsigned long unused)
{if (pdflush_operation(wb_kupdate, 0) < 0)mod_timer(&wb_timer, jiffies + HZ); /* delay 1 second */
}
代码整体功能
这个代码定义了一个写回定时器,用于定期触发脏页写回操作,并在操作失败时实现退避重试机制
代码分段详解
第一段:定时器静态初始化
static struct timer_list wb_timer =TIMER_INITIALIZER(wb_timer_fn, 0, 0);
变量声明:
static struct timer_list wb_timer
static
: 静态变量,限制作用域在当前文件struct timer_list
: 内核定时器结构体类型wb_timer
: 写回定时器变量名
定时器初始化宏:
TIMER_INITIALIZER(wb_timer_fn, 0, 0)
TIMER_INITIALIZER
: 内核定时器静态初始化宏wb_timer_fn
: 定时器到期时调用的回调函数- 第一个
0
: 定时器到期时间,0表示未激活 - 第二个
0
: 传递给回调函数的参数,这里为0
第二段:定时器回调函数定义
static void wb_timer_fn(unsigned long unused)
{
static void
: 静态函数,无返回值wb_timer_fn
: 函数名,写回定时器回调函数unsigned long unused
: 参数,名为unused表示未使用- 虽然传递了参数,但函数体内没有使用它
- 保持函数签名与定时器回调要求一致
第三段:尝试执行写回操作
if (pdflush_operation(wb_kupdate, 0) < 0)
函数调用:
pdflush_operation(wb_kupdate, 0)
pdflush_operation
: 前面分析的pdflush
任务提交函数wb_kupdate
: 实际的写回操作函数指针0
: 传递给wb_kupdate
函数的参数
返回值检查:
if (... < 0)
- 检查
pdflush_operation
的返回值 < 0
表示操作失败(通常是没有可用的pdflush
线程)
wb_kupdate
函数的作用:
- 这是实际的脏页写回函数
- 遍历内存中的脏页并写回到存储设备
- 是页面写回系统的核心逻辑
第四段:失败时的退避重试
mod_timer(&wb_timer, jiffies + HZ); /* delay 1 second */
mod_timer函数:
mod_timer(&wb_timer, jiffies + HZ)
mod_timer
: 内核函数,修改定时器的到期时间&wb_timer
: 要修改的定时器指针jiffies + HZ
: 新的到期时间
时间计算:
jiffies
: 系统当前的时钟滴答数HZ
: 系统时钟频率,表示每秒的时钟滴答数jiffies + HZ
: 当前时间 + 1秒
作用: 如果写回操作失败,在1秒后重试
实际执行场景
场景1:正常执行
时间点T: 定时器到期
→ 调用wb_timer_fn
→ pdflush_operation成功提交任务
→ 函数返回,定时器停止
→ 需要其他代码在适当时候重新启动定时器
场景2:资源紧张
时间点T: 定时器到期
→ 调用wb_timer_fn
→ pdflush_operation失败(无可用线程)
→ 设置1秒后重试
→ 时间点T+1秒: 定时器再次到期
→ 重试写回操作...
与其他组件的交互
与pdflush_operation
的关系
// 这是一个生产者-消费者模式
wb_timer_fn (生产者) → pdflush_operation → pdflush线程 (消费者)
与系统初始化的关系
在 page_writeback_init
中:
mod_timer(&wb_timer, jiffies + (dirty_writeback_centisecs * HZ) / 100);
这里设置了定时器的第一次触发时间
定期将过期的脏页写回到存储设备wb_kupdate
static void wb_kupdate(unsigned long arg)
{unsigned long oldest_jif;unsigned long start_jif;unsigned long next_jif;long nr_to_write;struct writeback_state wbs;struct writeback_control wbc = {.bdi = NULL,.sync_mode = WB_SYNC_NONE,.older_than_this = &oldest_jif,.nr_to_write = 0,.nonblocking = 1,.for_kupdate = 1,};sync_supers();get_writeback_state(&wbs);oldest_jif = jiffies - (dirty_expire_centisecs * HZ) / 100;start_jif = jiffies;next_jif = start_jif + (dirty_writeback_centisecs * HZ) / 100;nr_to_write = wbs.nr_dirty + wbs.nr_unstable +(inodes_stat.nr_inodes - inodes_stat.nr_unused);while (nr_to_write > 0) {wbc.encountered_congestion = 0;wbc.nr_to_write = MAX_WRITEBACK_PAGES;writeback_inodes(&wbc);if (wbc.nr_to_write > 0) {if (wbc.encountered_congestion)blk_congestion_wait(WRITE, HZ/10);elsebreak; /* All the old data is written */}nr_to_write -= MAX_WRITEBACK_PAGES - wbc.nr_to_write;}if (time_before(next_jif, jiffies + HZ))next_jif = jiffies + HZ;if (dirty_writeback_centisecs)mod_timer(&wb_timer, next_jif);
}
函数整体功能
这个函数是Linux内核脏页写回的核心实现,负责定期将过期的脏页写回到存储设备,维护系统内存的清洁度
代码分段详解
第一段:函数定义和变量声明
static void wb_kupdate(unsigned long arg)
{unsigned long oldest_jif;unsigned long start_jif;unsigned long next_jif;long nr_to_write;struct writeback_state wbs;
static void
: 静态函数,无返回值wb_kupdate
: 函数名,写回更新操作unsigned long arg
: 参数(未使用)oldest_jif
: 最老脏页的时间戳阈值start_jif
: 本次写回开始时间next_jif
: 下一次写回计划时间nr_to_write
: 需要写回的页面数量估计wbs
: 写回状态结构,用于获取系统脏页统计
第二段:写回控制结构初始化
struct writeback_control wbc = {.bdi = NULL,.sync_mode = WB_SYNC_NONE,.older_than_this = &oldest_jif,.nr_to_write = 0,.nonblocking = 1,.for_kupdate = 1,};
写回控制参数:
.bdi = NULL
: 后备设备信息,NULL表示所有设备.sync_mode = WB_SYNC_NONE
: 异步模式,不等待写回完成.older_than_this = &oldest_jif
: 指向时间阈值,只写回比这个时间老的脏页.nr_to_write = 0
: 初始写回页面数为0,后面会设置.nonblocking = 1
: 非阻塞模式.for_kupdate = 1
: 标记为定期更新操作
第三段:同步超级块
sync_supers();
sync_supers()
: 内核函数,将文件系统超级块同步到磁盘- 超级块包含文件系统元数据,需要定期持久化
- 确保文件系统结构的一致性
第四段:获取系统脏页状态
get_writeback_state(&wbs);
get_writeback_state(&wbs)
: 获取当前系统的写回状态- 填充
wbs
结构,包含:nr_dirty
: 脏页数量nr_unstable
: 不稳定页数量- 其他脏页统计信息
第五段:计算时间阈值
oldest_jif = jiffies - (dirty_expire_centisecs * HZ) / 100;
jiffies
: 当前系统时间戳dirty_expire_centisecs
: 全局变量,脏页过期时间(百分之一秒)HZ
: 系统时钟频率- 计算公式:
最老时间 = 当前时间 - 过期时间
- 作用: 只写回存在时间超过
dirty_expire_centisecs
的脏页
第六段:记录开始时间和计算下次执行时间
start_jif = jiffies;next_jif = start_jif + (dirty_writeback_centisecs * HZ) / 100;
start_jif = jiffies
: 记录本次写回开始时间next_jif = ...
: 计算下一次写回执行时间dirty_writeback_centisecs
: 全局变量,写回间隔时间- 作用: 建立定期写回的时间计划
第七段:估算需要写回的页面数量
nr_to_write = wbs.nr_dirty + wbs.nr_unstable +(inodes_stat.nr_inodes - inodes_stat.nr_unused);
wbs.nr_dirty
: 当前脏页数量wbs.nr_unstable
: 不稳定页数量(正在写回中的页)inodes_stat.nr_inodes - inodes_stat.nr_unused
: 活跃inode
数量估计- 作用: 粗略估算可能需要写回的最大页面数量
- 这是一个保守估计,确保不会遗漏脏页
第八段:写回循环
while (nr_to_write > 0) {
while (nr_to_write > 0)
: 循环直到所有需要写回的页面都处理完- 或者遇到无法继续写回的情况
第九段:设置本次写回参数
wbc.encountered_congestion = 0;wbc.nr_to_write = MAX_WRITEBACK_PAGES;
wbc.encountered_congestion = 0
: 重置拥塞标志wbc.nr_to_write = MAX_WRITEBACK_PAGES
: 设置本次最大写回页面数MAX_WRITEBACK_PAGES
: 常量,通常为1024,表示每次最多写回1024页
第十段:执行实际写回操作
writeback_inodes(&wbc);
writeback_inodes(&wbc)
: 核心写回函数- 遍历所有
inode
,将其中的脏页写回到存储设备 - 根据
wbc
参数控制写回行为
第十一段:检查写回完成情况
if (wbc.nr_to_write > 0) {
if (wbc.nr_to_write > 0)
: 检查是否还有剩余的写回额度- 如果
nr_to_write > 0
,说明没有用完本次配额,可能因为:- 所有旧数据都已写回
- 遇到IO拥塞无法继续
第十二段:处理IO拥塞
if (wbc.encountered_congestion)blk_congestion_wait(WRITE, HZ/10);elsebreak; /* All the old data is written */}
拥塞情况:
if (wbc.encountered_congestion)blk_congestion_wait(WRITE, HZ/10);
wbc.encountered_congestion
: 写回过程中检测到IO拥塞blk_congestion_wait(WRITE, HZ/10)
: 等待写操作拥塞缓解HZ/10
: 等待0.1秒
无拥塞情况:
elsebreak; /* All the old data is written */
- 所有旧数据都已写回
- 跳出循环,结束本次写回操作
第十三段:更新剩余写回数量
nr_to_write -= MAX_WRITEBACK_PAGES - wbc.nr_to_write;}
MAX_WRITEBACK_PAGES - wbc.nr_to_write
: 本次实际写回的页面数nr_to_write -= ...
: 从总估计值中减去已写回的数量- 循环继续处理剩余的脏页
第十四段:确保最小间隔
if (time_before(next_jif, jiffies + HZ))next_jif = jiffies + HZ;
time_before(next_jif, jiffies + HZ)
: 检查计划的下次时间是否在1秒内next_jif = jiffies + HZ
: 如果在1秒内,设置为至少1秒后- 作用: 防止写回操作过于频繁,保证最小1秒间隔
第十五段:重新启动定时器
if (dirty_writeback_centisecs)mod_timer(&wb_timer, next_jif);
}
if (dirty_writeback_centisecs)
: 检查写回间隔是否非零mod_timer(&wb_timer, next_jif)
: 重新设置写回定时器- 作用: 建立下一次定期写回
控制脏页写回的速度set_ratelimit
static struct notifier_block ratelimit_nb = {.notifier_call = ratelimit_handler,.next = NULL,
};
static int
ratelimit_handler(struct notifier_block *self, unsigned long u, void *v)
{set_ratelimit();return 0;
}
static void set_ratelimit(void)
{ratelimit_pages = total_pages / (num_online_cpus() * 32);if (ratelimit_pages < 16)ratelimit_pages = 16;if (ratelimit_pages * PAGE_CACHE_SIZE > 4096 * 1024)ratelimit_pages = (4096 * 1024) / PAGE_CACHE_SIZE;
}
代码整体功能
这个代码实现了基于CPU数量的动态速率限制机制,用于控制脏页写回的速度,防止在多CPU系统中产生IO风暴
代码分段详解
第一段:通知器块定义
static struct notifier_block ratelimit_nb = {.notifier_call = ratelimit_handler,.next = NULL,
};
结构体定义:
static struct notifier_block ratelimit_nb
: 静态通知器块变量struct notifier_block
: 内核通知链机制的结构体
成员初始化:
.notifier_call = ratelimit_handler
: 通知回调函数指针.next = NULL
: 下一个通知器指针,NULL表示链表结束
作用: 注册到CPU热插拔通知链,当CPU状态变化时自动调用ratelimit_handler
第二段:通知器回调函数
ratelimit_handler(struct notifier_block *self, unsigned long u, void *v)
{set_ratelimit();return 0;
}
函数签名:
ratelimit_handler
: 回调函数名struct notifier_block *self
: 指向自身的通知器块指针unsigned long u
: 事件类型(CPU上线、下线等)void *v
: 事件相关数据
函数体:
set_ratelimit()
: 调用设置速率限制函数return 0
: 返回0表示处理成功
特点:
- 忽略具体的事件类型和参数
- 任何CPU状态变化都触发速率限制重计算
- 保持简单的响应逻辑
第三段:速率限制设置函数
static void set_ratelimit(void)
{ratelimit_pages = total_pages / (num_online_cpus() * 32);
计算公式:
ratelimit_pages = total_pages / (num_online_cpus() * 32)
total_pages
: 全局变量,系统总页面数num_online_cpus()
: 返回当前在线的CPU数量32
: 经验系数,每个CPU分配32分之一的总页面作为速率限制
设计原理:
- CPU越多,允许的并发写回页面越少
- 防止多CPU同时产生大量写回IO
- 平衡并行性和IO带宽
第四段:下限保护
if (ratelimit_pages < 16)ratelimit_pages = 16;
if (ratelimit_pages < 16)
: 检查计算值是否小于16页ratelimit_pages = 16
: 如果太小,设置为最小值16页- 作用: 确保即使在大规模多CPU系统中也有基本的写回能力
第五段:上限保护
if (ratelimit_pages * PAGE_CACHE_SIZE > 4096 * 1024)ratelimit_pages = (4096 * 1024) / PAGE_CACHE_SIZE;
}
条件检查:
ratelimit_pages * PAGE_CACHE_SIZE > 4096 * 1024
PAGE_CACHE_SIZE
: 页面缓存大小,通常4096字节(4KB)4096 * 1024
: 4MB(4096KB)
上限设置:
ratelimit_pages = (4096 * 1024) / PAGE_CACHE_SIZE
- 如果超过4MB,限制为4MB对应的页面数
- 对于4KB页面:
4096 * 1024 / 4096 = 1024
页
作用: 防止在少CPU大内存系统中单次写回过多数据
设计原理分析
1. 基于CPU数量的自适应
// 核心思想:CPU越多,限制越严格
ratelimit_pages = total_pages / (num_online_cpus() * 32)
为什么是32?
- 经验值,平衡并行性和IO效率
- 每个CPU允许写回总内存的1/32份额
- 防止多CPU同时发起写回产生IO竞争
2. 边界保护的意义
下限保护(16页):
- 确保基本功能:即使在大规模系统中也能进行写回
- 维持吞吐量:64KB的最小写回单元
上限保护(4MB):
- 控制单次写回规模:避免大块IO阻塞系统
- 公平性:防止少CPU系统占用过多IO带宽
- 响应性:保证系统及时响应其他IO请求
3. 热插拔感知
通过通知器机制:
- CPU上线:自动收紧限制(分母变大)
- CPU下线:自动放宽限制(分母变小)
- 实时适应系统拓扑变化