虛擬內存地址與實際內存地址之間的關系,是如此轉換的,邏輯地址-->線性地址-->物理地址。也是從分段單元到分頁單元的轉換。在 linux中,用戶程序所使用的地址與硬件使用的物理地址是不等同的。虛擬內存引入一個間接層,它使得許多操作成為可能。在引入虛擬內存這個概念和方法 后,在系統中運行的程序可以分配比物理內存更多的內存。而linux的地址有分下面幾個類型:
用戶虛擬地址:用戶空間所能看到的常規地址
物 理地址:在處理器和系統內存之間使用
總線地址:在外圍總線和內存之間使用
內核邏輯地址:組成內核的常規地址空間,該地址映射了部分或者全 部內存,并經常被視為物理地址。
內核虛擬地址:其與物理地址的映射不必是線性的和一對一的,所有的邏輯地址都是內核虛擬地址,但是有些內核地址不 是邏輯地址。
UMA,一致內存訪問體系結構,此結構通常用pg_data_t來引用,系統中每個節點鏈接到一個以NULL結尾的 pgdat_list鏈表中,而其中的每個節點利用pg_data_tnode_next字段鏈接到下一個節點。
對大型機來說,內存會分 成很多簇,每個簇都被認為是一個節點。struct pg_data_t體現此概念,系統中的每個節點鏈接到一個以NULL結尾的pgdat_list臉表中,而其中的每個節點利用 pg_data_tnode_next連接到下一個節點。內存中,每個節點被分成很多的成為管理區的塊,而用于表示內存中的某個范圍,一個管理區由 sruct zone_struct描述,并被定義為zone_t,且每個管理區的類型都是ZONE_DMA(低端范圍的物理內存,內存首部 16MB),ZONE_NORMAL(由內核直接映射到線性地址空間的較高部分,16MB--896MB)或者ZONE_HIGHMEM(系統中預留的可 用內存空間,不被內核直接映射,896MB--末尾)中的一種。而三者中,ZONE_NORMAL是影響系統性能最重要的管理區。
系統的 內存劃分成大小確定的許多塊,這些塊用struct page結構體來描述,所有的結構都存儲在一個全局mem_map數組中,通常存放在ZONE_NORMAL的首部,或者就在小內存系統中裝入內核映像而 預留的區域之后。就因為ZONE_NORMAL大小有限,所以linux才會提出高端內存這個概念。
linux中,運用page結構體來保存內核 需要知道的所有物理內存信息,對系統中每個物理頁,都有一個page結構體相對應。
內存中的每個節點都由pa_data_t描述,而此則 由struct pglist_data定義而來,在分配一個頁面時,linux 采用節點局部分配的策略,從最靠近運行中的cpu的節點分配內存,由于進程往往是在同一個cpu上運行,因此從當前節點得到的內存很有可能被用到,每個管 理區由一個struct zone_t描述,此結構體用于跟蹤頁面使用情況統計數,空閑區域信息和鎖信息等,在<linux/mmzone.h>文件中聲明.
以 下這三個結構體互相結合,彼此指向形成了一個關于內存分配的樹型結構。
typedef struct pglist_data {
??????? zone_t node_zones[MAX_NR_ZONES];該節點所在管理區為HIGHMEM、NORMAL、DAM三者之一。
??????? zonelist_t node_zonelists[NR_GFPINDEX];按照分配時的管理區順序排列。通過page_alloc.c中的 bulid)zaonelists()建立順序。
??????? struct page *node_mem_map;struct page數組中的第一個頁面被放置在全局mem_map數組中。
??????? unsigned long *valid_addr_bitmap;描述內存節點中空洞的位圖,因為并沒有實際的內存空間存在。
??????? struct bootmem_data *bdata;指向內存引導程序
??????? unsigned long node_start_paddr;節點的起始物理地址
??????? unsigned long node_start_mapnr;
它 指出該節點在全局mem_map中的頁面偏移,通過計算mem_map與該節點的局部mem_map中成為lmem_map之間的頁面數,得到頁面偏移。
??????? unsigned long node_size;管理區中的頁面總數
??????? int node_id;節點的ID號(NID),從0開始
??????? struct pglist_data *node_next;指向下一個節點。
} pg_data_t;
所有的節點都有一個pgdat_list的鏈表維護,這些節點 都放在該鏈表中。
typedef struct zone_struct {
??????? spinlock_t??????????????? lock;并行訪問時保護該管理區的自旋鎖。
??????? unsigned long??????????????? offset;
??????? unsigned long??????????????? free_pages;該管理區中空閑頁面的總數。
??????? unsigned long??????????????? inactive_clean_pages;
??????? unsigned long??????????????? inactive_dirty_pages;
??????? unsigned long??????????????? pages_min, pages_low, pages_high;管理區極值
??????? struct list_head??????? inactive_clean_list;
??????? free_area_t??????????????? free_area[MAX_ORDER];空閑區域位圖。
??????? char??????????????????????? *name;管理區的字符串名字“DMA”“Normal”“HighMem”
??????? unsigned long??????????????? size;該管理區的大小,以頁面數計算
??????? struct pglist_data??????? *zone_pgdat;指向父pg_data_t
??????? unsigned long??????????????? zone_start_paddr;節點的起始物理地址
??????? unsigned long??????????????? zone_start_mapnr;節點在全局mem_map中的頁面偏移
??????? struct page??????????????? *zone_mem_map;設計的管理區在全局mem_map中的第一頁
} zone_t;
unsigned long??????????????? pages_min, pages_low, pages_high;管理區極值
當空閑頁面數達到pages_low時,伙伴分配器就會喚醒kswapd(守護程序)釋放頁面,默認值是 pages_low的兩倍。
當達到pages_min時,分配器會以同步方式啟動kswapd。
kswapd被喚醒并開始釋放頁面后,在 high個頁面被釋放以前,是不會認為該管理區已經“平衡”的,當到達極值后,kswapd再次睡眠。
struct page {
?? ?unsigned long flags;//page狀態的標志信息。
?? ?atomic_t _count;//count: page的訪問計數,為0說明page空閑,大于0時,page被一個或多個進程真正使用或者kernel用于在等待I/O。?? ?
?? ?union {
?? ??? ?atomic_t _mapcount;
?? ??? ?struct {?? ??? ?/* SLUB */
?? ??? ??? ?u16 inuse;
?? ??? ??? ?u16 objects;
?? ??? ?};
?? ?};
?? ?union {
?? ???? struct {
?? ??? ?unsigned long private;
//private:這個保存了一些和mapping(文件mapping到內存)有關的一些特定的信息。
// 如果page是一個buffer page,則它就保存了一個指向buffer_head的指針。
?? ??? ?struct address_space *mapping;
//當文件或設備需要內存映射,文件或設備的inode對象有一個address_space類 型的成員。如果page屬于這個文件或設備,mapping將指向inode中這個成員。如果page不屬于任何文件或設備,但是mapping被設置 了,則mapping指向了一個address_space類型的swapper_space對象,則page用于管理交換地址空間 (swap address space)了。
?? ???? };
#if NR_CPUS >= CONFIG_SPLIT_PTLOCK_CPUS
?? ???? spinlock_t ptl;
#endif
?? ???? struct kmem_cache *slab;?? ?/* SLUB: Pointer to slab */
?? ???? struct page *first_page;?? ?/* Compound tail pages */
?? ?};
?? ?union {
?? ??? ?pgoff_t index;
//index:這個成員根據page的使用的目的有2種可能的含義。
// 第一種情況:如果page是file mapping的一部分,它指明在文件中的偏移。如果page是交換緩存,則它指明在address_space所聲明的對象: swapper_space(交換地址空間)中的偏移。
//第二種情況:如果這個page是一個特殊的進程將要釋放的一個page塊,則這是一個 將要釋放的page塊的序列值,這個值在__free_page_ok()函數中設置。
?? ??? ?void *freelist;?? ??? ?/* SLUB: freelist req. slab lock */
?? ?};
?? ?struct list_head lru;//lru: page交換調度策略使用。page可能被調度到active_list或者inactive_list隊列里。就是使用lru這個list_head。
#if defined(WANT_PAGE_VIRTUAL)
?? ?void *virtual;//不再用于將high memory的映射到ZONE_NORMAL區域的作用了,除了一些其他的體系結構會用到外。
#endif /* WANT_PAGE_VIRTUAL */
#ifdef CONFIG_CGROUP_MEM_RES_CTLR
?? ?unsigned long page_cgroup;
#endif
};
linux采用了一種與具體體系結構無關代碼 的三層頁表機制來完成內存管理,即使底層的體系結構并不支持這個概念。每一個進程都有一個指向其自己的PGD指針 (mm_struct->pgd),這就是一個物理頁面號,其中包含了一個pgd_t類型的數組,進程頁表的載入是通過把這個結構體復制到cr3寄 存器完成。PGD表中每個有效的項都指向一個頁面號,此頁面號包含一個pmd_t類型的PMD項數組,每一個pmd_t又指向另外的頁面號,這些頁面號由 很多個pte_t類型的PTE構成,而pte_t最終指向包含真正用戶數組的頁面。
整體結構是這樣的:PGD進程內偏移量PMD頁面號內 偏移量PTE頁面號內偏移量數據號內偏移量。這些結構各自擁有自己的偏移量(offset)在尋址過程中,不斷的通過基地址和偏移量來找到下一個相關結構 體最后尋到帶有用戶數據的頁面號。
由于所有在vmlinuz中的普通內核代碼都編譯成以PAGE_OFFSET+1MB為起始地址,實際 上系統將內核裝載到以第一個1MB(0x00100000)為起始地址,實際上系統內核裝載到以第一個1MB為起始地址的物理空間中,第一個1MB的地址 常在以些設備用作和BISO進行通訊的地方自行跳過。該文件中的引導初始化代碼總是把虛擬地址減去__PAGE_OFFSET,從而獲得以1M為起始地址 的物理地址。在開啟換頁單元以前,必須首先建立相應的頁表映射,從而將8MB的物理空間轉換為虛擬地址PAGE_OFFSET.
而物理空 間和struct page之間的映射概念也很重要,系統將內核映像裝載到1MB物理地址起始位置,這個物理地址就是虛擬地址 PAGE_OFFSET+0x00100000.物理內存為內核映像預留了8MB的虛擬空間,耗個空間可以被兩個PGD所訪問到,linux為 ZONE_DMA預留了16MB的內存空間,所以真正被內核分配使用的內存起始位置應在0xc1000000,這個位置久違全局量mem_map所在的位 置。通過把物理地址作為mem_map里的一個下標,從而將其轉換成對應的struct page。通過把物理地址位右移PAGE_SHIFT位,從而將右移后的物理地址作為物理地址0開始的頁面號PFN,同樣也是mem_map數組的一個下 標。??
在linux中,為了可以更快的運行程序,從內存中裝入數據,還設置了高速緩存管理。其實在,基本上都存在一級緩存和二級緩存,后者更大一點,但是速度要 慢的一些。而地址映射高級緩存行的方式因為體系結構的不同可能會有差別,但是基本會是如下三個方法:
1直接映射,每個內存塊只與唯一一個可能的高 速緩存相映射
2關聯映射,任意的內存塊可以與任意的高速緩存行相對應
3關聯集映射,任意的內存塊可以與任意的高速緩存行相映射,但是只能 在一個可用行的子集里面映射
虛擬內存的好處之一就是可以讓進程都有屬于自己的虛擬地址空間,這種虛擬地址空間可以通過操作系統映射到物理 內存。對于進程,它通過一個頁表項指針指向一個只讀的全局全零頁面,以實現在進程的線性地址空間中保留空間。一旦進程對該頁面進行寫操作,就會發生缺頁中 斷,這時系統會分配一個新的全零頁面,并由一個頁表指定,且標記為可寫,新的全零頁面看起來和原來的全局全零頁面完全一樣。
地址空間由 sruct mm_struct結構體來管理,而它到struct page之間,還需要有幾個步驟,它們之間的關系是通過如下結構體相連,并且這些結構體構成了一個關于文件的各個方面的描述:
struct mm_struct,struct vm_area_struct,struct _file,struct vm_operations_struct,struct dentry,struct inode,struct address_space,struct address_space_operations,struct page.
描述進程地址 空間,一個進程只有一個mm_struct結構,且該結構在進程用戶空間中由多個線程共享,線程正是通過任務鏈表里的任務是否指向同一個 mm_struct來判定的。主要字段:
struct mm_struct{
??? struct vm_area_struct * mmap;地址空間中所有VMA的鏈表首部
??? struct vm_area_struct * mmap_avl;
??? rb_root_t mm_rb;
??? struct vm_area_struct * mmap_cache;最后一次通過find_vma()找到的VMA存放處
??? pgd_t * pgd;全局目錄表的起始地址
??? atomic_t mm_users;訪問用戶空間部分的用戶計數值
??? atomic_t mm_count;匿名用戶計數值
??? int map_count;正在被使用中的vma數量
??? struct semaphore mmap_sem;讀寫保護鎖,長期有效
??? spinlock_t page_table_lock;用于保護mm_struct中大部分字段
??? struct list_head mmlist;所有的mm_struct結構通過它鏈接在一起
??? 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, total_vm,locked_vm; (resident set -->rss 某一時刻,一般一個進程虛存空間不會完全在內存中,一般駐留在內存中的為其虛存空間的子集,rss描述有多少頁駐留內存中)
駐留集的大小是該進程常駐內存的頁面數,不包括全局零頁面,進程 中所有vma區域的內存空間總和,內存中被鎖住的常駐頁面數。
??? unsigned long def_flags;VM_LOCKED用于指定在默認情況下將來所有的映射是上鎖還是未鎖。
??? unsigned long cpu_vm_mask;
??? unsigned long swap_cnt;
??? unsigned long swap_address;當換出整個進程時,頁換出進程記錄最后一次被換出的地址
??? mm_context_t context;
}
與 內存區域描述器相關的函 數:mm_init(),allocate_mm(),mm_alloc(),exit_mmap(),copy_mm(),free_mm().
系 統中第一個mm_struct通過init_mm()初始化,以后的mm_struct都會通過復制它來進行設置,所以第一個要手動靜態設置,這是一個模 板:
mm_rb:RB_ROOT,
pgd:swapper_pg_dir,
mm_users:ATOMIC_INIT(2),
mm_count:ATOMIC_INIT(1),
mmap_sem:__RWSEM_INITIALLZER(name,mmap_sem),
page_table_lock:SPIN_LOCK_UNLOCKED,
mmlist:LIST_HEAD_INIT(name.mmlist),
而 系統用于分配mm_struct結構的函數有兩個:Allocate_mm()是一個預處理宏,從slab allocator中分配mm,而mm_alloc()從slab中分配,然后init.
源代碼主要語句:
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
函數為 給定的進程復制一份mm,僅在創建一個新進程后且需要它自己的mm時由do_fork調用。
{
??? struct mm_struct * mm, *oldmm;
??? int retval;
??? tsk->min_flt = tsk->maj_flt = 0;初始化與內存管理相關的task_struct字段,tsk是一個進程控制塊。
??? tsk->nvcsw = tsk->nivcsw = 0;
??? tsk->mm = NULL;
??? tsk->active_mm = NULL;
??? oldmm = current->mm;借用當前運行進程的mm來復制。
??? if (clone_flags & CLONE_VM) {如果設置clone_vm,則子進程將與父進程共享mm
??? ??? atomic_inc(&oldmm->mm_users);用戶數量加1,以便于不會過早銷毀
??? ??? mm = oldmm;
??? ??? goto good_mm;
??? }
??? mm = dup_mm(tsk);//鏈接過程,以及文件信息的設置。
good_mm:
??? mm->token_priority = 0;
??? mm->last_interval = 0;
??? tsk->mm = mm;
??? tsk->active_mm = mm;
??? return 0;
}
static struct mm_struct * mm_init(struct mm_struct * mm, struct task_struct *p)
{
??? atomic_set(&mm->mm_users, 1);用戶數為1
??? atomic_set(&mm->mm_count, 1);mm引用數為1
??? init_rwsem(&mm->mmap_sem);初始化保護vma鏈表的信號量
??? INIT_LIST_HEAD(&mm->mmlist);初始化mm鏈表
??? mm->flags = (current->mm) ? current->mm->flags : MMF_DUMP_FILTER_DEFAULT;設置標識位
??? mm->core_waiters = 0;
??? mm->nr_ptes = 0;
??? set_mm_counter(mm, file_rss, 0);
??? set_mm_counter(mm, anon_rss, 0);
??? spin_lock_init(&mm->page_table_lock);
??? rwlock_init(&mm->ioctx_list_lock);
??? mm->ioctx_list = NULL;
??? mm->free_area_cache = TASK_UNMAPPED_BASE;
??? mm->cached_hole_size = ~0UL;
??? mm_init_owner(mm, p);//void mm_init_owner(struct mm_struct *mm, struct task_struct *p){mm->owner = p;}
??? if (likely(!mm_alloc_pgd(mm))) {
??? ??? mm->def_flags = 0;
??? ??? return mm;
??? }
??? free_mm(mm);
??? return NULL;
#define allocate_mm()??? (kmem_cache_alloc(mm_cachep, GFP_KERNEL))
#define free_mm(mm)??? (kmem_cache_free(mm_cachep, (mm)))
struct mm_struct * mm_alloc(void)
{
??? struct mm_struct * mm;
??? mm = allocate_mm();//kmem_cache_alloc(mm_cachep, GFP_KERNEL)從slab分配器分配一個mm_struct
??? if (mm) {
??? ??? memset(mm, 0, sizeof(*mm));字段歸零
??? ??? mm = mm_init(mm, current);//初始化
??? }
??? return mm;
}
進程的地址空間很少用滿,而一般僅僅用到其中一些分離的區域,這個區域由結構體 vm_area_struct來表示,區域之間是不會交叉的,各自代表一個有著相同屬性和用途的地址集合。一個進程所有被映射的區域都可以在/proc /PID/maps里面看到。
當一個文件被映射到內存,則可以通過vm_file字段得到struct_file.而這個字段又指向 struct inode,索引節點用于找到struct address_space,而在后者中,包含與文件有關的所有信息,包括一系列指向與文件系統相關操作函數的指針。
struct vm_area_struct {
??? struct mm_struct * vm_mm;所述的mm_struct
??? unsigned long? vm_start;這個區域的起始地址
??? unsigned long vm_end;這個區域的結束地址
??? struct vm_area_struct * vm_next;一個地址空間中的所有vma都按地址空間次序通過該字段簡單的鏈接在一起。
??? pgrot t_vm_page_prot;對應的每個pte里的保護標志位
??? unsigned long vm_flags;這個vma的保護標志位和屬性標志位
??? short vm_avl_height;
??? rb_node_ vm_rb;所有的vma都存儲在一個紅黑樹上以加快查找速度
??? struct vm_area_struct * vm_avl_left;
??? struct vm_area_struct * vm_avl_rigth;??
??? struct vm_area_struct * vm_next_share;把文件映射而來的vma共享區域鏈接在一起
??? struct vm_area_struct ** vm_pprev_share;vm_next_share的輔助指針
??? struct vm_operations_struct * vm_ops;包含指向與磁盤同步操作時所需要函數的指針。此字段包含有指向open(),close(),nopage()的函數指針
??? unsigned long vm_pgoff;在已被映射文件里對齊頁面的偏移
??? struct file * vm_file;指向被映射的文件的指針
??? unsigned long vm_raend;預讀窗口的結束地址,在發生錯誤時,一些額外的頁面將被收回,這個值決定了這些額外頁面的個數。
??? void * vm_private_data;一些設備驅動私有數據的存儲,與內存管理無關。
}
pgrot t_vm_page_prot;對應的每個pte里的保護標志位:
_PAGE_PRESENT頁面常駐內存,不進行換出操作
_PAGE_PROTNONE 頁面常駐內存,但不可訪問
_PAGE_RW頁面可能被寫入時設置該位
_PAGE_USER頁面可以被用戶空間訪問時設置該位
_PAGE_DIRTY 頁面被寫入時設置該位
_PAGE_ACCESSED頁面被訪問時設置該位
unsigned long vm_flags;這個vma的保護標志位和屬性標志位
保護標志位
VM_READ頁面可能被讀取
VM_WRITE頁面可 能被寫入
VM_EXEC頁面可能被執行
VM_SHARED頁面可能被共享
VM_DONTCOPY vma不能在fork時被復制
VM_DONTEXPAND 防止一個區域被重新設置大小。標志位沒有被使用過
struct vm_operations_struct {
??? void (*open) (struct vm_area_struct * area);
??? void (*close) (struct vm_area_struct * area);
??? struct page * (*nopage)(struct vm_area_struct *area, unsigned long address, int write_access);
}?
struct address_space {定義的文件信息
??? struct inode??? ??? *host;
??? struct radix_tree_root??? page_tree;
??? rwlock_t??? ??? tree_lock;
??? unsigned int??? ??? i_mmap_writable;
??? struct prio_tree_root??? i_mmap;
??? struct list_head??? i_mmap_nonlinear;
??? spinlock_t??? ??? i_mmap_lock;
??? unsigned int??? ??? truncate_count;
??? unsigned long??? ??? nrpages;在地址空間中正被使用且常駐內存的頁面數
??? pgoff_t??? ??? ??? writeback_index;
??? const struct address_space_operations *a_ops;操縱文件系統的函數結構。每一個文件系統都提供其自身的operations.
??? unsigned long??? ??? flags;
??? struct backing_dev_info *backing_dev_info;
??? spinlock_t??? ??? private_lock;
??? struct list_head??? private_list;
??? struct address_space??? *assoc_mapping;????
}?
struct address_space_operations {定義的函數方法
??? int (*writepage)(struct page *page, struct writeback_control *wbc);
??? int (*readpage)(struct file *, struct page *);
??? void (*sync_page)(struct page *);
??? int (*writepages)(struct address_space *, struct writeback_control *);
??? int (*set_page_dirty)(struct page *page);
??? int (*readpages)(struct file *filp, struct address_space *mapping,struct list_head *pages, unsigned nr_pages);
??? int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
??? int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
??? int (*write_begin)(struct file *, struct address_space *mapping,loff_t pos, unsigned len, unsigned flags,struct page **pagep, void **fsdata);
??? int (*write_end)(struct file *, struct address_space *mapping,loff_t pos, unsigned len, unsigned copied,struct page *page, void *fsdata);
??? sector_t (*bmap)(struct address_space *, sector_t);
??? void (*invalidatepage) (struct page *, unsigned long);
??? int (*releasepage) (struct page *, gfp_t);
??? ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,loff_t offset, unsigned long nr_segs);
??? int (*get_xip_mem)(struct address_space *, pgoff_t, int,void **, unsigned long *);
??? int (*migratepage) (struct address_space *,struct page *, struct page *);
??? int (*launder_page) (struct page *);
};
用戶虛擬地址:用戶空間所能看到的常規地址
物 理地址:在處理器和系統內存之間使用
總線地址:在外圍總線和內存之間使用
內核邏輯地址:組成內核的常規地址空間,該地址映射了部分或者全 部內存,并經常被視為物理地址。
內核虛擬地址:其與物理地址的映射不必是線性的和一對一的,所有的邏輯地址都是內核虛擬地址,但是有些內核地址不 是邏輯地址。
UMA,一致內存訪問體系結構,此結構通常用pg_data_t來引用,系統中每個節點鏈接到一個以NULL結尾的 pgdat_list鏈表中,而其中的每個節點利用pg_data_tnode_next字段鏈接到下一個節點。
對大型機來說,內存會分 成很多簇,每個簇都被認為是一個節點。struct pg_data_t體現此概念,系統中的每個節點鏈接到一個以NULL結尾的pgdat_list臉表中,而其中的每個節點利用 pg_data_tnode_next連接到下一個節點。內存中,每個節點被分成很多的成為管理區的塊,而用于表示內存中的某個范圍,一個管理區由 sruct zone_struct描述,并被定義為zone_t,且每個管理區的類型都是ZONE_DMA(低端范圍的物理內存,內存首部 16MB),ZONE_NORMAL(由內核直接映射到線性地址空間的較高部分,16MB--896MB)或者ZONE_HIGHMEM(系統中預留的可 用內存空間,不被內核直接映射,896MB--末尾)中的一種。而三者中,ZONE_NORMAL是影響系統性能最重要的管理區。
系統的 內存劃分成大小確定的許多塊,這些塊用struct page結構體來描述,所有的結構都存儲在一個全局mem_map數組中,通常存放在ZONE_NORMAL的首部,或者就在小內存系統中裝入內核映像而 預留的區域之后。就因為ZONE_NORMAL大小有限,所以linux才會提出高端內存這個概念。
linux中,運用page結構體來保存內核 需要知道的所有物理內存信息,對系統中每個物理頁,都有一個page結構體相對應。
內存中的每個節點都由pa_data_t描述,而此則 由struct pglist_data定義而來,在分配一個頁面時,linux 采用節點局部分配的策略,從最靠近運行中的cpu的節點分配內存,由于進程往往是在同一個cpu上運行,因此從當前節點得到的內存很有可能被用到,每個管 理區由一個struct zone_t描述,此結構體用于跟蹤頁面使用情況統計數,空閑區域信息和鎖信息等,在<linux/mmzone.h>文件中聲明.
以 下這三個結構體互相結合,彼此指向形成了一個關于內存分配的樹型結構。
typedef struct pglist_data {
??????? zone_t node_zones[MAX_NR_ZONES];該節點所在管理區為HIGHMEM、NORMAL、DAM三者之一。
??????? zonelist_t node_zonelists[NR_GFPINDEX];按照分配時的管理區順序排列。通過page_alloc.c中的 bulid)zaonelists()建立順序。
??????? struct page *node_mem_map;struct page數組中的第一個頁面被放置在全局mem_map數組中。
??????? unsigned long *valid_addr_bitmap;描述內存節點中空洞的位圖,因為并沒有實際的內存空間存在。
??????? struct bootmem_data *bdata;指向內存引導程序
??????? unsigned long node_start_paddr;節點的起始物理地址
??????? unsigned long node_start_mapnr;
它 指出該節點在全局mem_map中的頁面偏移,通過計算mem_map與該節點的局部mem_map中成為lmem_map之間的頁面數,得到頁面偏移。
??????? unsigned long node_size;管理區中的頁面總數
??????? int node_id;節點的ID號(NID),從0開始
??????? struct pglist_data *node_next;指向下一個節點。
} pg_data_t;
所有的節點都有一個pgdat_list的鏈表維護,這些節點 都放在該鏈表中。
typedef struct zone_struct {
??????? spinlock_t??????????????? lock;并行訪問時保護該管理區的自旋鎖。
??????? unsigned long??????????????? offset;
??????? unsigned long??????????????? free_pages;該管理區中空閑頁面的總數。
??????? unsigned long??????????????? inactive_clean_pages;
??????? unsigned long??????????????? inactive_dirty_pages;
??????? unsigned long??????????????? pages_min, pages_low, pages_high;管理區極值
??????? struct list_head??????? inactive_clean_list;
??????? free_area_t??????????????? free_area[MAX_ORDER];空閑區域位圖。
??????? char??????????????????????? *name;管理區的字符串名字“DMA”“Normal”“HighMem”
??????? unsigned long??????????????? size;該管理區的大小,以頁面數計算
??????? struct pglist_data??????? *zone_pgdat;指向父pg_data_t
??????? unsigned long??????????????? zone_start_paddr;節點的起始物理地址
??????? unsigned long??????????????? zone_start_mapnr;節點在全局mem_map中的頁面偏移
??????? struct page??????????????? *zone_mem_map;設計的管理區在全局mem_map中的第一頁
} zone_t;
unsigned long??????????????? pages_min, pages_low, pages_high;管理區極值
當空閑頁面數達到pages_low時,伙伴分配器就會喚醒kswapd(守護程序)釋放頁面,默認值是 pages_low的兩倍。
當達到pages_min時,分配器會以同步方式啟動kswapd。
kswapd被喚醒并開始釋放頁面后,在 high個頁面被釋放以前,是不會認為該管理區已經“平衡”的,當到達極值后,kswapd再次睡眠。
struct page {
?? ?unsigned long flags;//page狀態的標志信息。
?? ?atomic_t _count;//count: page的訪問計數,為0說明page空閑,大于0時,page被一個或多個進程真正使用或者kernel用于在等待I/O。?? ?
?? ?union {
?? ??? ?atomic_t _mapcount;
?? ??? ?struct {?? ??? ?/* SLUB */
?? ??? ??? ?u16 inuse;
?? ??? ??? ?u16 objects;
?? ??? ?};
?? ?};
?? ?union {
?? ???? struct {
?? ??? ?unsigned long private;
//private:這個保存了一些和mapping(文件mapping到內存)有關的一些特定的信息。
// 如果page是一個buffer page,則它就保存了一個指向buffer_head的指針。
?? ??? ?struct address_space *mapping;
//當文件或設備需要內存映射,文件或設備的inode對象有一個address_space類 型的成員。如果page屬于這個文件或設備,mapping將指向inode中這個成員。如果page不屬于任何文件或設備,但是mapping被設置 了,則mapping指向了一個address_space類型的swapper_space對象,則page用于管理交換地址空間 (swap address space)了。
?? ???? };
#if NR_CPUS >= CONFIG_SPLIT_PTLOCK_CPUS
?? ???? spinlock_t ptl;
#endif
?? ???? struct kmem_cache *slab;?? ?/* SLUB: Pointer to slab */
?? ???? struct page *first_page;?? ?/* Compound tail pages */
?? ?};
?? ?union {
?? ??? ?pgoff_t index;
//index:這個成員根據page的使用的目的有2種可能的含義。
// 第一種情況:如果page是file mapping的一部分,它指明在文件中的偏移。如果page是交換緩存,則它指明在address_space所聲明的對象: swapper_space(交換地址空間)中的偏移。
//第二種情況:如果這個page是一個特殊的進程將要釋放的一個page塊,則這是一個 將要釋放的page塊的序列值,這個值在__free_page_ok()函數中設置。
?? ??? ?void *freelist;?? ??? ?/* SLUB: freelist req. slab lock */
?? ?};
?? ?struct list_head lru;//lru: page交換調度策略使用。page可能被調度到active_list或者inactive_list隊列里。就是使用lru這個list_head。
#if defined(WANT_PAGE_VIRTUAL)
?? ?void *virtual;//不再用于將high memory的映射到ZONE_NORMAL區域的作用了,除了一些其他的體系結構會用到外。
#endif /* WANT_PAGE_VIRTUAL */
#ifdef CONFIG_CGROUP_MEM_RES_CTLR
?? ?unsigned long page_cgroup;
#endif
};
linux采用了一種與具體體系結構無關代碼 的三層頁表機制來完成內存管理,即使底層的體系結構并不支持這個概念。每一個進程都有一個指向其自己的PGD指針 (mm_struct->pgd),這就是一個物理頁面號,其中包含了一個pgd_t類型的數組,進程頁表的載入是通過把這個結構體復制到cr3寄 存器完成。PGD表中每個有效的項都指向一個頁面號,此頁面號包含一個pmd_t類型的PMD項數組,每一個pmd_t又指向另外的頁面號,這些頁面號由 很多個pte_t類型的PTE構成,而pte_t最終指向包含真正用戶數組的頁面。
整體結構是這樣的:PGD進程內偏移量PMD頁面號內 偏移量PTE頁面號內偏移量數據號內偏移量。這些結構各自擁有自己的偏移量(offset)在尋址過程中,不斷的通過基地址和偏移量來找到下一個相關結構 體最后尋到帶有用戶數據的頁面號。
由于所有在vmlinuz中的普通內核代碼都編譯成以PAGE_OFFSET+1MB為起始地址,實際 上系統將內核裝載到以第一個1MB(0x00100000)為起始地址,實際上系統內核裝載到以第一個1MB為起始地址的物理空間中,第一個1MB的地址 常在以些設備用作和BISO進行通訊的地方自行跳過。該文件中的引導初始化代碼總是把虛擬地址減去__PAGE_OFFSET,從而獲得以1M為起始地址 的物理地址。在開啟換頁單元以前,必須首先建立相應的頁表映射,從而將8MB的物理空間轉換為虛擬地址PAGE_OFFSET.
而物理空 間和struct page之間的映射概念也很重要,系統將內核映像裝載到1MB物理地址起始位置,這個物理地址就是虛擬地址 PAGE_OFFSET+0x00100000.物理內存為內核映像預留了8MB的虛擬空間,耗個空間可以被兩個PGD所訪問到,linux為 ZONE_DMA預留了16MB的內存空間,所以真正被內核分配使用的內存起始位置應在0xc1000000,這個位置久違全局量mem_map所在的位 置。通過把物理地址作為mem_map里的一個下標,從而將其轉換成對應的struct page。通過把物理地址位右移PAGE_SHIFT位,從而將右移后的物理地址作為物理地址0開始的頁面號PFN,同樣也是mem_map數組的一個下 標。??
在linux中,為了可以更快的運行程序,從內存中裝入數據,還設置了高速緩存管理。其實在,基本上都存在一級緩存和二級緩存,后者更大一點,但是速度要 慢的一些。而地址映射高級緩存行的方式因為體系結構的不同可能會有差別,但是基本會是如下三個方法:
1直接映射,每個內存塊只與唯一一個可能的高 速緩存相映射
2關聯映射,任意的內存塊可以與任意的高速緩存行相對應
3關聯集映射,任意的內存塊可以與任意的高速緩存行相映射,但是只能 在一個可用行的子集里面映射
虛擬內存的好處之一就是可以讓進程都有屬于自己的虛擬地址空間,這種虛擬地址空間可以通過操作系統映射到物理 內存。對于進程,它通過一個頁表項指針指向一個只讀的全局全零頁面,以實現在進程的線性地址空間中保留空間。一旦進程對該頁面進行寫操作,就會發生缺頁中 斷,這時系統會分配一個新的全零頁面,并由一個頁表指定,且標記為可寫,新的全零頁面看起來和原來的全局全零頁面完全一樣。
地址空間由 sruct mm_struct結構體來管理,而它到struct page之間,還需要有幾個步驟,它們之間的關系是通過如下結構體相連,并且這些結構體構成了一個關于文件的各個方面的描述:
struct mm_struct,struct vm_area_struct,struct _file,struct vm_operations_struct,struct dentry,struct inode,struct address_space,struct address_space_operations,struct page.
描述進程地址 空間,一個進程只有一個mm_struct結構,且該結構在進程用戶空間中由多個線程共享,線程正是通過任務鏈表里的任務是否指向同一個 mm_struct來判定的。主要字段:
struct mm_struct{
??? struct vm_area_struct * mmap;地址空間中所有VMA的鏈表首部
??? struct vm_area_struct * mmap_avl;
??? rb_root_t mm_rb;
??? struct vm_area_struct * mmap_cache;最后一次通過find_vma()找到的VMA存放處
??? pgd_t * pgd;全局目錄表的起始地址
??? atomic_t mm_users;訪問用戶空間部分的用戶計數值
??? atomic_t mm_count;匿名用戶計數值
??? int map_count;正在被使用中的vma數量
??? struct semaphore mmap_sem;讀寫保護鎖,長期有效
??? spinlock_t page_table_lock;用于保護mm_struct中大部分字段
??? struct list_head mmlist;所有的mm_struct結構通過它鏈接在一起
??? 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, total_vm,locked_vm; (resident set -->rss 某一時刻,一般一個進程虛存空間不會完全在內存中,一般駐留在內存中的為其虛存空間的子集,rss描述有多少頁駐留內存中)
駐留集的大小是該進程常駐內存的頁面數,不包括全局零頁面,進程 中所有vma區域的內存空間總和,內存中被鎖住的常駐頁面數。
??? unsigned long def_flags;VM_LOCKED用于指定在默認情況下將來所有的映射是上鎖還是未鎖。
??? unsigned long cpu_vm_mask;
??? unsigned long swap_cnt;
??? unsigned long swap_address;當換出整個進程時,頁換出進程記錄最后一次被換出的地址
??? mm_context_t context;
}
與 內存區域描述器相關的函 數:mm_init(),allocate_mm(),mm_alloc(),exit_mmap(),copy_mm(),free_mm().
系 統中第一個mm_struct通過init_mm()初始化,以后的mm_struct都會通過復制它來進行設置,所以第一個要手動靜態設置,這是一個模 板:
mm_rb:RB_ROOT,
pgd:swapper_pg_dir,
mm_users:ATOMIC_INIT(2),
mm_count:ATOMIC_INIT(1),
mmap_sem:__RWSEM_INITIALLZER(name,mmap_sem),
page_table_lock:SPIN_LOCK_UNLOCKED,
mmlist:LIST_HEAD_INIT(name.mmlist),
而 系統用于分配mm_struct結構的函數有兩個:Allocate_mm()是一個預處理宏,從slab allocator中分配mm,而mm_alloc()從slab中分配,然后init.
源代碼主要語句:
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
函數為 給定的進程復制一份mm,僅在創建一個新進程后且需要它自己的mm時由do_fork調用。
{
??? struct mm_struct * mm, *oldmm;
??? int retval;
??? tsk->min_flt = tsk->maj_flt = 0;初始化與內存管理相關的task_struct字段,tsk是一個進程控制塊。
??? tsk->nvcsw = tsk->nivcsw = 0;
??? tsk->mm = NULL;
??? tsk->active_mm = NULL;
??? oldmm = current->mm;借用當前運行進程的mm來復制。
??? if (clone_flags & CLONE_VM) {如果設置clone_vm,則子進程將與父進程共享mm
??? ??? atomic_inc(&oldmm->mm_users);用戶數量加1,以便于不會過早銷毀
??? ??? mm = oldmm;
??? ??? goto good_mm;
??? }
??? mm = dup_mm(tsk);//鏈接過程,以及文件信息的設置。
good_mm:
??? mm->token_priority = 0;
??? mm->last_interval = 0;
??? tsk->mm = mm;
??? tsk->active_mm = mm;
??? return 0;
}
static struct mm_struct * mm_init(struct mm_struct * mm, struct task_struct *p)
{
??? atomic_set(&mm->mm_users, 1);用戶數為1
??? atomic_set(&mm->mm_count, 1);mm引用數為1
??? init_rwsem(&mm->mmap_sem);初始化保護vma鏈表的信號量
??? INIT_LIST_HEAD(&mm->mmlist);初始化mm鏈表
??? mm->flags = (current->mm) ? current->mm->flags : MMF_DUMP_FILTER_DEFAULT;設置標識位
??? mm->core_waiters = 0;
??? mm->nr_ptes = 0;
??? set_mm_counter(mm, file_rss, 0);
??? set_mm_counter(mm, anon_rss, 0);
??? spin_lock_init(&mm->page_table_lock);
??? rwlock_init(&mm->ioctx_list_lock);
??? mm->ioctx_list = NULL;
??? mm->free_area_cache = TASK_UNMAPPED_BASE;
??? mm->cached_hole_size = ~0UL;
??? mm_init_owner(mm, p);//void mm_init_owner(struct mm_struct *mm, struct task_struct *p){mm->owner = p;}
??? if (likely(!mm_alloc_pgd(mm))) {
??? ??? mm->def_flags = 0;
??? ??? return mm;
??? }
??? free_mm(mm);
??? return NULL;
#define allocate_mm()??? (kmem_cache_alloc(mm_cachep, GFP_KERNEL))
#define free_mm(mm)??? (kmem_cache_free(mm_cachep, (mm)))
struct mm_struct * mm_alloc(void)
{
??? struct mm_struct * mm;
??? mm = allocate_mm();//kmem_cache_alloc(mm_cachep, GFP_KERNEL)從slab分配器分配一個mm_struct
??? if (mm) {
??? ??? memset(mm, 0, sizeof(*mm));字段歸零
??? ??? mm = mm_init(mm, current);//初始化
??? }
??? return mm;
}
進程的地址空間很少用滿,而一般僅僅用到其中一些分離的區域,這個區域由結構體 vm_area_struct來表示,區域之間是不會交叉的,各自代表一個有著相同屬性和用途的地址集合。一個進程所有被映射的區域都可以在/proc /PID/maps里面看到。
當一個文件被映射到內存,則可以通過vm_file字段得到struct_file.而這個字段又指向 struct inode,索引節點用于找到struct address_space,而在后者中,包含與文件有關的所有信息,包括一系列指向與文件系統相關操作函數的指針。
struct vm_area_struct {
??? struct mm_struct * vm_mm;所述的mm_struct
??? unsigned long? vm_start;這個區域的起始地址
??? unsigned long vm_end;這個區域的結束地址
??? struct vm_area_struct * vm_next;一個地址空間中的所有vma都按地址空間次序通過該字段簡單的鏈接在一起。
??? pgrot t_vm_page_prot;對應的每個pte里的保護標志位
??? unsigned long vm_flags;這個vma的保護標志位和屬性標志位
??? short vm_avl_height;
??? rb_node_ vm_rb;所有的vma都存儲在一個紅黑樹上以加快查找速度
??? struct vm_area_struct * vm_avl_left;
??? struct vm_area_struct * vm_avl_rigth;??
??? struct vm_area_struct * vm_next_share;把文件映射而來的vma共享區域鏈接在一起
??? struct vm_area_struct ** vm_pprev_share;vm_next_share的輔助指針
??? struct vm_operations_struct * vm_ops;包含指向與磁盤同步操作時所需要函數的指針。此字段包含有指向open(),close(),nopage()的函數指針
??? unsigned long vm_pgoff;在已被映射文件里對齊頁面的偏移
??? struct file * vm_file;指向被映射的文件的指針
??? unsigned long vm_raend;預讀窗口的結束地址,在發生錯誤時,一些額外的頁面將被收回,這個值決定了這些額外頁面的個數。
??? void * vm_private_data;一些設備驅動私有數據的存儲,與內存管理無關。
}
pgrot t_vm_page_prot;對應的每個pte里的保護標志位:
_PAGE_PRESENT頁面常駐內存,不進行換出操作
_PAGE_PROTNONE 頁面常駐內存,但不可訪問
_PAGE_RW頁面可能被寫入時設置該位
_PAGE_USER頁面可以被用戶空間訪問時設置該位
_PAGE_DIRTY 頁面被寫入時設置該位
_PAGE_ACCESSED頁面被訪問時設置該位
unsigned long vm_flags;這個vma的保護標志位和屬性標志位
保護標志位
VM_READ頁面可能被讀取
VM_WRITE頁面可 能被寫入
VM_EXEC頁面可能被執行
VM_SHARED頁面可能被共享
VM_DONTCOPY vma不能在fork時被復制
VM_DONTEXPAND 防止一個區域被重新設置大小。標志位沒有被使用過
struct vm_operations_struct {
??? void (*open) (struct vm_area_struct * area);
??? void (*close) (struct vm_area_struct * area);
??? struct page * (*nopage)(struct vm_area_struct *area, unsigned long address, int write_access);
}?
struct address_space {定義的文件信息
??? struct inode??? ??? *host;
??? struct radix_tree_root??? page_tree;
??? rwlock_t??? ??? tree_lock;
??? unsigned int??? ??? i_mmap_writable;
??? struct prio_tree_root??? i_mmap;
??? struct list_head??? i_mmap_nonlinear;
??? spinlock_t??? ??? i_mmap_lock;
??? unsigned int??? ??? truncate_count;
??? unsigned long??? ??? nrpages;在地址空間中正被使用且常駐內存的頁面數
??? pgoff_t??? ??? ??? writeback_index;
??? const struct address_space_operations *a_ops;操縱文件系統的函數結構。每一個文件系統都提供其自身的operations.
??? unsigned long??? ??? flags;
??? struct backing_dev_info *backing_dev_info;
??? spinlock_t??? ??? private_lock;
??? struct list_head??? private_list;
??? struct address_space??? *assoc_mapping;????
}?
struct address_space_operations {定義的函數方法
??? int (*writepage)(struct page *page, struct writeback_control *wbc);
??? int (*readpage)(struct file *, struct page *);
??? void (*sync_page)(struct page *);
??? int (*writepages)(struct address_space *, struct writeback_control *);
??? int (*set_page_dirty)(struct page *page);
??? int (*readpages)(struct file *filp, struct address_space *mapping,struct list_head *pages, unsigned nr_pages);
??? int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
??? int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
??? int (*write_begin)(struct file *, struct address_space *mapping,loff_t pos, unsigned len, unsigned flags,struct page **pagep, void **fsdata);
??? int (*write_end)(struct file *, struct address_space *mapping,loff_t pos, unsigned len, unsigned copied,struct page *page, void *fsdata);
??? sector_t (*bmap)(struct address_space *, sector_t);
??? void (*invalidatepage) (struct page *, unsigned long);
??? int (*releasepage) (struct page *, gfp_t);
??? ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,loff_t offset, unsigned long nr_segs);
??? int (*get_xip_mem)(struct address_space *, pgoff_t, int,void **, unsigned long *);
??? int (*migratepage) (struct address_space *,struct page *, struct page *);
??? int (*launder_page) (struct page *);
};
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
