本博,我們重點關注fork()系統呼叫為子程序建立乙個完整的新位址空間。相反,當程序結束時,核心撤消它的位址空間。我們重點來討論linux如何執行這兩種操作。
回憶一下「程序的建立 —— do_fork()函式詳解 」博文:當建立乙個新的程序時核心呼叫copy_mm()函式。這個函式通過建立新程序的所有頁表和記憶體描述符來建立程序一的位址空間:
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
retval = -enomem;
mm = dup_mm(tsk);
if (!mm)
goto fail_nomem;
good_mm:
tsk->mm = mm;
tsk->active_mm = mm;
return 0;
fail_nomem:
return retval;
}通常,每個程序都有自己的位址空間,但是輕程序可以通過呼叫clone()函式(設定了clone_vm標誌)來建立。這些輕量級程序共享同一位址空間,也就是說,允許它們對同一組頁進行定址。
按照前面講述的「寫時複製 」方法,傳統的程序繼承父程序的位址空間,只要頁是唯讀的,就依然共享它們。當其中的乙個程序試圖對某個頁進行寫時,此時,這個頁才被複製乙份。一段時間之後,所建立的子程序通常會因為缺頁異常而獲得與父程序不一樣的完全屬於自己的位址空間。
另一方面,輕量級的程序使用父程序的位址空間。linux實現輕量級程序很簡單,即不複製父程序位址空間。建立輕量級的程序(clone)比建立普通程序相應要快得多,而且只要父程序和子程序謹慎地協調它們的訪問,就可以認為頁的共享是有益的。
如果通過clone()系統呼叫已經建立了新程序,並且flag引數的clone_vm標誌被設定,則copy_mm()函式把父程序(current)位址空間給子程序(tsk):
if (clone_flags & clone_vm)
good_mm:
tsk->mm = mm;
tsk->active_mm = mm;
return 0;
如果沒有設定clone_vm標誌,copy_mm()函式就必須建立乙個新的位址空間(在程序請求乙個位址之前,即使在位址空間內沒有分配記憶體):
mm = dup_mm(tsk);
dup_mm()函式分配乙個新的記憶體描述符,把它的位址存放在新程序描述符tsk的mm欄位中,並把current->mm的內容複製到tsk->mm中。然後改變新程序描述符的一些字段:
static struct mm_struct *dup_mm(struct task_struct *tsk)
#define allocate_mm() (kmem_cache_alloc(mm_cachep, slab_kernel))
函式首先使用allocate_mm()函式呼叫kmem_cache_alloc(mm_cachep, slab_kernel)從slab中分配乙個mm_struct結構,然後呼叫mm_init對其進行初始化:
static struct mm_struct * mm_init(struct mm_struct * mm)
return pgd;
out_oom:
for (i--; i >= 0; i--)
kmem_cache_free(pmd_cache, (void *)__va(pgd_val(pgd[i])-1));
kmem_cache_free(pgd_cache, pgd);
return null;
}#define user_ptrs_per_pgd (task_size/pgdir_size)
#define pgdir_size (1ul << pgdir_shift)
#define pgdir_shift 22
#define task_size (page_offset) /* user space process size: 3gb (default). */
注意,執行完mm_alloc_pgd()函式之後,子程序的pgd和pmd有了(32位i386體系結構),但是pte是沒有的,後面的工作需要dup_mmap()函式來完成,馬上會談到。
接著來,隨後呼叫依賴於體系結構的init_new_context()函式:對於80x86處理器,該函式檢查當前程序是否擁有定製的區域性描述符表,如果是,init_new_context()複製乙份current的區域性描述符表並把它插入tsk的位址空間:
int init_new_context(struct task_struct *tsk, struct mm_struct *mm)
return retval;
}下乙個重點步驟:err = dup_mmap(mm, oldmm):
static inline int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
charge = 0;
if (mpnt->vm_flags & vm_account)
tmp = kmem_cache_alloc(vm_area_cachep, slab_kernel);
if (tmp->vm_ops && tmp->vm_ops->open)
tmp->vm_ops->open(tmp);
if (retval)
goto out;
}#ifdef arch_dup_mmap
arch_dup_mmap(mm, oldmm);
#endif
retval = 0;
out:
up_write(&mm->mmap_sem);
flush_tlb_mm(oldmm);
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;
}dup_mmap()函式既複製父程序的線性區,也複製父程序的頁表。
然後,從current->mm->mmap所指向的線性區開始掃瞄父程序的線性區鍊錶:
for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next)
它複製遇到的每個vm_area_struct線性區描述符,並把複製品插入到子程序的線性區煉表和紅-黑樹中:
tmp = kmem_cache_alloc(vm_area_cachep, slab_kernel);
*tmp = *mpnt; /* 完全複製,這個技巧在這裡又用到了 */
……rb_link = &mm->mm_rb.rb_node;
rb_parent = null;
__vma_link_rb(mm, tmp, rb_link, rb_parent);
rb_link = &tmp->vm_rb.rb_right;
rb_parent = &tmp->vm_rb;
在插入乙個新的線性區描述符之後,如果需要的話,dup_mmap()立即呼叫copy_page_range()建立必要的頁表來對映這個線性區所包含的一組頁,並且初始化新頁表的表項。尤其是,與私有的、可寫的頁(vm_shared標誌關閉,vm_maywrite標誌開啟)所對應的任一頁框都標記為對父子程序是唯讀的,以便這種頁框能用寫時複製機制進行處理:
int copy_page_range(struct mm_struct *dst_mm, struct mm_struct *src_mm,
struct vm_area_struct *vma)
} while (dst_pgd++, src_pgd++, addr = next, addr != end);
return -enomem;
} while (dst_pud++, src_pud++, addr = next, addr != end);
return 0;
}static inline int copy_pmd_range(struct mm_struct *dst_mm, struct mm_struct *src_mm,
pud_t *dst_pud, pud_t *src_pud, struct vm_area_struct *vma,
unsigned long addr, unsigned long end)
while (dst_pmd++, src_pmd++, addr = next, addr != end);
return 0;
}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)
if (pte_none(*src_pte))
copy_one_pte(dst_mm, src_mm, dst_pte, src_pte, vma, addr, rss);
progress += 8;
} while (dst_pte++, src_pte++, addr += page_size, addr != end);
spin_unlock(src_ptl);
pte_unmap_nested(src_pte - 1);
add_mm_rss(dst_mm, rss[0], rss[1]);
pte_unmap_unlock(dst_pte - 1, dst_ptl);
cond_resched();
if (addr != end)
goto again;
return 0;
}
程序位址空間
這篇文章應該不能說是原創的,這裡的記錄都是我通過閱讀整理來的,並沒有太多的自己的想法。資料 現代作業系統 之所以去了解位址空間也是因為在學習dll的時候看到要將dll對映到程式的位址空間,不甚明了所以去查詢相關的資料。位址空間其實很好理解 當然針對早期的機器 早期的機器是沒有ram,rom,cach...
程序位址空間
kernel筆記 程序位址空間 2013 09 03 09 53 49 分類 linux 下圖是x86 64下linux程序的預設記憶體布局形式 下面逐一分析以上各個位址段的含義。text 段 段,從虛擬記憶體位址00400000開始,使用pmap 可以檢視到,這個位址是固定的 linux pmap...
程序位址空間
程序位址空間 1.可執行檔案 的記憶體對映,稱為 段 2.可執行檔案的已初始化全域性變數的記憶體對映,稱為資料段 3.包含未初始化的全域性變數,也就是bss段的零頁的記憶體對映 4.用於程序使用者控制項棧的零頁的記憶體對映 5.任何記憶體對映檔案 6.任何共享記憶體段 7.任何匿名的記憶體對映,比如...