閱讀975 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Linux內核剖析 之 進程地址空間(一)

緒論

    內核獲取內存方式——直接了當:

    1. 從分區頁框分配器獲取內存(__get_free_pages()alloc_pages())

    2. 使用slab分配器為專用或通用對象分配內存(kmem_cache_alloc()或kmalloc());

    3. 使用vmallocvmalloc_32獲取一塊非連續內存區

    如果申請的內存得以滿足,這些函數返回一個頁描述符地址或線性地址。


    *內核申請內存使用這些簡單方法基於以下兩個原因:

    1.內核是操作係統中優先級最高的成分。如果內核申請動態內存,那麼必定有正當理由。因此,沒有理由推遲處理這個請求。

    2.內核信任自己。所有內核函數都是假定沒有錯誤的,因此內核函數不必插入針對編程錯誤的任何保護措施。

    *當給用戶態進程分配內存時,情況完全不同:

    1.進程對動態內存的請求被認為是不緊迫的。內核總是盡量推遲給用戶態進程分配動態內存。

    2.用戶進程是不可信任的,因此,內核必須能隨時準備捕捉用戶進程引起的所有尋址錯誤。


主要內容:

    *內核如何實現對進程動態內存的推遲分配?

    ——使用一種新的資源(線性地址區)

    當用戶進程請求動態內存時,並沒有獲得請求的頁框,而是獲得了一個新的線性地址的使用權。這線性地址區域就成為進程地址空間的一部分。

    *為什麼要推遲分配?

    *進程怎樣看待動態內存。

    *進程地址空間的基本組成。

    *缺頁異常處理程序在推遲給進程分配頁框中所起的作用。

    *內核怎樣創建和刪除進程的整個地址空間?

    *與進程的地址空間管理有關的API和係統調用。

====>>>>>

    1. 進程地址空間

    2. 內存描述符

    3. 線性區

    4. 缺頁異常處理程序

    5. 創建和刪除進程的地址空間

    6. 堆的管理


進程地址空間

    進程的地址空間(Address Space)由允許進程使用的全部線性地址組成。一個進程使用的進程地址空間與另一個進程使用的進程地址空間之間沒有關係。如果進程之間共享相同的地址空間,則被稱為線程。

    內核可以通過增加或刪除某些線性地址區間來動態修改進程的地址空間。

    內核通過所謂線性區的資源來表示線性地址空間,線性地址空間由起始線性地址、長度以及相應訪問權限來描述。

    起始地址和長度都是4096(一頁)的整數倍——提高效率。

    *進程創建新的線性區的典型情況(六種):

    程序執行

    exec()函數

    缺頁異常處理程序

    內存映射

    IPC共享內存

    malloc()函數—heap

    *與創建、刪除線性區相關的係統調用:

係統調用

說明

brk()

改變進程堆的大小

execve()

裝入可執行文件,從而改變進程的地址空間

_exit()

結束當前進程並撤銷它的進程地址空間

fork()

創建一個新的進程,並創建新的地址空間。

mmap(),mmap2()

為文件創建一個內存映射,從而擴大進程的地址空間

mremap()

擴大或縮小線性區

remap_file_pages()

為文件創建非線性映射

munmap()

撤銷對文件的內存映射,從而縮小進程的地址空間

shmat()

創建一個共享線性區

shmdt()

撤銷一個共享線性區


    確定進程所擁有的線性地址區是內核的任務,這使得缺頁處理程序能夠有效的處理以下兩種無效的線性地址:

    1. 由編程錯誤引發的非法地址訪問(如數組越界、非法指針);

    2. 由缺頁(物理頁)引發的無效線性地址,即使這個線性地址屬於進程地址空間,但是內核還未分配物理頁。——請求調頁。

 

內存描述符

    與進程地址空間的全部信息都包含在一個叫做內存描述符(Memory Descriptor)的數據結構中,這個結構的類型是mm_struct,進程描述符的mm字段指向此結構。

    無論是內核線程還是用戶進程,對於內核來說,都是task_struct這個數據結構的一個實例,task_struct被稱為進程描述符(process descriptor),因為它記錄了這個進程所有的上下文(context)。其中有一個被稱為“內存描述符”(memory descriptor)的數據結構 mm_struct,該結構抽象並描述了Linux視角下管理進程地址空間的所有信息。


    [start_code,end_code)表示代碼段的地址空間範圍。 

    [start_data,end_data)表示數據段的地址空間範圍。 

    [start_brk,brk)分別表示heap段的起始空間和當前的heap指針。 

    [start_stack,end_stack)表示stack段的地址空間範圍。 

    mmap_base表示memory mapping段的起始地址。 

    具體結構圖:

 

    內存描述符:

    mm_strcut定義:

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;

	unsigned long hiwater_rss;	/* High-water RSS usage */
	unsigned long hiwater_vm;	/* High-water virtual memory usage */
};
    mm_struct字段:

類型

字段

說明

struct vm_area_struct*

mmap

指向線性區域對象的鏈表頭

struct rb_root

mm_rb

指向線性區對象的紅黑樹的根

struct vm_area_struct*

mmap_cache

指向最後一個引用的線性區對象

unsigned long (*) ()

get_unmapped_area

在進程地址空間中搜索有效線性地址區

void (*) ()

unmap_area

釋放線性地址區間時調用的方法

unsigned long

free_area_cache 

內核從這個地址開始搜索進程地址空間中線性地址的空閑區域

pgd_t *

pdg

指向頁全局目錄

atomic_t

mm_users

次使用計數器

atomic_t

mm_count

主使用計數器

int

map_count

線性區的個數

struct rw_semaphore

mmap_sem

線性區的讀/寫信號量

spinlock_t

page_table_lock

線性區的自旋所和頁表的自旋鎖

struct list_head

mmlist

指向內存描述符鏈表中的相鄰元素

unsigned long

start_code

可執行代碼的起始地址

unsigned long

end_code

可執行代碼的最後地址

unsigned long

start_data

已初始化數據的起始地址

unsigned long

end_data

已初始化數據的最後地址

unsigned long

start_brk

堆的起始地址

unsigned long

brk

堆的當前最後地址

unsigned long

start_stack

用戶堆棧的起始地址

unsigned long

arg_start

命令行參數的起始地址

unsigned long

arg_end

命令行參數的最後地址

unsigned long

env_start

環境變量的起始地址

unsigned long

env_end

環境變量的最後地址

unsigned long

rss

分配給進程的頁框數

unsigned long

anon_rss

非配給匿名內存映射的頁框數

unsigned long

total_vm

進程地址空間的大小(頁數)

unsigned long

locked_vm

鎖住而不能換出的頁的個數

unsigned long

shared_vm

共享文件內存映射中的頁數

unsigned long

exec_vm

可執行內存映射中的頁數

unsigned long

stack_vm

用戶堆棧中的頁數

unsigned long

reserved_vm

在保留區中的頁數或在特殊線性區中的頁數

unsigned long

def_flags

線性區默認的訪問標誌

unsigned long

nr_ptes

進程的頁表數

unsigned long []

saved_auxv

開始執行ELF程序時使用

unsigned int

dumpable

表示是否可以產生內存轉儲信息的標誌

cpumask_t

cpu_vm_mask

用於惰性TLB交換的位掩碼

mm_context_t

context

指向有關特定體係結構信息的表

unsigned long

swap_token_time

進程在這個時間將有資格獲得交換標誌

char

recent_pagein

最近發生了主缺頁,則設置該標誌

int

core_waiters

正在把進程地址空間的內容轉儲到core文件中的輕量級進程的數目

struct completion *

core_startup_done

指向創建內存轉儲文件的補充原語

struct completion

core_done

指向創建內存轉儲文件的補充原語

rwlock_t

ioctx_list_lock

用於保護異步I/O上下文鏈表的鎖

struct kioctx *

ioctx_list

異步I/O上下文鏈表

struct kioctx

default_kioctx

默認的異步I/O上下文

unsigned long

hiwater_rss

進程所擁有的最大頁框數

unsigned long

hiwater_vm

進程線性區中的最大頁數

    所有的內存描述符存放在一個雙向鏈表中。每個內存描述符在mmlist字段存放鏈表相鄰元素的地址。鏈表的第一個元素是init_mmmmlist字段,init_mm是初始化階段進程0使用的內存描述符。

    注意理解和比較兩個字段:mm_usersmm_count字段。

    # mm_users字段存放共享mm_struct數據結構的輕量級進程的個數。

    # mm_count字段是內存描述符的主使用計數器,在mm_users次使用計數器中的所有用戶在mm_count中隻作為一個單元。每當mm_count遞減時,內核都要檢查它是否變為0,如果是,就要解除這個內存描述符,因為不再有用戶使用它。

===>為什麼要設置mm_count字段?見下文。

    內核線程的內存描述符

    內核線程就能運行在內核態,因此,它們永遠不會訪問低於TASK_SIZE(等於PAGE_OFFSET)的地址。與普通進程相反,內核線程不用線性區。因此描述符的很多字段對內核線程是沒有意義的。

    內核線程使用前一個進程的內存描述符。

    在每個進程描述符中包含了兩種內存描述符指針:mmactive_mm

    進程描述符中的mm字段指向進程所擁有的內存描述符,而active_mm字段指向進程運行時所使用的內存描述符。對於普通進程,這兩個字段存放相同的指針。但是,對於內核線程,由於內核線程不擁有任何內存描述符,因此,它們的mm字段總是NULL。當內核線程運行時,它的active_mm字段被初始化為前一個運行進程的active_mm值。

這裏,可以解釋上麵mm_users字段和mm_count字段的區別

<<<====>>>

    mm_users字段記錄共享該內存描述符的普通進程的個數,mm_count的目的在於支持內核線程級別。

Linux來說,用戶進程和內核線程(kernel thread)都是task_struct的實例(tsk),唯一的區別是內核線程是沒有進程地址空間的,內核線程也沒有mm內存描述符,所以內核線程的tsk->mm域(其中,struct task_struct * tsk)是空(NULL)。當內核scheduler在執行進程上下文切換(context switching)時,會根據tsk->mm判斷即將調度的進程是用戶進程還是內核線程。雖然內核線程不訪問用戶進程地址空間,但是仍然需要通過page table來訪問內核線程的內核地址空間。幸運的是,對於任何用戶進程來說,它們的內核空間都是完全相同的(3G-4G),所以內核可以“借用”上一個被調用的用戶進程的內存描述符mm中的頁表來訪問內核地址,並將此mm記錄在active_mm。簡而言之,對於用戶進程,tsk->mm == tsk->active_mm;而對於內核線程,tsk->mm == NULL表示自己內核線程的身份,而tsk->active_mm是使用上一個用戶進程的mm,通過此mmpage table來訪問內核空間。

    為了支持內核線程級別,mm_struct裏麵引入了另外一個counter,主使用計數器mm_count。前麵有mm_users表示這個進程地址空間被多少普通進程共享或者引用,而mm_count則表示這個地址空間被內核線程引用的次數+1(這裏的1,指的是在mm_users次使用計數器中的所有用戶在mm_count中隻作為一個單元)。內核不會因為mm_users == 0而銷毀此mm_struct,內核隻會當mm_count == 0時才會釋放mm_struct,因為這個時候既沒有用戶進程使用這個地址空間,也沒有內核線程引用這個地址空間。

最後更新:2017-04-03 05:39:37

  上一篇:go 在listView中多個listItem布局時,convertView緩存及使用
  下一篇:go LPC1768之串口UART0