【讀書筆記】Linux內核設計與實現--進程地址空間

進程地址空間:內核除了管理本身的內存外,還必須管理用戶空間中進程的內存,這個內存就是進程地址空間,即系統中每個用戶空間進程所看到的內存

ps:注意是用戶空間。

Linux操作系統採用虛擬內存技術系統中的所有進程之間以虛擬方式共享內存

1.地址空間

進程地址空間由進程可尋址的虛擬內存組成,內核允許進程使用這種虛擬內存中的地址。

一個進程的地址空間與另一個進程的地址空間即使有相同的內存地址,實際上也彼此互不相干–這樣的進程稱爲線程

內存地址是一個給定的值,要在地址空間範圍之內。

進程沒有權限訪問所有的虛擬地址,可被訪問的合法地址空間被稱爲內存區域
進程可以通過內核給自己的地址空間動態的添加或減少內存區域。

內存區域(也叫虛擬內存區域)可以包含各種內存對象,如下:

  1. 可執行文件代碼的內存映射,稱爲代碼段(text section);
  2. 可執行文件的已初始化全局變量的內存映射,稱爲數據段(data section);
  3. 包含未初始化全局變量,bss段的零頁(頁面中的信息全部都爲0值,所以可用於映射bss段等目的)的內存映射;
  4. 用於進程用戶空間棧的零頁的內存映射;
  5. 每一個諸如C庫或動態鏈接程序等共享庫的代碼段、數據段和bs也會被載入進程的地址空間;
  6. 任何內存映射文件;
  7. 任何共享內存段;
  8. 任何匿名的內存映射,如malloc()分配的內存。

2.內存描述符–mm_struct

內核使用內存描述符結構體表示進程的地址空間,該結構包含了和進程地址空間有關的全部信息。
內存描述符由mm_struct結構體表示,定義在文件<linux/mm_types.h>書中是<linux/sched.h>。

struct mm_struct {
	struct vm_area_struct * mmap;		/* list of VMAs 內存區域鏈表 */
	struct rb_root mm_rb;				/* VMA 形成的紅黑樹 */
	struct vm_area_struct * mmap_cache;	/* last find_vma result 最近使用的內存區域 */
#ifdef CONFIG_MMU
	unsigned long (*get_unmapped_area) (struct file *filp,
				unsigned long addr, unsigned long len,
				unsigned long pgoff, unsigned long flags);
#endif
	unsigned long mmap_base;		/* base of mmap area */
	unsigned long mmap_legacy_base;         /* base of mmap area in bottom-up allocations */
	unsigned long task_size;		/* size of task vm space */
	unsigned long highest_vm_end;		/* highest vma end address */
	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 內存區域的個數 */

	spinlock_t page_table_lock;		/* Protects page tables and some counters 頁表鎖 */
	struct rw_semaphore mmap_sem;	/*  內存區域的信號量*/

	struct list_head mmlist;		/* 所有mm_struct 形成的鏈表 List of maybe swapped mm's.	These are globally strung
						 * together off init_mm.mmlist, and are protected
						 * by mmlist_lock
						 */


	unsigned long hiwater_rss;	/* High-watermark of RSS usage */
	unsigned long hiwater_vm;	/* High-water virtual memory usage */

	unsigned long total_vm;		/* Total pages mapped */
	unsigned long locked_vm;	/* Pages that have PG_mlocked set */
	unsigned long pinned_vm;	/* Refcount permanently increased */
	unsigned long shared_vm;	/* Shared pages (files) */
	unsigned long exec_vm;		/* VM_EXEC & ~VM_WRITE */
	unsigned long stack_vm;		/* VM_GROWSUP/DOWN */
	unsigned long def_flags;
	unsigned long nr_ptes;		/* Page table pages */
	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 saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

	/*
	 * Special counters, in some configurations protected by the
	 * page_table_lock, in other configurations by being atomic.
	 */
	struct mm_rss_stat rss_stat;

	struct linux_binfmt *binfmt;

	cpumask_var_t cpu_vm_mask_var;

	/* Architecture-specific MM context */
	mm_context_t context;	/* 體系結構特殊數據 */

	unsigned long flags; /* Must use atomic bitops to access the bits 狀態標誌 */

	struct core_state *core_state; /* coredumping support 核心轉儲的支持 */
#ifdef CONFIG_AIO
	spinlock_t		ioctx_lock;		/* AIO I/O 鏈表鎖*/
	struct hlist_head	ioctx_list;	/* AIO I/O 鏈表 */
#endif
#ifdef CONFIG_MM_OWNER
	/*
	 * "owner" points to a task that is regarded as the canonical
	 * user/owner of this mm. All of the following must be true in
	 * order for it to be changed:
	 *
	 * current == mm->owner
	 * current->mm != mm
	 * new_owner->mm == mm
	 * new_owner->alloc_lock is held
	 */
	struct task_struct __rcu *owner;
#endif

	/* store ref to file /proc/<pid>/exe symlink points to */
	struct file *exe_file;
#ifdef CONFIG_MMU_NOTIFIER
	struct mmu_notifier_mm *mmu_notifier_mm;
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
	pgtable_t pmd_huge_pte; /* protected by page_table_lock */
#endif
#ifdef CONFIG_CPUMASK_OFFSTACK
	struct cpumask cpumask_allocation;
#endif
#ifdef CONFIG_NUMA_BALANCING
	/*
	 * numa_next_scan is the next time that the PTEs will be marked
	 * pte_numa. NUMA hinting faults will gather statistics and migrate
	 * pages to new nodes if necessary.
	 */
	unsigned long numa_next_scan;

	/* numa_next_reset is when the PTE scanner period will be reset */
	unsigned long numa_next_reset;

	/* Restart point for scanning and setting pte_numa */
	unsigned long numa_scan_offset;

	/* numa_scan_seq prevents two threads setting pte_numa */
	int numa_scan_seq;

	/*
	 * The first node a task was scheduled on. If a task runs on
	 * a different node than Make PTE Scan Go Now.
	 */
	int first_nid;
#endif
#if defined(CONFIG_NUMA_BALANCING) || defined(CONFIG_COMPACTION)
	/*
	 * An operation with batched TLB flushing is going on. Anything that
	 * can move process memory needs to flush the TLB when moving a
	 * PROT_NONE or PROT_NUMA mapped page.
	 */
	bool tlb_flush_pending;
#endif
	struct uprobes_state uprobes_state;
};

mmap 和 mm_rb 這兩個不同數據結構體描述的對象是相同的:該地址空間中的全部內存區域
mmap 是以鏈表形式存放的,mm_rb以紅黑樹的形式存放。

所有的mm_struct 結構體都通過自身的mmlist域連接在一個雙向鏈表中,該鏈表的首元素是init_mm內存描述符,它代表init進程的地址空間。

ps:操作該鏈表的時候需要使用mmlist_lock鎖來防止併發訪問,該鎖定義在文件kernel/fork.c中。

2.1 分配內存描述符–copy_mm()

在進程的進程描述符(task_struct)中,mm域存放着該進程使用內存描述符(mm_struct),所以current->mm指向當前進程的內存描述符。

fork()函數利用copy_mm()函數複製父進程的內存描述符,也就是current->mm域給其子進程,而子進程中mm_struct結構體實際是通過文件kernel/fork.c中的allocate_mm()宏從mm_cachep slab緩存中分配得到的。因此通常,每個進程都有唯一的mm_struct結構體,即唯一的進程地址空間

ps:是否共享地址空間幾乎是進程和Linux中所謂的線程間本質上的唯一區別。線程對內核來說僅僅是一個共享特定資源的進程而已。

Q:如何讓父進程和子進程共享地址空間?
A:在調用clone()時,設置CLONE_VM標誌。(這樣的子進程稱爲線程)當CLONE_VM被指定後,內核就不再需要調用allocate_mm()函數,而僅僅需要在調用copy_mm()函數中將mm域指向其父進程的內存描述符就可以了。

2.2 撤銷內存描述符–exit_mm->mmput->mmdrop->free_mm->kmem_cache_free

當進程退出時,內核會調用定義在kernel/exit.c中的exit_mm()函數,該函數執行一些常規的撤銷工作,同時更新一些統計量。
其中,該函數會調用mmput()函數減少內存描述符中mm_users用戶計數,如果用戶計數降到零,將調用mmdrop()函數,減少mm_count使用計數。
如果使用計數也等於零了,說明該內存描述符不再有任何使用者了,那麼調用free_mm()宏通過kmem_cache_free()函數將mm_struct結構體歸還到mm_cachep slab緩存中。

2.3 mm_struct 與內核線程

內核線程沒有進程地址空間,也沒有相關的內存描述符,所以內核線程對應的進程描述符中的mm域爲空,即task_struct裏的mm_struct爲空。(因爲內核線程沒有用戶上下文)

Q:內核線程對應的進程描述符中mm域爲空,使用誰的?
A:內核線程使用前一個進程的內存描述符–active_mm域的作用
當一個進程被調度時,該進程的mm域指向的地址空間被裝載到內存,進程描述符中active_mm域會被更新,指向新的地址空間。內核線程沒有地址空間,所有mm域爲NULL。於是,當一個內核線程被調度時,內核發現它的mm域爲NULL,就會保留前一個進程的地址空間,隨後內核更新內核線程對應的進程描述符中active_mm域,使其指向前一個進程的內存描述符。所以在需要的時候,內核線程便可以使用前一個進程的頁表。因爲內核線程不訪問用戶空間的內存,所以它們僅僅使用地址空間和內核內存相關的信息。

3.虛擬內存區域–vm_area_struct

內存區域由vm_area_struct結構體描述,定義在文件<linux/mm_types.h>中。
內存區域在Linux內核中頁稱作虛擬內存區域(VMAs:virtual memortAreas)。

vm_area_struct結構體描述了指定地址空間內連續區間上的一個獨立內存範圍。
內核將每個內存區域作爲一個單獨的內存對象管理,每個內存區域都擁有一致的屬性。

每一個VMA就可以代表不同類型的內存區域。

struct vm_area_struct {
	/* The first cache line has the info for VMA tree walking. */

	unsigned long vm_start;		/* Our start address within vm_mm. 區間的首地址 */
	unsigned long vm_end;		/* 區間的尾地址 The first byte after our end address
					   within vm_mm. */

	/* linked list of VM areas per task, sorted by address */
	struct vm_area_struct *vm_next, *vm_prev;	/* VMA 鏈表 */

	struct rb_node vm_rb;		/* 樹上該VMA的節點 */

	/*
	 * Largest free memory gap in bytes to the left of this VMA.
	 * Either between this VMA and vma->vm_prev, or between one of the
	 * VMAs below us in the VMA rbtree and its ->vm_prev. This helps
	 * get_unmapped_area find a free area of the right size.
	 */
	unsigned long rb_subtree_gap;

	/* Second cache line starts here. */

	struct mm_struct *vm_mm;	/* The address space we belong to.  相關的mm_struct內存描述符結構體 */
	pgprot_t vm_page_prot;		/* Access permissions of this VMA. 訪問控制權限 */
	unsigned long vm_flags;		/* Flags, see mm.h. 標誌 */

	/*
	 * For areas with an address space and backing store,
	 * linkage into the address_space->i_mmap interval tree, or
	 * linkage of vma in the address_space->i_mmap_nonlinear list.
	 *
	 * For private anonymous mappings, a pointer to a null terminated string
	 * in the user process containing the name given to the vma, or NULL
	 * if unnamed.
	 */
	union {
		struct {
			struct rb_node rb;						/* 樹上該VMA的節點 */
			unsigned long rb_subtree_last;
		} linear;
		struct list_head nonlinear;
		const char __user *anon_name;
	} shared;

	/*
	 * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
	 * list, after a COW of one of the file pages.	A MAP_SHARED vma
	 * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
	 * or brk vma (with NULL file) can only be in an anon_vma list.
	 */
	struct list_head anon_vma_chain; /* Serialized by mmap_sem &
					  * page_table_lock */
	struct anon_vma *anon_vma;	/* Serialized by page_table_lock 匿名VMA對象  */

	/* Function pointers to deal with this struct. */
	const struct vm_operations_struct *vm_ops;	/* 相關的操作表 */

	/* Information about our backing store: */
	unsigned long vm_pgoff;		/* 文件中的偏移量 Offset (within vm_file) in PAGE_SIZE
					   units, *not* PAGE_CACHE_SIZE */
	struct file * vm_file;		/* File we map to (can be NULL). 被映射的文件(如果存在) */
	void * vm_private_data;		/* was vm_pte (shared mem) 私有數據 */

#ifndef CONFIG_MMU
	struct vm_region *vm_region;	/* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
	struct mempolicy *vm_policy;	/* NUMA policy for the VMA */
#endif
};

每個內存描述符都對應域進倉地址空間中的唯一區間。
vm_start域指向區間的首(最低)地址,vm_end域指向區間的尾(最高)地址之後的第一個字節–vm_start在區間內,vm_end在區間外。

在同一個地址空間內的不同內存區間不能重疊。

vm_mm域指向和VMA相關的mm_struct結構體,注意,每個VMA對其相關的mm_struct結構體來說都是唯一的,所以即使兩個獨立的進程將同一個文件映射到各自的地址空間,它們分別都會有一個vm_area_struct結構體來標誌自己的內存區域;

如果兩個線程共享一個地址空間,那麼它們也同時共享其中的所有vm_area_struct結構體。

3.1 VMA 標誌

VMA標誌是一種位標誌,其定義在<linux/mm/h>。
它包含在vm_flags域內,標誌了內存區域所包含的頁面的行爲和信息。

VMA標誌可取值如下表:

標誌 對VMA及其頁面的影響
VM_READ 頁面可讀取
VM_WRITE 頁面可寫
VM_EXEC 頁面可執行
VM_SHARED 頁面可共享
VM_MAYREAD VM_READ 標誌可被設置
VM_MAYWRITE VM_WRITE 標誌可被設置
VM_MAYEXEC VM_EXEC 標誌可被設置
VM_MAYSHARE VM_SHARE 標誌可被設置
VM_GROWSDOWN 區域可向下增長
VM_GROWSUP 區域可向上增長
VM_SHM 區域可用作共享內存
VM_DENYWRITE 區域映射一個不可寫文件
VM_EXECUTABLE 區域映射一個可執行文件
VM_LOCKED 區域中的頁面被鎖定
VM_IO 區域映射設備 I/O 空間
VM_SEQ_READ 頁面可能被連續訪問
VM_RAND_READ 頁面可能被隨機訪問
VM_DONTCOPY 區域不能再fork()時被拷貝
VM_DONTEXPAND 區域不能通過mremap()增加
VM_RESERVED 區域不能被換出
VM_ACCOUNT 該區域是一個記賬 VM 對象
VM_HUGETLB 區域使用了hugetlb 頁面
VM_NONLINEAR 該區域是非線性映射的

3.2 VMA 操作–vm_ops -> vm_operations_struct

vm_area_struct 結構體中的vm_ops 域指向與指定內存區域相關的操作函數表,內核使用表中的方法操作VMA。

vm_area_struct 作爲通用對象代表了任何類型的內存區域,而操作表描述符對特定的對象實例的特定方法。

操作函數表由vm_operations_struct 結構體表示,定義在文件 <linux/mm.h>中:

/*
 * These are the virtual MM functions - opening of an area, closing and
 * unmapping it (needed to keep files on disk up-to-date etc), pointer
 * to the functions called when a no-page or a wp-page exception occurs. 
 */
struct vm_operations_struct {
	void (*open)(struct vm_area_struct * area);
	void (*close)(struct vm_area_struct * area);
	int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);

	/* notification that a previously read-only page is about to become
	 * writable, if an error is returned it will cause a SIGBUS */
	int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);

	/* called by access_process_vm when get_user_pages() fails, typically
	 * for use by special VMAs that can switch between memory and hardware
	 */
	int (*access)(struct vm_area_struct *vma, unsigned long addr,
		      void *buf, int len, int write);
#ifdef CONFIG_NUMA
	/*
	 * set_policy() op must add a reference to any non-NULL @new mempolicy
	 * to hold the policy upon return.  Caller should pass NULL @new to
	 * remove a policy and fall back to surrounding context--i.e. do not
	 * install a MPOL_DEFAULT policy, nor the task or system default
	 * mempolicy.
	 */
	int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);

	/*
	 * get_policy() op must add reference [mpol_get()] to any policy at
	 * (vma,addr) marked as MPOL_SHARED.  The shared policy infrastructure
	 * in mm/mempolicy.c will do this automatically.
	 * get_policy() must NOT add a ref if the policy at (vma,addr) is not
	 * marked as MPOL_SHARED. vma policies are protected by the mmap_sem.
	 * If no [shared/vma] mempolicy exists at the addr, get_policy() op
	 * must return NULL--i.e., do not "fallback" to task or system default
	 * policy.
	 */
	struct mempolicy *(*get_policy)(struct vm_area_struct *vma,
					unsigned long addr);
	int (*migrate)(struct vm_area_struct *vma, const nodemask_t *from,
		const nodemask_t *to, unsigned long flags);
#endif
	/* called by sys_remap_file_pages() to populate non-linear mapping */
	int (*remap_pages)(struct vm_area_struct *vma, unsigned long addr,
			   unsigned long size, pgoff_t pgoff);
};
函數 描述
*void open(struct vm_are_struct *area) 當制定的內存區域被加入到一個地址空間時,該函數被調用
*void close(struct vm_area_struct *area) 當指定的內存區域從地址空間刪除時,該函數被調用
*void fault(struct vm_area_struct *area,struct vm_fault *vmf) 當沒有出現在物理內存中的頁面被訪問時,該函數被頁面故障處理調用
*int page_mkwrite(struct vm_area_struct *area, struct vm_fault *vmf) 當某個頁面爲只讀頁面時,該函數被頁面故障處理調用
*int access(struct vm_area_struct *vma, unsigned long address, void *buf, int int write) 當get_uset_pages()函數調用失敗時,該函數被access_process_vm()函數調用

3.3 內存區域的樹型結構和內存區域的鏈表結構–mm_struct之*mmap和mm_rb

mmap域使用單獨鏈表連接所有的內存區域對象。每一個vm_area_struct結構體通過自身的vm_next域被連入鏈表,所有的區域按地址增長的方法排序,mmap域指向鏈表中第一個內存區域,鏈中最後一個結構體指針指向空。

mm_rb域使用紅黑樹連接所有的內存區域對象。mm_rb域指向紅黑樹的根節點,地址空間中每一個vm_area_struct結構體通過自身的vm_rb域連接到樹中。

鏈表用於需要遍歷全部節點的時候,而紅黑樹適用於在地址空間中定位特定內存區域的時候。
內核爲了內存區域上的各種不同操作都能獲得高性能,所以同時使用了這兩種數據結構。

3.4 實際使用中的內存區域–/proc文件系統或pmap(1)工具

可以使用/proc 文件系統和pmap(1)工具查看給定進程的內存空間和其中所含的內存區域。

eg:
在這裏插入圖片描述
可以使用cat /proc/<pid>/maps或pmap <pid>列出該進程地址空間中包含的內存區域。
其中由代碼段、數據段和bss段等,假設該進程與C庫動態鏈接,那麼地址空間還將分別包括libc.so和ld.so對應的上述三種內存區域。瓷碗,地址空間還要包含進程棧對應的內存區域。
在這裏插入圖片描述
在這裏插入圖片描述

每行數據格式如下
開始-結束 訪問權限 偏移 主設備號:次設備號 i 節點 文件

前三行分別對應C庫中的libc.so的代碼段、數據段和bss段;
接着兩行是可執行對象的代碼段和數據段;
接下來三行是動態鏈接程序ld.so的代碼段、數據段和bss段;
最後一行是進程的棧。

ps:
代碼段具有可讀且可執行權限,數據段和bss具有可讀、可寫但不可執行權限。而堆棧則可讀、可寫,甚至還可執行。
共享不可寫內存的方法節約了大量的內存空間。
沒有映射文件的內存區域的設備標誌位00:00,索引節點標誌也爲0,這個區域就是零頁零頁映射的內容全爲零
如果將零頁映射到可寫的內存區域,那麼該區域將全初始化爲0(bss段需要的就是全0的內存區域)。
由於內存未被共享,所以只要一有進程寫該處數據,那麼該處數據就將被拷貝出來(寫時拷貝-COW),然後才被更新。
每個和進程相關的內存區域都對應一個vm_area_struct結構體,進程不同於線程,進程結構體stask_struct包含唯一的mm_struct結構體引用,而線程則是共享了地址空間,即copy_mm。

4.操作內存區域

4.1 find_vma()

內核提供了find_vma()函數用來找到一個給定的內存地址屬於哪一個內存區域(在指定的地址空間中搜索第一個vm_end大於addr的內存區域,也就是尋找第一個包含addr或者首地址大於addr的內存區域)。
定義在文件<mm/mmap.c>中:

struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr);

4.2 find_vma_prev()

find_vma_prev()函數和find_vma()工作方式相同,但它返回第一個小於addr的VMA。
該函數定義和聲明分別在文件mm/mmap.c和文件<linux/mm.h>中:

struct vm_area_struct *find_vma_prev(struct mm_struct *mm,unsigned long addr,struct vm_area_struct **pprev);

pprev參數存放指向先於addr的VMA指針。

4.3 find_vma_intersection()

find_vma_intersection()函數返回第一個和指定地址區間相交的VMA。
該函數是內聯函數,定義在文件<linux/mm.h>中:

/* Look up the first VMA which intersects the interval start_addr..end_addr-1,
   NULL if none.  Assume start_addr < end_addr. */
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;
}

參數mm是要搜索的地址空間;
start_addr是區間的開始首地址;
end_addr是區間的尾地址。

如果find_vma()返回NULL,那麼find_vma_interesection()也會返回NULL。
如果find_vma()返回有效的VMA,find_vma_intersection()只有在該VMA的起始位置於給定的地址區間結束位置之前,纔將其返回。如果VMA的起始位置大於指定位置範圍的結束位置,則該函數返回NULL。

5.mmap()和do_mmap():創建地址區間

mmap()->do_mmap()
內核使用do_mmap()函數創建一個新的線性地址區間。但是說該函數創建了一個新的VMA並不非常準確,因爲如果創建的地址區間和一個已經存在的地址區間相鄰,並且它們具有相同的訪問權限,兩個區間合併爲一個。如果不能合併,則確實需要創建一個新的VMA。
無論哪種情況,do_mmap()函數都會將一個地址區間加入到進程的地址空間中–無論是擴展已存在的內存區域還是創建一個新的區域。

do_mmap()函數定義在文件kernel/arch/powerpc/kernel/syscalls.c,以內聯形式展示,書中<linux/mm.h>中。 ps:比較舊的內核版本是do_mmap,後面更新成do_mmap2,區別僅僅在於最後一個參數,後者使用頁面偏移作爲最後一個參數,使用頁面偏移量可以映射更大的文件和更大的偏移位置:

unsigned long sys_mmap2(unsigned long addr, size_t len,
                        unsigned long prot, unsigned long flags,
                        unsigned long fd, unsigned long pgoff)
{
        return do_mmap2(addr, len, prot, flags, fd, pgoff, PAGE_SHIFT-12);
}

unsigned long sys_mmap(unsigned long addr, size_t len,
                       unsigned long prot, unsigned long flags,
                       unsigned long fd, off_t offset)
{
        return do_mmap2(addr, len, prot, flags, fd, offset, PAGE_SHIFT);
}


static inline unsigned long do_mmap2(unsigned long addr, size_t len,
                        unsigned long prot, unsigned long flags,
                        unsigned long fd, unsigned long off, int shift)
{
        unsigned long ret = -EINVAL;

        if (!arch_validate_prot(prot))
                goto out;

        if (shift) {
                if (off & ((1 << shift) - 1))
                        goto out;
                off >>= shift;
        }

        ret = sys_mmap_pgoff(addr, len, prot, flags, fd, off);
out:
        return ret;
}

ps:
映射沒有和文件相關,該情況稱作匿名映射(anonymous mapping);
如果指定了文件名和偏移量,則該映射稱爲文件映射(file-backed mapping);
雖然C庫仍然可以使用原始版本的映射方法(mmap),但它其實還是基於函數mmap2()進行的,因爲對原始mmap()方法的調用是通過將字節偏移轉化爲頁面偏移,從而轉化爲對mmap2()函數的調用來實現的。

6.munmap()和do_munmap():刪除地址區間

munmap()(kernel/include/linux/syscalls.h) ->do_munmap()
do_munmap()函數從特定的進程地址空間中刪除指定地址區間,該函數定義在文件kernel/mm/nommu.c,書中是<linux/mm.h>。

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

第一個參數指定要刪除區域所在的地址空間,刪除從地址start開始,長度爲len字節的地址區間;
成功返回零,失敗返回負數錯誤碼。

系統調用munmap()給用戶空間程序提供了一種從自身地址空間刪除指定地址區間的方法,和系統調用mmap()作用相反:

asmlinkage long sys_munmap(unsigned long addr, size_t len);

7.頁表–轉換虛擬地址和物理地址的工作

雖然應用程序操作的對象是映射到物理內存上的虛擬內存,但是處理器直接操作的卻是物理內存

Q:頁表爲何存在?
A:當應用程序訪問一個虛擬地址時,首先必須將虛擬地址轉化成物理地址,然後處理器才能解析地址訪問請求。地址的轉換工作需要通過查詢頁表才能完成,即地址轉換需要將虛擬地址分段,使每段虛擬地址都作爲一個索引指向頁表,而頁表項則指向下一級別或指向最終的物理頁面。

Linux中使用三級頁表完成地址轉換。利用多級頁表能夠節約地址轉換需佔用的存放空間。
在這裏插入圖片描述
頂級頁表頁全局目錄(PGD),包含了一個pgd_t類型數組,多數體系結構圖中pgd_t類型等同於無符號長整型類型。PGD中的表項指向二級頁目錄中的表項:PMD;
二級頁表中間頁目錄(PMD),是個pmd_t類型數組,其中的表項指向PTE中的表項;
最後一級的頁表簡稱頁表(PTE),包含了pte_t類型的頁表項,該頁表項指向物理頁面

每個進程都有自己的頁表(線程會共享頁表)。
內存描述符的pgd域指向的就是進程的頁全局目錄(PGD)。
ps:操作和檢索頁表時,必須使用page_table_lock鎖,該鎖在相應的進程的內存描述符中,以防止競爭條件。

頁表對應的結構體依賴於具體的體系結構,定義在文件<asm/oage.h>。

Q:頁表機制的性能?
A:由於幾乎每次對虛擬內存中的頁面訪問都必須先解析頁表,從而得到物理內存中的對應地址,所以頁表操作的性能非常關鍵。但不幸的是,搜索內存的物理地址速度很有限,因此爲了加可搜索,多數體系結構圖都實現了一個翻譯後緩衝器(translate lookaside buffer,TLB)。
TLB作爲一個將虛擬地址映射到物理地址的硬件緩存,當請求訪問一個虛擬地址時,處理器將首先檢查TLB中是否緩存了該虛擬地址到物理地址的映射,如果在緩存中直接命中,物理地址立刻返回;否則,就需要再通過頁表搜索需要的物理地址。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章