Linux中页面分配alloc_pages相关函数

返回清零的页面get_zeroed_page

c 复制代码
fastcall unsigned long get_zeroed_page(unsigned int gfp_mask)
{
        struct page * page;

        /*
         * get_zeroed_page() returns a 32-bit address, which cannot represent
         * a highmem page
         */
        BUG_ON(gfp_mask & __GFP_HIGHMEM);

        page = alloc_pages(gfp_mask, 0);
        if (page) {
                void *address = page_address(page);
                clear_page(address);
                return (unsigned long) address;
        }
        return 0;
}

1. 函数定义

c 复制代码
fastcall unsigned long get_zeroed_page(unsigned int gfp_mask)
  • fastcall: 一个编译器宏,表示这个函数使用寄存器来传递参数,而不是通过堆栈,这样可以提高函数调用速度
  • unsigned long: 返回值类型,返回分配页的虚拟地址
  • get_zeroed_page: 函数名,明确表示获取一个内容全为零的页面
  • unsigned int gfp_mask: 参数,GFP(Get Free Pages)标志,控制分配行为

2. 局部变量声明

c 复制代码
        struct page * page;
  • struct page * page : 声明一个指向 page 结构体的指针,用于保存 alloc_pages 返回的页面描述符

3. HIGHMEM 检查断言

c 复制代码
        /*
         * get_zeroed_page() returns a 32-bit address, which cannot represent
         * a highmem page
         */
        BUG_ON(gfp_mask & __GFP_HIGHMEM);
  • 这个函数返回32位地址,无法表示高端内存页面
  • BUG_ON(gfp_mask & __GFP_HIGHMEM) :
    • __GFP_HIGHMEM 标志表示允许从高端内存区域分配页面。
    • BUG_ON(condition) 是一个内核宏,如果条件为真,会触发内核错误导致系统崩溃
    • 作用: 确保调用者没有请求高端内存,因为此函数无法处理高端内存页面

4. 分配单个页面

c 复制代码
        page = alloc_pages(gfp_mask, 0);
  • alloc_pages(gfp_mask, 0) : 核心分配函数调用
    • gfp_mask : GFP分配标志,如 GFP_KERNEL, GFP_ATOMIC
    • 0 : 阶数参数,2^0 = 1,表示分配单个页面
    • 返回值 : 成功返回 struct page *,失败返回 NULL

5. 页面处理逻辑

c 复制代码
        if (page) {
                void *address = page_address(page);
                clear_page(address);
                return (unsigned long) address;
        }
  • if (page): 检查页面分配是否成功
  • void *address = page_address(page) :
    • page_address(page) : 将 struct page * 转换为内核虚拟地址
    • 这个函数只对低端内存有效,因为低端内存有直接的线性映射
  • clear_page(address) :
    • 内核函数,将指定页面内容全部清零
  • return (unsigned long) address :
    • 将虚拟地址转换为 unsigned long 类型并返回
    • 这就是调用者得到的清零后的页面地址

6. 分配失败处理

c 复制代码
        return 0;
  • 执行条件 : 当 alloc_pages 返回 NULL(分配失败)时执行
  • 返回值 : 返回 0,表示分配失败

内核页面分配器alloc_pages

c 复制代码
#define alloc_pages(gfp_mask, order) \
                alloc_pages_node(numa_node_id(), gfp_mask, order)
static inline struct page *alloc_pages_node(int nid, unsigned int gfp_mask,
                                                unsigned int order)
{
        if (unlikely(order >= MAX_ORDER))
                return NULL;

        return __alloc_pages(gfp_mask, order,
                NODE_DATA(nid)->node_zonelists + (gfp_mask & GFP_ZONEMASK));
}
struct page * fastcall
__alloc_pages(unsigned int gfp_mask, unsigned int order,
                struct zonelist *zonelist)
{
        const int wait = gfp_mask & __GFP_WAIT;
        unsigned long min;
        struct zone **zones, *z;
        struct page *page;
        struct reclaim_state reclaim_state;
        struct task_struct *p = current;
        int i;
        int alloc_type;
        int do_retry;
        int can_try_harder;

        might_sleep_if(wait);

        /*
         * The caller may dip into page reserves a bit more if the caller
         * cannot run direct reclaim, or is the caller has realtime scheduling
         * policy
         */
        can_try_harder = (unlikely(rt_task(p)) && !in_interrupt()) || !wait;

        zones = zonelist->zones;  /* the list of zones suitable for gfp_mask */

        if (unlikely(zones[0] == NULL)) {
                /* Should this ever happen?? */
                return NULL;
        }

        alloc_type = zone_idx(zones[0]);

        /* Go through the zonelist once, looking for a zone with enough free */
        for (i = 0; (z = zones[i]) != NULL; i++) {
                min = z->pages_low + (1<<order) + z->protection[alloc_type];

                if (z->free_pages < min)
                        continue;

                page = buffered_rmqueue(z, order, gfp_mask);
                if (page)
                        goto got_pg;
        }

        for (i = 0; (z = zones[i]) != NULL; i++)
                wakeup_kswapd(z);
        /*
         * Go through the zonelist again. Let __GFP_HIGH and allocations
         * coming from realtime tasks to go deeper into reserves
         */
        for (i = 0; (z = zones[i]) != NULL; i++) {
                min = z->pages_min;
                if (gfp_mask & __GFP_HIGH)
                        min /= 2;
                if (can_try_harder)
                        min -= min / 4;
                min += (1<<order) + z->protection[alloc_type];

                if (z->free_pages < min)
                        continue;

                page = buffered_rmqueue(z, order, gfp_mask);
                if (page)
                        goto got_pg;
        }

        /* This allocation should allow future memory freeing. */
        if ((p->flags & (PF_MEMALLOC | PF_MEMDIE)) && !in_interrupt()) {
                /* go through the zonelist yet again, ignoring mins */
                for (i = 0; (z = zones[i]) != NULL; i++) {
                        page = buffered_rmqueue(z, order, gfp_mask);
                        if (page)
                                goto got_pg;
                }
                goto nopage;
        }

        /* Atomic allocations - we can't balance anything */
        if (!wait)
                goto nopage;

rebalance:
        /* We now go into synchronous reclaim */
        p->flags |= PF_MEMALLOC;
        reclaim_state.reclaimed_slab = 0;
        p->reclaim_state = &reclaim_state;

        try_to_free_pages(zones, gfp_mask, order);

        p->reclaim_state = NULL;
        p->flags &= ~PF_MEMALLOC;

        /* go through the zonelist yet one more time */
        for (i = 0; (z = zones[i]) != NULL; i++) {
                min = z->pages_min;
                if (gfp_mask & __GFP_HIGH)
                        min /= 2;
                if (can_try_harder)
                        min -= min / 4;
                min += (1<<order) + z->protection[alloc_type];

                if (z->free_pages < min)
                        continue;

                page = buffered_rmqueue(z, order, gfp_mask);
                if (page)
                        goto got_pg;
        }

        /*
         * Don't let big-order allocations loop unless the caller explicitly
         * requests that.  Wait for some write requests to complete then retry.
         *
         * In this implementation, __GFP_REPEAT means __GFP_NOFAIL for order
         * <= 3, but that may not be true in other implementations.
         */
        do_retry = 0;
        if (!(gfp_mask & __GFP_NORETRY)) {
                if ((order <= 3) || (gfp_mask & __GFP_REPEAT))
                        do_retry = 1;
                if (gfp_mask & __GFP_NOFAIL)
                        do_retry = 1;
        }
        if (do_retry) {
                blk_congestion_wait(WRITE, HZ/50);
                goto rebalance;
        }

nopage:
        if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit()) {
                printk(KERN_WARNING "%s: page allocation failure."
                        " order:%d, mode:0x%x\n",
                        p->comm, order, gfp_mask);
                dump_stack();
        }
        return NULL;
got_pg:
        zone_statistics(zonelist, z);
        kernel_map_pages(page, 1 << order, 1);
        return page;
}

1. 函数定义和宏展开

c 复制代码
#define alloc_pages(gfp_mask, order) \
                alloc_pages_node(numa_node_id(), gfp_mask, order)
  • 作用 : alloc_pages 宏展开为 alloc_pages_node,自动传入当前NUMA节点ID。
c 复制代码
static inline struct page *alloc_pages_node(int nid, unsigned int gfp_mask,
                                                unsigned int order)
{
        if (unlikely(order >= MAX_ORDER))
                return NULL;
        return __alloc_pages(gfp_mask, order,
                NODE_DATA(nid)->node_zonelists + (gfp_mask & GFP_ZONEMASK));
}
  • 参数:

    • nid: NUMA节点ID
    • gfp_mask: GFP分配标志
    • order: 分配阶数(2^order个页面)
  • 检查 : order >= MAX_ORDER 时直接返回NULL(阶数过大)

  • 计算zonelist: 根据节点数据和GFP掩码中的区域掩码选择合适的内存区域列表

    NODE_DATA(nid)

    • 作用: 获取指定NUMA节点的描述符

    ->node_zonelists

    • 作用 : 访问节点的zonelist数组
    • 说明 : 每个NUMA节点有多个zonelist,用于不同的分配策略

    GFP_ZONEMASK

    • 作用 : 从gfp_mask中提取zone类型选择位

    gfp_mask & GFP_ZONEMASK

    • 作用: 提取GFP掩码中的zone选择标志

    示例1: 普通内核分配

    c 复制代码
    gfp_mask = GFP_KERNEL;  // 通常不包含特殊zone标志
    gfp_mask & GFP_ZONEMASK = 0;
    NODE_DATA(nid)->node_zonelists + 0  // 使用默认zonelist[0]
c 复制代码
struct page * fastcall
__alloc_pages(unsigned int gfp_mask, unsigned int order,
                struct zonelist *zonelist)
  • 函数定义: 实际的页面分配核心函数
  • 参数 :
    • gfp_mask: 分配标志
    • order: 分配阶数
    • zonelist: 适合的内存区域列表

2. 变量声明

c 复制代码
        const int wait = gfp_mask & __GFP_WAIT;
        unsigned long min;
        struct zone **zones, *z;
        struct page *page;
        struct reclaim_state reclaim_state;
        struct task_struct *p = current;
        int i;
        int alloc_type;
        int do_retry;
        int can_try_harder;
  • wait: 判断是否允许等待和回收
  • min: 计算每个zone需要的最小空闲页面
  • zones, z: 内存区域数组和当前区域指针
  • page: 返回的页面指针
  • reclaim_state: 页面回收状态
  • p = current: 当前进程指针
  • 其他: 循环计数器、分配类型、重试标志等

3. 初始检查和设置

c 复制代码
        might_sleep_if(wait);
  • 作用: 如果允许等待,那么这里可能睡眠,在原子上下文中将报错
c 复制代码
        can_try_harder = (unlikely(rt_task(p)) && !in_interrupt()) || !wait;
  • 计算: 实时任务且不在中断上下文,或者不允许等待时,可以更努力尝试分配
c 复制代码
        zones = zonelist->zones;  /* the list of zones suitable for gfp_mask */
  • 获取 : 从zonelist中提取实际的zone数组
c 复制代码
        if (unlikely(zones[0] == NULL)) {
                /* Should this ever happen?? */
                return NULL;
        }
  • 检查: 如果zone列表为空(不应该发生),直接返回NULL
c 复制代码
        alloc_type = zone_idx(zones[0]);
  • 记录: 第一个zone的索引作为分配类型

  • #define zone_idx(zone) ((zone) - (zone)->zone_pgdat->node_zones)

    (zone)->zone_pgdat

    • 作用: 访问该zone所属的NUMA节点描述符
    • 关系: 每个zone都知道它属于哪个NUMA节点

    (zone)->zone_pgdat->node_zones

    • 作用: 访问该节点中zones数组的起始地址
    • 类型 : struct zone node_zones[MAX_NR_ZONES](zone数组)
    • 说明: 这是节点中所有内存区域的数组

    (zone) - (zone)->zone_pgdat->node_zones

    • 操作: 指针减法
    • 含义: 计算目标zone在节点zones数组中的偏移量(即索引)

4. 第一次分配尝试 - 宽松条件

c 复制代码
        /* Go through the zonelist once, looking for a zone with enough free */
        for (i = 0; (z = zones[i]) != NULL; i++) {
                min = z->pages_low + (1<<order) + z->protection[alloc_type];
  • 循环: 遍历所有合适的zone

  • min计算 : pages_low(低水位线) + 所需页面数 + 保护值

    z->pages_low - 低水位线

    • 含义: 当空闲页面低于这个值时,系统开始压力回收

    (1<<order) - 请求的页面数量

    • 作用: 计算本次分配请求需要多少连续页面
    • <<操作: 左移运算,相当于2的order次方

    z->protection[alloc_type] - 保护机制

    • protection数组: 每个zone对其他zone的保护值
    • alloc_type : 之前计算的 zone_idx(zones[0])
    • 作用: 防止高优先级zone耗尽低优先级zone的内存
c 复制代码
                if (z->free_pages < min)
                        continue;
  • 检查: 如果空闲页面不足,跳过这个zone
c 复制代码
                page = buffered_rmqueue(z, order, gfp_mask);
                if (page)
                        goto got_pg;
        }
  • 尝试分配: 从zone的per-CPU缓存中分配页面
  • 成功 : 跳转到 got_pg 标签返回页面

5. 唤醒kswapd和第二次尝试 - 中等条件

c 复制代码
        for (i = 0; (z = zones[i]) != NULL; i++)
                wakeup_kswapd(z);
  • 唤醒回收 : 唤醒每个zone的kswapd内核线程开始异步页面回收
c 复制代码
        for (i = 0; (z = zones[i]) != NULL; i++) {
                min = z->pages_min;
                if (gfp_mask & __GFP_HIGH)
                        min /= 2;
                if (can_try_harder)
                        min -= min / 4;
                min += (1<<order) + z->protection[alloc_type];
  • 新的min计算 :
    • 基础: pages_min(最小水位线)
    • __GFP_HIGH: 降低要求一半
    • can_try_harder: 再降低25%
    • 加上所需页面和保护值
c 复制代码
                if (z->free_pages < min)
                        continue;

                page = buffered_rmqueue(z, order, gfp_mask);
                if (page)
                        goto got_pg;
        }
  • 再次尝试分配: 使用更宽松的条件

6. 内存分配者特权路径

c 复制代码
        if ((p->flags & (PF_MEMALLOC | PF_MEMDIE)) && !in_interrupt()) {
  • 条件: 当前进程是内存分配者或正在死亡,且不在中断上下文
c 复制代码
                for (i = 0; (z = zones[i]) != NULL; i++) {
                        page = buffered_rmqueue(z, order, gfp_mask);
                        if (page)
                                goto got_pg;
                }
                goto nopage;
        }
  • 特权分配: 忽略所有水位线限制,直接尝试分配
  • PF_MEMALLOC: 确保内存回收等关键操作永远有内存可用
  • PF_MEMDIE: 确保被杀死进程能够优雅退出
  • 失败 : 跳转到 nopage

7. 原子分配处理

c 复制代码
        if (!wait)
                goto nopage;
  • 原子分配: 不允许等待时直接失败

8. 同步回收路径

c 复制代码
rebalance:
        p->flags |= PF_MEMALLOC;
        reclaim_state.reclaimed_slab = 0;
        p->reclaim_state = &reclaim_state;
  • 设置标志: 标记为内存分配者
  • 初始化: 设置回收状态
c 复制代码
        try_to_free_pages(zones, gfp_mask, order);
  • 核心回收: 同步尝试释放页面
c 复制代码
        p->reclaim_state = NULL;
        p->flags &= ~PF_MEMALLOC;
  • 清理: 恢复进程状态
c 复制代码
        for (i = 0; (z = zones[i]) != NULL; i++) {
                min = z->pages_min;
                if (gfp_mask & __GFP_HIGH)
                        min /= 2;
                if (can_try_harder)
                        min -= min / 4;
                min += (1<<order) + z->protection[alloc_type];

                if (z->free_pages < min)
                        continue;

                page = buffered_rmqueue(z, order, gfp_mask);
                if (page)
                        goto got_pg;
        }
  • 第三次尝试: 回收后再次尝试分配

9. 重试逻辑

c 复制代码
        do_retry = 0;
        if (!(gfp_mask & __GFP_NORETRY)) {
                if ((order <= 3) || (gfp_mask & __GFP_REPEAT))
                        do_retry = 1;
                if (gfp_mask & __GFP_NOFAIL)
                        do_retry = 1;
        }
  • 重试条件 :
    • 没有设置 __GFP_NORETRY
    • 小阶数(≤8页)或设置了 __GFP_REPEAT
    • 设置了 __GFP_NOFAIL(必须成功)
c 复制代码
        if (do_retry) {
                blk_congestion_wait(WRITE, HZ/50);
                goto rebalance;
        }
  • 实际重试: 等待IO拥塞缓解,然后回到回收路径

10. 失败处理

c 复制代码
nopage:
        if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit()) {
                printk(KERN_WARNING "%s: page allocation failure."
                        " order:%d, mode:0x%x\n",
                        p->comm, order, gfp_mask);
                dump_stack();
        }
        return NULL;
  • 警告 : 除非设置了 __GFP_NOWARN,否则打印分配失败信息
  • 返回: 返回NULL表示失败

11. 成功处理

c 复制代码
got_pg:
        zone_statistics(zonelist, z);
        kernel_map_pages(page, 1 << order, 1);
        return page;
  • 统计: 更新zone统计信息
  • 映射: 内核映射页面
  • 返回: 返回分配的页面

处理块设备IO拥塞的等待函blk_congestion_wait

c 复制代码
long blk_congestion_wait(int rw, long timeout)
{
        long ret;
        DEFINE_WAIT(wait);
        wait_queue_head_t *wqh = &congestion_wqh[rw];

        prepare_to_wait(wqh, &wait, TASK_UNINTERRUPTIBLE);
        ret = io_schedule_timeout(timeout);
        finish_wait(wqh, &wait);
        return ret;
}

1. 函数定义

c 复制代码
long blk_congestion_wait(int rw, long timeout)
  • 返回值 : long - 剩余的等待时间
  • 参数 :
    • rw: 读写方向,READWRITE
    • timeout: 最大等待时间(单位:jiffies)

2. 逐行代码解析

2.1. 变量声明

c 复制代码
        long ret;
        DEFINE_WAIT(wait);
        wait_queue_head_t *wqh = &congestion_wqh[rw];

long ret:

  • 存储函数返回值,表示剩余的等待时间

DEFINE_WAIT(wait):

  • 宏,定义并初始化一个等待队列条目

wait_queue_head_t *wqh = &congestion_wqh[rw]:

  • congestion_wqh: 全局的拥塞等待队列数组
  • rw 作为索引:[READ][WRITE] 分别有独立的等待队列
  • 这样读写操作可以在不同的队列中等待,互不干扰

2.2. 准备等待

c 复制代码
        prepare_to_wait(wqh, &wait, TASK_UNINTERRUPTIBLE);

作用: 将当前进程加入到拥塞等待队列,并设置进程状态。

参数:

  • wqh: 等待队列头(读或写队列)
  • &wait: 等待队列条目
  • TASK_UNINTERRUPTIBLE: 进程状态 - 不可中断睡眠

不可中断睡眠的特点:

  • 进程不会响应信号(如Ctrl+C)
  • 只能被特定的唤醒操作唤醒
  • 适用于必须完成的关键操作

2.3. 调度超时

c 复制代码
        ret = io_schedule_timeout(timeout);

io_schedule_timeout(timeout):

  • 让出CPU,进入睡眠状态
  • 最多睡眠 timeout 个jiffies
  • 返回剩余的等待时间

工作流程:

  1. 将当前进程状态设置为睡眠
  2. 调用调度器选择其他进程运行
  3. 当被唤醒或超时时,重新被调度执行
  4. 返回剩余的时间:剩余时间 = timeout - 已等待时间

返回值含义:

  • > 0: 超时前被唤醒,返回剩余时间
  • = 0: 正常超时
  • < 0: 错误情况

2.4. 完成等待

c 复制代码
        finish_wait(wqh, &wait);

作用: 清理等待状态,将进程从等待队列中移除。

具体操作:

  • 将进程状态设置回 TASK_RUNNING(可运行)
  • 从等待队列 wqh 中移除 wait 条目
  • 如果进程已经被自动移除(被唤醒时),则不做重复操作

2.5. 返回结果

c 复制代码
        return ret;

返回剩余的等待时间给调用者。

3. 拥塞管理机制

拥塞触发条件

当块设备的请求队列过于繁忙时:

  • 太多未完成的IO请求
  • 设备处理能力达到上限
  • 需要等待一些IO完成才能继续

4. 设计意义

  1. 避免忙等待: 让出CPU而不是循环检查,提高系统效率
  2. 响应拥塞: 感知块设备状态,在拥塞时适当等待
  3. 协同工作: 内存分配与IO子系统协同,共同应对系统压力
  4. 避免活锁: 在内存紧张时通过短暂等待打破竞争状态

页面分配buffered_rmqueue

c 复制代码
static struct page *
buffered_rmqueue(struct zone *zone, int order, int gfp_flags)
{
        unsigned long flags;
        struct page *page = NULL;
        int cold = !!(gfp_flags & __GFP_COLD);

        if (order == 0) {
                struct per_cpu_pages *pcp;

                pcp = &zone->pageset[get_cpu()].pcp[cold];
                local_irq_save(flags);
                if (pcp->count <= pcp->low)
                        pcp->count += rmqueue_bulk(zone, 0,
                                                pcp->batch, &pcp->list);
                if (pcp->count) {
                        page = list_entry(pcp->list.next, struct page, lru);
                        list_del(&page->lru);
                        pcp->count--;
                }
                local_irq_restore(flags);
                put_cpu();
        }

        if (page == NULL) {
                spin_lock_irqsave(&zone->lock, flags);
                page = __rmqueue(zone, order);
                spin_unlock_irqrestore(&zone->lock, flags);
        }

        if (page != NULL) {
                BUG_ON(bad_range(zone, page));
                mod_page_state_zone(zone, pgalloc, 1 << order);
                prep_new_page(page, order);
                if (order && (gfp_flags & __GFP_COMP))
                        prep_compound_page(page, order);
        }
        return page;
}

1. 函数定义和变量声明

c 复制代码
static struct page *
buffered_rmqueue(struct zone *zone, int order, int gfp_flags)
{
        unsigned long flags;
        struct page *page = NULL;
        int cold = !!(gfp_flags & __GFP_COLD);
  • static struct page *: 静态函数,返回页面指针
  • zone: 要从哪个内存区域分配
  • order: 分配阶数(2^order个连续页面)
  • gfp_flags: GFP分配标志
  • flags: 保存中断状态,用于本地中断控制
  • page = NULL: 初始化返回的页面指针为NULL
  • cold = !!(gfp_flags & __GFP_COLD) :
    • gfp_flags & __GFP_COLD: 检查是否请求冷页面
    • !!: 双重逻辑非,让结果转换成0或者1
    • 冷页面: 不太可能被很快访问的页面,放在Per-CPU列表的尾部
    • 热页面: 可能被很快访问的页面,放在Per-CPU列表的头部

2. 单页分配路径(order == 0)

c 复制代码
        if (order == 0) {
                struct per_cpu_pages *pcp;

                pcp = &zone->pageset[get_cpu()].pcp[cold];
  • if (order == 0): 单页分配的特殊优化路径
  • struct per_cpu_pages *pcp: Per-CPU页面缓存结构指针
  • get_cpu(): 获取当前CPU ID并禁用内核抢占
  • zone->pageset[get_cpu()].pcp[cold] :
    • 获取当前CPU在指定zone的pageset
    • pcp[cold]: 根据冷热页面选择对应的Per-CPU列表
    • pcp[0]: 热页面列表,pcp[1]: 冷页面列表
c 复制代码
                local_irq_save(flags);
  • 保存本地中断状态并禁用中断,防止并发访问Per-CPU缓存
c 复制代码
                if (pcp->count <= pcp->low)
                        pcp->count += rmqueue_bulk(zone, 0,
                                                pcp->batch, &pcp->list);
  • pcp->count: 当前Per-CPU缓存中的页面数量
  • pcp->low: 低水位线,当页面数量低于此值时需要补充
  • pcp->batch: 每次批量补充的页面数量
  • rmqueue_bulk(zone, 0, pcp->batch, &pcp->list) :
    • 从zone的伙伴系统中批量分配单个页面
    • 参数:zone, order=0, 数量=batch, 添加到pcp->list
    • 返回实际分配的页面数量
  • 作用: 如果缓存不足,从伙伴系统批量补充页面
c 复制代码
                if (pcp->count) {
                        page = list_entry(pcp->list.next, struct page, lru);
                        list_del(&page->lru);
                        pcp->count--;
                }
  • if (pcp->count): 如果缓存中有页面可用
  • list_entry(pcp->list.next, struct page, lru) :
    • 从链表头部获取第一个页面
    • list_entry宏:通过成员指针获取包含它的结构体指针
    • pcp->list.next: 链表中的第一个页面
    • lru: 页面结构体中用于链接的list_head成员
  • list_del(&page->lru): 从链表中删除这个页面
  • pcp->count--: 减少缓存中的页面计数
c 复制代码
                local_irq_restore(flags);
                put_cpu();
        }
  • local_irq_restore(flags): 恢复中断状态
  • put_cpu(): 启用内核抢占

3. 多页分配或单页分配失败路径

c 复制代码
        if (page == NULL) {
                spin_lock_irqsave(&zone->lock, flags);
                page = __rmqueue(zone, order);
                spin_unlock_irqrestore(&zone->lock, flags);
        }
  • if (page == NULL): 如果Per-CPU缓存分配失败(或order>0)
  • spin_lock_irqsave(&zone->lock, flags): 获取zone锁并保存中断状态
  • page = __rmqueue(zone, order): 核心函数,从伙伴系统分配连续页面
  • spin_unlock_irqrestore(&zone->lock, flags): 释放zone锁并恢复中断

4. 页面后期处理

c 复制代码
        if (page != NULL) {
                BUG_ON(bad_range(zone, page));
  • if (page != NULL): 如果成功分配到页面
  • BUG_ON(bad_range(zone, page)) :
    • bad_range(zone, page): 检查页面是否在zone的有效范围内
    • BUG_ON(): 如果检查失败,触发内核错误(严重bug)
c 复制代码
                mod_page_state_zone(zone, pgalloc, 1 << order);
  • 更新页面分配统计信息
    • zone: 目标内存区域
    • pgalloc: 页面分配计数器
    • 1 << order: 分配的页面数量
c 复制代码
                prep_new_page(page, order);
  • 准备新页面
    • 设置页面标志位
    • 初始化页面结构
    • 清除页面内容(如果需要)
c 复制代码
                if (order && (gfp_flags & __GFP_COMP))
                        prep_compound_page(page, order);
        }
  • if (order && (gfp_flags & __GFP_COMP)) :
    • 如果分配多个页面且请求复合页面
    • __GFP_COMP: 允许复合页面(用于大页等)
  • prep_compound_page(page, order): 设置复合页面结构
c 复制代码
        return page;
}
  • 返回分配的页面指针(成功)或NULL(失败)

从内存区域批量分配页面块到指定链表rmqueue_bulk

c 复制代码
static int rmqueue_bulk(struct zone *zone, unsigned int order,
                        unsigned long count, struct list_head *list)
{
        unsigned long flags;
        int i;
        int allocated = 0;
        struct page *page;

        spin_lock_irqsave(&zone->lock, flags);
        for (i = 0; i < count; ++i) {
                page = __rmqueue(zone, order);
                if (page == NULL)
                        break;
                allocated++;
                list_add_tail(&page->lru, list);
        }
        spin_unlock_irqrestore(&zone->lock, flags);
        return allocated;
}

1. 函数定义和变量声明

c 复制代码
static int rmqueue_bulk(struct zone *zone, unsigned int order,
                        unsigned long count, struct list_head *list)
  • static int: 静态函数,只在当前文件内可见,返回实际分配的页面块数量
  • zone: 目标内存区域指针,要从这个zone分配页面
  • order: 分配阶数,每个页面块包含 2^order 个连续物理页面
  • count: 请求分配的页面块数量
  • list: 目标链表头,分配的页面块将添加到这个链表中
c 复制代码
        unsigned long flags;
  • flags: 用于保存中断状态的无符号长整型变量,在操作自旋锁时使用
c 复制代码
        int i;
  • i: 循环计数器,用于控制分配循环的次数
c 复制代码
        int allocated = 0;
  • allocated: 实际成功分配的页面块计数器,初始化为0
c 复制代码
        struct page *page;
  • page : 指向分配到的页面块的指针,struct page 是内核中表示物理页面的数据结构

2. 加锁和循环分配

c 复制代码
        spin_lock_irqsave(&zone->lock, flags);
  • spin_lock_irqsave(&zone->lock, flags) :
    • 获取zone结构的自旋锁,并保存当前中断状态到flags变量中
    • 这会禁用本地CPU的中断,防止中断处理程序并发访问zone的空闲列表
    • 保护zone的空闲页面数据结构不被多个CPU同时修改
c 复制代码
        for (i = 0; i < count; ++i) {
  • 循环开始 : 尝试分配count个页面块
    • i = 0: 循环计数器从0开始
    • i < count: 循环条件,最多分配count次
    • ++i: 每次循环后计数器加1
c 复制代码
                page = __rmqueue(zone, order);
  • 核心分配调用 :
    • __rmqueue(zone, order): 从zone的伙伴系统中分配一个2^order大小的连续页面块
    • 这是实际执行伙伴算法进行内存分配的函数
    • 返回指向第一个页面的struct page指针,如果分配失败则返回NULL
c 复制代码
                if (page == NULL)
                        break;
  • 分配失败处理 :
    • 检查__rmqueue是否返回NULL(表示分配失败)
    • 如果分配失败,立即执行break语句跳出循环
    • 此时allocated变量记录的是已经成功分配的页面块数量
c 复制代码
                allocated++;
  • 成功分配计数 :
    • 只有当页面分配成功时才会执行到这里
    • allocated++: 将成功分配的页面块计数器加1
c 复制代码
                list_add_tail(&page->lru, list);
  • 页面添加到链表:

    • &page->lru: 获取页面结构中链表节点的地址
      • lrustruct page中用于链接的list_head成员
    • list_add_tail(&page->lru, list): 将页面添加到链表的尾部
  • 循环结束: 循环体结束,可能因以下原因退出:

    • 正常完成:成功分配了count个页面块(i == count
    • 提前退出:分配失败跳出(i < count

3. 清理和返回

c 复制代码
        spin_unlock_irqrestore(&zone->lock, flags);
  • 释放锁和恢复中断 :
    • spin_unlock_irqrestore(&zone->lock, flags):
    • 释放zone的自旋锁,允许其他CPU访问zone
    • 恢复中断状态到加锁前的设置(重新启用中断如果之前是开启的)
    • flags参数确保正确恢复之前的中断状态
c 复制代码
        return allocated;
  • 返回结果 :
    • 返回实际成功分配的页面块数量
    • 如果全部成功,allocated == count
    • 如果部分成功,0 < allocated < count
    • 如果完全失败,allocated == 0

分配连续物理页面块__rmqueue

c 复制代码
static struct page *__rmqueue(struct zone *zone, unsigned int order)
{
        struct free_area * area;
        unsigned int current_order;
        struct page *page;
        unsigned int index;

        for (current_order = order; current_order < MAX_ORDER; ++current_order) {
                area = zone->free_area + current_order;
                if (list_empty(&area->free_list))
                        continue;

                page = list_entry(area->free_list.next, struct page, lru);
                list_del(&page->lru);
                index = page - zone->zone_mem_map;
                if (current_order != MAX_ORDER-1)
                        MARK_USED(index, current_order, area);
                zone->free_pages -= 1UL << order;
                return expand(zone, page, index, order, current_order, area);
        }

        return NULL;
}

1. 函数功能

从指定内存区域的伙伴系统中分配指定阶数的连续物理页面块。这是伙伴分配器的核心实现,通过从高阶到低阶的搜索和拆分机制来满足分配请求

2. 逐行代码解析

c 复制代码
static struct page *__rmqueue(struct zone *zone, unsigned int order)
{
  • static struct page *: 静态函数,返回分配的页面块指针
  • zone: 目标内存区域
  • order: 请求的分配阶数(需要 2^order 个连续页面)
c 复制代码
        struct free_area * area;
  • area: 指向free_area结构的指针,用于访问不同阶数的空闲链表
c 复制代码
        unsigned int current_order;
  • current_order: 当前搜索的阶数,从请求的order开始向上搜索
c 复制代码
        struct page *page;
  • page: 指向分配到的页面块的指针
c 复制代码
        unsigned int index;
  • index: 页面在zone内存映射数组中的索引
c 复制代码
        for (current_order = order; current_order < MAX_ORDER; ++current_order) {
  • 循环开始: 从请求的order开始,向高阶搜索直到MAX_ORDER
  • current_order = order: 初始化当前阶数为请求的阶数
  • current_order < MAX_ORDER: 循环条件,不超过最大阶数
  • ++current_order: 每次循环阶数加1,向更高阶搜索
c 复制代码
                area = zone->free_area + current_order;
  • 获取当前阶数的free_area :
    • zone->free_area: 指向zone的free_area数组起始位置
    • zone->free_area + current_order: 计算第current_order阶的free_area地址
    • free_area数组按阶数索引,每个元素管理该阶数的空闲页面块
c 复制代码
                if (list_empty(&area->free_list))
                        continue;
  • 检查空闲链表是否为空 :
    • list_empty(&area->free_list): 检查该阶数的空闲链表是否为空
    • 如果为空,执行continue跳过当前阶数,继续搜索更高阶数
    • 这实现了"最佳适配"策略:使用能满足要求的最小可用块
c 复制代码
                page = list_entry(area->free_list.next, struct page, lru);
  • 从链表中获取页面 :
    • area->free_list.next: 空闲链表的第一个节点
    • list_entry(area->free_list.next, struct page, lru):
      • 通过链表节点指针获取包含它的struct page结构体指针
      • lru是struct page中用于链接的list_head成员
c 复制代码
                list_del(&page->lru);
  • 从空闲链表中删除页面 :
    • list_del(&page->lru): 将页面从其所在的空闲链表中移除
    • 现在这个页面块不再属于空闲状态
c 复制代码
                index = page - zone->zone_mem_map;
  • 计算页面索引 :
    • page - zone->zone_mem_map: 指针减法,计算页面在zone内存映射中的索引
    • zone->zone_mem_map: 指向zone中第一个页面的指针
    • 结果是页面在zone中的偏移量(页面号)
c 复制代码
                if (current_order != MAX_ORDER-1)
                        MARK_USED(index, current_order, area);
  • 标记页面块为已使用 :
    • current_order != MAX_ORDER-1: 如果不是最高阶,需要标记位图
    • MARK_USED(index, current_order, area):
      • 宏,在位图中标记该页面块为已使用
      • 参数:页面索引、当前阶数、free_area指针
      • 用于伙伴系统的合并操作时判断相邻块的状态
c 复制代码
                zone->free_pages -= 1UL << order;
  • 更新zone的空闲页面计数 :
    • 1UL << order: 计算实际分配的页面数量(2^order)
    • zone->free_pages -= ...: 从zone的总空闲页面中减去分配的数量
    • 注意:这里减去的是请求的order对应的页面数,不是当前order
c 复制代码
                return expand(zone, page, index, order, current_order, area);
  • 调用expand处理块拆分:

    • expand(zone, page, index, order, current_order, area):
      • 如果当前阶数大于请求阶数,需要拆分大的页面块
      • 返回请求阶数的页面块,将剩余部分放回空闲链表
  • 循环结束: 如果所有阶数都搜索完毕仍未找到可用块,继续执行

c 复制代码
        return NULL;
  • 分配失败: 返回NULL指针,表示无法满足分配请

3. 关键设计要点

  1. 最佳适配策略

    • 从请求阶数开始向上搜索,使用能满足要求的最小可用块
    • 减少内存碎片,提高内存利用率
  2. 伙伴系统核心

    • 通过高阶块的拆分来满足低阶请求
    • 通过低阶块的合并来形成高阶块
  3. 位图管理

    • MARK_USED设置位图标志,记录页面块状态
    • 用于后续的伙伴合并判断

拆分高阶连续页面块expand

c 复制代码
static inline struct page *
expand(struct zone *zone, struct page *page,
         unsigned long index, int low, int high, struct free_area *area)
{
        unsigned long size = 1 << high;

        while (high > low) {
                area--;
                high--;
                size >>= 1;
                BUG_ON(bad_range(zone, &page[size]));
                list_add(&page[size].lru, &area->free_list);
                MARK_USED(index + size, high, area);
        }
        return page;
}

1. 函数功能

将高阶的连续页面块拆分为请求的低阶页面块。这是伙伴分配器的核心拆分机制,将一个大内存块逐步拆分成所需大小的块,并将剩余部分放回相应的空闲链表

2. 逐行代码解析

c 复制代码
static inline struct page *
expand(struct zone *zone, struct page *page,
         unsigned long index, int low, int high, struct free_area *area)
{
  • static inline struct page *: 静态内联函数,返回拆分后的页面块指针
  • zone: 目标内存区域
  • page: 要拆分的高阶页面块的起始页面指针
  • index: 起始页面在zone内存映射中的索引
  • low: 请求的低阶数(目标阶数)
  • high: 当前的高阶数(实际找到的阶数)
  • area: 当前高阶数对应的free_area指针
c 复制代码
        unsigned long size = 1 << high;
  • 计算当前块的大小 :
    • 1 << high: 计算高阶块包含的页面数量
    • size 表示当前要拆分的块的总页面数
c 复制代码
        while (high > low) {
  • 循环拆分条件 :
    • high > low: 当当前阶数大于目标阶数时继续循环
    • 循环将高阶块逐步拆分成低阶块,直到达到目标阶数
c 复制代码
                area--;
  • 移动到低一级的free_area :
    • area--: 将free_area指针指向前一个(低一阶的)free_area
    • 因为free_area数组是按阶数递增排列的:&free_area[0], &free_area[1], ...
    • area-- 指向更小阶数的空闲区域管理结构
c 复制代码
                high--;
  • 降低当前阶数 :
    • high--: 当前阶数减1
    • 例如:从order3降到order2,然后order2降到order1
c 复制代码
                size >>= 1;
  • 块大小减半 :
    • size >>= 1: 右移一位,相当于除以2
    • 每次拆分块大小减半,符合伙伴系统的二分特性
    • 例如:size从8→4→2(当high从3→1时)
c 复制代码
                BUG_ON(bad_range(zone, &page[size]));
  • 范围检查 :
    • &page[size]: 计算后半部分块的起始页面指针
      • page 是前半部分块的起始
      • page + size 是后半部分块的起始
    • bad_range(zone, &page[size]): 检查后半部分块是否在zone的有效范围内
    • BUG_ON(...): 如果范围检查失败,触发内核错误(严重bug)
c 复制代码
                list_add(&page[size].lru, &area->free_list);
  • 将后半部分块加入空闲链表 :
    • &page[size].lru: 后半部分块的链表节点地址
    • &area->free_list: 当前阶数的空闲链表头
    • list_add(&page[size].lru, &area->free_list):
      • 将后半部分块添加到对应阶数的空闲链表头部
      • 使用头插法,新块成为链表的第一个元素
c 复制代码
                MARK_USED(index + size, high, area);
  • 标记后半部分块为已使用(在空闲链表中):

    • index + size: 后半部分块的起始页面索引
      • index 是原始块的起始索引
      • index + size 是后半部分块的起始索引
    • high: 当前阶数(后半部分块的阶数)
    • area: 当前阶数的free_area指针
    • MARK_USED(index + size, high, area):
      • 在位图中标记该块的状态
      • 虽然块在空闲链表中,但标记为"已使用"是指它在伙伴系统管理下
  • 循环结束 : 当 high == low 时退出循环,此时:

    • 原始高阶块已被拆分成目标阶数的块
    • 所有剩余的半块都已放入相应的空闲链表
c 复制代码
        return page;
  • 返回前半部分块 :
    • 返回原始页面指针 page,现在它指向一个大小为 1 << low 的块
    • 这个块是拆分后留给调用者使用的目标块

原子更新页面状态mod_page_state_zone

c 复制代码
#define mod_page_state_zone(zone, member, delta)                        \
        do {                                                            \
                unsigned long flags;                                    \
                local_irq_save(flags);                                  \
                if (is_highmem(zone))                                   \
                        __get_cpu_var(page_states).member##_high += (delta);\
                else if (is_normal(zone))                               \
                        __get_cpu_var(page_states).member##_normal += (delta);\
                else                                                    \
                        __get_cpu_var(page_states).member##_dma += (delta);\
                local_irq_restore(flags);                               \
        } while (0)

1. 宏功能

以原子方式更新指定内存区域(zone)的页面统计状态。该宏根据zone的类型(DMA、NORMAL、HIGHMEM)将增量值安全地添加到对应的Per-CPU页面统计变量中。

2. 逐行代码解析

c 复制代码
#define mod_page_state_zone(zone, member, delta)                        \
  • 宏定义开始
    • mod_page_state_zone: 宏名称
    • zone: 内存区域指针参数
    • member: 统计成员名称参数(如pgallocpgfree等)
    • delta: 增量值参数(正数表示增加,负数表示减少)
c 复制代码
                unsigned long flags;                                    \
  • 中断状态变量
    • flags: 用于保存当前中断状态的无符号长整型变量
    • 在禁用/启用中断时使用,确保正确恢复中断状态
c 复制代码
                local_irq_save(flags);                                  \
  • 保存中断状态并禁用本地中断
    • local_irq_save(flags): 宏,执行以下操作:
      • 保存当前CPU的中断使能状态到flags变量
      • 禁用当前CPU的中断
    • 目的:防止中断处理程序并发修改Per-CPU统计变量
c 复制代码
                if (is_highmem(zone))                                   \
  • 检查是否为高端内存区域
    • is_highmem(zone): 宏或函数,检查zone是否为HIGHMEM类型
    • 返回真表示这是高端内存区域
c 复制代码
                        __get_cpu_var(page_states).member##_high += (delta);\
  • 更新高端内存统计
    • __get_cpu_var(page_states): 获取当前CPU的page_states变量
      • page_states是Per-CPU变量,每个CPU有自己独立的副本
      • 避免多CPU间的锁竞争
    • .member##_high:
      • member参数和_high拼接成完整的成员名
      • 例如:如果memberpgalloc,则变成pgalloc_high
    • += (delta): 将增量值加到对应的统计变量
c 复制代码
                else if (is_normal(zone))                               \
  • 检查是否为普通内存区域
    • is_normal(zone): 检查zone是否为NORMAL类型
    • 如果不是HIGHMEM,检查是否是NORMAL区域
c 复制代码
                        __get_cpu_var(page_states).member##_normal += (delta);\
  • 更新普通内存统计
    • member##_normal: 将member_normal拼接
    • 例如:pgalloc_normal
    • 将增量值加到普通内存的统计变量
c 复制代码
                else                                                    \
  • 默认情况(DMA内存区域)
    • 如果既不是HIGHMEM也不是NORMAL,则假定为DMA区域
c 复制代码
                        __get_cpu_var(page_states).member##_dma += (delta);\
  • 更新DMA内存统计
    • member##_dma: 将member_dma拼接
    • 例如:pgalloc_dma
    • 将增量值加到DMA内存的统计变量
c 复制代码
                local_irq_restore(flags);                               \
  • 恢复中断状态
    • local_irq_restore(flags): 恢复之前保存的中断状态
    • 如果之前中断是启用的,则重新启用中断
    • 如果之前是禁用的,保持禁用状态
c 复制代码
        } while (0)
  • 宏定义结束
    • while (0)确保循环只执行一次
    • 这是一种标准的宏封装技术

初始化新分配的页面prep_new_page

c 复制代码
static void prep_new_page(struct page *page, int order)
{
        if (page->mapping || page_mapped(page) ||
            (page->flags & (
                        1 << PG_private |
                        1 << PG_locked  |
                        1 << PG_lru     |
                        1 << PG_active  |
                        1 << PG_dirty   |
                        1 << PG_reclaim |
                        1 << PG_swapcache |
                        1 << PG_writeback )))
                bad_page(__FUNCTION__, page);

        page->flags &= ~(1 << PG_uptodate | 1 << PG_error |
                        1 << PG_referenced | 1 << PG_arch_1 |
                        1 << PG_checked | 1 << PG_mappedtodisk);
        page->private = 0;
        set_page_refs(page, order);
}

1. 函数功能

准备和初始化新分配的页面,确保页面处于干净可用的状态。该函数执行页面状态验证、标志位清理和引用计数设置,是新分配页面的质量保证机制

2. 逐行代码解析

c 复制代码
static void prep_new_page(struct page *page, int order)
{
  • static void: 静态函数,无返回值
  • page: 要准备的新分配页面指针
  • order: 分配阶数,用于设置正确的引用计数
c 复制代码
        if (page->mapping || page_mapped(page) ||
  • 页面状态检查第一部分
    • page->mapping: 检查页面是否有地址空间映射
      • 如果非NULL,表示页面已关联到某个文件的地址空间
    • page_mapped(page): 函数调用,检查页面是否被映射到进程页表
      • 返回真表示页面正在被某个进程使用
c 复制代码
            (page->flags & (
                        1 << PG_private |
  • 页面标志位检查开始
    • 使用位掩码检查多个页面标志位
    • 1 << PG_private:
      • PG_private 标志表示页面有私有数据
      • 新分配的页面不应该有私有数据
c 复制代码
                        1 << PG_locked  |
  • PG_locked 检查
    • PG_locked 表示页面被锁定,通常用于IO操作
    • 新分配的页面不应该被锁定
c 复制代码
                        1 << PG_lru     |
  • PG_lru 检查
    • PG_lru 表示页面在LRU链表中
    • 新分配的页面不应该在LRU链表中
c 复制代码
                        1 << PG_active  |
  • PG_active 检查
    • PG_active 表示页面在活跃LRU链表中
    • 新分配的页面不应该在活跃链表中
c 复制代码
                        1 << PG_dirty   |
  • PG_dirty 检查
    • PG_dirty 表示页面内容已被修改但未写回存储设备
    • 新分配的页面应该是干净的
c 复制代码
                        1 << PG_reclaim |
  • PG_reclaim 检查
    • PG_reclaim 表示页面被标记为可回收
    • 新分配的页面不应该是可回收状态
c 复制代码
                        1 << PG_swapcache |
  • PG_swapcache 检查
    • PG_swapcache 表示页面在交换缓存中
    • 新分配的页面不应该在交换缓存中
c 复制代码
                        1 << PG_writeback )))
  • PG_writeback 检查
    • PG_writeback 表示页面正在被写回存储设备
    • 新分配的页面不应该有正在进行的写操作
c 复制代码
                bad_page(__FUNCTION__, page);
  • 坏页处理
    • 如果上述任何条件为真,调用 bad_page 函数
    • __FUNCTION__: 预定义宏,展开为当前函数名 "prep_new_page"
    • page: 有问题的页面指针
    • bad_page 打印错误信息并可能触发内核错误
c 复制代码
        page->flags &= ~(1 << PG_uptodate | 1 << PG_error |
  • 清理页面标志位第一部分
    • page->flags &= ~(...): 使用位与和位非操作清除指定的标志位
    • 1 << PG_uptodate:
      • PG_uptodate 表示页面数据是最新的
      • 新分配的页面内容未定义,所以清除此标志
c 复制代码
                        1 << PG_referenced | 1 << PG_arch_1 |
  • 清理更多标志位
    • 1 << PG_referenced: 清除被引用标志
    • 1 << PG_arch_1: 清除架构特定的标志1(不同架构有不同用途)
c 复制代码
                        1 << PG_checked | 1 << PG_mappedtodisk);
  • 完成标志位清理
    • 1 << PG_checked: 清除已检查标志(用于文件系统)
    • 1 << PG_mappedtodisk: 清除映射到磁盘标志
c 复制代码
        page->private = 0;
  • 清零私有数据字段
    • page->private: 页面的私有数据字段
    • 设置为0,确保没有残留的私有数据指针
c 复制代码
        set_page_refs(page, order);
  • 设置页面引用计数
    • set_page_refs(page, order): 函数调用,根据order设置正确的引用计数
    • 对于单页(order=0),通常设置为1
    • 对于复合页(order>0),设置适当的引用计数

3. 页面标志位清理详解

3.1. 被清理的标志位

标志位 含义 清理原因
PG_uptodate 页面数据是最新的 新页面内容未初始化
PG_error 页面有IO错误 新页面没有IO历史
PG_referenced 页面被引用过 清除访问历史
PG_arch_1 架构特定标志 确保架构无关性
PG_checked 页面已被检查 文件系统相关,新页面不需要
PG_mappedtodisk 页面映射到磁盘 清除文件系统映射信息

初始化复合页面prep_compound_page

c 复制代码
static void prep_compound_page(struct page *page, unsigned long order)
{
        int i;
        int nr_pages = 1 << order;

        page[1].mapping = NULL;
        page[1].index = order;
        for (i = 0; i < nr_pages; i++) {
                struct page *p = page + i;

                SetPageCompound(p);
                p->private = (unsigned long)page;
        }
}

1. 函数功能

初始化复合页面(Compound Page),将多个连续的物理页面组合成一个大的逻辑页面。该函数设置复合页面的元数据,建立所有组成页面与首页面的关联关系

2. 逐行代码解析

c 复制代码
static void prep_compound_page(struct page *page, unsigned long order)
{
  • static void: 静态函数,无返回值
  • page: 复合页面的首页面指针(第一个页面的struct page)
  • order: 分配阶数,决定复合页面包含的页面数量(2^order个页面)
c 复制代码
        int i;
  • 循环计数器:用于遍历复合页面中的所有组成页面
c 复制代码
        int nr_pages = 1 << order;
  • 计算页面总数
    • 1 << order: 计算复合页面包含的物理页面数量
c 复制代码
        page[1].mapping = NULL;
  • 设置第二个页面的mapping字段
    • page[1]: 通过指针运算访问第二个页面结构
      • page 指向第一个页面
      • page + 1page[1] 指向第二个页面
    • mapping = NULL: 将第二个页面的地址空间映射指针设为NULL
    • 特殊用途:在复合页面中,第二个页面通常用于存储复合页面的元数据
c 复制代码
        page[1].index = order;
  • 在第二个页面中存储阶数信息
    • page[1].index = order: 将分配阶数存储在第二个页面的index字段中
    • 作用:后续可以通过第二个页面快速知道这个复合页面的大小
c 复制代码
        for (i = 0; i < nr_pages; i++) {
  • 遍历所有页面
    • i = 0: 从第一个页面开始
    • i < nr_pages: 循环条件,处理所有nr_pages个页面
    • i++: 每次循环处理一个页面
c 复制代码
                struct page *p = page + i;
  • 获取当前页面指针
    • page + i: 通过指针运算获取第i个页面的struct page指针
    • page 是首页面,page + i 是第i个页面(0-based)
c 复制代码
                SetPageCompound(p);
  • 设置复合页面标志
    • SetPageCompound(p): 宏,设置页面的PG_compound标志位
    • 作用:标记这个页面是复合页面的一部分
    • 重要性:这个标志告诉内核这个页面不是独立的,而是大页面的一部分
c 复制代码
                p->private = (unsigned long)page;
  • 建立与首页面的关联
    • p->private = (unsigned long)page: 将首页面指针存储在每个页面的private字段中
    • 类型转换(unsigned long)page 将指针转换为无符号长整型存储
    • 作用:任何组成页面都能通过private字段找到复合页面的首页面
    • 关键设计:这是复合页面管理的核心机制
相关推荐
做运维的阿瑞1 天前
CentOS 7 停止维护后 YUM 源配置速查手册
linux·运维·centos
mc23561 天前
Linux 基本命令
linux
巴渝小禹1 天前
【Ubuntu】ubuntu虚拟机磁盘不够扩容后开机黑屏-解决方案
linux·ubuntu
老黄编程1 天前
08-ubuntu如何获取发行版代号
linux·运维·ubuntu
草莓熊Lotso1 天前
Linux 权限管理进阶:从 umask 到粘滞位的深度解析
linux·运维·服务器·人工智能·ubuntu·centos·unix
尘似鹤1 天前
linux驱动学习---有些节点不会生成platform_device,怎么访问它们
linux
iCxhust1 天前
windows环境下在Bochs中运行Linux0.12系统
linux·运维·服务器·windows·minix
七七七七071 天前
【计算机网络】深入理解ARP协议:工作原理、报文格式与安全防护
linux·服务器·网络·计算机网络·安全
lhxcc_fly1 天前
Linux网络--8、NAT,代理,网络穿透
linux·服务器·网络·nat
摇滚侠1 天前
Spring Boot3零基础教程,Spring Boot 应用打包成 exe 可执行文件,笔记91 笔记92 笔记93
linux·spring boot·笔记