内存管理篇-06Per-CPU页帧缓存

per-CPU缓存是对伙伴系统的完善,也是伙伴系统中的一部分。再回顾一下zone结构体的内容,这里的__percpu *pageset实际上就是Per-CPU的实现机制,所以这里的内存实际上最少有三部分,(1)free_area管理了大部分的公共伙伴系统内存;(2)lowmem_reserve预留了一部分;(3)然后就是__percpu *pagset这里对每个CPU都分配一部分管理起来:

1.per-CPU缓存的定义

是一种缓存机制,对伙伴系统的完善。由于伙伴系统管理的页面都是全局的,每个进程在申请页面的时候都需要加锁解锁等操作,极大的引入了开销。为了提高效率就引入页帧缓存,为每个cpu提供一个变量指针__percpu *pageset(定义在struct zone),这样每个cpu就不用去加锁解锁申请,直接使用本地物理页面。

把单个物理页面的申请和释放做成缓存,每个cpu都有这个链表。给每个cpu本地定义一个页表,维护这样一个变量。因此,不需要去全局伙伴系统上去申请释放。

2.核心结构体和实现

可以理解为struct zone 把内存分为了几大块,__percpu *pageset指针(每个CPU都有这么一个指针),指向了一片内存区域。这片区域不需要再各个CPU之间进行同步,struct list_head list[3], 指向了三种不同页表链表(可移动的,不可移动的,可回收的),并且三个链表都是的块大小都是4KB。

3.每个CPU分配缓存如何实现

struct per_cpu_pageset __percpu *pageset;这里就是宏,在每个CPU定义这样一个变量,并且每个CPU自己申请和释放内存的时候,不会去从伙伴系统申请释放内存,而是从自己的缓存中。。

在 Linux 内核中,__percpu 是一个特殊的类型修饰符,用于表示一个变量在每个 CPU 上都有一个独立的副本。struct per_cpu_pageset 是一个用于在每个 CPU 上维护一个页面集合的数据结构。这种机制允许每个 CPU 在本地访问页面集合,从而减少跨 CPU 的同步开销。在内核启动时,为每个 CPU 分配一个 struct per_cpu_pageset 的实例,并将其指针存储在一个全局数组中。通过调用 get_cpu_var() 函数来访问当前 CPU 的 struct per_cpu_pageset 实例。访问完之后,通常需要调用 put_cpu_var() 函数来释放对 struct per_cpu_pageset 的引用。

4.使用示例

cpp 复制代码
#include <linux/percpu.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/mm.h>
#include <linux/mmzone.h>

#define NR_PERCPU_PAGES 16

struct per_cpu_pageset {
    spinlock_t lock;
    struct page *pages[NR_PERCPU_PAGES];
    unsigned long nr_pages;
};

static DEFINE_PER_CPU(struct per_cpu_pageset *, pageset);

static void init_per_cpu_pageset(void)
{
    int cpu;

    for_each_possible_cpu(cpu) {
        struct per_cpu_pageset *ps = alloc_percpu(struct per_cpu_pageset);
        if (!ps) {
            printk(KERN_ERR "Failed to allocate per-cpu pageset\n");
            continue;
        }

        ps->nr_pages = 0;
        spin_lock_init(&ps->lock);
        per_cpu(pageset, cpu) = ps;
    }
}

static void release_per_cpu_pageset(void)
{
    int cpu;

    for_each_possible_cpu(cpu) {
        free_percpu(per_cpu(pageset, cpu));
    }
}

static void add_page_to_set(struct page *page)
{
    int cpu = raw_smp_processor_id();
    struct per_cpu_pageset *ps = per_cpu(pageset, cpu);

    spin_lock(&ps->lock);

    // 检查是否还有空间添加页面
    if (ps->nr_pages < NR_PERCPU_PAGES) {
        ps->pages[ps->nr_pages++] = page;
    } else {
        printk(KERN_WARNING "Per-CPU pageset full on CPU %d\n", cpu);
    }

    spin_unlock(&ps->lock);
}

static void remove_page_from_set(struct page *page)
{
    int cpu = raw_smp_processor_id();
    struct per_cpu_pageset *ps = per_cpu(pageset, cpu);
    int i;

    spin_lock(&ps->lock);

    // 查找页面并移除
    for (i = 0; i < ps->nr_pages; i++) {
        if (ps->pages[i] == page) {
            ps->pages[i] = ps->pages[--ps->nr_pages];
            break;
        }
    }

    spin_unlock(&ps->lock);
}

static int __init init_module(void)
{
    init_per_cpu_pageset();
    return 0;
}

static void __exit cleanup_module(void)
{
    release_per_cpu_pageset();
}

module_init(init_module);
module_exit(cleanup_module);
相关推荐
KNeeg_23 分钟前
Spring循环依赖以及三个级别缓存
java·spring·缓存
lkf1971129 分钟前
商品中心—1.B端建品和C端缓存
开发语言·后端·缓存
小王子10246 小时前
Django缓存机制详解:从配置到实战应用
redis·缓存·django·rbac
Antonio9157 小时前
【Redis】Redis 数据存储原理和结构
数据库·redis·缓存
problc8 小时前
大模型API和秘钥获取地址
数据库·redis·缓存
Rover.x9 小时前
内存泄漏问题排查
java·linux·服务器·缓存
木宇(记得热爱生活)10 小时前
Qt GUI缓存实现
开发语言·qt·缓存
Antonio91512 小时前
【Redis】 Redis 基础命令和原理
数据库·redis·缓存
daixin88481 天前
什么是缓存雪崩?缓存击穿?缓存穿透?分别如何解决?什么是缓存预热?
java·开发语言·redis·缓存
daixin88481 天前
Redis过期数据的删除策略是什么?有哪些?
数据库·redis·缓存