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);