进程创建使用do_fork函数,其中重要的部分如子进程的地址空间的建立,进程空间的建立主要包括线性地址空间和物理地址空间两个方面。其中线性地址空间使用VMA的方式管理,在dup_mmap函数中进行复制和初始化,而物理地址空间使用页目录,页表来管理。
下面分析进程物理地址空间的初始化过程。
整个调用流程:do_fork -> copy_process -> copy_mm -> dup_mm ->
其中进程的页表分为两段,0G-3G用户页表,3G-4G内核页表
内核页表
内核页表的创建 :~/ ->mm_init -> mm_alloc_pgd -> pgd_alloc中
pgd_t *pgd_alloc(struct mm_struct *mm) { pgd_t *pgd; pmd_t *pmds[PREALLOCATED_PMDS]; unsigned long flags; pgd = (pgd_t *)__get_free_page(PGALLOC_GFP); //创建页目录 if (pgd == NULL) goto out; mm->pgd = pgd; if (preallocate_pmds(pmds) != 0) //针对PTE模式下的预分配操作 goto out_free_pgd; if (paravirt_pgd_alloc(mm) != 0) goto out_free_pmds; /* * Make sure that pre-populating the pmds is atomic with * respect to anything walking the pgd_list, so that they * never see a partially populated pgd. */ spin_lock_irqsave(&pgd_lock, flags); pgd_ctor(pgd); //拷贝3G-4G对应的的内核页目录项,因此共享内核页表 pgd_prepopulate_pmd(mm, pgd, pmds); spin_unlock_irqrestore(&pgd_lock, flags); return pgd; out_free_pmds: free_pmds(pmds); out_free_pgd: free_page((unsigned long)pgd); out: return NULL; }
用户页表
用户页表创建:~/ ->dup_mmap ->copy_page_range -> ** -> copy_one_pte
这里的copy_page_range在所有的VMA区域都会执行,根据VMA区域的FLAG做出不同的处理,如SHARE,COW等等。
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) { unsigned long vm_flags = vma->vm_flags; pte_t pte = *src_pte; //父进程的页表中分配的page地址 struct page *page; /* pte contains position in swap or file, so copy. */ if (unlikely(!pte_present(pte))) { if (!pte_file(pte)) { swp_entry_t entry = pte_to_swp_entry(pte); if (swap_duplicate(entry) < 0) return entry.val; /* make sure dst_mm is on swapoff's mmlist. */ if (unlikely(list_empty(&dst_mm->mmlist))) { spin_lock(&mmlist_lock); if (list_empty(&dst_mm->mmlist)) list_add(&dst_mm->mmlist, &src_mm->mmlist); spin_unlock(&mmlist_lock); } if (likely(!non_swap_entry(entry))) rss[MM_SWAPENTS]++; else if (is_write_migration_entry(entry) && is_cow_mapping(vm_flags)) { /* * COW mappings require pages in both parent * and child to be set to read. */ make_migration_entry_read(&entry); pte = swp_entry_to_pte(entry); set_pte_at(src_mm, addr, src_pte, pte); } } goto out_set_pte; } /* * If it's a COW mapping, write protect it both * in the parent and the child */ if (is_cow_mapping(vm_flags)) { //COW机制,修改页表的标志 ptep_set_wrprotect(src_mm, addr, src_pte); pte = pte_wrprotect(pte); } /* * If it's a shared mapping, mark it clean in * the child */ if (vm_flags & VM_SHARED) pte = pte_mkclean(pte); pte = pte_mkold(pte); page = vm_normal_page(vma, addr, pte); //返回对应的page号 if (page) { get_page(page); //增加page的引用次数 page_dup_rmap(page); if (PageAnon(page)) //区分是匿名映射,还是文件映射,用于系统分析 rss[MM_ANONPAGES]++; else rss[MM_FILEPAGES]++; } out_set_pte: set_pte_at(dst_mm, addr, dst_pte, pte); //最后设置页表项 return 0; }
以上就是进程的物理地址空间的初始化,写的比较简单,没有针对各个VMA标志进行分析~~