内存管理篇-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);
相关推荐
SPC的存折7 小时前
1、Redis数据库基础
linux·运维·服务器·数据库·redis·缓存
身如柳絮随风扬14 小时前
Redis如何实现高效插入大量数据
数据库·redis·缓存
予早15 小时前
Redis 设置库的数量
数据库·redis·缓存
黑金IT15 小时前
vLLM本地缓存实战,重复提交直接复用不浪费算力
人工智能·缓存
Rick199317 小时前
Redis查询为什么快
数据库·redis·缓存
Rick199318 小时前
Redis 底层架构图
数据库·redis·缓存
Arva .19 小时前
Redis 数据类型
数据库·redis·缓存
笑我归无处19 小时前
Redis和数据库的数据一致性问题研究
数据库·redis·缓存
小红的布丁20 小时前
操作系统与高性能 IO:零拷贝、一次读 IO、CPU 缓存与伪共享
缓存
SPC的存折20 小时前
(自用)LNMP-Redis-Discuz5.0部署指南-openEuler24.03-测试环境
linux·运维·服务器·数据库·redis·缓存