1. 程式人生 > >Linux 記憶體管理 重要結構體

Linux 記憶體管理 重要結構體

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為起始地址,實際上系統將核心裝載到以第一個1MB0x00100000)為起始地址,實際上系統核心裝載到以第一個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 *);
};