Skip to content

Linux 内核跨子系统深度源码分析(MM/锁/调度/网络)

深入 Buddy 系统核心算法

1.1 Buddy 合并算法的数学证明

Buddy 系统的核心是地址对齐合并。让我们分析合并条件:

c
/*
 * 合并条件:
 * 1. 两个页面是相邻的 (pfn + (1 << order) == buddy_pfn)
 * 2. 伙伴页面也是自由的 (PageBuddy(buddy))
 * 3. 伙伴的阶数相同 (buddy_order == order)
 * 4. 迁移类型兼容
 */
static inline void __free_one_page(struct page *page,
        unsigned long pfn, struct zone *zone, unsigned int order,
        int migratetype, fpi_t fpi_flags)
{
    unsigned long buddy_pfn;
    struct page *buddy;

    while (order < MAX_PAGE_ORDER) {
        /* 计算伙伴页面地址 */
        buddy_pfn = __find_buddy_pfn(pfn, order);
        buddy = page + (buddy_pfn - pfn);

        /* 伙伴阶数必须相同 */
        VM_BUG_ON_PAGE(buddy_order(buddy) != order, buddy);

        /* 检查是否可以合并 */
        if (!page_is_guard(buddy) &&
            buddy_migratetype(buddy) == migratetype &&
            PageBuddy(buddy)) {
            /* 合并:删除伙伴,加入到更高阶链表 */
            __del_page_from_free_list(buddy, zone, order, migratetype);
            combined_pfn = buddy_pfn & pfn;
            page = page + (combined_pfn - pfn);
            pfn = combined_pfn;
            order++;
        } else {
            break;
        }
    }
}

数学性质:

给定页面 pfn = 0x1000 (4KB aligned), order = 0 (1页)

伙伴计算: buddy_pfn = pfn ^ (1 << order)
         = 0x1000 ^ 0x1 = 0x1001 (相邻页面)

当 order = 1 (2页):
buddy_pfn = 0x1000 ^ 0x2 = 0x1002
页面 0x1000-0x1001 的伙伴是 0x1002-0x1003

递归合并形成二叉树结构:
        [0x0000 - 0x7FFF] (order 15)
           /              \
    [0x0000-0x3FFF]    [0x4000-0x7FFF] (order 14)
        /      \            /      \
    ...        ...        ...        ...

1.2 Buddy 分配算法的反向追踪

c
/*
 * __rmqueue 核心分配函数
 * 关键点:从高阶到低阶遍历,寻找最佳匹配
 */
static __always_inline struct page *
__rmqueue(struct zone *zone, unsigned int order, int migratetype,
          unsigned int alloc_flags)
{
    struct page *page;

retry:
    /* 1. 尝试从 requested order 开始分配 */
    for (current_order = order; current_order < MAX_PAGE_ORDER; current_order++) {
        area = &zone->free_area[current_order];

        /* 2. 查找匹配的空闲页面 */
        page = list_first_entry_or_null(
            &area->free_list[migratetype],
            struct page, buddy_list);

        if (page) {
            /* 找到!删除并可能分割 */
            list_del(&page->buddy_list);
            area->nr_free--;

            /* 3. 如果需要分割(从更高阶获取) */
            if (current_order != order)
                page = split_free_page(page, current_order - order, order);

            if (page)
                return page;
        }
    }

    /* 4. 回退到其他迁移类型 */
    if (fallback_migratetype != migratetype) {
        migratetype = fallback_migratetype;
        goto retry;
    }

    return NULL;
}

Expand 算法(分割高阶页面):

分配 order=2 (4页) 但只有 order=4 (16页):

初始: [0x0000-0x000F] (16页, order=4)

Step 1: 分割为两个 order=3 (8页)
  [0x0000-0x0007] [0x0008-0x000F]

Step 2: 取第一个 order=3, 分割为两个 order=2 (4页)
  [0x0000-0x0003] [0x0004-0x0007] [0x0008-0x000F]

Step 3: 取第一个 order=2, 分割为两个 order=1 (2页)
  [0x0000-0x0001] [0x0002-0x0003] [0x0004-0x0007] [0x0008-0x000F]

Step 4: 取第一个 order=1, 分割为两个 order=0 (1页)
  [0x0000] [0x0001] [0x0002-0x0003] [0x0004-0x0007] [0x0008-0x000F]

最终分配: 0x0000 (4页)
剩余: 0x0001 (1页), 0x0002-0x0003 (2页), 0x0004-0x0007 (4页), 0x0008-0x000F (8页)

深入 SLUB 分配器核心机制

2.1 Sheaf 机制的本质

SLUB 的 sheaf 是 per-CPU 的对象缓存,替代了早期 SLAB 的 per-CPU array。

c
/*
 * Sheaf 是 SLUB 的核心创新
 * 每个 CPU 有两个 sheaf: main 和 spare
 */
struct slub_percpu_sheaves {
    local_lock_t lock;  // 仅禁用抢占,不使用原子操作
    struct slab_sheaf {
        void **freelist;           // 空闲对象链表
        unsigned long counters;     // 计数器 (inuse, objects, frozen)
    } main, spare;
};

为什么使用 cmpxchg 而不是锁:

c
/*
 * __cmpxchg_double 是 SLUB 的核心原子操作
 * 它同时原子地更新 freelist 和 counters
 *
 * 原理:freelist 和 counters 在内存中必须连续且对齐
 * x86_64: cmpxchg16b 支持 16 字节双操作数原子比较交换
 */
static inline bool __cmpxchg_double_slab(struct slab *slab,
        void **freelist_old, unsigned long counters_old,
        void **freelist_new, unsigned long counters_new,
        const char *n)
{
    bool ret;

    asm volatile(LOCK_PREFIX "cmpxchg16b %2\n\t"
        "setz %1"
        : "=a" (freelist_old), "=q" (ret), "+m" (slab->freelist),
          "+m" (slab->counters)
        : "b" (freelist_new), "c" (counters_new),
          "a" (freelist_old), "d" (counters_old));

    return ret;
}

2.2 分配快速路径的完整实现

c
/*
 * kmem_cache_alloc - SLUB 快速路径
 *
 * 优化流程:
 * 1. 检查 sheaf 是否有空间
 * 2. 使用 cmpxchg 原子获取对象
 * 3. 失败则尝试 refill
 * 4. 最后回退到 barn
 */
static __always_inline void *
__kmem_cache_alloc_node(struct kmem_cache *s, gfp_t gfpflags, int node)
{
    void *ret;

    if (likely(cache_has_sheaves(s))) {
        struct slub_percpu_sheaf *pcss = this_cpu_ptr(s->cpu_sheaves);
        struct slab_sheaf *sheaf = &pcss->main;

        /* 快速路径:直接从 sheaf 分配 */
        ret = __kmem_cache_alloc_from_sheaf(s, sheaf, gfpflags);
        if (likely(ret))
            return ret;

        /* sheaf 耗尽,交换并重新填充 */
        if (try_fill_main(pcss, s, gfpflags))
            return __kmem_cache_alloc_from_sheaf(s, &pcss->main, gfpflags);

        /* 尝试从 spare 获取 */
        if (pcss->spare) {
            swap(pcss->main, pcss->spare);
            ret = __kmem_cache_alloc_from_sheaf(s, &pcss->main, gfpflags);
            if (likely(ret))
                return ret;
        }

        /* 最后回退:从 barn 获取 */
        return __kmem_cache_alloc_from_barn(s, gfpflags);
    }

    /* 无 sheaf 的缓存:回退到慢路径 */
    return slab_alloc(s, gfpflags, _RET_IP_);
}

2.3 cmpxchg 双操作的核心价值

c
/*
 * Slab 布局 - 关键是对齐和连续性
 *
 * struct slab {
 *     memdesc_flags_t flags;
 *     struct kmem_cache *slab_cache;
 *     union {
 *         struct {
 *             struct list_head slab_list;
 *             void *freelist;           <-- 8 bytes
 *             union {
 *                 unsigned long counters;  <-- 8 bytes
 *                 struct {            <-- 两个 8 字节必须连续!
 *                     unsigned inuse:16;
 *                     unsigned objects:15;
 *                     unsigned frozen:1;
 *                 };
 *             };
 *         };
 *     };
 * };
 *
 * freelist (8字节) + counters (8字节) = 16字节 = cmpxchg16b 操作数大小
 */

2.4 分配器状态机

┌────────────────────────────────────────────────────────────────┐
│                 SLUB Object Allocation State Machine                │
│                                                                   │
│   [Start]                                                        │
│      │                                                           │
│      ▼                                                           │
│   ┌─────────────────┐                                           │
│   │ Check Sheaf     │──── Has Objects? ────► [Return Object]      │
│   │ (main)         │      Yes                                   │
│   └────────┬────────┘                                           │
│            │ No                                                 │
│            ▼                                                    │
│   ┌─────────────────┐                                           │
│   │ Try Refill Main │──── Success? ─────► [Retry Sheaf]         │
│   └────────┬────────┘      Yes                                   │
│            │ No                                                 │
│            ▼                                                    │
│   ┌─────────────────┐                                           │
│   │ Swap Main/Spare │──── Spare exists? ──► [Try Alloc]         │
│   └────────┬────────┘      Yes                                   │
│            │ No                                                 │
│            ▼                                                    │
│   ┌─────────────────┐                                           │
│   │ Get from Barn   │──── Success? ─────► [Return Object]        │
│   │ (slow path)    │      Yes                                   │
│   └────────┬────────┘                                           │
│            │ No                                                 │
│            ▼                                                    │
│   ┌─────────────────┐                                           │
│   │ Allocate New    │──── Success? ─────► [Init & Return]        │
│   │ Slab           │      Yes                                   │
│   └────────┬────────┘                                           │
│            │ No                                                 │
│            ▼                                                    │
│       [Return NULL / OOM]                                       │
└────────────────────────────────────────────────────────────────┘

深入内存回收 (VMSCAN) 算法

3.1 LRU 列表结构与 pagevec

c
/*
 * Per-CPU LRU 页面向量
 * 用于批量回收,减少锁竞争
 */
struct pagevec {
    unsigned long nr;          // 当前页面数
    unsigned long cold;        // 冷/热页面
    struct page *pages[PAGEVEC_SIZE];  // 页面指针数组
};

/*
 * LRU 列表定义
 * 关键洞察:anon 和 file 分离实现更好的回收策略
 */
enum lru_list {
    LRU_INACTIVE_ANON = 0,    // 非活跃匿名页面
    LRU_ACTIVE_ANON,          // 活跃匿名页面
    LRU_INACTIVE_FILE,        // 非活跃文件页面
    LRU_ACTIVE_FILE,          // 活跃文件页面
    LRU_UNEVICTABLE,          // 不可驱逐页面 (mlocked)
    NR_LRU_LISTS
};

3.2 folio 冻结 ( folio_batch )

c
/*
 * folio_batch - 批量处理 folio
 * 减少函数调用开销,提高缓存命中率
 */
struct folio_batch {
    unsigned long nr;          // 当前 folio 数
    unsigned long expire;      // 批次过期时间
    struct folio *folios[];
};

/*
 * 典型批量回收流程
 * 1. 从 LRU 批量获取页面
 * 2. 尝试批量映射/解映射
 * 3. 批量写回
 * 4. 批量释放
 */
static unsigned int shrink_folio_list(struct folio_batch *folio_list,
                                     struct scan_control *sc)
{
    LIST_HEAD(ret_folios);
    unsigned int nr_reclaimed = 0;

    for (folio_batch_init(&fb); fb.nr; folio_batch_clear(&fb)) {
        struct folio *folio = fb.folios[i];

        /* 锁定 folio */
        if (!folio_trylock(folio))
            continue;

        /* 检查是否可回收 */
        if (folio_test_dirty(folio) && folio_mapping(folio)) {
            /* 尝试写回 */
            folio_unlock(folio);
            continue;
        }

        /* 从 LRU 移除 */
        if (folio_isolate_lru(folio)) {
            nr_reclaimed += folio_put_back_lru(folio);
        } else {
            folio_unlock(folio);
        }
    }

    return nr_reclaimed;
}

3.3 工作集检测的 refault distance 算法

c
/*
 * 工作集检测算法
 *
 * 核心概念:refault_distance = eviction_time - last_access_time
 *
 * 如果 distance < threshold → 页面仍在工作集
 * 如果 distance > threshold → 页面已被替换
 */
struct work_set {
    atomic_long_t refaults[ANON_AND_FILE];    // 重新入队计数
    atomic_long_t activations[ANON_AND_FILE];  // 激活计数
};

static unsigned long refault_distance(struct folio *folio)
{
    unsigned long evictions, activations;

    evictions = atomic_long_read(&folio->eviction_time);
    activations = atomic_long_read(&folio->last_fault_time);

    if (evictions == 0)
        return ULONG_MAX;

    return activations - evictions;
}

static void update_work_set(struct folio *folio, bool activate)
{
    if (activate) {
        atomic_long_inc(&work_set.activations[folio_is_anon(folio)]);
        folio->last_fault_time = jiffies;
    } else {
        folio->eviction_time = jiffies;
        atomic_long_inc(&work_set.refaults[folio_is_anon(folio)]);
    }
}

深入页表管理

4.1 四级页表遍历

c
/*
 * 页表遍历核心宏
 * 页表级别: PGD → PUD → PMD → PTE
 */
#define pgd_offset(mm, address) \
    ((mm)->pgd + pgd_index(address))

#define pud_offset(pgd, address) \
    ((pud_t *)pgd_page_vaddr(*(pgd)) + pud_index(address))

#define pmd_offset(pud, address) \
    ((pmd_t *)pud_page_vaddr(*(pud)) + pmd_index(address))

#define pte_offset_kernel(pmd, address) \
    ((pte_t *)pmd_page_vaddr(*(pmd)) + pte_index(address))

/*
 * 页表查找流程 (x86_64 48-bit VA)
 *
 * 虚拟地址位分解:
 * [63:48]  符号扩展 (47:47)
 * [47:39]  PGD 索引 (9 bits)
 * [38:30]  PUD 索引 (9 bits)
 * [29:21]  PMD 索引 (9 bits)
 * [20:12]  PTE 索引 (9 bits)
 * [11:0]   页面偏移 (12 bits)
 */

4.2 页面错误处理完整流程

c
/*
 * handle_pte_fault - 页面错误处理核心
 *
 * 页面错误类型:
 * 1. 初次访问 (demand zero)
 * 2. 文件映射 (mmap)
 * 3. 匿名映射 (堆/栈)
 * 4. 交换 (swap in)
 * 5. COW (copy-on-write)
 */
static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
{
    pte_t entry;

    /* 1. 获取 PTE 指针 */
    vmf->pte = pte_offset_map(vmf->pmd, vmf->address);
    entry = *vmf->pte;

    /* 2. 页面不存在 */
    if (!pte_present(entry)) {
        if (pte_none(entry)) {
            /* 匿名页面或文件映射 */
            if (vmf->vma->vm_ops->fault)
                return vmf->vma->vm_ops->fault(vmf);
            return do_anonymous_page(vmf);
        }

        /* 页面被换出到 swap */
        if (pte_swap(entry))
            return do_swap_page(vmf);

        /* COW 页面 */
        if (is_cow_mapping(vma->vm_flags) && pte_write(entry))
            return do_wp_page(vmf);
    }

    /* 3. 写访问但页面只读 */
    if (vmf->flags & FAULT_FLAG_WRITE) {
        if (!pte_write(entry))
            return do_wp_page(vmf);
        pte_mkwrite(entry);
    }

    /* 4. 更新 accessed 和 dirty 位 */
    if (vmf->flags & FAULT_FLAG_WRITE)
        pte_mkdirty(entry);
    else
        pte_mkyoung(entry);

    /* 5. 更新 PTE */
    set_pte_at(vmf->mm, vmf->address, vmf->pte, entry);
    update_mmu_cache(vma, vmf->address, vmf->pte);

    return VM_FAULT_NOPAGE;
}

4.3 TLB shootdown 机制

c
/*
 * TLB shootdown - 多核间 TLB 同步
 *
 * 当一个 CPU 修改了页表,其他 CPU 的 TLB 缓存需要失效
 */
void flush_tlb_mm(struct mm_struct *mm)
{
    cpumask_var_t mask;
    int cpu;

    /* 1. 获取需要刷新的 CPU 掩码 */
    cpumask_clear(mask);
    for_each_online_cpu(cpu) {
        if (cpu == smp_processor_id())
            continue;
        if (cpumask_test_cpu(cpu, mm_cpumask(mm)))
            cpumask_set_cpu(cpu, mask);
    }

    /* 2. 发送 IPI 中断 */
    if (!cpumask_empty(mask)) {
        smp_call_function_many(mask, flush_tlb_func, mm, 1);
    }

    /* 3. 本地 TLB 刷新 */
    local_flush_tlb();
}

/*
 * TLB 刷新优化
 *
 * 1. 单页面刷新: flush_tlb_page(vma, address)
 * 2. 范围刷新: flush_tlb_range(vma, start, end)
 * 3. 全部刷新: flush_tlb_all()
 */

深入锁与同步机制

5.1 Per-CPU 数据结构的锁优化

c
/*
 * Per-CPU 数据结构的同步模式
 *
 * 模式1: 仅禁用抢占 (local_lock)
 *   - 用于短暂操作,不需要原子性
 *   - 例: percpu sheaves
 *
 * 模式2: Per-CPU 锁
 *   - 每个 CPU 有自己的锁
 *   - 用于需要保护的操作
 *
 * 模式3: RCU (Read-Copy-Update)
 *   - 读多写少场景
 *   - 例: 路由表查找
 */
struct slub_percpu_sheaves {
    local_lock_t lock;  // 编译时禁用抢占
};

/*
 * local_lock 使用示例
 */
static inline void *alloc_from_sheaf(struct kmem_cache *s)
{
    void *obj;

    local_lock(&s->cpu_sheaves->lock);
    obj = sheaf->freelist;
    sheaf->freelist = *(void **)obj;
    local_unlock(&s->cpu_sheaves->lock);

    return obj;
}

5.2 RCU 在内核中的典型应用

c
/*
 * RCU (Read-Copy-Update) 核心概念
 *
 * 写操作:
 * 1. 复制并修改数据
 * 2. 等待所有 RCU 读临界区结束 (grace period)
 * 3. 释放旧数据
 *
 * 读操作:
 * 1. 进入 RCU 读临界区 (rcu_read_lock)
 * 2. 访问数据
 * 3. 离开 RCU 读临界区 (rcu_read_unlock)
 */
struct list_head {
    struct list_head *next, *prev;
};

/* RCU 保护的链表遍历 */
static inline void list_add_rcu(struct list_head *new, struct list_head *head)
{
    new->next = head->next;
    new->prev = head->prev;
    rcu_assign_pointer(head->next, new);
    new->next->prev = new;
}

/* RCU 遍历 */
rcu_read_lock();
list_for_each_entry_rcu(pos, head, member) {
    /* 安全访问 */
}
rcu_read_unlock();

/* 延迟释放 */
void free_obj(struct rcu_head *head)
{
    kfree(container_of(head, struct obj, rcu_head));
}

call_rcu(&obj->rcu_head, free_obj);

5.3 内存顺序与原子操作

c
/*
 * 内存顺序模型 (x86_64)
 *
 * 编译器和 CPU 可能会重新排序内存访问
 * 不同的内存顺序提供不同的保证
 */
struct foo {
    int a;
    int b;
};

/* 示例:store 操作 */
void write_example(struct foo *f)
{
    /* 1. STORE (无保证) */
    f->a = 1;                    // store a

    /* 2. STORE with release (禁止重排序到之前) */
    smp_store_release(&f->a, 1);

    /* 3. STORE with seq_cst (完全顺序) */
    ACCESS_ONCE(f->a) = 1;      // seq_cst store
}

/* 示例:load 操作 */
int read_example(struct foo *f)
{
    int a, b;

    /* 1. LOAD (无保证) */
    a = f->a;                   // load a

    /* 2. LOAD with acquire (禁止重排序到之后) */
    a = smp_load_acquire(&f->a);

    /* 3. LOAD with seq_cst (完全顺序) */
    a = READ_ONCE(f->a);       // seq_cst load

    return a + b;
}

/*
 * 典型使用场景
 *
 * 1. cmpxchg_release: 用于实现 SLUB 的 freelist 更新
 *    - release 保证 freelist 指针更新在计数器更新之前
 *
 * 2. smp_load_acquire: 用于读取共享状态
 *    - acquire 保证后续读取看到之前的所有写入
 */

深入 CPU 调度算法

6.1 CFS 虚拟时间计算细节

c
/*
 * vruntime 计算 - CFS 核心
 *
 * 公式: vruntime += delta_exec * (NICE_0_LOAD / weight)
 *
 * 这意味着:
 * - nice=0 的任务,vruntime 增长 = 实际运行时间
 * - nice=-20 (最高优先级),weight 是 nice=0 的 4 倍
 *   vruntime 增长 = 实际时间 * (1024/4100) ≈ 1/4
 *   所以相同实际运行时间,vruntime 增长更慢
 * - nice=+19 (最低优先级),weight 是 nice=0 的 1/16
 *   vruntime 增长 = 实际时间 * (1024/64) = 16 倍
 *   所以相同实际运行时间,vruntime 增长更快
 */

/*
 * load_weight 结构
 */
struct load_weight {
    unsigned long weight;       // 任务权重
    u32 inv_weight;           // 权重的倒数 (用于除法优化)
};

/*
 * delta_exec 计算
 */
static inline u64 calc_delta_fair(u64 delta_exec, struct sched_entity *se)
{
    if (se->load.weight != NICE_0_LOAD) {
        /* 非标准权重:乘以 (NICE_0_LOAD / weight) */
        return mul_u64_u32_div(delta_exec,
                               NICE_0_LOAD,
                               se->load.weight);
    }
    return delta_exec;
}

6.2 红黑树操作复杂度

c
/*
 * CFS 红黑树 - O(log n) 操作
 *
 * 插入/删除: O(log n),其中 n 是运行队列中的任务数
 * 查找最左节点: O(1) (因为缓存了最左节点)
 *
 * 关键优化:缓存 next 和 last 节点
 */
struct cfs_rq {
    struct rb_root_cached tasks_timeline;  // 红黑树根 + 最左缓存
    struct sched_entity *curr;            // 当前运行实体
    struct sched_entity *next;             // 下一个 (优化)
    struct sched_entity *last;            // 上一个 (优化)
    struct sched_entity *skip;             // 跳过 (用于负载均衡)
};

/*
 * pick_next_entity - 选择下一个调度实体
 */
static struct sched_entity *pick_next_entity(struct cfs_rq *cfs_rq,
                                            struct sched_entity *curr)
{
    struct sched_entity *left = __pick_first_entity(cfs_rq);

    /* 检查是否可以被抢占 */
    if (cfs_rq->nr_running == 1 && left && curr &&
        entity_before(curr, left)) {
        /* 当前任务仍在运行,使用它 */
        return curr;
    }

    return left;
}

/*
 * enqueue_entity - 入队
 *
 * 1. 更新 vruntime
 * 2. 插入红黑树
 * 3. 更新统计信息
 */
static void enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se,
                           int flags)
{
    /* 更新虚拟时间 */
    update_curr(cfs_rq);

    /* 如果实体不在队列,更新 vruntime */
    if (se->on_rq == 0)
        se->vruntime += cfs_rq->min_vruntime;

    /* 插入红黑树 */
    __enqueue_entity(cfs_rq, se);
    se->on_rq = 1;

    /* 更新统计 */
    update_load_avg(cfs_rq, se, UPDATE_TG);
}

6.3 负载均衡的数学建模

c
/*
 * 负载均衡算法
 *
 * 目标:保持各 CPU 负载均衡
 *
 * 关键概念:
 * 1. load = weight * running_time / period
 * 2. imbalance = 目标负载差
 * 3. 迁移阈值:只有当 imbalance 超过阈值时才迁移
 */

/*
 * 计算运行队列负载
 */
static unsigned long cfs_rq_load_avg(struct cfs_rq *cfs_rq)
{
    return cfs_rq->avg.load_avg;
}

/*
 * 计算 CPU 间的负载差
 */
static long cpu_load_diff(struct cpu_cpu_similarity *c)
{
    long load = c->dst_cpu_load - c->src_cpu_load;
    long imb = c->imbalance;

    /* 考虑容量差异 */
    return (load * SCHED_CAPACITY_SCALE) /
           (c->dst_cpu_capacity + c->src_cpu_capacity / 2);
}

/*
 * 判断是否需要均衡
 *
 * 只有当目标 CPU 有显著空闲容量时才迁移
 */
static bool need_balance(struct lb_env *env)
{
    struct sched_group *group = env->sd->groups;

    /* 计算组的平均负载 */
    unsigned long avg_load = group->avg_load;

    /* 判断是否可以接收任务 */
    return avg_load < env->sd->min_capacity;
}

深入网络协议栈

7.1 TCP 三次握手状态机实现

c
/*
 * TCP 状态转换
 *
 * LISTEN → SYN_SENT → ESTABLISHED
 *
 * 关键代码路径:
 */
static int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);

    /* 1. 分配 request_sock */
    req = inet_reqsk_alloc(&tcp_request_sock_ops);
    if (!req)
        return 0;

    /* 2. 初始化 SYN cookie */
    tcp_reqsk_deschedule(req);

    /* 3. 设置初始序列号 */
    tcp_rsk(req)->snt_isn = tcp_skb_timestamp(skb);

    /* 4. 发送 SYN+ACK */
    tcp_v4_send_synack(sk, skb, req);

    return 0;
}

/*
 * TCP 握手完成
 */
static int tcp_rcv_synsent_state_process(struct sock *sk,
                                          struct sk_buff *skb,
                                          const struct tcphdr *th)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (th->ack) {
        /* 收到 ACK,三次握手完成 */
        tcp_finish_connect(sk, skb);
    } else {
        /* 收到 SYN,转到 SYN_RCVD 状态 */
        tcp_set_state(sk, TCP_SYN_RECV);
    }
}

7.2 TCP 拥塞控制算法

c
/*
 * TCP 拥塞控制
 *
 * 核心概念:
 * 1. cwnd (拥塞窗口) - 发送方可以发送的未确认字节数
 * 2. ssthresh (慢启动阈值) - 决定使用哪种算法
 * 3. 拥塞算法:Reno, Cubic, BBR 等
 */

/*
 * 慢启动算法
 *
 * 每次 RTT,cwnd 加倍
 * 直到 cwnd >= ssthresh
 */
void tcp_slow_start(struct tcp_sock *tp)
{
    int inc = tp->snd_cwnd_cnt;  // ACK 计数

    if (tp->snd_ssthresh < TCP_INFINITE_SSTHRESH) {
        /* 指数增长 */
        inc = min(inc, tp->snd_ssthresh);
        tp->snd_cwnd += inc;
        tp->snd_cwnd_cnt = 0;
    }
}

/*
 * 拥塞避免算法
 *
 * 每次 RTT,cwnd 加 1
 */
void tcp_cong_avoid(struct tcp_sock *tp)
{
    if (tp->snd_cwnd < tp->snd_ssthresh) {
        /* 慢启动 */
        tcp_slow_start(tp);
    } else {
        /* 线性增长 */
        tcp_reno_ai(tp);
    }
}

/*
 * 丢包检测后的处理
 */
void tcp_enter_loss(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);

    /* 1. 减小 ssthresh */
    tp->snd_ssthresh = tcp_fackets_before(tp->snd_una) / 2;

    /* 2. 减小 cwnd */
    tp->snd_cwnd = tp->snd_ssthresh;

    /* 3. 进入恢复状态 */
    tcp_set_ca_state(sk, TCP_CA_Loss);
}

7.3 Socket 缓冲区 (sk_buff) 管理

c
/*
 * sk_buff 结构 - 网络数据包表示
 *
 * 关键设计:
 * 1. 分层结构:MAC header, IP header, TCP header, data
 * 2. 零拷贝支持:共享数据区域
 * 3. 线性/非线性数据分离
 */
struct sk_buff {
    unsigned int len;           // 数据总长度
    __u32       hash;          // 流量哈希

    struct sk_buff *next;       // SKB 链表
    struct sk_buff *prev;

    struct sock *sk;            // 所属 socket
    struct net_device *dev;    // 网络设备

    /* 头部信息 */
    __u16 protocol;           // 协议类型
    __u8  ip_summed;          // checksum 状态

    /* 时间戳 */
    struct skb_mstamp skb_mstamp;

    /* 线性数据区域 */
    unsigned char *head;       // 缓冲区开始
    unsigned char *data;        // 数据开始
    unsigned char *tail;       // 数据结束
    unsigned char *end;        // 缓冲区结束

    /* 分片信息 */
    struct sk_buff *frag_list;  // 分片链表
    struct skb_shared_info *skb_shinfo;
};

/*
 * 克隆 SKB (用于转发)
 *
 * 共享数据区域,只复制元数据
 */
static struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp)
{
    struct sk_buff *n;

    n = kmem_cache_alloc(skbuff_cache, gfp);
    if (!n)
        return NULL;

    /* 复制 SKB 头 */
    memcpy(n, skb, sizeof(struct sk_buff));

    /* 增加引用计数 */
    refcount_set(&n->users, 1);

    /* 共享数据区域 */
    n->cloned = 1;

    return n;
}

/*
 * 追加数据到 SKB
 */
static unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
    unsigned char *tmp = skb->tail;

    skb->tail += len;
    skb->len += len;

    return tmp;
}

附录:核心算法复杂度总结

操作算法时间复杂度
Buddy 分配空闲链表遍历O(log MAX_ORDER)
Buddy 释放 + 合并递归伙伴查找O(log MAX_ORDER)
SLUB 分配 (fast)cmpxchgO(1) amortized
SLUB 分配 (slow)barn 查找O(1)
CFS 入队红黑树插入O(log n)
CFS 选取下一个取最左节点O(1)
页面错误处理页表遍历O(4) 固定
TCP 连接查找哈希表O(1) average
LRU 回收批量扫描O(batch_size)

参考代码路径

文件功能
mm/page_alloc.cBuddy 分配器核心
mm/slub.cSLUB 分配器核心
mm/vmscan.c页面回收
mm/memory.c页面错误处理
net/ipv4/tcp_ipv4.cTCP 实现
net/core/skbuff.cSKB 管理
kernel/sched/fair.cCFS 调度器

基于 VitePress 构建