日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

內核地址空間分布


 

直接映射區:線性空間中從3G開始最大896M的區間,為直接內存映射區,該區域的線性地址和物理地址存在線性轉換關系:線性地址=3G+物理地址。

動態內存映射區:該區域由內核函數VMALLOC來分配,特點是:線性空間連續,但是對應的物理空間不一定連續。vmalloc分配的線性地址所對應的物理頁可能處于低端內存,也可能處于高端內存。

永久內存映射區:該區域可訪問高端內存。訪問方法是使用alloc_page(_GFP_HIGHMEM)分配高端內存頁或者使用kmap函數將分配到的高端內存映射到該區域。

固定映射區:該區域和4G的頂端只有4k的隔離帶,其每個地址項都服務于特定的用途,如ACPI_BASE等。

進程的地址空間

linux采用虛擬內存管理技術,每一個進程都有一個3G大小的獨立的進程地址空間,這個地址空間就是用戶空間。每個進程的用戶空間都是完全獨立、互不相干的。進程訪問內核空間的方式:系統調用和中斷。
創建進程等進程相關操作都需要分配內存給進程。這時進程申請和獲得的不是物理地址,僅僅是虛擬地址。
實際的物理內存只有當進程真的去訪問新獲取的虛擬地址時,才會由“請頁機制”產生“缺頁”異常,從而進入分配實際頁框的程序。該異常是虛擬內存機制賴以存在的基本保證,它會告訴內核去為進程分配物理頁,并建立對應的頁表,這之后虛擬地址才實實在在的映射到了物理地址上。


 

vmalloc和kmalloc區別

1,kmalloc對應于kfree,分配的內存處于3GB~high_memory之間,這段內核空間與物理內存的映射一一對應,可以分配連續的物理內存; vmalloc對應于vfree,分配的內存在VMALLOC_START~4GB之間,分配連續的虛擬內存,但是物理上不一定連續。

2,vmalloc() 分配的物理地址無需連續,而kmalloc() 確保頁在物理上是連續的
3,kmalloc分配內存是基于slab,因此slab的一些特性包括著色,對齊等都具備,性能較好。物理地址和邏輯地址都是連續的。
4,最主要的區別是分配大小的問題,比如你需要28個字節,那一定用kmalloc,如果用vmalloc,分配不多次機器就罷工了。
盡管僅僅在某些情況下才需要物理上連續的內存塊,但是,很多內核代碼都調用kmalloc(),而不是用vmalloc()獲得內存。這主要是出于性能的考慮。vmalloc()函數為了把物理上不連續的頁面轉換為虛擬地址空間上連續的頁,必須專門建立頁表項。還有,通過 vmalloc()獲得的頁必須一個一個的進行映射(因為它們物理上不是連續的),這就會導致比直接內存映射大得多的緩沖區刷新。因為這些原因,vmalloc()僅在絕對必要時才會使用,最典型的就是為了獲得大塊內存時,例如,當模塊被動態插入到內核中時,就把模塊裝載到由vmalloc()分配的內存上。

進程地址空間

前邊我已經說過了內核是如何管理物理內存。但事實是內核是操作系統的核心,不光管理本身的內存,還要管理進程的地址空間。linux操作系統采用虛擬內存技術,所有進程之間以虛擬方式共享內存。進程地址空間由每個進程中的線性地址區組成,而且更為重要的特點是內核允許進程使用該空間中的地址。通常情況況下,每個進程都有唯一的地址空間,而且進程地址空間之間彼此互不相干。但是進程之間也可以選擇共享地址空間,這樣的進程就叫做線程。
內核使用內存描述符結構表示進程的地址空間,由結構體mm_struct結構體表示,定義在linux/sched.h中,如下:

struct mm_struct { struct vm_area_struct *mmap; /* list of memory areas */ struct rb_root mm_rb; /* red-black tree of VMAs */ struct vm_area_struct *mmap_cache; /* last used memory area */ unsigned long free_area_cache; /* 1st address space hole */ pgd_t *pgd; /* page global directory */ atomic_t mm_users; /* address space users */ atomic_t mm_count; /* primary usage counter */ int map_count; /* number of memory areas */ struct rw_semaphore mmap_sem; /* memory area semaphore */ spinlock_t page_table_lock; /* page table lock */ struct list_head mmlist; /* list of all mm_structs */ unsigned long start_code; /* start address of code */ unsigned long end_code; /* final address of code */ unsigned long start_data; /* start address of data */ unsigned long end_data; /* final address of data */ unsigned long start_brk; /* start address of heap */ unsigned long brk; /* final address of heap */ unsigned long start_stack; /* start address of stack */ unsigned long arg_start; /* start of arguments */ unsigned long arg_end; /* end of arguments */ unsigned long env_start; /* start of environment */ unsigned long env_end; /* end of environment */ unsigned long rss; /* pages allocated */ unsigned long total_vm; /* total number of pages */ unsigned long locked_vm; /* number of locked pages */ unsigned long def_flags; /* default access flags */ unsigned long cpu_vm_mask; /* lazy TLB switch mask */ unsigned long swap_address; /* last scanned address */ unsigned dumpable:1; /* can this mm core dump? */ int used_hugetlb; /* used hugetlb pages? */ mm_context_t context; /* arch-specific data */ int core_waiters; /* thread core dump waiters */ struct completion *core_startup_done; /* core start completion */ struct completion core_done; /* core end completion */ rwlock_t ioctx_list_lock; /* AIO I/O list lock */ struct kioctx *ioctx_list; /* AIO I/O list */ struct kioctx default_kioctx; /* AIO default I/O context */ };

mm_users記錄了正在使用該地址的進程數目(比如有兩個進程在使用,那就為2)。mm_count是該結構的主引用計數,只要mm_users不為0,它就為1。但其為0時,后者就為0。這時也就說明再也沒有指向該mm_struct結構體的引用了,這時該結構體會被銷毀。內核之所以同時使用這兩個計數器是為了區別主使用計數器和使用該地址空間的進程的數目。mmap和mm_rb描述的都是同一個對象:該地址空間中的全部內存區域。不同只是前者以鏈表,后者以紅黑樹的形式組織。所有的mm_struct結構體都通過自身的mmlist域連接在一個雙向鏈表中,該鏈表的首元素是init_mm內存描述符,它代表init進程的地址空間。另外需要注意,操作該鏈表的時候需要使用mmlist_lock鎖來防止并發訪問,該鎖定義在文件kernel/fork.c中。內存描述符的總數在mmlist_nr全局變量中,該變量也定義在文件fork.c中。

我前邊說過的進程描述符中有一個mm域,這里邊存放的就是該進程使用的內存描述符,通過current->mm便可以指向當前進程的內存描述符。fork函數利用copy_mm()函數就實現了復制父進程的內存描述符,而子進程中的mm_struct結構體實際是通過文件kernel/fork.c中的allocate_mm()宏從mm_cachep slab緩存中分配得到的。通常,每個進程都有唯一的mm_struct結構體。

前邊也說過,在linux中,進程和線程其實是一樣的,唯一的不同點就是是否共享這里的地址空間。這個可以通過CLONE_VM標志來實現。linux內核并不區別對待它們,線程對內核來說僅僅是一個共向特定資源的進程而已。好了,如果你設置這個標志了,似乎很多問題都解決了。不再要allocate_mm函數了,前邊剛說作用。而且在copy_mm()函數中將mm域指向其父進程的內存描述符就可以了,如下:

if (clone_flags & CLONE_VM) { /* * current is the parent process and * tsk is the child process during a fork() */ atomic_inc(¤t->mm->mm_users); tsk->mm = current->mm; }

最后,當進程退出的時候,內核調用exit_mm()函數,這個函數調用mmput()來減少內存描述符中的mm_users用戶計數。如果計數降為0,繼續調用mmdrop函數,減少mm_count使用計數。如果使用計數也為0,則調用free_mm()宏通過kmem_cache_free()函數將mm_struct結構體歸還到mm_cachep slab緩存中。

但對于內核而言,內核線程沒有進程地址空間,也沒有相關的內存描述符,內核線程對應的進程描述符中mm域也為空。但內核線程還是需要使用一些數據的,比如頁表,為了避免內核線程為內存描述符和頁表浪費內存,也為了當新內核線程運行時,避免浪費處理器周期向新地址空間進行切換,內核線程將直接使用前一個進程的內存描述符。回憶一下我剛說的進程調度問題,當一個進程被調度時,進程結構體中mm域指向的地址空間會被裝載到內存,進程描述符中的active_mm域會被更新,指向新的地址空間。但我們這里的內核是沒有mm域(為空),所以,當一個內核線程被調度時,內核發現它的mm域為NULL,就會保留前一個進程的地址空間,隨后內核更新內核線程對應的進程描述符中的active域,使其指向前一個進程的內存描述符。所以在需要的時候,內核線程便可以使用前一個進程的頁表。因為內核線程不妨問用戶空間的內存,所以它們僅僅使用地址空間中和內核內存相關的信息,這些信息的含義和普通進程完全相同。
內存區域由vm_area_struct結構體描述,定義在linux/mm.h中,內存區域在內核中也經常被稱作虛擬內存區域或VMA.它描述了指定地址空間內連續區間上的一個獨立內存范圍。內核將每個內存區域作為一個單獨的內存對象管理,每個內存區域都擁有一致的屬性。結構體如下:

struct vm_area_struct { struct mm_struct *vm_mm; /* associated mm_struct */ unsigned long vm_start; /* VMA start, inclusive */ unsigned long vm_end; /* VMA end , exclusive */ struct vm_area_struct *vm_next; /* list of VMA's */ pgprot_t vm_page_prot; /* access permissions */ unsigned long vm_flags; /* flags */ struct rb_node vm_rb; /* VMA's node in the tree */ union { /* links to address_space->i_mmap or i_mmap_nonlinear */ struct { struct list_head list; void *parent; struct vm_area_struct *head; } vm_set; struct prio_tree_node prio_tree_node; } shared; struct list_head anon_vma_node; /* anon_vma entry */ struct anon_vma *anon_vma; /* anonymous VMA object */ struct vm_operations_struct *vm_ops; /* associated ops */ unsigned long vm_pgoff; /* offset within file */ struct file *vm_file; /* mApped file, if any */ void *vm_private_data; /* private data */ };

每個內存描述符都對應于地址進程空間中的唯一區間。vm_mm域指向和VMA相關的mm_struct結構體。兩個獨立的進程將同一個文件映射到各自的地址空間,它們分別都會有一個vm_area_struct結構體來標志自己的內存區域;但是如果兩個線程共享一個地址空間,那么它們也同時共享其中的所有vm_area_struct結構體。

在上面的vm_flags域中存放的是VMA標志,標志了內存區域所包含的頁面的行為和信息,反映了內核處理頁面所需要遵循的行為準則,如下表下述:


 

上表已經相當詳細了,而且給出了說明,我就不說了。在vm_area_struct結構體中的vm_ops域指向域指定內存區域相關的操作函數表,內核使用表中的方法操作VMA。vm_area_struct作為通用對象代表了任何類型的內存區域,而操作表描述針對特定的對象實例的特定方法。操作函數表由vm_operations_struct結構體表示,定義在linux/mm.h中,如下:

struct vm_operations_struct { void (*open) (struct vm_area_struct *); void (*close) (struct vm_area_struct *); struct page * (*nopage) (struct vm_area_struct *, unsigned long, int); int (*populate) (struct vm_area_struct *, unsigned long, unsigned long,pgprot_t, unsigned long, int); };

open:當指定的內存區域被加入到一個地址空間時,該函數被調用。 close:當指定的內存區域從地址空間刪除時,該函數被調用。 nopages:當要訪問的頁不在物理內存中時,該函數被頁錯誤處理程序調用。 populate:該函數被系統調用remap_pages調用來為將要發生的缺頁中斷預映射一個新映射。

 

記性好的你一定記得內存描述符中的mmap和mm_rb域都獨立地指向與內存描述符相關的全體內存區域對象。它們包含完全相同的vm_area_struct結構體的指針,僅僅組織方式不同而已。前者以鏈表的方式進行組織,所有的區域按地址增長的方向排序,mmap域指向鏈表中第一個內存區域,鏈中最后一個VMA結構體指針指向空。而mm_rb域采用紅--黑樹連接所有的內存區域對象。它指向紅--黑輸的根節點。地址空間中每一個vm_area_struct結構體通過自身的vm_rb域連接到樹中。關于紅黑二叉樹結構我就不細講了,以后可能會詳細說這個問題。內核之所以采用這兩種結構來表示同一內存區域,主要是鏈表結構便于遍歷所有節點,而紅黑樹結構體便于在地址空間中定位特定內存區域的節點。我么可以使用/proc文件系統和pmap工具查看給定進程的內存空間和其中所包含的內存區域。這里就不細說了。

內核也為我們提供了對內存區域操作的API,定義在linux/mm.h中:

 

記性好的你一定記得內存描述符中的mmap和mm_rb域都獨立地指向與內存描述符相關的全體內存區域對象。它們包含完全相同的vm_area_struct結構體的指針,僅僅組織方式不同而已。前者以鏈表的方式進行組織,所有的區域按地址增長的方向排序,mmap域指向鏈表中第一個內存區域,鏈中最后一個VMA結構體指針指向空。而mm_rb域采用紅--黑樹連接所有的內存區域對象。它指向紅--黑輸的根節點。地址空間中每一個vm_area_struct結構體通過自身的vm_rb域連接到樹中。關于紅黑二叉樹結構我就不細講了,以后可能會詳細說這個問題。內核之所以采用這兩種結構來表示同一內存區域,主要是鏈表結構便于遍歷所有節點,而紅黑樹結構體便于在地址空間中定位特定內存區域的節點。我么可以使用/proc文件系統和pmap工具查看給定進程的內存空間和其中所包含的內存區域。這里就不細說了。 內核也為我們提供了對內存區域操作的API,定義在linux/mm.h中:

 

接下來要說的兩個函數就非常重要了,它們負責創建和刪除地址空間。
內核使用do_mmap()函數創建一個新的線性地址空間。但如果創建的地址區間和一個已經存在的地址區間相鄰,并且它們具有相同的訪問權限的話,那么兩個區間將合并為一個。如果不能合并,那么就確實需要創建一個新的vma了,但無論哪種情況,do_mmap()函數都會將一個地址區間加入到進程的地址空間中。這個函數定義在linux/mm.h中,如下:

unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot,unsigned long flag, unsigned long offset)

這個函數中由file指定文件,具體映射的是文件中從偏移offset處開始,長度為len字節的范圍內的數據,如果file參數是NULL并且offset參數也是0,那么就代表這次映射沒有和文件相關,該情況被稱作匿名映射。如果指定了文件和偏移量,那么該映射被稱為文件映射(file-backed mapping),其中參數prot指定內存區域中頁面的訪問權限,這些訪問權限定義在asm/mman.h中,如下:


 

flag參數指定了VMA標志,這些標志定義在asm/mman.h中,如下:


 

如果系統調用do_mmap的參數中有無效參數,那么它返回一個負值;否則,它會在虛擬內存中分配一個合適的新內存區域,如果有可能的話,將新區域和臨近區域進行合并,否則內核從vm_area_cach
ep長字節緩存中分配一個vm_area_struct結構體,并且使用vma_link()函數將新分配的內存區域添加到地址空間的內存區域鏈表和紅黑樹中,隨后還要更新內存描述符中的total_vm域,然后才返回新分配的地址區間的初始地址。在用戶空間,我們可以通過mmap()系統調用獲取內核函數do_mmap()的功能,這個在unix環境高級編程中講的很詳細,我就不好意思繼續說了。我們繼續往下走。
我們說既然有了創建,當然要有刪除了,是不?do_mummp()函數就是干這事的。它從特定的進程地址空間中刪除指定地址空間,該函數定義在文件linux/mm.h中,如下:

int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)

第一個參數指定要刪除區域所在的地址空間,刪除從地址start開始,長度為len字節的地址空間,如果成功,返回0,否則返回負的錯誤碼。與之相對應的用戶空間系統調用是munmap。

下面開始最后一點內容:頁表

我們知道應用程序操作的對象是映射到物理內存之上的虛擬內存,但是處理器直接操作的確實物理內存。所以當應用程序訪問一個虛擬地址時,首先必須將虛擬地址轉化為物理地址,然后處理器才能解析地址訪問請求。這個轉換工作需要通過查詢頁面才能完成,概括地講,地址轉換需要將虛擬地址分段,使每段虛地址都作為一個索引指向頁表,而頁表項則指向下一級別的頁表或者指向最終的物理頁面。linux中使用三級頁表完成地址轉換。多數體系結構中,搜索頁表的工作由硬件完成,下表描述了虛擬地址通過頁表找到物理地址的過程:


 

在上面這個圖中,頂級頁表是頁全局目錄(PGD),二級頁表是中間頁目錄(PMD).最后一級是頁表(PTE),該頁表結構指向物理頁。上圖中的頁表對應的結構體定義在文件asm/page.h中。為了加快查找速度,在linux中實現了快表(TLB),其本質是一個緩沖器,作為一個將虛擬地址映射到物理地址的硬件緩存,當請求訪問一個虛擬地址時,處理器將首先檢查TLB中是否緩存了該虛擬地址到物理地址的映射,如果找到了,物理地址就立刻返回,否則,就需要再通過頁表搜索需要的物理地址。

分享到:
標簽:內核 Linux
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定