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
相关推荐
groundhappy2 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕3 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫4 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生5 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.116 小时前
linux基础IO
linux·运维·服务器
BelongPanda8 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯8 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_777998348 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器
yunwei378 小时前
eBPF 教程:检查 exec 后真正安装的可执行镜像
linux·开源
笑锝没心没肺9 小时前
yum update 报错:GPG key retrieval failed: [Errno 14] HTTPS Error 404 - Not Found
linux·运维·服务器