Linux中dcache和inode缓存回收函数的实现

Linux 内核目录项缓存与内存回收机制总结

一、目录项缓存(dcache)回收机制

1. shrink_dcache_memory 核心函数

  • 功能 :控制目录项缓存(dcache)的内存回收
  • 关键参数
    • nr:目标回收数量
    • gfp_mask:内存分配标志
  • 返回值 :剩余未使用dentry的估算值(受vfs_cache_pressure影响)

2. prune_dcache 实际回收实现

  • LRU策略:从链表尾部(最近最少使用)开始回收
  • 三级检查
    1. 引用计数检查(d_count
    2. 最近访问标志(DCACHE_REFERENCED
    3. 实际回收条件

二、inode缓存回收机制

1. shrink_icache_memory 核心函数

  • dcache区别
    • 使用不同的统计结构(inodes_stat
    • 相同的压力系数计算方式

2. prune_icache 实际回收实现

  • 特殊处理
    • 缓冲区清理(remove_inode_buffers
    • 页面缓存无效化(invalidate_inode_pages
  • 双重锁机制
    • 全局信号量(iprune_sem
    • 自旋锁(inode_lock

三、内存收缩器框架

1. 收缩器注册

c 复制代码
struct shrinker *set_shrinker(int seeks, shrinker_t shrinker_func) {
    struct shrinker *s = kmalloc(sizeof(*s), GFP_KERNEL);
    if (s) {
        s->shrinker = shrinker_func;  // 设置回调函数
        s->seeks = seeks;  // 设置查找代价
        down_write(&shrinker_rwsem);
        list_add(&s->list, &shrinker_list);  // 加入全局列表
        up_write(&shrinker_rwsem);
    }
    return s;
}
  • 核心字段
    • shrinker:实际回收函数
    • seeks:查找代价参数
  • 保护机制 :读写信号量(shrinker_rwsem

五、核心机制对比

机制 dcache回收 inode回收 收缩器框架
数据结构 dentry + LRU链表 inode + LRU链表 shrinker结构体
锁机制 dcache_lock + dentry->d_lock iprune_sem + inode_lock shrinker_rwsem
回收条件 引用计数=0且无REFERENCED标志 引用计数=0且无脏数据 回调函数决定
性能优化 预取(prefetch) 页面向量(pagevec)批处理 seeks参数调整优先级
错误处理 竞争条件重试 状态检查后跳过 信号量保护

六、系统调优参数

  1. vfs_cache_pressure
    • 值=100:默认行为
    • 值>100:更积极回收(内存紧张时)
    • 值<100:更保守回收(高性能需求时)
  2. 收缩器seeks参数
    • 影响内存回收时的扫描优先级
    • 值越小表示回收代价越低,优先级越高

七、典型应用场景

  1. 内存不足时
    • kswapd后台线程调用收缩器
    • 直接内存回收路径调用
  2. 文件系统卸载时
    • 清理关联的dentryinode缓存
    • 确保资源完全释放
  3. 性能敏感场景
    • 调整vfs_cache_pressure平衡性能与内存使用
    • 优化seeks参数减少回收开销

八、设计模式总结

  1. LRU缓存管理
    • 最近最少使用优先回收
    • 双向链表实现高效插入/删除
  2. 多级锁机制
    • 全局锁保护列表结构
    • 细粒度锁保护单个对象
    • 信号量序列化关键操作
  3. 安全回收策略
    • 引用计数防止使用中回收
    • 状态标志避免竞争条件
    • 回调函数实现灵活策略
  4. 性能优化技术
    • 批量处理减少锁持有时间
    • 预取指令提高缓存命中率
    • 条件调度避免长时间阻塞

目录项缓存的内存回收shrink_dcache_memory

c 复制代码
static int shrink_dcache_memory(int nr, unsigned int gfp_mask)
{
        if (nr) {
                if (!(gfp_mask & __GFP_FS))
                        return -1;
                prune_dcache(nr);
        }
        return (dentry_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
}
static void prune_dcache(int count)
{
        spin_lock(&dcache_lock);
        for (; count ; count--) {
                struct dentry *dentry;
                struct list_head *tmp;

                tmp = dentry_unused.prev;
                if (tmp == &dentry_unused)
                        break;
                list_del_init(tmp);
                prefetch(dentry_unused.prev);
                dentry_stat.nr_unused--;
                dentry = list_entry(tmp, struct dentry, d_lru);

                spin_lock(&dentry->d_lock);
                /*
                 * We found an inuse dentry which was not removed from
                 * dentry_unused because of laziness during lookup.  Do not free
                 * it - just keep it off the dentry_unused list.
                 */
                if (atomic_read(&dentry->d_count)) {
                        spin_unlock(&dentry->d_lock);
                        continue;
                }
                /* If the dentry was recently referenced, don't free it. */
                if (dentry->d_flags & DCACHE_REFERENCED) {
                        dentry->d_flags &= ~DCACHE_REFERENCED;
                        list_add(&dentry->d_lru, &dentry_unused);
                        dentry_stat.nr_unused++;
                        spin_unlock(&dentry->d_lock);
                        continue;
                }
                prune_one_dentry(dentry);
        }
        spin_unlock(&dcache_lock);
}

函数功能

shrink_dcache_memoryprune_dcache函数共同实现了目录项缓存的内存回收机制,在系统内存不足时释放未使用的dentry对象来回收内存

shrink_dcache_memory 函数

第1行:函数声明

c 复制代码
static int shrink_dcache_memory(int nr, unsigned int gfp_mask)
  • 参数 :
    • nr: 要回收的对象数量目标
    • gfp_mask: 内存分配标志位,指示回收上下文

第2-7行:回收条件检查

c 复制代码
        if (nr) {
                if (!(gfp_mask & __GFP_FS))
                        return -1;
                prune_dcache(nr);
        }
  • if (nr): 检查是否有回收目标数量
  • if (!(gfp_mask & __GFP_FS)) : 检查是否允许文件系统操作
    • __GFP_FS标志表示允许调用到文件系统层
    • 如果没有此标志,返回-1表示无法回收
  • prune_dcache(nr): 调用实际的回收函数

第8行:返回剩余对象估算

c 复制代码
        return (dentry_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
  • 计算逻辑 : 剩余未使用dentry数量除以100,乘以缓存压力系数
  • dentry_stat.nr_unused : 全局统计的未使用dentry数量
  • sysctl_vfs_cache_pressure: 系统可调参数,控制缓存回收积极性
  • 最终得出各种缓存应该回收数量所占的比例

prune_dcache 函数

第1行:函数声明

c 复制代码
static void prune_dcache(int count)
  • 参数 : count - 要尝试回收的dentry数量

第2-3行:获取全局锁并开始循环

c 复制代码
        spin_lock(&dcache_lock);
        for (; count ; count--) {
  • spin_lock(&dcache_lock) : 获取dentry缓存全局自旋锁
  • for (; count ; count--) : 循环尝试回收count个dentry

第4-12行:从LRU链表获取dentry

c 复制代码
                struct dentry *dentry;
                struct list_head *tmp;

                tmp = dentry_unused.prev;
                if (tmp == &dentry_unused)
                        break;
                list_del_init(tmp);
                prefetch(dentry_unused.prev);
                dentry_stat.nr_unused--;
                dentry = list_entry(tmp, struct dentry, d_lru);
  • tmp = dentry_unused.prev: 从LRU链表尾部获取(最近最少使用)
  • if (tmp == &dentry_unused): 检查链表是否为空
  • list_del_init(tmp): 从链表中移除该节点
  • prefetch(dentry_unused.prev) : 预取下一个dentry,提高性能
  • dentry_stat.nr_unused-- : 减少未使用dentry统计计数
  • dentry = list_entry(tmp, struct dentry, d_lru) : 获取dentry结构体指针

第14-20行:检查引用计数

c 复制代码
                spin_lock(&dentry->d_lock);
                /*
                 * We found an inuse dentry which was not removed from
                 * dentry_unused because of laziness during lookup.  Do not free
                 * it - just keep it off the dentry_unused list.
                 */
                if (atomic_read(&dentry->d_count)) {
                        spin_unlock(&dentry->d_lock);
                        continue;
                }
  • spin_lock(&dentry->d_lock) : 获取单个dentry的锁
  • 可能找到正在使用的dentry
  • atomic_read(&dentry->d_count) : 检查dentry引用计数
  • 如果被引用: 释放锁并继续下一个

第21-29行:检查最近引用标志

c 复制代码
                /* If the dentry was recently referenced, don't free it. */
                if (dentry->d_flags & DCACHE_REFERENCED) {
                        dentry->d_flags &= ~DCACHE_REFERENCED;
                        list_add(&dentry->d_lru, &dentry_unused);
                        dentry_stat.nr_unused++;
                        spin_unlock(&dentry->d_lock);
                        continue;
                }
  • DCACHE_REFERENCED: 最近被访问的标志位
  • 清除标志 : dentry->d_flags &= ~DCACHE_REFERENCED
  • 重新加入链表: 放回LRU链表头部(最近使用)
  • 增加统计 : dentry_stat.nr_unused++
  • 继续下一个 : 这个dentry暂时保留

第30行:回收符合条件的dentry

c 复制代码
                prune_one_dentry(dentry);
        }
        spin_unlock(&dcache_lock);
  • prune_one_dentry(dentry) : 回收这个dentry
  • 循环结束: 完成count次尝试或链表为空
  • spin_unlock(&dcache_lock): 释放全局锁

详细机制分析

LRU链表管理策略

LRU链表dentry_unused 链表头部
最近使用 链表尾部
最近最少使用 新加入或最近访问 回收候选 DCACHE_REFERENCED标志 无标志的dentry被回收

引用计数和状态标志

c 复制代码
// dentry的三种状态:
1. 正在使用: d_count > 0 → 不能被回收
2. 最近使用: DCACHE_REFERENCED → 暂时保留
3. 可回收: d_count=0 且 无DCACHE_REFERENCED → 可安全回收

// 状态转换:
使用中 → 释放后 → 最近使用 → 长时间未用 → 可回收

系统调优参数

sysctl_vfs_cache_pressure

c 复制代码
// 缓存压力参数影响:
值 = 100: 默认行为
值 > 100: 更积极回收(适合内存紧张系统)
值 < 100: 更保守回收(适合需要高性能的系统)

// 在shrink_dcache_memory中的影响:
返回值 = (未使用dentry数 / 100) * 压力系数
// 影响内存回收子系统对dcache的回收优先级

inode缓存的内存回收shrink_icache_memory

c 复制代码
static int shrink_icache_memory(int nr, unsigned int gfp_mask)
{
        if (nr) {
                /*
                 * Nasty deadlock avoidance.  We may hold various FS locks,
                 * and we don't want to recurse into the FS that called us
                 * in clear_inode() and friends..
                 */
                if (!(gfp_mask & __GFP_FS))
                        return -1;
                prune_icache(nr);
        }
        return (inodes_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
}
static void prune_icache(int nr_to_scan)
{
        LIST_HEAD(freeable);
        int nr_pruned = 0;
        int nr_scanned;
        unsigned long reap = 0;

        down(&iprune_sem);
        spin_lock(&inode_lock);
        for (nr_scanned = 0; nr_scanned < nr_to_scan; nr_scanned++) {
                struct inode *inode;

                if (list_empty(&inode_unused))
                        break;

                inode = list_entry(inode_unused.prev, struct inode, i_list);

                if (inode->i_state || atomic_read(&inode->i_count)) {
                        list_move(&inode->i_list, &inode_unused);
                        continue;
                }
                if (inode_has_buffers(inode) || inode->i_data.nrpages) {
                        __iget(inode);
                        spin_unlock(&inode_lock);
                        if (remove_inode_buffers(inode))
                                reap += invalidate_inode_pages(&inode->i_data);
                        iput(inode);
                        spin_lock(&inode_lock);

                        if (inode != list_entry(inode_unused.next,
                                                struct inode, i_list))
                                continue;       /* wrong inode or list_empty */
                        if (!can_unuse(inode))
                                continue;
                }
                hlist_del_init(&inode->i_hash);
                list_move(&inode->i_list, &freeable);
                inode->i_state |= I_FREEING;
                nr_pruned++;
        }
        inodes_stat.nr_unused -= nr_pruned;
        spin_unlock(&inode_lock);

        dispose_list(&freeable);
        up(&iprune_sem);

        if (current_is_kswapd())
                mod_page_state(kswapd_inodesteal, reap);
        else
                mod_page_state(pginodesteal, reap);
}

函数功能

shrink_icache_memoryprune_icache函数共同实现了inode缓存的内存回收机制,在系统内存不足时释放未使用的inode对象来回收内存。

shrink_icache_memory 函数

第1行:函数声明

c 复制代码
static int shrink_icache_memory(int nr, unsigned int gfp_mask)
  • 参数 :
    • nr: 要回收的inode数量目标
    • gfp_mask: 内存分配标志位,指示回收上下文

第2-10行:回收条件检查和注释

c 复制代码
        if (nr) {
                /*
                 * Nasty deadlock avoidance.  We may hold various FS locks,
                 * and we don't want to recurse into the FS that called us
                 * in clear_inode() and friends..
                 */
                if (!(gfp_mask & __GFP_FS))
                        return -1;
                prune_icache(nr);
        }
  • if (nr): 检查是否有回收目标数量
  • 这里可能持有各种文件系统锁,不希望递归进入调用我们的文件系统
    • 这个注释描述的是Linux内核中一个经典且危险的死锁场景:
      1. 根本原因: 内存回收操作递归进入正在等待内存的文件系统
      2. 触发条件: 文件系统持有锁时发生内存分配,而内存回收又需要相同的锁
      3. 解决方案 : 通过__GFP_FS标志识别安全上下文,只在允许文件系统操作时执行inode回收
      4. 设计原则: 安全性优先于内存回收效率
  • if (!(gfp_mask & __GFP_FS)): 检查是否允许文件系统操作
  • prune_icache(nr) : 调用实际的inode回收函数

第11行:返回剩余对象估算

c 复制代码
        return (inodes_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
  • 计算逻辑 : 剩余未使用inode数量除以100,乘以缓存压力系数
  • inodes_stat.nr_unused : 全局统计的未使用inode数量

prune_icache 函数

第1-5行:函数声明和变量定义

c 复制代码
static void prune_icache(int nr_to_scan)
{
        LIST_HEAD(freeable);
        int nr_pruned = 0;
        int nr_scanned;
        unsigned long reap = 0;
  • 参数 : nr_to_scan - 要扫描的inode数量
  • LIST_HEAD(freeable) : 创建临时链表头,用于存放要释放的inode
  • nr_pruned : 实际修剪的inode计数
  • nr_scanned : 已扫描的inode计数
  • reap: 回收的页面计数

第7-8行:获取锁

c 复制代码
        down(&iprune_sem);
        spin_lock(&inode_lock);
  • down(&iprune_sem) : 获取inode修剪信号量(可能睡眠)
  • spin_lock(&inode_lock) : 获取inode全局自旋锁

第9-11行:开始扫描循环

c 复制代码
        for (nr_scanned = 0; nr_scanned < nr_to_scan; nr_scanned++) {
                struct inode *inode;

                if (list_empty(&inode_unused))
                        break;
  • 循环条件 : 扫描nr_to_scan个inode或链表为空
  • list_empty(&inode_unused) : 检查未使用inode链表是否为空

第13-14行:获取inode

c 复制代码
                inode = list_entry(inode_unused.prev, struct inode, i_list);
  • inode_unused.prev: 从LRU链表尾部获取(最近最少使用)
  • list_entry : 获取inode结构体指针

第16-20行:检查inode状态

c 复制代码
                if (inode->i_state || atomic_read(&inode->i_count)) {
                        list_move(&inode->i_list, &inode_unused);
                        continue;
                }
  • inode->i_state : 检查inode状态标志(如I_DIRTY等)
  • atomic_read(&inode->i_count): 检查引用计数
  • 如果被使用: 移动到链表头部,继续下一个

第21-36行:处理有缓存页的inode

c 复制代码
                if (inode_has_buffers(inode) || inode->i_data.nrpages) {
                        __iget(inode);
                        spin_unlock(&inode_lock);
                        if (remove_inode_buffers(inode))
                                reap += invalidate_inode_pages(&inode->i_data);
                        iput(inode);
                        spin_lock(&inode_lock);

                        if (inode != list_entry(inode_unused.next,
                                                struct inode, i_list))
                                continue;       /* wrong inode or list_empty */
                        if (!can_unuse(inode))
                                continue;
                }

详细步骤:

  1. 检查是否有缓存 : inode_has_buffers()inode->i_data.nrpages
  2. 增加引用 : __iget(inode)防止在清理过程中被释放
  3. 释放锁 : spin_unlock(&inode_lock)因为后续操作可能睡眠
  4. 移除缓冲区 : remove_inode_buffers(inode)
  5. 无效化页面 : invalidate_inode_pages()并统计回收页面
  6. 减少引用 : iput(inode)
  7. 重新获取锁 : spin_lock(&inode_lock)
  8. 验证inode : 检查inode是否仍在列表中且可释放

第37-41行:标记要释放的inode

c 复制代码
                hlist_del_init(&inode->i_hash);
                list_move(&inode->i_list, &freeable);
                inode->i_state |= I_FREEING;
                nr_pruned++;
        }
  • hlist_del_init(&inode->i_hash): 从哈希表中移除
  • list_move(&inode->i_list, &freeable): 移动到释放链表
  • inode->i_state |= I_FREEING: 标记为正在释放状态
  • nr_pruned++: 增加修剪计数

第42-46行:更新统计和释放锁

c 复制代码
        inodes_stat.nr_unused -= nr_pruned;
        spin_unlock(&inode_lock);

        dispose_list(&freeable);
        up(&iprune_sem);
  • 更新统计 : 减少未使用inode计数
  • 释放锁 : spin_unlock(&inode_lock)
  • 释放inode : dispose_list(&freeable)实际释放内存
  • 释放信号量 : up(&iprune_sem)

第48-52行:更新页面回收统计

c 复制代码
        if (current_is_kswapd())
                mod_page_state(kswapd_inodesteal, reap);
        else
                mod_page_state(pginodesteal, reap);
  • 区分上下文 : 如果是kswapd线程还是直接回收
  • 更新统计 : 记录通过inode回收的页面数量

关键机制详解

锁的使用策略

锁层次结构 iprune_sem 信号量 inode_lock 自旋锁 保护整个修剪过程
可睡眠 保护inode链表操作
不可睡眠 序列化多个修剪操作 保证链表操作原子性 避免并发修剪冲突 防止数据结构损坏

inode状态检查逻辑

c 复制代码
// 不能释放的inode条件:
1. i_state != 0        // 有特殊状态标志(如脏数据)
2. i_count > 1         // 被其他组件引用
3. 有缓存页面          // 需要先清理页面缓存
4. 有缓冲区            // 需要先清理缓冲区
5. can_unuse()失败     // 文件系统特定检查

// 可释放的inode:
- 在inode_unused链表中
- i_count = 1(只有缓存引用)
- 无特殊状态标志
- 无缓存页面和缓冲区

页面缓存清理过程

c 复制代码
// 清理有缓存页的inode:
1. __iget(inode):       增加引用,防止在清理时被释放
2. remove_inode_buffers: 移除缓冲区头
3. invalidate_inode_pages: 使页面缓存无效,回收内存
4. iput(inode):         减少引用,可能触发实际释放

// reap统计:记录通过inode回收的页面数量

性能优化特性

临时链表的使用

c 复制代码
// 为什么使用freeable链表?
1. 在持有锁时只进行链表操作
2. 释放锁后再进行实际的内存释放
3. 减少锁持有时间,提高并发性
4. 避免在持有锁时调用可能睡眠的函数

区分回收上下文

c 复制代码
// kswapd vs 直接回收:
kswapd_inodesteal:   后台回收线程的统计
pginodesteal:        直接内存回收的统计

// 意义:
- 监控不同回收路径的效果
- 调优系统内存回收策略
- 诊断性能问题

错误处理和边界情况

竞争条件处理

c 复制代码
// 可能的竞争场景:
1. 其他线程在修剪过程中增加i_count
2. 其他线程将inode重新加入使用
3. 其他修剪操作同时进行

// 防护措施:
- iprune_sem信号量序列化修剪操作
- inode_lock保护链表操作
- 引用计数防止意外释放
- 状态标志确保一致性

安全释放保证

c 复制代码
// 确保inode安全释放的步骤:
1. 检查引用计数和状态
2. 清理关联的缓存页面
3. 从所有链表中移除
4. 设置I_FREEING状态
5. 实际释放内存

总结

这两个函数共同实现了Linux inode缓存的高效内存回收:

  1. 安全第一 : 通过__GFP_FS检查防止死锁,确保只在安全上下文中回收
  2. 精确选择 : 基于LRU算法和inode状态智能选择回收候选
  3. 完整清理 : 不仅释放inode本身,还清理关联的页面缓存和缓冲区
  4. 并发安全: 通过多级锁机制防止竞争条件
  5. 性能监控: 区分不同回收上下文并统计回收效果

检查inode是否有关联的缓冲区inode_has_buffers

c 复制代码
int inode_has_buffers(struct inode *inode)
{
        return !list_empty(&inode->i_data.private_list);
}

函数功能

inode_has_buffers函数用于检查一个inode是否有关联的缓冲区(buffer heads)。它是文件系统缓冲区管理的基础函数,用于确定inode是否需要特殊的缓冲区清理操作

代码逐段解释

第1行:函数声明

c 复制代码
int inode_has_buffers(struct inode *inode)
  • 返回类型 : int - 返回1表示有缓冲区,0表示没有缓冲区
  • 参数 : struct inode *inode - 要检查的inode指针
  • 功能 : 检查指定inode是否包含缓冲区

第2行:函数实现

c 复制代码
        return !list_empty(&inode->i_data.private_list);

这是函数的唯一操作,包含几个关键步骤:

步骤分解:
  1. &inode->i_data.private_list:

    • inode->i_datainode的地址空间结构(struct address_space)
    • private_list 是地址空间中的一个链表头,用于链接该inode的所有缓冲区头
  2. list_empty(&inode->i_data.private_list):

    • list_empty() 函数检查链表是否为空
    • 如果链表为空,返回1(真)
    • 如果链表不为空,返回0(假)
  3. !(逻辑非操作):

    • list_empty()的结果取反
    • 如果链表为空:list_empty()返回1 → !1 = 0 → 函数返回0(没有缓冲区)
    • 如果链表不为空:list_empty()返回0 → !0 = 1 → 函数返回1(有缓冲区)

详细数据结构分析

inode 和 address_space 的关系

struct inode struct address_space i_data struct list_head private_list 缓冲区头链表 struct buffer_head struct buffer_head ... 块设备缓冲区1 块设备缓冲区2 块设备缓冲区N

private_list 链表的实际内容

c 复制代码
// private_list中存储的是buffer_head结构体:
struct buffer_head {
    struct list_head b_assoc_buffers;  // 用于链接到private_list
    struct buffer_head *b_this_page;   // 同一页面的其他缓冲区
    sector_t b_blocknr;                // 块号
    // ... 其他字段
};

// 链表连接关系:
inode->i_data.private_list → buffer_head->b_assoc_buffers → buffer_head->b_assoc_buffers → ...

移除与inode关联的所有缓冲区remove_inode_buffers

c 复制代码
int remove_inode_buffers(struct inode *inode)
{
        int ret = 1;

        if (inode_has_buffers(inode)) {
                struct address_space *mapping = &inode->i_data;
                struct list_head *list = &mapping->private_list;
                struct address_space *buffer_mapping = mapping->assoc_mapping;

                spin_lock(&buffer_mapping->private_lock);
                while (!list_empty(list)) {
                        struct buffer_head *bh = BH_ENTRY(list->next);
                        if (buffer_dirty(bh)) {
                                ret = 0;
                                break;
                        }
                        __remove_assoc_queue(bh);
                }
                spin_unlock(&buffer_mapping->private_lock);
        }
        return ret;
}
static inline void __remove_assoc_queue(struct buffer_head *bh)
{
        list_del_init(&bh->b_assoc_buffers);
}

函数功能

remove_inode_buffers函数用于移除与inode关联的所有缓冲区。它会遍历inode的缓冲区链表,移除所有干净的缓冲区,但如果遇到脏缓冲区则停止并返回失败。

代码逐段解释

remove_inode_buffers 函数

第1-2行:函数声明和变量初始化
c 复制代码
int remove_inode_buffers(struct inode *inode)
{
        int ret = 1;
  • 参数 : struct inode *inode - 要清理缓冲区的inode指针
  • 返回值 : int - 1表示成功移除所有缓冲区,0表示遇到脏缓冲区而中止
  • ret = 1: 初始化返回值为成功状态
第3-7行:条件检查和变量准备
c 复制代码
        if (inode_has_buffers(inode)) {
                struct address_space *mapping = &inode->i_data;
                struct list_head *list = &mapping->private_list;
                struct address_space *buffer_mapping = mapping->assoc_mapping;
  • if (inode_has_buffers(inode)) : 检查inode是否有缓冲区,没有则直接返回
  • mapping = &inode->i_data : 获取inode的地址空间结构
  • list = &mapping->private_list: 获取缓冲区链表头指针
  • buffer_mapping = mapping->assoc_mapping: 获取关联的缓冲区映射地址空间
第9行:获取缓冲区映射锁
c 复制代码
                spin_lock(&buffer_mapping->private_lock);
  • spin_lock(&buffer_mapping->private_lock): 获取缓冲区映射的私有锁
  • 目的: 保护缓冲区链表操作的原子性,防止竞争条件
第10-17行:遍历缓冲区链表
c 复制代码
                while (!list_empty(list)) {
                        struct buffer_head *bh = BH_ENTRY(list->next);
                        if (buffer_dirty(bh)) {
                                ret = 0;
                                break;
                        }
                        __remove_assoc_queue(bh);
                }
详细步骤:
  1. while (!list_empty(list)): 循环直到缓冲区链表为空
  2. bh = BH_ENTRY(list->next) :
    • 从链表头部获取下一个缓冲区头
    • BH_ENTRY宏将list_head转换为buffer_head
  3. if (buffer_dirty(bh)): 检查缓冲区是否脏(有未写回的数据)
  4. ret = 0; break;: 如果缓冲区脏,设置返回值为失败并跳出循环
  5. __remove_assoc_queue(bh): 如果缓冲区干净,从链表中移除
第18行:释放锁
c 复制代码
                spin_unlock(&buffer_mapping->private_lock);
  • 释放之前获取的缓冲区映射锁
第20行:返回结果
c 复制代码
        }
        return ret;
}
  • 返回操作结果:1=成功,0=遇到脏缓冲区

__remove_assoc_queue 函数

第1-4行:函数实现
c 复制代码
static inline void __remove_assoc_queue(struct buffer_head *bh)
{
        list_del_init(&bh->b_assoc_buffers);
}
  • 参数 : struct buffer_head *bh - 要移除的缓冲区头
  • list_del_init(&bh->b_assoc_buffers) :
    • 从链表中删除缓冲区节点
    • 并重新初始化节点指针(prev/next指向自己)

实际使用场景

在内存回收中的角色

c 复制代码
// 完整的inode回收流程:
1. 找到可回收的inode
2. remove_inode_buffers() 清理缓冲区
3. invalidate_inode_pages() 清理页面缓存
4. 实际释放inode内存

// 如果remove_inode_buffers返回0:
- 这个inode暂时不能回收
- 需要等待脏数据写回
- 或者由其他机制处理

页面缓存无效化函数invalidate_inode_pages

c 复制代码
unsigned long invalidate_inode_pages(struct address_space *mapping)
{
        return invalidate_mapping_pages(mapping, 0, ~0UL);
}
unsigned long invalidate_mapping_pages(struct address_space *mapping,
                                pgoff_t start, pgoff_t end)
{
        struct pagevec pvec;
        pgoff_t next = start;
        unsigned long ret = 0;
        int i;

        pagevec_init(&pvec, 0);
        while (next <= end &&
                        pagevec_lookup(&pvec, mapping, next, PAGEVEC_SIZE)) {
                for (i = 0; i < pagevec_count(&pvec); i++) {
                        struct page *page = pvec.pages[i];

                        if (TestSetPageLocked(page)) {
                                next++;
                                continue;
                        }
                        if (page->index > next)
                                next = page->index;
                        next++;
                        if (PageDirty(page) || PageWriteback(page))
                                goto unlock;
                        if (page_mapped(page))
                                goto unlock;
                        ret += invalidate_complete_page(mapping, page);
unlock:
                        unlock_page(page);
                        if (next > end)
                                break;
                }
                pagevec_release(&pvec);
                cond_resched();
        }
        return ret;
}

函数功能

invalidate_inode_pagesinvalidate_mapping_pages函数用于使地址空间中的页面缓存无效,从内存中移除文件的缓存页面,通常在inode回收或文件截断时调用

代码逐段解释

invalidate_inode_pages 函数

第1-3行:包装函数
c 复制代码
unsigned long invalidate_inode_pages(struct address_space *mapping)
{
        return invalidate_mapping_pages(mapping, 0, ~0UL);
}
  • 参数 : struct address_space *mapping - inode的地址空间
  • 返回值 : unsigned long - 成功无效化的页面数量
  • invalidate_mapping_pages(mapping, 0, ~0UL) :
    • 调用实际实现函数
    • 0: 起始页面索引
    • ~0UL: 结束页面索引(最大值,表示所有页面)

invalidate_mapping_pages 函数

第1-5行:函数声明和变量定义
c 复制代码
unsigned long invalidate_mapping_pages(struct address_space *mapping,
                                pgoff_t start, pgoff_t end)
{
        struct pagevec pvec;
        pgoff_t next = start;
        unsigned long ret = 0;
        int i;
  • 参数 :
    • mapping: 地址空间指针
    • start: 起始页面索引
    • end: 结束页面索引
  • struct pagevec pvec: 页面向量,用于批量处理页面
  • pgoff_t next = start: 下一个要处理的页面索引,初始化为start
  • unsigned long ret = 0: 成功无效化的页面计数
  • int i: 循环计数器
第7行:初始化页面向量
c 复制代码
        pagevec_init(&pvec, 0);
  • pagevec_init(&pvec, 0): 初始化页面向量
  • 参数0: 冷页面(cold page),表示这些页面不太可能被再次访问
第8-9行:批量查找页面循环
c 复制代码
        while (next <= end &&
                        pagevec_lookup(&pvec, mapping, next, PAGEVEC_SIZE)) {
  • next <= end: 检查是否还有要处理的页面范围
  • pagevec_lookup(&pvec, mapping, next, PAGEVEC_SIZE) :
    • 从地址空间中批量查找页面
    • next索引开始,最多查找PAGEVEC_SIZE个页面
    • 返回true表示找到了页面
第10-11行:遍历页面向量
c 复制代码
                for (i = 0; i < pagevec_count(&pvec); i++) {
                        struct page *page = pvec.pages[i];
  • pagevec_count(&pvec): 获取页面向量中实际包含的页面数量
  • page = pvec.pages[i]: 获取第i个页面指针
第13-16行:尝试锁定页面
c 复制代码
                        if (TestSetPageLocked(page)) {
                                next++;
                                continue;
                        }
  • TestSetPageLocked(page): 原子性地测试并设置页面锁定标志
  • 返回值: 如果页面已被锁定,返回true(非零)
  • 处理: 如果页面已被锁定,跳过这个页面,继续下一个
第17-20行:更新下一个页面索引
c 复制代码
                        if (page->index > next)
                                next = page->index;
                        next++;
  • page->index: 页面在文件中的索引位置
  • 逻辑: 如果页面索引大于当前next,更新next以避免重复处理
  • next++: 准备处理下一个页面
第21-23行:检查页面状态
c 复制代码
                        if (PageDirty(page) || PageWriteback(page))
                                goto unlock;
  • PageDirty(page): 检查页面是否脏(有未写回的数据)
  • PageWriteback(page): 检查页面是否正在写回磁盘
  • 如果页面脏或正在写回: 跳转到unlock标签,跳过这个页面
第24-25行:检查页面映射
c 复制代码
                        if (page_mapped(page))
                                goto unlock;
  • page_mapped(page): 检查页面是否被映射到进程地址空间
  • 如果页面被映射: 跳转到unlock标签,跳过这个页面
第26行:执行页面无效化
c 复制代码
                        ret += invalidate_complete_page(mapping, page);
  • invalidate_complete_page(mapping, page): 实际执行页面无效化
  • 返回值: 成功无效化返回1,失败返回0
  • 累加到ret: 统计成功无效化的页面数量
第27-31行:解锁页面和循环控制
c 复制代码
unlock:
                        unlock_page(page);
                        if (next > end)
                                break;
                }
  • unlock:: 标签,用于各种跳过情况的统一处理
  • unlock_page(page): 释放页面锁
  • if (next > end): 检查是否已超出处理范围,如果是则跳出内层循环
第33-35行:释放页面向量和调度
c 复制代码
                pagevec_release(&pvec);
                cond_resched();
        }
  • pagevec_release(&pvec): 释放页面向量,减少页面的引用计数
  • cond_resched(): 条件调度,如果需要则让出CPU,避免长时间占用
第36行:返回结果
c 复制代码
        return ret;
}
  • 返回成功无效化的页面总数

详细机制分析

页面向量(Pagevec)批处理

Pagevec批处理 一次性查找多个页面 减少锁竞争 提高缓存局部性 PAGEVEC_SIZE通常=14 批量操作减少全局锁持有时间 连续页面可能在同一缓存行 平衡内存使用和性能 更好的多核扩展性 减少缓存失效

页面状态检查逻辑

c 复制代码
// 不能无效化的页面条件:
1. 页面已被锁定: TestSetPageLocked()失败
2. 页面脏: PageDirty(page)为真
3. 正在写回: PageWriteback(page)为真  
4. 被进程映射: page_mapped(page)为真

// 可以无效化的页面:
- 未被锁定
- 干净(非脏)
- 不在写回过程中
- 未被进程映射

向内核注册一个内存收缩器set_shrinker

c 复制代码
struct shrinker *set_shrinker(int seeks, shrinker_t theshrinker)
{
        struct shrinker *shrinker;

        shrinker = kmalloc(sizeof(*shrinker), GFP_KERNEL);
        if (shrinker) {
                shrinker->shrinker = theshrinker;
                shrinker->seeks = seeks;
                shrinker->nr = 0;
                down_write(&shrinker_rwsem);
                list_add(&shrinker->list, &shrinker_list);
                up_write(&shrinker_rwsem);
        }
        return shrinker;
}

函数功能

set_shrinker函数用于(shrinker),使得该收缩器能够参与系统的内存回收过程。它是Linux内核内存管理子系统的基础设施函数

代码逐段解释

第1行:函数声明

c 复制代码
struct shrinker *set_shrinker(int seeks, shrinker_t theshrinker)
  • 返回类型 : struct shrinker * - 返回注册的收缩器指针,失败返回NULL
  • 参数 :
    • int seeks - 查找代价参数,影响回收优先级
    • shrinker_t theshrinker - 实际的收缩函数指针

第2-3行:变量声明

c 复制代码
        struct shrinker *shrinker;
  • struct shrinker *shrinker: 声明收缩器结构体指针

第5-6行:内存分配

c 复制代码
        shrinker = kmalloc(sizeof(*shrinker), GFP_KERNEL);
        if (shrinker) {
  • kmalloc(sizeof(*shrinker), GFP_KERNEL) :
    • 分配收缩器结构体大小的内存
    • sizeof(*shrinker): 使用指针解引用获取正确大小
    • GFP_KERNEL: 标准内核内存分配标志
  • if (shrinker): 检查内存分配是否成功

第7-9行:初始化收缩器字段

c 复制代码
                shrinker->shrinker = theshrinker;
                shrinker->seeks = seeks;
                shrinker->nr = 0;
  • shrinker->shrinker = theshrinker : 设置收缩函数指针
    • 这是实际执行内存回收的回调函数
  • shrinker->seeks = seeks : 设置查找代价参数
    • 影响内存回收时的优先级
    • 值越小表示回收代价越低,优先级越高
  • shrinker->nr = 0 : 初始化统计字段为0
    • 用于记录收缩器回收的对象数量

第10-12行:注册到全局收缩器列表

c 复制代码
                down_write(&shrinker_rwsem);
                list_add(&shrinker->list, &shrinker_list);
                up_write(&shrinker_rwsem);
  1. down_write(&shrinker_rwsem): 获取收缩器读写信号量的写锁

    • 保护全局收缩器列表的并发访问
    • 写锁确保在修改列表时没有其他读者或写者
  2. list_add(&shrinker->list, &shrinker_list):

    • 将新收缩器添加到全局收缩器链表
    • &shrinker->list: 收缩器中的链表节点
    • &shrinker_list: 全局收缩器链表头
  3. up_write(&shrinker_rwsem): 释放写锁

第13-14行:返回结果

c 复制代码
        }
        return shrinker;
}
  • 返回分配和初始化好的收缩器指针
  • 如果内存分配失败,返回NULL
相关推荐
Мартин.3 小时前
[Meachines] [Hard] Pollution MyBB+Redis_session+PHP-Filter+PHP-FPM+prototype
linux
总有刁民想爱朕ha3 小时前
银河麒麟v10 Mysql8部署教程(小白版)
linux·mysql数据库备份
LCG元4 小时前
性能排查必看!当Linux服务器CPU/内存飙高,如何快速定位并"干掉"罪魁祸首进程?
linux·后端
christine-rr4 小时前
MySQL数据库管理、DDL、DQL、DML、DCL等总结
linux·数据库·mysql
奥尔特星云大使5 小时前
CentOS 7 上通过 RPM 包安装 Zabbix 4.x
linux·centos·zabbix
程序员勾践5 小时前
安装nginx
linux·nginx·centos
郝学胜-神的一滴5 小时前
Linux 进程控制块(PCB)解析:深入理解进程管理机制
linux·服务器·开发语言
CSCN新手听安5 小时前
【linux】多线程(六)生产者消费者模型,queue模拟阻塞队列的生产消费模型
linux·运维·服务器·c++
Wang's Blog5 小时前
Linux小课堂: 软件安装与源码编译实战之从 RPM 到源码构建的完整流程
linux·运维·服务器