Linux内核设计与实现---进程地址空间
進(jìn)程地址空間
- 1 內(nèi)存描述符
- 分配內(nèi)存描述符
- 銷毀內(nèi)存描述符
- mm_struct與內(nèi)核線程
- 2 內(nèi)存區(qū)域
- VMA標(biāo)志
- VMA操作
- 內(nèi)存區(qū)域的樹形結(jié)構(gòu)和內(nèi)存區(qū)域的鏈表結(jié)構(gòu)
- 3 操作內(nèi)存區(qū)域
- find_vma()
- find_vma_prev()
- find_vma_intersection()
- 4 mmap()和do_mmap():創(chuàng)建地址空間
- mmap() 系統(tǒng)調(diào)用
- 5 munmap()和do_munmap():刪除地址空間
- munmap()系統(tǒng)調(diào)用
- 6 頁表
內(nèi)核除了管理本身的內(nèi)存外,還必須管理進(jìn)程的地址空間,也就是系統(tǒng)中每個(gè)用戶空間地址所看到的內(nèi)存。Linux操作系統(tǒng)采用虛擬內(nèi)存計(jì)數(shù),因此,系統(tǒng)中的所有進(jìn)程之間以虛擬方法共享內(nèi)存,對(duì)每個(gè)進(jìn)程來說,它們好像都可以訪問整個(gè)系統(tǒng)的所有物理內(nèi)存。
進(jìn)程地址空間由每個(gè)進(jìn)程中的線性地址區(qū)組成,而且更為重要的特定是內(nèi)核允許進(jìn)程使用該空間中的地址。每個(gè)進(jìn)程都有一個(gè)32位或64位的flat地址空間,空間的具體大小取決于體系結(jié)構(gòu),flag地址空間是指地址空間范圍是一個(gè)獨(dú)立的連續(xù)空間(比如,地址從0擴(kuò)展到429496729位地址空間)。一些操作系統(tǒng)提供了段地址空間,這種地址空間并非是一個(gè)獨(dú)立的線性區(qū)域,而是被分段的,但現(xiàn)代采用虛擬內(nèi)存的操作系統(tǒng)通常都使用flat地址空間而不是分段式的內(nèi)存模式。通常情況下,每個(gè)進(jìn)程都有唯一的這種flat地址空間,而進(jìn)程地址空間之間彼此互不相干,兩個(gè)不同的進(jìn)程可以在它們各自地址空間的相同地址內(nèi)存放不同的數(shù)據(jù)。進(jìn)程之間也可以選擇共享地址空間,我們稱這樣的進(jìn)程為線程。
1 內(nèi)存描述符
內(nèi)核使用內(nèi)存描述符結(jié)構(gòu)體表示進(jìn)程的地址空間,該結(jié)構(gòu)包含了和進(jìn)程地址空間有關(guān)的全部信息。內(nèi)存描述符由mm_struct結(jié)構(gòu)體表示,定義在文件linux/sched.h中。
struct mm_struct {struct vm_area_struct * mmap; /* list of VMAs */struct rb_root mm_rb;struct vm_area_struct * mmap_cache; /* last find_vma result */unsigned long (*get_unmapped_area) (struct file *filp,unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags);void (*unmap_area) (struct vm_area_struct *area);unsigned long mmap_base; /* base of mmap area */unsigned long free_area_cache; /* first hole */pgd_t * pgd;atomic_t mm_users; /* How many users with user space? */atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */int map_count; /* number of VMAs */struct rw_semaphore mmap_sem;spinlock_t page_table_lock; /* Protects page tables, mm->rss, mm->anon_rss */struct list_head mmlist; /* List of maybe swapped mm's. These are globally strung* together off init_mm.mmlist, and are protected* by mmlist_lock*/unsigned long start_code, end_code, start_data, end_data;unsigned long start_brk, brk, start_stack;unsigned long arg_start, arg_end, env_start, env_end;unsigned long rss, anon_rss, total_vm, locked_vm, shared_vm;unsigned long exec_vm, stack_vm, reserved_vm, def_flags, nr_ptes;unsigned long saved_auxv[42]; /* for /proc/PID/auxv */unsigned dumpable:1;cpumask_t cpu_vm_mask;/* Architecture-specific MM context */mm_context_t context;/* Token based thrashing protection. */unsigned long swap_token_time;char recent_pagein;/* coredumping support */int core_waiters;struct completion *core_startup_done, core_done;/* aio bits */rwlock_t ioctx_list_lock;struct kioctx *ioctx_list;struct kioctx default_kioctx; };mm_users域記錄正在使用該地址的進(jìn)程數(shù)目。比如,有兩個(gè)進(jìn)程共享該地址空間,那么mm_users的值便等于2;mm_count是mm_struct的主引用計(jì)數(shù),只要mm_users不為0,那么mm_count值就等于1。當(dāng)mm_users的值減為0時(shí),mm_count域的值才為0,如果mm_count的值等于0,說明已經(jīng)沒有任何指向該mm_struct結(jié)構(gòu)體的引用了,這個(gè)時(shí)候該結(jié)構(gòu)體會(huì)被銷毀。
mmap和mm_rb這兩個(gè)數(shù)據(jù)結(jié)構(gòu)描述的對(duì)象是相同的:該地址空間中的全部?jī)?nèi)存區(qū)域。但是mmap是以鏈表形式存放而后者以紅-黑樹形式存放。mmap結(jié)構(gòu)體作為鏈表,利于簡(jiǎn)單、高效地遍歷所有元素,而mm_rb結(jié)構(gòu)體更適合搜索指定元素。
所有的mm_struct結(jié)構(gòu)體都通過自身的mmlist域連接在一個(gè)雙向鏈表中,該鏈表的首元素是init_mm內(nèi)存描述符,它代表init進(jìn)程的地址空間,內(nèi)存描述符的總數(shù)存放在mmlist_nr全局變量中,該變量定義在kernel/fork.c中。
分配內(nèi)存描述符
在進(jìn)程的struct task_struct進(jìn)程描述符中,mm域存放著該進(jìn)程使用的內(nèi)存描述符,所以current->mm便指向當(dāng)前進(jìn)程的內(nèi)存描述符。fork函數(shù)利用copy_mm()函數(shù)復(fù)制父進(jìn)程的內(nèi)存描述符,也就是current->mm域給其子進(jìn)程,而子進(jìn)程中的mm_struct結(jié)構(gòu)體實(shí)際是通過文件kernel/fork.c中的allocate_mm()宏從mm_cachep slab緩存中分配得到的。
如果父進(jìn)程希望和其子進(jìn)程共享地址空間,可以在調(diào)用clone()時(shí),設(shè)置CLONE_VM標(biāo)志。我們把這樣的進(jìn)程稱作線程。當(dāng)CLONE_VM被指定后,內(nèi)核就不再需要調(diào)用allocate_mm()函數(shù)了,而僅僅需要在調(diào)用copy_mm()函數(shù)中將mm域指向其父進(jìn)程的內(nèi)存描述符就可以了。
銷毀內(nèi)存描述符
當(dāng)進(jìn)程退出時(shí),內(nèi)核會(huì)調(diào)用exit_mm函數(shù),該函數(shù)執(zhí)行一些常規(guī)的銷毀工作,同時(shí)更新一些統(tǒng)計(jì)量。其中,該函數(shù)會(huì)調(diào)用mmput()函數(shù)減少內(nèi)存描述符的mm_users用戶計(jì)數(shù),如果mm_users降到0,繼續(xù)調(diào)用mmdrop()函數(shù),減少mm_count,如果mm_count也等于0了,說明該內(nèi)存描述符不再有任何使用者了,那么調(diào)用free_mm宏通過kmem_cache_free()將mm_struct結(jié)構(gòu)體歸還到mm_cachep_slab緩存中。
mm_struct與內(nèi)核線程
內(nèi)核線程沒有進(jìn)程地址空間,也沒有相關(guān)的內(nèi)存描述符,所以內(nèi)核線程對(duì)應(yīng)的進(jìn)程描述符中mm域?yàn)榭铡?/p>
當(dāng)一個(gè)進(jìn)程被調(diào)度時(shí),該進(jìn)程的mm域指向的地址空間被裝載到內(nèi)存,進(jìn)程描述符中的active_mm域會(huì)被更新,指向新的地址空間。內(nèi)核線程沒有地址空間,所以mm域?yàn)镹ULL,于是當(dāng)一個(gè)內(nèi)核線程被調(diào)用時(shí),就會(huì)保留前一個(gè)進(jìn)程的地址空間,隨后內(nèi)核更新內(nèi)核線程對(duì)應(yīng)的進(jìn)程描述符中的active_mm域,使其指向前一個(gè)進(jìn)程的內(nèi)存描述符。
2 內(nèi)存區(qū)域
內(nèi)存區(qū)域由vm_area_struct結(jié)構(gòu)體描述,定義在文件linux/mm.h中,內(nèi)存區(qū)域在內(nèi)核中也經(jīng)常被稱作虛擬內(nèi)存區(qū)域或VMA。
vm_area_struct結(jié)構(gòu)體描述了指定地址空間內(nèi)連續(xù)區(qū)間上的一個(gè)獨(dú)立內(nèi)存范圍。內(nèi)核將每個(gè)內(nèi)存區(qū)域作為一個(gè)單獨(dú)的內(nèi)存對(duì)象管理,每個(gè)內(nèi)存區(qū)域都擁有一致的屬性,
每個(gè)內(nèi)存描述符都對(duì)應(yīng)于進(jìn)程地址空間的唯一區(qū)間,vm_start域指向區(qū)間的首地址,vm_end域指向區(qū)間的尾地址之后的第一個(gè)字節(jié),vm_end~vm_start的大小便是內(nèi)存區(qū)間的長(zhǎng)度,內(nèi)存區(qū)域的位置就在[vm_start,vm_end]之中,注意,在同一個(gè)地址空間內(nèi)的不同內(nèi)存區(qū)間不能重疊。
vm_mm域指向和VMA相關(guān)的mm_struct結(jié)構(gòu)體,注意每個(gè)VMA對(duì)其相關(guān)的mm_struct來說都是唯一的,所以即使兩個(gè)獨(dú)立的進(jìn)程將同一個(gè)文件映射到各自的地址空間,它們分別都會(huì)有一個(gè)vm_area_struct結(jié)構(gòu)體標(biāo)志自己的內(nèi)存區(qū)域,但是如果兩個(gè)線程共享一個(gè)地址空間,那么它們也同時(shí)共享其中所有的vm_area_struct結(jié)構(gòu)體。
VMA標(biāo)志
VMA標(biāo)志是一種位標(biāo)志,其定義在linux/mm.h中,它包含在vm_flags域內(nèi),標(biāo)志了內(nèi)存區(qū)域所包含的頁面的行為和信息,和物理頁的訪問權(quán)限不同,VMA標(biāo)志反映了內(nèi)核處理頁面所需要遵守的行為準(zhǔn)則,而不是硬件要求。
VMA操作
vm_area_struct結(jié)構(gòu)體中的vm_ops指向與指定內(nèi)存區(qū)域相關(guān)的操作函數(shù)表,內(nèi)核使用表中的方法操作VMA。vm_area_struct作為通用對(duì)象代表了任何類型的內(nèi)存區(qū)域,而操作表描述針對(duì)特定的對(duì)象實(shí)例的特定方法。
操作函數(shù)表由vm_operations_struct結(jié)構(gòu)體表示,定義在文件linux/mm.h中
內(nèi)存區(qū)域的樹形結(jié)構(gòu)和內(nèi)存區(qū)域的鏈表結(jié)構(gòu)
上面說過,可以通過內(nèi)存描述符中的mmap和mm_rb域之一訪問內(nèi)存區(qū)域,這兩個(gè)域各自獨(dú)立地指向與內(nèi)存描述符相關(guān)的全部?jī)?nèi)存區(qū)域?qū)ο髒m_area_struct。
mmap使用單獨(dú)鏈表連接所有的內(nèi)存區(qū)域?qū)ο髒m_area_struct,每一個(gè)vm_area_struct結(jié)構(gòu)體通過自身的vm_next域被連入鏈表,所有的區(qū)域按地址增長(zhǎng)的方向排序,mmap域指向鏈表中第一個(gè)內(nèi)存區(qū)域,鏈中最后一個(gè)VMA結(jié)構(gòu)體指針指向空。
mm_rb域使用紅-黑樹連接所有內(nèi)存區(qū)域?qū)ο?#xff0c;mm_rb域指向紅-黑樹的根結(jié)點(diǎn),地址空間中每一個(gè)vm_area_struct結(jié)構(gòu)體通過自身的vm_rb域連接到樹中。
鏈表用于需要遍歷全部結(jié)點(diǎn)的時(shí)候,而紅-黑樹適用于在地址空間中定位特定內(nèi)存區(qū)域的時(shí)候。內(nèi)核為了內(nèi)存區(qū)域上的各種不同操作都能獲得高性能,所以同時(shí)使用了這兩種數(shù)據(jù)結(jié)構(gòu)。
3 操作內(nèi)存區(qū)域
內(nèi)核定義了許多內(nèi)存區(qū)域操作函數(shù),它們都聲明在文件linux/mm.h中
find_vma()
find_vma()函數(shù)定義在mm/mmap.c中。
/* Look up the first VMA which satisfies addr < vm_end, NULL if none. */ extern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr);該函數(shù)在指定的地址空間中搜索第一個(gè)vm_end大于addr的內(nèi)存區(qū)域。換句話說,該函數(shù)尋找第一個(gè)包含addr或首地址大于addr的內(nèi)存區(qū)域,如果沒有發(fā)現(xiàn)這樣的區(qū)域,該函數(shù)返回NULL。否則返回指向匹配的內(nèi)存區(qū)域的vm_area_struct結(jié)構(gòu)體指針,返回的結(jié)構(gòu)會(huì)被緩存在內(nèi)存描述符的mmap_cache域中,所以find_vma會(huì)先在緩存中查找,如果指定的地址不在緩存中,那么必須搜搜和內(nèi)存描述符相關(guān)的所有內(nèi)存區(qū)域,這種搜索通過紅-黑樹進(jìn)行。
find_vma_prev()
find_vma_prev()函數(shù)和find_vma()工作方式相同,但是它返回第一個(gè)小于addr的VMA。該函數(shù)定義和聲明分別在文件mm/mmap.c中和文件linux/mm.h中
extern struct vm_area_struct * find_vma_prev(struct mm_struct * mm, unsigned long addr,struct vm_area_struct **pprev)pprev參數(shù)存放指向先于addr的VMA指針。
find_vma_intersection()
find_vma_intersection()返回第一個(gè)和指定地址區(qū)間相交的VMA。因?yàn)樵摵瘮?shù)和內(nèi)聯(lián)函數(shù),所以定義在文件linux/mm.h中:
static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr) {struct vm_area_struct * vma = find_vma(mm,start_addr);if (vma && end_addr <= vma->vm_start)vma = NULL;return vma; }第一個(gè)參數(shù)是要搜索的地址空間,start_addr是區(qū)間的開始首位置,end_addr是區(qū)間的尾位置,
4 mmap()和do_mmap():創(chuàng)建地址空間
內(nèi)核使用do_mmap()函數(shù)創(chuàng)建一個(gè)新的線性地址區(qū)間。如果創(chuàng)建的地址區(qū)間和一個(gè)已經(jīng)存在的地址區(qū)間相鄰,并且它們具有相同的訪問權(quán)限的話,那么兩個(gè)區(qū)間將合并為一個(gè)。do_mmap()函數(shù)會(huì)將一個(gè)地址區(qū)間加入到進(jìn)程的地址空間中。
do_mmap()函數(shù)定義在linux/mm.h中
static inline unsigned long do_mmap(struct file *file, unsigned long addr,unsigned long len, unsigned long prot,unsigned long flag, unsigned long offset)該函數(shù)映射由file指定的文件,具體映射的是文件中從偏移量offset處開始,長(zhǎng)度為len字節(jié)的范圍內(nèi)的數(shù)據(jù)。如果file參數(shù)是NULL并且offset參數(shù)也是0,那么就代碼這次映射沒有和文件相關(guān),該情況被稱作匿名映射,如果指定了文件名和偏移量,那么該映射被稱為文件映射。
addr是可選參數(shù),它指定搜索空閑區(qū)域的起始位置。
prot參數(shù)指定內(nèi)存區(qū)域中頁面的訪問權(quán)限。訪問權(quán)限標(biāo)志定義在文件asm/mman.h中。
flag參數(shù)指定了VMA標(biāo)志,這些標(biāo)志也定義在文件asm/mman.h中
mmap() 系統(tǒng)調(diào)用
在用戶空間可以通過mmap()系統(tǒng)調(diào)用獲取內(nèi)核函數(shù)do_mmap()的功能。
5 munmap()和do_munmap():刪除地址空間
do_munmap()函數(shù)從特定的進(jìn)程地址空間中刪除指定地址區(qū)間,該函數(shù)定義在文件linux/mm.h中:
extern int do_munmap(struct mm_struct *mm, unsigned long start, size_t len);第一個(gè)參數(shù)指定要?jiǎng)h除區(qū)域所在的地址空間,刪除從地址start開始,長(zhǎng)度為len字節(jié)的地址區(qū)間,如果成功,返回0.
munmap()系統(tǒng)調(diào)用
系統(tǒng)調(diào)用munmap()給用戶空間程序提供了一種從自身地址空間刪除指定區(qū)間的方法。
int munmap(void *start ,size_t length)該系統(tǒng)調(diào)用定義在mm/mmap.c中,它是對(duì)do_munmap的一個(gè)簡(jiǎn)單封裝
6 頁表
雖然應(yīng)用程序操作的對(duì)象是映射到物理內(nèi)存之上的虛擬內(nèi)存,但是處理器直接操作的卻是物理內(nèi)存,所以當(dāng)應(yīng)用程序訪問一個(gè)虛擬地址時(shí),首先必須將虛擬地址轉(zhuǎn)化為物理地址,然后處理器才能解析地址訪問請(qǐng)求。地址的轉(zhuǎn)換工作是通過查詢頁表完成的。
頁表對(duì)應(yīng)的結(jié)構(gòu)體依賴具體的體系結(jié)構(gòu),所以定義在文件asm/page.h中
總結(jié)
以上是生活随笔為你收集整理的Linux内核设计与实现---进程地址空间的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux内核设计与实现---虚拟文件系
- 下一篇: Linux内核设计与实现---页高速缓存