K03 — Memory Copy During Process Creation: copy_page_range and COW Details

Key Insight: The elegance of fork lies in turning “copying” into “sharing, until it must be otherwise”.


Memory Copy Flow in fork

The core of fork is copy_process. The memory-related part is copy_mm, which calls copy_page_range to copy the parent’s address space.

Bash
fork 内存复制的调用链:

sys_fork() → do_fork() → copy_process()
  → copy_mm() → dup_mm()
      → dup_mmap() → copy_page_range()
          → copy_pud_range() → copy_pmd_range() → copy_pte_range()

copy_page_range iterates through each VMA of the parent process, creating corresponding page table entries for the child:

Bash
copy_page_range() 的核心逻辑:

int copy_page_range(struct mm_struct *dst, struct mm_struct *src,
                    struct vm_area_struct *vma)
{
    unsigned long addr = vma->vm_start;
    unsigned long end = vma->vm_end;

    for (; addr < end; addr += PAGE_SIZE) {
        // 1. 为子进程分配新的 PTE
        // 2. 复制 PTE 内容
        // 3. 设置 COW 标志(只读)
        // 4. 共享物理页面(引用计数 +1)
    }
}

COW Implementation in fork

Bash
fork 时 COW 的关键步骤:

1. 复制页表条目(PTE),但共享物理页面
   - 父 PTE:物理页面 + RW 位
   - 子 PTE:同样的物理页面 + RO 位(只读)
   - 物理页面的引用计数 +1

2. 设置页面为只读
   - 父子都有页表指向同一个物理页
   - 页面被标记为只读
   - 当任一方写入时,触发 page fault

3. Page Fault 处理 COW
   - Page fault handler 发现:
     a. 页面存在
     b. 只读
     c. 引用计数 > 1(共享)
   - 分配新页面
   - 复制旧页面内容
   - 建立新页面的映射
   - 把写入方的 PTE 更新为新页面 + RW

关键数据结构:
  struct page {
      atomic_t _refcount;  // 引用计数
      // ...
      unsigned long flags; // PG_dirty / PG_writeback 等
  };

  PG_referenced 标志:
    - 页面最近被访问
    - COW 时检查这个标志
    - 如果是 clean + referenced,优先不回收

Special COW Handling for File Mappings

Bash
文件映射的 fork COW 与匿名映射不同:

匿名映射(MAP_ANON):
  - 页面内容是内存(全是零或匿名数据)
  - COW 时直接分配新页,复制内容
  - 不需要写回文件系统

文件映射(MAP_FILE):
  - 页面内容来自文件
  - COW 时需要先读取文件内容到新页
  - 写时复制后变成"私有"(不再同步到文件)

writeback 处理:
  - COW 后的脏页不会写回原文件
  - 只对当前进程可见
  - 进程退出时释放(不回写文件)

MAP_SHARED 的处理:
  - fork 时不复制页表(共享同一个 VMA)
  - 父子共享同一个 page cache
  - 写入会直接修改 page cache(立即对其他进程可见)
  - 不是 COW,是真正的共享

Special Handling of vfork and clone

Bash
vfork(virtual fork)的特殊处理:

vfork() 和 fork() 的区别:

fork():
  - 复制整个地址空间(COW)
  - 父子可以独立运行
  - 写时复制,内存可能翻倍

vfork():
  - 完全共享父进程地址空间(不复制)
  - 子进程先运行,调用 exec 或 _exit 后父进程才继续
  - 效率更高(但有风险)
  - 现代 Linux 实际用 clone(CLONE_VM) 实现

clone(CLONE_VM) 的效果:
  - 父子共享同一个 mm_struct
  - 没有 COW,没有页表复制
  - 共享所有 VMA 和页表
  - 立即 exec 时效率最高(不复制任何内存)

vfork 的风险:
  - 如果子进程不调用 exec/_exit,父进程无法运行
  - 子进程修改内存会影响父进程(完全共享)
  - 现代代码很少用 vfork,fork + COW 更安全

Summary

  • fork Memory Copy: copy_page_range iterates VMA; copies page tables but shares physical pages; marks read-only (COW)
  • COW Implementation: PTE read-only + page refcount +1 → write triggers page fault → allocate new page → copy content → update PTE
  • Anonymous vs File Mapping: Anonymous COW copies directly; file COW reads file content into new page (privatization)
  • MAP_SHARED: Not copied on fork; parent and child share the same page cache; writes are immediately visible to other processes
  • vfork: Fully shares address space (no copy); child runs first until exec/_exit; modern systems use clone(CLONE_VM)

Next (K04): When memory is exhausted, how does the kernel choose which process to kill? OOM Killer scoring mechanism and cgroup memory limits.


Last modified: 2024年1月8日

Author

Comments

Write a Reply or Comment

Your email address will not be published.