fork写时复制实现原理

fork实现原理

在这里插入图片描述

clone,fork,vfork系统调用的区别

这几个系统调用最终都会调用_do_fork内核函数

long _do_fork(unsigned long clone_flags,
	      unsigned long stack_start,
	      unsigned long stack_size,
	      int __user *parent_tidptr,
	      int __user *child_tidptr,
	      unsigned long tls)

fork

SYSCALL_DEFINE0(fork)
{
    return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
}

只使用SIGCHLD标志位,子进程采用写时复制技术,只复制父进程的页表,不会复制页面内容。当子进程需要写入新内容时才触发写时复制机制,并为子进程创建一个副本。

vfork

SYSCALL_DEFINE0(vfork)
{
    return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
           0, NULL, NULL, 0);
}

CLONE_VM表示父、子进程执行在相同的进程地址空间中。另外,通过vfork()可以避免复制父进程的页表项。

SYSCALL_DEFINE5(clone, unsigned long, clone_flags, 
          unsigned long, newsp,
          int __user *, parent_tidptr,
          int __user *, child_tidptr,
          unsigned long, tls)
   {
    return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
   }

clone()函数功能强大,可以传递众多参数,可以有选择地继承父进程的资源。如可以和vfork()一样,与父进程共享一个进程地址空间,从而创建线程;也可以不和父进程共享进程地址空间,甚至可以创建兄弟关系进程。

copy on write写时复制的实现

在这里插入图片描述

在dup_mm函数中,首先直接memcpy复制父进程的mm_struct结构体,然后调用dup_mmap函数

static struct mm_struct *dup_mm(struct task_struct *tsk)
{
    ......
	mm = allocate_mm();
	
	memcpy(mm, oldmm, sizeof(*mm));
    
    err = dup_mmap(mm, oldmm);

	return mm;
    ......
}

dup_mmap函数

<kernel/fork.c>

static __latent_entropy int dup_mmap(struct mm_struct *mm,
                    struct mm_struct *oldmm)
{
    down_write_killable(&oldmm->mmap_sem)

    //遍历父进程所有的vma
    for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) {
        tmp = vm_area_dup(mpnt);   //从slab系统申请一个vm_are_struct,并将父进程的vma逐字节拷贝过去
        tmp->vm_mm = mm;
        anon_vma_fork(tmp, mpnt)

        __vma_link_rb(mm, tmp, rb_link, rb_parent);
        rb_link = &tmp->vm_rb.rb_right;
        rb_parent = &tmp->vm_rb;         //将vma插入mm红黑树中

        copy_page_range(mm, oldmm, mpnt);
    }

    up_write(&mm->mmap_sem);
}

其中anon_vma_fork的作用如下图所示,分配avc枢纽,avc枢纽对应的av是父进程的av,对应的vma是子进程的vma

在这里插入图片描述

copy_page_range()函数是复制父进程的进程地址空间相应页表的核心实现函数。它会沿着页表中的PGD、P4D、PUD、PMD以及PTE的方向查询和遍历页表(子进程如果不存在会申请)。遍历PTE的函数为copy_pte_range(),下面是该函数实现的代码片段。

 mm/memory.c>

static int copy_pte_range(struct mm_struct *dst_mm, struct mm_struct *src_mm, 
pmd_t *dst_pmd, pmd_t *src_pmd, struct vm_area_struct *vma,
           unsigned long addr, unsigned long end)
{
    do {
        entry.val = copy_one_pte(dst_mm, src_mm, dst_pte, src_pte,
                            vma, addr, rss);
    } while (dst_pte++, src_pte++, addr += PAGE_SIZE, addr != end);
    return 0;
}

写时复制技术的精华就在copy_one_pte()函数中

<mm/memory.c>

static inline unsigned long
copy_one_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,
        pte_t *dst_pte, pte_t *src_pte, struct vm_area_struct *vma,
        unsigned long addr, int *rss)
{
    if (unlikely(!pte_present(pte))) {
        swp_entry_t entry = pte_to_swp_entry(pte);
        goto out_set_pte;
    }

    if (is_cow_mapping(vm_flags) && pte_write(pte)) {
        ptep_set_wrprotect(src_mm, addr, src_pte);
        pte = pte_wrprotect(pte);
    }

    if (vm_flags & VM_SHARED)
        pte = pte_mkclean(pte);
    pte = pte_mkold(pte);

    page = vm_normal_page(vma, addr, pte);
    if (page) {
        get_page(page);
        rss[mm_counter(page)]++;
    }

out_set_pte:
    set_pte_at(dst_mm, addr, dst_pte, pte);
    return 0;
}


static inline bool is_cow_mapping(vm_flags_t flags)
{
	return (flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE;
}

在这里插入图片描述

总的来说就是将子进程对应页表里的pte设置为只读,这样对子进程vma写操作的时候就会触发缺页异常,copy on write

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值