+ -
当前位置:首页 → 问答吧 → 请问初始化新进程的函数在哪里?

请问初始化新进程的函数在哪里?

时间:2004-09-27

来源:互联网

大家好,请问初始化新进程的函数在哪里?Linux 中的 do_fork() 似乎和 UNIX 中的 fork() 不太一样。

作者: herberteuler   发布时间: 2004-09-27

Linux 综合使用了BSD的vfork()和SystemV的fork()COW技术,


fork和vfork都是调用同一个核心函数do_fork。

作者: 锋锋   发布时间: 2004-09-30

UNIX V6 的 fork() 函数调用了 newproc() 来初始化页表,我想读读 Linux 中的对应代码,可是没有找到。虽然 Linux 中使用了动态的结构,但原理应该一样吧,它在哪里呢?谢谢。

作者: herberteuler   发布时间: 2004-09-30

很久没讨论了。。
这段时间看了一下linux内核方面内容,写一点,不对的地方请各位指正。

重点讨论linux fork()系统调用虚拟地址空间的重建过程:


PHP 代码:
新进程是通过克隆旧进程或说克隆当前进程而创建。新任务通过一个系统调用( f o r k或
c l o n e )而创建,克隆过程发生在核心模式下的内核中,在系统调用的末尾会有一个新进程等待
调度器选中它并运行。一个新的t a s k _ s t r u c t数据结构被从系统物理内存分配,以及一页或多页
物理内存作为克隆的进程的栈(用户的和核心的)。一个进程标识符可能会被创建:一个在系统
进程标识符集合中唯一的标识符。然而,克隆的进程完全有理由保存其父进程的标识符。新
的t a s k _ s t r u c t新加入到t a s k向量并且老的(当前)进程的t a s k _ s t r u c t的内容被复制到克隆出的
t a s k _ s t r u c t中。
当克隆进程时, L i n u x允许两个进程共享资源而不是有两份独立的副本。这种共享可用于
进程的文件、信号处理器和虚拟内存。当资源被共享时,它们的c o u n t字段将被递增,以便在
两个进程都结束访问它们之前Linux 不会回收这些资源。举例来说,如果克隆的进程将共享虚
拟内存,其t a s k _ s t r u c t将包含指向原先进程的m m _ s t r u c t的指针并且该m m _ s t r u c t将把其c o u n t
字段递增以表明当前共享该页的进程数。
克隆一个进程的虚拟内存是很有技巧性的。一个新的v m _ a r e a _ s t r u c t集合以及它们拥有的
m m _ s t r u c t数据结构,还有被克隆的进程的页表必须被产生出来。在这时没有进程的任何虚拟
内存被复制。复制将是一件很困难和冗长的工作,因为一些虚拟内存在物理内存,一些在进
程正在执行的可执行映像中,还可能有一些在交换文件中;相反, L i n u x使用称为“写时复制
(copy on write)”的技巧,这意味着仅当两个进程试图写它时虚拟内存才会被复制。任何虚拟
内存只要没被写,即使它可以被写,也将在两个进程间共享而不会引起任何危害。只读的内
存(比如可执行代码)总是被共享。为了使“写时复制”工作,可写区域将其页表项标识为只读
并且v m _ a r e a _ s t r u c t数据结构描述为它们被标识成”写时复制”。当一个进程试图写该虚拟内
存时将产生一个页故障。正是在此时L i n u x将产生该内存的一个副本,并修改两个进程的页表
和虚拟内存数据结构。 
上面这些摘自《Linux编程白皮书》中描述的。
原理也就这样。

作者: 锋锋   发布时间: 2004-10-08

fork系统调用内部调用copy_mm()实现虚拟地址空间的复制。
\linux-2.6.8\kernel\fork.c

代码:
 static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
{
        struct mm_struct * mm, *oldmm;
        int retval;

        tsk->min_flt = tsk->maj_flt = 0;
        tsk->cmin_flt = tsk->cmaj_flt = 0;
        tsk->nvcsw = tsk->nivcsw = tsk->cnvcsw = tsk->cnivcsw = 0;

        tsk->mm = NULL;
        tsk->active_mm = NULL;
 /*  初始化tsk中(子进程)
一些mm相关域*/ 

        /*
 * Are we cloning a kernel thread?
 *
 * We need to steal a active VM for that..
 */
        oldmm = current->mm;
        if (!oldmm)
 return 0;

        if (clone_flags & CLONE_VM) {
 atomic_inc(&oldmm->mm_users);
 mm = oldmm;/* 如由vfork()调用,
直接将父进程mm_struct结构(oldmm = current->mm)返回给子进程,父子共享!*/ 
 /*
 * There are cases where the PTL is held to ensure no
 * new threads start up in user mode using an mm, which
 * allows optimizing out ipis; the tlb_gather_mmu code
 * is an example.
 */
 spin_unlock_wait(&oldmm->page_table_lock);
 goto good_mm;
        }

        retval = -ENOMEM;
        mm = allocate_mm();/* 由fork()调用,
为子进程创建新的mm_struct结构。*/ 
        if (!mm)
 goto fail_nomem;

        /* Copy the current MM stuff.. */
        memcpy(mm, oldmm, sizeof(*mm));/* 复制oldmm信息到mm.
子进程继承父进程全部属性。*/ 
        if (!mm_init(mm))/* 初始化mm_struct结构,
包括处理页面的统计信息,信号量,分配页目录表空间。*/ 
 goto fail_nomem;

        if (init_new_context(tsk,mm))/* 初始化新的上下文,
i386平台为空。*/ 
 goto fail_nocontext;/* 2.6内核的东东??,没看懂...*/ 

        retval = dup_mmap(mm, oldmm);/* 将父进程中所有虚拟内存区域
都复制到子进程中来。*/ 
        if (retval)
 goto free_pt;

good_mm:
        tsk->mm = mm;
        tsk->active_mm = mm;
        return 0;

free_pt:
        mmput(mm);
fail_nomem:
        return retval;

fail_nocontext:
        /*
 * If init_new_context() failed, we cannot use mmput() to free the mm
 * because it calls destroy_context()
 */
        mm_free_pgd(mm);
        free_mm(mm);
        return retval;
}

作者: 锋锋   发布时间: 2004-10-08

\linux-2.6.8\kernel\fork.c

代码:
 
static inline int dup_mmap(struct mm_struct * mm, struct mm_struct * oldmm)
{
        struct vm_area_struct * mpnt, *tmp, **pprev;
        struct rb_node **rb_link, *rb_parent;
        int retval;
        unsigned long charge;
        struct mempolicy *pol;

        down_write(&oldmm->mmap_sem);
        flush_cache_mm(current->mm);
        mm->locked_vm = 0;
        mm->mmap = NULL;
        mm->mmap_cache = NULL;
        mm->free_area_cache = TASK_UNMAPPED_BASE;
        mm->map_count = 0;
        mm->rss = 0;
        cpus_clear(mm->cpu_vm_mask);
        mm->mm_rb = RB_ROOT;
        rb_link = &mm->mm_rb.rb_node;
        rb_parent = NULL;
        pprev = &mm->mmap;

        /*
 * Add it to the mmlist after the parent.
 * Doing it this way means that we can order the list,
 * and fork() won't mess up the ordering significantly.
 * Add it first so that swapoff can see any swap entries.
 */
        spin_lock(&mmlist_lock);
        list_add(&mm->mmlist, &current->mm->mmlist);
        mmlist_nr++;
        spin_unlock(&mmlist_lock);

        for (mpnt = current->mm->mmap ; mpnt ; mpnt = mpnt->vm_next) {
/*用for循环全部复制父进程的虚拟内存区域!*/ 
 struct file *file;

 if(mpnt->vm_flags & VM_DONTCOPY)
 continue;
 charge = 0;
 if (mpnt->vm_flags & VM_ACCOUNT) {
 unsigned int len = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT;
 if (security_vm_enough_memory(len))
 goto fail_nomem;
 charge = len;
 }
 tmp = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
/*为子进程分配vm_area_struct结构*/ 
 if (!tmp)
 goto fail_nomem;
 *tmp = *mpnt;
/*直接复制父进程vm_area_struct结构。*/ 
 pol = mpol_copy(vma_policy(mpnt));
 retval = PTR_ERR(pol);
 if (IS_ERR(pol))
 goto fail_nomem_policy;
 vma_set_policy(tmp, pol);
 tmp->vm_flags &= ~VM_LOCKED;
 tmp->vm_mm = mm;
 tmp->vm_next = NULL;
 anon_vma_link(tmp);
 vma_prio_tree_init(tmp);
 file = tmp->vm_file;
 if (file) {
 struct inode *inode = file->f_dentry->d_inode;
 get_file(file);
 if (tmp->vm_flags & VM_DENYWRITE)
 atomic_dec(&inode->i_writecount);
 
 /* insert tmp into the share list, just after mpnt */
 spin_lock(&file->f_mapping->i_mmap_lock);
 flush_dcache_mmap_lock(file->f_mapping);
 vma_prio_tree_add(tmp, mpnt);
 flush_dcache_mmap_unlock(file->f_mapping);
 spin_unlock(&file->f_mapping->i_mmap_lock);
 }

 /*
 * Link in the new vma and copy the page table entries:
 * link in first so that swapoff can see swap entries,
 * and try_to_unmap_one's find_vma find the new vma.
 */
 spin_lock(&mm->page_table_lock);
 *pprev = tmp;
 pprev = &tmp->vm_next;

 __vma_link_rb(mm, tmp, rb_link, rb_parent);
 rb_link = &tmp->vm_rb.rb_right;
 rb_parent = &tmp->vm_rb;

 mm->map_count++;
 retval = copy_page_range(mm, current->mm, tmp);/*将父进程中位于
内存区域tmp中的页目录,页表复制到新进程中!*/ 
 spin_unlock(&mm->page_table_lock);

 if (tmp->vm_ops && tmp->vm_ops->open)
 tmp->vm_ops->open(tmp);

 if (retval)
 goto out;
        }
        retval = 0;

out:
        flush_tlb_mm(current->mm);
        up_write(&oldmm->mmap_sem);
        return retval;
fail_nomem_policy:
        kmem_cache_free(vm_area_cachep, tmp);
fail_nomem:
        retval = -ENOMEM;
        vm_unacct_memory(charge);
        goto out;
}

作者: 锋锋   发布时间: 2004-10-08

\linux-2.6.8\mm\memory.c

copy_page_range()这函数太长,以后再看。。头痛...:(



PHP 代码:
int copy_page_range(struct mm_struct *dst, struct mm_struct *src,
            struct vm_area_struct *vma)
{
    pgd_t * src_pgd, * dst_pgd;
    unsigned long address = vma->vm_start;
    unsigned long end = vma->vm_end;
    unsigned long cow;

    if (is_vm_hugetlb_page(vma))
        return copy_hugetlb_page_range(dst, src, vma);

    cow = (vma->vm_flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE;
    src_pgd = pgd_offset(src, address)-1;
    dst_pgd = pgd_offset(dst, address)-1;

    for (;;) {
        pmd_t * src_pmd, * dst_pmd;

        src_pgd++; dst_pgd++;
        
        /* copy_pmd_range */
        
        if (pgd_none(*src_pgd))
            goto skip_copy_pmd_range;
        if (unlikely(pgd_bad(*src_pgd))) {
            pgd_ERROR(*src_pgd);
            pgd_clear(src_pgd);
skip_copy_pmd_range:    address = (address + PGDIR_SIZE) & PGDIR_MASK;
            if (!address || (address >= end))
                goto out;
            continue;
        }

        src_pmd = pmd_offset(src_pgd, address);
        dst_pmd = pmd_alloc(dst, dst_pgd, address);
        if (!dst_pmd)
            goto nomem;

        do {
            pte_t * src_pte, * dst_pte;
        
            /* copy_pte_range */
        
            if (pmd_none(*src_pmd))
                goto skip_copy_pte_range;
            if (unlikely(pmd_bad(*src_pmd))) {
                pmd_ERROR(*src_pmd);
                pmd_clear(src_pmd);
skip_copy_pte_range:
                address = (address + PMD_SIZE) & PMD_MASK;
                if (address >= end)
                    goto out;
                goto cont_copy_pmd_range;
            }

            dst_pte = pte_alloc_map(dst, dst_pmd, address);
            if (!dst_pte)
                goto nomem;
            spin_lock(&src->page_table_lock);    
            src_pte = pte_offset_map_nested(src_pmd, address);
            do {
                pte_t pte = *src_pte;
                struct page *page;
                unsigned long pfn;

                /* copy_one_pte */

                if (pte_none(pte))
                    goto cont_copy_pte_range_noset;
                /* pte contains position in swap, so copy. */
                if (!pte_present(pte)) {
                    if (!pte_file(pte))
                        swap_duplicate(pte_to_swp_entry(pte));
                    set_pte(dst_pte, pte);
                    goto cont_copy_pte_range_noset;
                }
                pfn = pte_pfn(pte);
                /* the pte points outside of valid memory, the
                 * mapping is assumed to be good, meaningful
                 * and not mapped via rmap - duplicate the
                 * mapping as is.
                 */
                page = NULL;
                if (pfn_valid(pfn)) 
                    page = pfn_to_page(pfn); 

                if (!page || PageReserved(page)) {
                    set_pte(dst_pte, pte);
                    goto cont_copy_pte_range_noset;
                }

                /*
                 * If it's a COW mapping, write protect it both
                 * in the parent and the child
                 */
                if (cow) {
                    ptep_set_wrprotect(src_pte);
                    pte = *src_pte;
                }

                /*
                 * If it's a shared mapping, mark it clean in
                 * the child
                 */
                if (vma->vm_flags & VM_SHARED)
                    pte = pte_mkclean(pte);
                pte = pte_mkold(pte);
                get_page(page);
                dst->rss++;
                set_pte(dst_pte, pte);
                page_dup_rmap(page);
cont_copy_pte_range_noset:
                address += PAGE_SIZE;
                if (address >= end) {
                    pte_unmap_nested(src_pte);
                    pte_unmap(dst_pte);
                    goto out_unlock;
                }
                src_pte++;
                dst_pte++;
            } while ((unsigned long)src_pte & PTE_TABLE_MASK);
            pte_unmap_nested(src_pte-1);
            pte_unmap(dst_pte-1);
            spin_unlock(&src->page_table_lock);
            cond_resched_lock(&dst->page_table_lock);
cont_copy_pmd_range:
            src_pmd++;
            dst_pmd++;
        } while ((unsigned long)src_pmd & PMD_TABLE_MASK);
    }
out_unlock:
    spin_unlock(&src->page_table_lock);
out:
    return 0;
nomem:
    return -ENOMEM;

作者: 锋锋   发布时间: 2004-10-08