当前位置: 首页 > news >正文

Linux中页面分配alloc_pages相关函数

返回清零的页面get_zeroed_page

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. 函数定义

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. 局部变量声明

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

3. HIGHMEM 检查断言

        /** 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. 分配单个页面

        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. 页面处理逻辑

        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. 分配失败处理

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

内核页面分配器alloc_pages

#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. 函数定义和宏展开

#define alloc_pages(gfp_mask, order) \alloc_pages_node(numa_node_id(), gfp_mask, order)
  • 作用: alloc_pages 宏展开为 alloc_pages_node,自动传入当前NUMA节点ID。
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: 普通内核分配

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

2. 变量声明

        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. 初始检查和设置

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

        /* 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的内存
                if (z->free_pages < min)continue;
  • 检查: 如果空闲页面不足,跳过这个zone
                page = buffered_rmqueue(z, order, gfp_mask);if (page)goto got_pg;}
  • 尝试分配: 从zone的per-CPU缓存中分配页面
  • 成功: 跳转到 got_pg 标签返回页面

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

        for (i = 0; (z = zones[i]) != NULL; i++)wakeup_kswapd(z);
  • 唤醒回收: 唤醒每个zone的kswapd内核线程开始异步页面回收
        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%
    • 加上所需页面和保护值
                if (z->free_pages < min)continue;page = buffered_rmqueue(z, order, gfp_mask);if (page)goto got_pg;}
  • 再次尝试分配: 使用更宽松的条件

6. 内存分配者特权路径

        if ((p->flags & (PF_MEMALLOC | PF_MEMDIE)) && !in_interrupt()) {
  • 条件: 当前进程是内存分配者或正在死亡,且不在中断上下文
                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. 原子分配处理

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

8. 同步回收路径

rebalance: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;
  • 清理: 恢复进程状态
        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. 重试逻辑

        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(必须成功)
        if (do_retry) {blk_congestion_wait(WRITE, HZ/50);goto rebalance;}
  • 实际重试: 等待IO拥塞缓解,然后回到回收路径

10. 失败处理

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. 成功处理

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

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

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. 函数定义

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

2. 逐行代码解析

2.1. 变量声明

        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. 准备等待

        prepare_to_wait(wqh, &wait, TASK_UNINTERRUPTIBLE);

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

参数:

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

不可中断睡眠的特点:

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

2.3. 调度超时

        ret = io_schedule_timeout(timeout);

io_schedule_timeout(timeout):

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

工作流程:

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

返回值含义:

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

2.4. 完成等待

        finish_wait(wqh, &wait);

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

具体操作:

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

2.5. 返回结果

        return ret;

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

3. 拥塞管理机制

拥塞触发条件

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

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

4. 设计意义

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

页面分配buffered_rmqueue

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. 函数定义和变量声明

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)

        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]: 冷页面列表
                local_irq_save(flags);
  • 保存本地中断状态并禁用中断,防止并发访问Per-CPU缓存
                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
    • 返回实际分配的页面数量
  • 作用: 如果缓存不足,从伙伴系统批量补充页面
                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--: 减少缓存中的页面计数
                local_irq_restore(flags);put_cpu();}
  • local_irq_restore(flags): 恢复中断状态
  • put_cpu(): 启用内核抢占

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

        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. 页面后期处理

        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)
                mod_page_state_zone(zone, pgalloc, 1 << order);
  • 更新页面分配统计信息
    • zone: 目标内存区域
    • pgalloc: 页面分配计数器
    • 1 << order: 分配的页面数量
                prep_new_page(page, order);
  • 准备新页面
    • 设置页面标志位
    • 初始化页面结构
    • 清除页面内容(如果需要)
                if (order && (gfp_flags & __GFP_COMP))prep_compound_page(page, order);}
  • if (order && (gfp_flags & __GFP_COMP)):
    • 如果分配多个页面且请求复合页面
    • __GFP_COMP: 允许复合页面(用于大页等)
  • prep_compound_page(page, order): 设置复合页面结构
        return page;
}
  • 返回分配的页面指针(成功)或NULL(失败)

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

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. 函数定义和变量声明

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: 目标链表头,分配的页面块将添加到这个链表中
        unsigned long flags;
  • flags: 用于保存中断状态的无符号长整型变量,在操作自旋锁时使用
        int i;
  • i: 循环计数器,用于控制分配循环的次数
        int allocated = 0;
  • allocated: 实际成功分配的页面块计数器,初始化为0
        struct page *page;
  • page: 指向分配到的页面块的指针,struct page 是内核中表示物理页面的数据结构

2. 加锁和循环分配

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

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

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

3. 清理和返回

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

分配连续物理页面块__rmqueue

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. 逐行代码解析

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

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

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

3. 关键设计要点

  1. 最佳适配策略

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

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

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

拆分高阶连续页面块expand

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. 逐行代码解析

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

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

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

原子更新页面状态mod_page_state_zone

#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. 逐行代码解析

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

初始化新分配的页面prep_new_page

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. 逐行代码解析

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

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. 逐行代码解析

static void prep_compound_page(struct page *page, unsigned long order)
{
  • static void: 静态函数,无返回值
  • page: 复合页面的首页面指针(第一个页面的struct page)
  • order: 分配阶数,决定复合页面包含的页面数量(2^order个页面)
        int i;
  • 循环计数器:用于遍历复合页面中的所有组成页面
        int nr_pages = 1 << order;
  • 计算页面总数
    • 1 << order: 计算复合页面包含的物理页面数量
        page[1].mapping = NULL;
  • 设置第二个页面的mapping字段
    • page[1]: 通过指针运算访问第二个页面结构
      • page 指向第一个页面
      • page + 1page[1] 指向第二个页面
    • mapping = NULL: 将第二个页面的地址空间映射指针设为NULL
    • 特殊用途:在复合页面中,第二个页面通常用于存储复合页面的元数据
        page[1].index = order;
  • 在第二个页面中存储阶数信息
    • page[1].index = order: 将分配阶数存储在第二个页面的index字段中
    • 作用:后续可以通过第二个页面快速知道这个复合页面的大小
        for (i = 0; i < nr_pages; i++) {
  • 遍历所有页面
    • i = 0: 从第一个页面开始
    • i < nr_pages: 循环条件,处理所有nr_pages个页面
    • i++: 每次循环处理一个页面
                struct page *p = page + i;
  • 获取当前页面指针
    • page + i: 通过指针运算获取第i个页面的struct page指针
    • page 是首页面,page + i 是第i个页面(0-based)
                SetPageCompound(p);
  • 设置复合页面标志
    • SetPageCompound(p): 宏,设置页面的PG_compound标志位
    • 作用:标记这个页面是复合页面的一部分
    • 重要性:这个标志告诉内核这个页面不是独立的,而是大页面的一部分
                p->private = (unsigned long)page;
  • 建立与首页面的关联
    • p->private = (unsigned long)page: 将首页面指针存储在每个页面的private字段中
    • 类型转换(unsigned long)page 将指针转换为无符号长整型存储
    • 作用:任何组成页面都能通过private字段找到复合页面的首页面
    • 关键设计:这是复合页面管理的核心机制
http://www.dtcms.com/a/473428.html

相关文章:

  • Qt---布局管理器
  • 基于单片机的图书馆智能座位管理平台
  • 中国机械工业建设集团有限公司网站高端网站建设论坛
  • Envoy Gateway + ext_authz 做“入口统一鉴权”,ABP 只做资源执行
  • vscode免密码认证ssh连接virtual box虚拟机
  • 3.6 JSON Mode与JSON Schema
  • React Native::关于react的匿名函数
  • 基于JETSON ORIN+FPGA+GMSL AI相机的工业双目视觉感知方案
  • 常规的鱼眼镜头有哪些类型?能做什么?
  • 虚实之间:AR/VR开发中的性能优化艺术
  • 新手要如何让网站被收录公司查询信息查询
  • PostgreSQL 的 hstore、arrays 数据类型
  • Java集合体系 —— Set篇
  • 硅基计划5.0 MySQL 贰 SQL约束三大范式
  • 设计模式——工厂模式
  • 变色龙哈希与隐私保护
  • 栈和队列:“单端吞吐”VS”双端通行“(第十讲)
  • ros2系统在ubuntu18.04环境下的环境搭建
  • 个人网站展示dw网站制作
  • 鸿蒙NEXT系列之精析NDK UI API(节点增删和属性设置)
  • 10个免费货源网站郑州网络科技公司有哪些
  • Spring 源码学习(十三)—— RequestMappingHandlerAdapter
  • 虚幻引擎虚拟制片入门教程 之 3D渲染基础知识:模型、材质、贴图、UV等
  • excel导出使用arthas动态追踪方法调用耗时后性能优化的过程
  • 【数据结构】强化训练:从基础到入门到进阶(2)
  • python异步编程 -什么是python的异步编程, 与多线程和多进程的区别
  • Linux系统--进程间通信--共享内存相关指令
  • 网站开发的实践报告石家庄市工程勘察设计咨询业协会
  • TensorFlow深度学习实战——图分类
  • SAP MM采购信息记录维护接口分享