① 寫內存算是訪問內存嗎
當用戶訪問用戶空間的這段地址范圍時,實際是訪問設備內存。
在linux上電時,並不會為外設地址空間建立頁表。
但我們知道,linux訪問內存使用的都是虛擬地址,因此如果想訪問外設的寄存器(一般包括數據寄存器、控制寄存器與狀態寄存器),需要在驅動初始化中將外設所處的物理地址映射為虛擬地址,使用ioremap介面可以實現該功能。
ioremap & ioremap_nocache
ioremap和ioremap_nocache實現相同,使用場景為映射device memory類型內存。同時不使用cache(device memory本身就沒有cacheable這個屬性),即CPU的讀寫操作直接操作設備內存。
ioremap_cached
ioremap_cached用來映射memory type為normal memory的設備,同時使用cache,這會提高內存的訪問速度,提高系統的性能。
ioremap_wc & ioremap_wt
ioremap_wc用來映射memory type為normal memory的設備,同時不使用cache。
I/O內存訪問流程
request_mem_region
ioremap
rw
iounmap
release_mem_region
二、設備地址映射到用戶空間
一般情況下,用戶空間是不能夠直接訪高肢問設備的。mmap可實現這個功能。
mmap通過將設備內存映射到用戶空間的一段內存上,這樣,當用戶訪問用戶空間的這段地址范圍時,實際是訪問設備內存。這樣在每次訪問時,節省了用戶空間和內核空間的復制過程。
無論是普通文件還是設備文件,讀寫都是基於系統的虛擬文件系統介面,普通文件為了保護磁碟,避免頻繁讀寫,還引入帶緩沖頁機制,通過read/write/ioctl訪問文件時,都需經歷「用戶到內核」的內存拷貝過程,然後才將文件內容寫入磁碟。
通過mmap方法,將文件(包括設備文件)映射到用戶進程虛擬內存空間,代替read/write/ioctl的訪問方式,此時內存拷貝過程只有「用戶空間到虛擬內存空間」,省去了「用戶到內核」的拷貝過程,在數據量大的情況下能顯著提升讀寫效率。因此,mmap也稱為「零拷貝」(zero )技術。
caddr_t *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
fd為文件描述符,一般由open返回。fd也可指定為-1,並指定flags參數中的MAP_ANON,表示匿名映射。
length指映射的位元組數,從offset開始計算;
prot指定訪問許可權;
start指定文件被映射到用戶空間的起始地址,一般設為NULL,由內核指定改地址;
函數返回值為映射到用戶空間的地址。
1
2
3
4
5
1
2
3
4
5
mmap過程
1、在虛擬內存中查找一塊VMA
2、將這塊VMA進行映射
3、如果設備驅動程序或文件系統的file_operation定義了mmap介面,則調用它;
4、將VMA插入進程的VMA鏈表中
1
2
3
4
1
2
3
4
進程在映射空間的對共享內容的修改不會實時同步寫回到磁碟文件中,只有調用munmap()函數釋放映射後才會執行同步操作。mmap機制提供msync()函數,用於手動同步修改內容到磁碟源文件。
linux內核使用vm_area_struct結構來表示一個獨立的虛擬內存區域,由於每個不同質的虛擬內存區域功能和內部機制都不同,因此一個進程使用多個vm_area_struct結構來分別表示不同滾老類型的虛擬內存區域。各個vm_area_struct結構使用鏈表或者樹形結構鏈接,方便進程快速訪問,如下圖所示:
在這里插入圖片描述
vm_area_struct結構中包含區域起始和終止地址以及其他相關信息,同時也包含一個vm_ops指針,其內部可引出所有針對這個區域可以使用的系統調用函數。這樣,進程對某一虛擬內存區域的任何操作需要用要的信息,都可以從vm_area_struct中獲得。mmap函數就是要創建一個新的vm_area_struct結構,並將其與文件的物理磁碟地址相連。
三、devmem原理
「/dev/mem」設備
「/大念升dev/mem」是linux系統的一個虛擬字元設備,無論是標准linux系統還是嵌入式linux系統,都支持該設備。
「/dev/mem」設備是內核所有物理地址空間的全映像,這些地址包括:
物理內存(RAM)空間
物理存儲(ROM)空間
cpu匯流排地址
cpu寄存器地址
外設寄存器地址,GPIO、定時器、ADC
1
2
3
4
5
1
2
3
4
5
「/dev/mem」設備通常與「mmap」結合使用,可將指定內存映射到用戶空間。
類似的還有/dev/kmem設備,kernel看到的虛擬內存的全鏡像。可以用來訪問kernel的內容。
devmem命令原理
應用程序通過mmap函數實現對/dev/mem驅動中mmap方法的使用,映射了設備的內存到用戶空間,實現對這些物理地址的讀寫操作。
類似的有devkmem命令,通過mmap函數實現對/dev/kmem驅動中mmap方法的使用,映射了設備的內核空間到用戶空間,實現對這些物理地址的讀寫操作。
四、malloc原理
malloc的工作原理
可執行文件載入到內存中的時候,就給棧和堆劃分了固定大小的空間。使用vm_area_struct結構體指明了一個連續區域的頭地址和尾地址。
malloc函數分配內存主要是使用brk和mmap系統調用
brk(): 小於128k
在堆段分配malloc的內存,將堆頂的指針brk往上推;
mmap(): 大於128k
是在堆和棧之間(文件映射區域)找分配一塊空閑的虛擬內存,
1
2
3
4
1
2
3
4
malloc系統調用後,並沒有實際分配物理內存。
這時候讀虛擬內存地址,返回值是0;
第一次寫的時候,發生缺頁中斷,才會實際分配物理內存,建立虛擬內存與物理內存的映射關系。
缺頁中斷
malloc的空間沒有實際分配的情況下,在寫的時候會報缺頁中斷。實際上:
進程線性地址空間里的頁面不必常駐內存,在執行一條指令時,如果發現他要訪問的頁沒有在內存中(即存在位為0),那麼停止該指令的執行,並產生一個頁不存在的異常,對應的故障處理程序可通過從外存載入該頁的方法來排除故障,之後,原先引起的異常的指令就可以繼續執行,而不再產生異常。
當一個進程發生缺頁中斷的時候,進程會陷入內核態,執行以下操作:
1、檢查要訪問的虛擬地址是否合法
2、查找/分配一個物理頁
3、填充物理頁內容(讀取磁碟,或者直接置0,或者啥也不幹)
4、建立映射關系(虛擬地址到物理地址)
1
2
3
4
1
2
3
4
重新執行發生缺頁中斷的那條指令
如果第3步,需要讀取磁碟,那麼這次缺頁中斷就是majflt,否則就是minflt。
如何查看進程發生缺頁中斷的次數:
ps -o majflt,minflt -C program
majflt代表major fault,中文名叫大錯誤,minflt代表minor fault,中文名叫小錯誤。
這兩個數值表示一個進程自啟動以來所發生的缺頁中斷的次數。
malloc的free
前面知道,通過移動brk申請的內存,存放在進程的堆區域中。
free是由運行庫實現,它只是在已分配的堆塊前面加一個可用標志,並不實際釋放內存,不論是物理內存還是進程的堆空間。
在下次的malloc時,這塊空間可能被重用。
如果進程的堆空間出現較多的碎片(這是邏輯地址中的碎片),運行庫的堆管理常式會移動/合並碎片,此時可能會出現物理內存的釋放/重新分配。
而對於brk指針,只有它指向的那片內存被free的時候才會下移。比如先malloc了一個A,然後malloc了一個B。free掉A之後,brk是不會下移的;free掉B的時候brk才會下移。
② linux鐵三角之內存(三)
一個用戶空間的進程,究竟消耗了多少內存。
首先要名確,一個application消耗的內存,一定指得是用戶空間的內存。
3g - 4g 的kernal space是共享的,每個進程都有自己用戶空間0 - 3G,只要通過系統調用就可以陷入kernal space, 就會從x86的3 rings升級到0 rings, 即陷入到內核空間。
app 調driver的iocrtrl, dirver 的ioctrl 內部通純枝過調用kmalloc/vmalloc申請的內存並不計算在內,因為是通過內核的api申請的,屬於內核消耗的。
vss、rss、pss、uss
pidof a.out
pmap a.out
vma的來源
在linux鐵三角(二)有過敘述,這里不再贅述。這里直接上圖把
MMU給CPU發送page fault的時候,在硬體中有2個寄存器
是否RSS就代表一個進程真正的內存消耗呢?
三個進程,其中2個Bash, 1 個 cat.
那麼對應三張頁表,每當切換進程,存儲頁表的 基地歷褲茄址就會卻換,從而切換到不同的地址空間中。
中間肢察的是內存條,通過三張頁表瓜分物理內存。
104進程內存消耗:
③ Linux將設備地址映射到用戶空間內存映射與VMA
一般情況下,用戶空間是不可能也不應該直接訪問設備的,但是,設備驅動程序中可實現mmap ()函數,這個函數可使得用戶空間能直接訪問設備的物理地址。實際上,mmap ()實現了這樣的一個映射過程:它將用戶空間的一段內存與設備內存關聯,當用戶訪問用戶空間的這段地址范圍時,實際上會轉化為對設備的訪問。
這種能力對於顯示適配器一類的設備非常有意義,如果用戶空間可直接通過內存映射訪問顯存的話,屏幕幀的各點像素將不再需要一個從用戶空間到內核空間的復制的過程。
mmap ()必須以PAGE_SIZE為單位進行映射,實際上,內存只能以頁為單位進行映射,若要映射非PAGE_SIZE整數倍的地址范圍,要先進行頁對齊,強行以PAGE_SIZE的倍數大小進行映射。
從file_operations文件操作結構體可以看出,驅動中mmap ()函數的原型如下:
int ( *mmap)(struct file *, struct vm_area_struct* ) ;
驅動中的mmap () 函數將在用戶進行mmap ()系統調用時最終被調用,mmap ()系統調用的原型與file_operations中mmap ()的原型區別很大,如下所示:
caddr_t mmap (caddr_t addr,size_t len,int prot,int flags,int fd,off_t offset);
參數fd為文件描述符,一般由open ()返回,fd也可以指定為-1,此時需指定flags參數中的MAP_ANON,表明進行的是匿名映射。
len是映射到調用用戶空間的位元組數,它從被映射文件開頭offset個位元組開始算起,offset參數一般設為0,表示從文件頭開始映射。
prot參數指定訪問許可權,可取如下幾個值的「或」:PROT_READ(可讀)、PROT_WRITE(可寫)、PROT_EXEC(可執行)和PROT_NONE(不可訪問)。
參數addr指定文件應被映射到用戶空間的起始地址,一般被指定為NULL,這樣,選擇起始地址的任務將由內核完成,而函數的返回值就是映射到用戶空間的地址。其類型caddr_t實際上就是void*。
當用戶調用mmap ())的時候,內核會進行如下處理。
1)在進程的虛擬空間查找一塊VMA。
2)將這塊VMA進行映射。
3)如果設備驅動程序或者文件系統的file_operations定義了mmap ()操作,則調用它。
4)將這個VMA插入進程的VMA鏈表中。
file_operations中mmap ()函數的第一個參數就是步驟1)找到的VMA。
由mmap ()系統調用映射的內存可由munmap ()解除映射,這個函數的原型如下:
int munmap(caddr_t addr, size_t len ) ;
驅動程序中mmap ()的實現機制是建立頁表,並填充VMA結構體中vm_operations_struct指針。
④ linux操作系統額外佔用物理內存是多少
這個看看你需要使用的情況.
如果是使用文本化界面的話,其稿戚實64M就足夠了.
但是如果你需要使用到圖形化界面(GNOME),那麼最好分轎數128M以上,個人覺得256M左右比較合適,當然這個根據你本閉敬首身自帶的硬體內存的大小決定.
因為是半虛擬化,所以對系統的資源佔用是比較大的,所以最好先確定你的硬體環境.
⑤ 簡述缺頁中斷和一般中斷的區別
1、范圍不同
一般中斷只需要保護現場,然後就直接跳到需及時處理的地方。
缺頁中斷除了保護現場之外,還要判斷內存中是否有足夠的空間存儲所需的頁或段,然後再把所需頁調進來再使用。
2、結果不同
一般中斷在處理完之後返回時,執行下一條指令。
缺頁中斷返回時,執行產生中斷的那一條指令。
3、次數不同
在扒困碼指令執行期間產生和處理缺頁中斷信號,一條指令在執行期間,可能產生多次缺頁中斷。
一般中斷只產生一尺譽次,發生中斷指令後轉入相應處理程序進行處理,恢復被中斷程序現場。
(5)linuxvma擴展閱讀
產生缺頁中斷的幾種情況
1、當內存管理單元(MMU)中確實沒有創建虛擬物理頁映射關系,並且在該虛擬地址之後再沒有當前進程的線性區(vma)的時候,這將殺掉該進程。
2、當MMU中確實沒有創建虛擬頁物理頁映射關系,並且在該虛擬地址之後存在當前進程的線性區vma的時候,這很可能是缺頁中斷,並且可能是棧溢出導致的缺頁中斷。
3、當使用malloc/mmap等希望訪問物理空間的庫函數/系統調用後,由於linux並未真正給新創建的vma映射物理頁,此時若先進行寫操作,將和2產生缺頁中斷的情況一樣;若先進行讀操作雖然也會產生缺頁異常,將被映射給默認的零頁,等再進行寫操作時,仍會產生缺頁中斷,這次必須分配1物理頁了,進入寫時復制的流程。
4、當使用fork等系統調用創建子進程時,子進程春哪不論有無自己的vma,它的vma都有對於物理頁的映射,但它們共同映射的這些物理頁屬性為只讀,即linux並未給子進程真正分配物理頁,當父子進程任何一方要寫相應物理頁時,導致缺頁中斷的寫時復制。
⑥ Linux 虛擬地址空間如何分布
一個進程的虛擬地址空間主要由兩個數據結來描述。一個是最高層次的:mm_struct,一個是較高層次的:vm_area_structs。最高層次的mm_struct結構描述了一個進程的整個虛擬地址空間。較高層次的結構vm_area_truct描述了虛擬地址空間的一個區間(簡稱虛擬區)。
1. MM_STRUCT結構
mm_strcut 用來描述一個進程的虛擬地址空間,在/include/linux/sched.h 中描述如下:
struct mm_struct {
struct vm_area_struct * mmap; /* 指向虛擬區間(VMA)鏈表 */
rb_root_t mm_rb; /*指向red_black樹*/
struct vm_area_struct * mmap_cache; /* 指向最近找到的虛擬區間*/
pgd_t * pgd; /*指向進程的頁目錄*/
atomic_t mm_users; /* 用戶空間中的有多少用戶*/
atomic_t mm_count; /* 對"struct mm_struct"有多少引用*/
int map_count; /* 虛擬區間的個數*/
struct rw_semaphore mmap_sem;
spinlock_t page_table_lock; /* 保護任務頁表和 mm->rss */
struct list_head mmlist; /*所有活動(active)mm的鏈表 */
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;
unsigned long def_flags;
unsigned long cpu_vm_mask;
unsigned long swap_address;
unsigned mpable:1;
/* Architecture-specific MM context */
mm_context_t context;
};
對該結構進一步說明如下:
在內核代碼中,指向這個數據結構的變數常常是mm。
每個進程只有一個mm_struct結構,在每個進程的task_struct結構中,有一個指向該進程的結構。可以說,mm_struct結構是對整個用戶空間的描述。
一個進程的虛擬空間中可能有多個虛擬區間(參見下面對vm_area_struct描述),對這些虛擬區間的組織方式有兩種,當虛擬區較少時採用單鏈表,由mmap指針指向這個鏈表,當虛擬區間多時採用「紅黑樹(red_black
tree)」結構,由mm_rb指向這顆樹。在2.4.10以前的版本中,採用的是AVL樹,因為與AVL樹相比,對紅黑樹進行操作的效率更高。
因為程序中用到的地址常常具有局部性,因此,最近一次用到的虛擬區間很可能下一次還要用到,因此,把最近用到的虛擬區間結構應當放入高速緩存,這個虛擬區間就由mmap_cache指向。
指針pgt指向該進程的頁目錄(每個進程都有自己的頁目錄,注意同內核頁目錄的區別),當調度程序調度一個程序運行時,就將這個地址轉成物理地址,並寫入控制寄存器(CR3)。
由於進程的虛擬空間及其下屬的虛擬區間有可能在不同的上下文中受到訪問,而這些訪問又必須互斥,所以在該結構中設置了用於P、V操作的信號量mmap_sem。此外,page_table_lock也是為類似的目的而設置。
雖然每個進程只有一個虛擬地址空間,但這個地址空間可以被別的進程來共享,如,子進程共享父進程的地址空間(也即共享mm_struct結構)。所以,用mm_user和mm_count進行計數。類型atomic_t實際上就是整數,但對這種整數的操作必須是「原子」的。
另外,還描述了代碼段、數據段、堆棧段、參數段以及環境段的起始地址和結束地址。這里的段是對程序的邏輯劃分,與我們前面所描述的段機制是不同的。
mm_context_t是與平台相關的一個結構,對i386 幾乎用處不大。
在後面對代碼的分析中對有些域給予進一步說明。
2. VM_AREA_STRUCT 結構
vm_area_struct描述進程的一個虛擬地址區間,在/include/linux/mm.h中描述如下:
struct vm_area_struct
struct mm_struct * vm_mm; /* 虛擬區間所在的地址空間*/
unsigned long vm_start; /* 在vm_mm中的起始地址*/
unsigned long vm_end; /*在vm_mm中的結束地址 */
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot; /* 對這個虛擬區間的存取許可權 */
unsigned long vm_flags; /* 虛擬區間的標志. */
rb_node_t vm_rb;
/*
* For areas with an address space and backing store,
* one of the address_space->i_mmap{,shared} lists,
* for shm areas, the list of attaches, otherwise unused.
*/
struct vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;
/*對這個區間進行操作的函數 */
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). */
unsigned long vm_raend; /* XXX: put full readahead info here. */
void * vm_private_data; /* was vm_pte (shared mem) */
};
vm_flag是描述對虛擬區間的操作的標志,其定義和描述如下
標志名 描述
VM_DENYWRITE 在這個區間映射一個打開後不能用來寫的文件。
VM_EXEC 頁可以被執行。
VM_EXECUTABLE 頁含有可執行代碼。
VM_GROWSDOWN 這個區間可以向低地址擴展。
VM_GROWSUP 這個區間可以向高地址擴展。
VM_IO 這個區間映射一個設備的I/O地址空間。
VM_LOCKED 頁被鎖住不能被交換出去。
VM_MAYEXEC VM_EXEC 標志可以被設置。
VM_MAYREAD VM_READ 標志可以被設置。
VM_MAYSHARE VM_SHARE 標志可以被設置。
VM_MAYWRITE VM_WRITE 標志可以被設置。
VM_READ 頁是可讀的。
VM_SHARED 頁可以被多個進程共享。
VM_SHM 頁用於IPC共享內存。
VM_WRITE 頁是可寫的。
較高層次的結構vm_area_structs是由雙向鏈表連接起來的,它們是按虛地址的降順序來排列的,每個這樣的結構都對應描述一個相鄰的地址空間范圍。之所以這樣分割,是因為每個虛擬區間可能來源不同,有的可能來自可執行映象,有的可能來自共享庫,而有的則可能是動態分配的內存區,所以對每一個由vm_area_structs結構所描述的區間的處理操作和它前後范圍的處理操作不同。因此Linux
把虛擬內存分割管理,並利用了虛擬內存處理常式(vm_ops)來抽象對不同來源虛擬內存的處理方法。不同的虛擬區間其處理操作可能不同,Linux在這里利用了面向對象的思想,即把一個虛擬區間看成一個對象,用vm_area_structs描述了這個對象的屬性,其中的vm_operation結構描述了在這個對象上的操作,其定義在/include/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);
struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int unused);
};
vm_operations結構中包含的是函數指針;其中,open、close分別用於虛擬區間的打開、關閉,而nopage用於當虛存頁面不在物理內存而引起的「缺頁異常」時所應該調用的函數。
3.紅黑樹結構
Linux內核從2.4.10開始,對虛擬區的組織不再採用AVL樹,而是採用紅黑樹,這也是出於效率的考慮,雖然AVL樹和紅黑樹很類似,但在插入和刪除節點方面,採用紅黑樹的性能更好一些,下面對紅黑樹給予簡單介紹。
一顆紅黑樹是具有以下特點的二叉樹:
每個節點著有顏色,或者為紅,或者為黑
根節點為黑色
如果一個節點為紅色,那麼它的子節點必須為黑色
從一個節點到葉子節點上的所有路徑都包含有相同的黑色節點數
⑦ linux內核模塊編寫要求從一個虛存區VMA和一個虛地址addr求這個地址所在的物理頁面
implicit declaration of function ***
查謹巧查英語也應該曉得了阿....
隱式的函數聲明,就是說你使用了kmap_atomic、kmap_atomic但是沒有聲明,一般是因為沒有包含頭文件,或者是內核的API發生了些許變化。橘碰
這兩個函數包含在linux/highmem.h中,你在程序中添加一句:#include <圓晌談linux/highmem.h>試試。
PS :這個是在2.6.38的內核中的,你在lxr.linux.no搜下對應版本的kernel,我沒有細細查。
⑧ linux一個線程需要有多少vma
$ ps -ef f | grep proftpd
nobody 23117 1 0 Dec23 ? S 0:00 proftpd: (accepting connections)
jack 23121 23117 0 Dec23 ? S 7:57 /_ proftpd: jack - ftpsrv: IDLE
jack 28944 23117 0 Dec23 ? S 4:56 /彎知_ proftpd: jack - ftpsrv: IDLE
這樣數螞就可以看到proftpd這個進程下面掛了兩個埋畢消線程。
⑨ linux中使用了什麼內存管理方法,為什麼
「事實勝於雄辯」,我們用一個小例子(原形取自《User-Level Memory Management》)來展示上面所講的各種內存區的差別與位置。
進程的地址空間對應的描述結構是「內存描述符結構」,它表示進程的全部地址空間,——包含了和進程地址空間有關的全部信息,其中當然包含進程的內存區域。
進程內存的分配與回收
創建進程fork()、程序載入execve()、映射文件mmap()、動態內存分配malloc()/brk()等進程相關操作都需要分配內存給進程。不過這時進程申請和獲得的還不是實際內存,而是虛擬內存,准確的說是「內存區域」。進程對內存區域的分配最終都會歸結到do_mmap()函數上來(brk調用被單獨以系統調用實現,不用do_mmap()),
內核使用do_mmap()函數創建一個新的線性地址區間。但是說該函數創建了一個新VMA並不非常准確,因為如果創建的地址區間和一個已經存在的地址區間相鄰,並且它們具有相同的訪問許可權的話,那麼兩個區間將合並為一個。如果不能合並,那麼就確實需要創建一個新的VMA了。但無論哪種情況,do_mmap()函數都會將一個地址區間加入到進程的地址空間中--無論是擴展已存在的內存區域還是創建一個新的區域。
同樣,釋放一個內存區域應使用函數do_ummap(),它會銷毀對應的內存區域。
如何由虛變實!
從上面已經看到進程所能直接操作的地址都為虛擬地址。當進程需要內存時,從內核獲得的僅僅是虛擬的內存區域,而不是實際的物理地址,進程並沒有獲得物理內存(物理頁面——頁的概念請大家參考硬體基礎一章),獲得的僅僅是對一個新的線性地址區間的使用權。實際的物理內存只有當進程真的去訪問新獲取的虛擬地址時,才會由「請求頁機制」產生「缺頁」異常,從而進入分配實際頁面的常式。
該異常是虛擬內存機制賴以存在的基本保證——它會告訴內核去真正為進程分配物理頁,並建立對應的頁表,這之後虛擬地址才實實在在地映射到了系統的物理內存上。(當然,如果頁被換出到磁碟,也會產生缺頁異常,不過這時不用再建立頁表了)
這種請求頁機制把頁面的分配推遲到不能再推遲為止,並不急於把所有的事情都一次做完(這種思想有點像設計模式中的代理模式(proxy))。之所以能這么做是利用了內存訪問的「局部性原理」,請求頁帶來的好處是節約了空閑內存,提高了系統的吞吐率。要想更清楚地了解請求頁機制,可以看看《深入理解linux內核》一書。
這里我們需要說明在內存區域結構上的nopage操作。當訪問的進程虛擬內存並未真正分配頁面時,該操作便被調用來分配實際的物理頁,並為該頁建立頁表項。在最後的例子中我們會演示如何使用該方法。
系統物理內存管理
雖然應用程序操作的對象是映射到物理內存之上的虛擬內存,但是處理器直接操作的卻是物理內存。所以當應用程序訪問一個虛擬地址時,首先必須將虛擬地址轉化成物理地址,然後處理器才能解析地址訪問請求。地址的轉換工作需要通過查詢頁表才能完成,概括地講,地址轉換需要將虛擬地址分段,使每段虛地址都作為一個索引指向頁表,而頁表項則指向下一級別的頁表或者指向最終的物理頁面。
每個進程都有自己的頁表。進程描述符的pgd域指向的就是進程的頁全局目錄。下面我們借用《linux設備驅動程序》中的一幅圖大致看看進程地址空間到物理頁之間的轉換關系。
上面的過程說起來簡單,做起來難呀。因為在虛擬地址映射到頁之前必須先分配物理頁——也就是說必須先從內核中獲取空閑頁,並建立頁表。下面我們介紹一下內核管理物理內存的機制。
物理內存管理(頁管理)
Linux內核管理物理內存是通過分頁機制實現的,它將整個內存劃分成無數個4k(在i386體系結構中)大小的頁,從而分配和回收內存的基本單位便是內存頁了。利用分頁管理有助於靈活分配內存地址,因為分配時不必要求必須有大塊的連續內存[3],系統可以東一頁、西一頁的湊出所需要的內存供進程使用。雖然如此,但是實際上系統使用內存時還是傾向於分配連續的內存塊,因為分配連續內存時,頁表不需要更改,因此能降低TLB的刷新率(頻繁刷新會在很大程度上降低訪問速度)。
鑒於上述需求,內核分配物理頁面時為了盡量減少不連續情況,採用了「夥伴」關系來管理空閑頁面。夥伴關系分配演算法大家應該不陌生——幾乎所有操作系統方面的書都會提到,我們不去詳細說它了,如果不明白可以參看有關資料。這里只需要大家明白Linux中空閑頁面的組織和管理利用了夥伴關系,因此空閑頁面分配時也需要遵循夥伴關系,最小單位只能是2的冪倍頁面大小。內核中分配空閑頁面的基本函數是get_free_page/get_free_pages,它們或是分配單頁或是分配指定的頁面(2、4、8…512頁)。
注意:get_free_page是在內核中分配內存,不同於malloc在用戶空間中分配,malloc利用堆動態分配,實際上是調用brk()系統調用,該調用的作用是擴大或縮小進程堆空間(它會修改進程的brk域)。如果現有的內存區域不夠容納堆空間,則會以頁面大小的倍數為單位,擴張或收縮對應的內存區域,但brk值並非以頁面大小為倍數修改,而是按實際請求修改。因此Malloc在用戶空間分配內存可以以位元組為單位分配,但內核在內部仍然會是以頁為單位分配的。
另外,需要提及的是,物理頁在系統中由頁結構structpage描述,系統中所有的頁面都存儲在數組mem_map[]中,可以通過該數組找到系統中的每一頁(空閑或非空閑)。而其中的空閑頁面則可由上述提到的以夥伴關系組織的空閑頁鏈表(free_area[MAX_ORDER])來索引。
內核內存使用
Slab
所謂尺有所長,寸有所短。以頁為最小單位分配內存對於內核管理系統中的物理內存來說的確比較方便,但內核自身最常使用的內存卻往往是很小(遠遠小於一頁)的內存塊——比如存放文件描述符、進程描述符、虛擬內存區域描述符等行為所需的內存都不足一頁。這些用來存放描述符的內存相比頁面而言,就好比是麵包屑與麵包。一個整頁中可以聚集多個這些小塊內存;而且這些小塊內存塊也和麵包屑一樣頻繁地生成/銷毀。
為了滿足內核對這種小內存塊的需要,Linux系統採用了一種被稱為slab分配器的技術。Slab分配器的實現相當復雜,但原理不難,其核心思想就是「存儲池[4]」的運用。內存片段(小塊內存)被看作對象,當被使用完後,並不直接釋放而是被緩存到「存儲池」里,留做下次使用,這無疑避免了頻繁創建與銷毀對象所帶來的額外負載。
Slab技術不但避免了內存內部分片(下文將解釋)帶來的不便(引入Slab分配器的主要目的是為了減少對夥伴系統分配演算法的調用次數——頻繁分配和回收必然會導致內存碎片——難以找到大塊連續的可用內存),而且可以很好地利用硬體緩存提高訪問速度。
Slab並非是脫離夥伴關系而獨立存在的一種內存分配方式,slab仍然是建立在頁面基礎之上,換句話說,Slab將頁面(來自於夥伴關系管理的空閑頁面鏈表)撕碎成眾多小內存塊以供分配,slab中的對象分配和銷毀使用kmem_cache_alloc與kmem_cache_free。
Kmalloc
Slab分配器不僅僅只用來存放內核專用的結構體,它還被用來處理內核對小塊內存的請求。當然鑒於Slab分配器的特點,一般來說內核程序中對小於一頁的小塊內存的請求才通過Slab分配器提供的介面Kmalloc來完成(雖然它可分配32到131072位元組的內存)。從內核內存分配的角度來講,kmalloc可被看成是get_free_page(s)的一個有效補充,內存分配粒度更靈活了。
有興趣的話,可以到/proc/slabinfo中找到內核執行現場使用的各種slab信息統計,其中你會看到系統中所有slab的使用信息。從信息中可以看到系統中除了專用結構體使用的slab外,還存在大量為Kmalloc而准備的Slab(其中有些為dma准備的)。
內核非連續內存分配(Vmalloc)
夥伴關系也好、slab技術也好,從內存管理理論角度而言目的基本是一致的,它們都是為了防止「分片」,不過分片又分為外部分片和內部分片之說,所謂內部分片是說系統為了滿足一小段內存區(連續)的需要,不得不分配了一大區域連續內存給它,從而造成了空間浪費;外部分片是指系統雖有足夠的內存,但卻是分散的碎片,無法滿足對大塊「連續內存」的需求。無論何種分片都是系統有效利用內存的障礙。slab分配器使得一個頁面內包含的眾多小塊內存可獨立被分配使用,避免了內部分片,節約了空閑內存。夥伴關系把內存塊按大小分組管理,一定程度上減輕了外部分片的危害,因為頁框分配不在盲目,而是按照大小依次有序進行,不過夥伴關系只是減輕了外部分片,但並未徹底消除。你自己比劃一下多次分配頁面後,空閑內存的剩餘情況吧。
所以避免外部分片的最終思路還是落到了如何利用不連續的內存塊組合成「看起來很大的內存塊」——這里的情況很類似於用戶空間分配虛擬內存,內存邏輯上連續,其實映射到並不一定連續的物理內存上。Linux內核借用了這個技術,允許內核程序在內核地址空間中分配虛擬地址,同樣也利用頁表(內核頁表)將虛擬地址映射到分散的內存頁上。以此完美地解決了內核內存使用中的外部分片問題。內核提供vmalloc函數分配內核虛擬內存,該函數不同於kmalloc,它可以分配較Kmalloc大得多的內存空間(可遠大於128K,但必須是頁大小的倍數),但相比Kmalloc來說,Vmalloc需要對內核虛擬地址進行重映射,必須更新內核頁表,因此分配效率上要低一些(用空間換時間)
與用戶進程相似,內核也有一個名為init_mm的mm_strcut結構來描述內核地址空間,其中頁表項pdg=swapper_pg_dir包含了系統內核空間(3G-4G)的映射關系。因此vmalloc分配內核虛擬地址必須更新內核頁表,而kmalloc或get_free_page由於分配的連續內存,所以不需要更新內核頁表。
vmalloc分配的內核虛擬內存與kmalloc/get_free_page分配的內核虛擬內存位於不同的區間,不會重疊。因為內核虛擬空間被分區管理,各司其職。進程空間地址分布從0到3G(其實是到PAGE_OFFSET,在0x86中它等於0xC0000000),從3G到vmalloc_start這段地址是物理內存映射區域(該區域中包含了內核鏡像、物理頁面表mem_map等等)比如我使用的系統內存是64M(可以用free看到),那麼(3G——3G+64M)這片內存就應該映射到物理內存,而vmalloc_start位置應在3G+64M附近(說"附近"因為是在物理內存映射區與vmalloc_start期間還會存在一個8M大小的gap來防止躍界),vmalloc_end的位置接近4G(說"接近"是因為最後位置系統會保留一片128k大小的區域用於專用頁面映射,還有可能會有高端內存映射區,這些都是細節,這里我們不做糾纏)。
上圖是內存分布的模糊輪廓
由get_free_page或Kmalloc函數所分配的連續內存都陷於物理映射區域,所以它們返回的內核虛擬地址和實際物理地址僅僅是相差一個偏移量(PAGE_OFFSET),你可以很方便的將其轉化為物理內存地址,同時內核也提供了virt_to_phys()函數將內核虛擬空間中的物理映射區地址轉化為物理地址。要知道,物理內存映射區中的地址與內核頁表是有序對應的,系統中的每個物理頁面都可以找到它對應的內核虛擬地址(在物理內存映射區中的)。
而vmalloc分配的地址則限於vmalloc_start與vmalloc_end之間。每一塊vmalloc分配的內核虛擬內存都對應一個vm_struct結構體(可別和vm_area_struct搞混,那可是進程虛擬內存區域的結構),不同的內核虛擬地址被4k大小的空閑區間隔,以防止越界——見下圖)。與進程虛擬地址的特性一樣,這些虛擬地址與物理內存沒有簡單的位移關系,必須通過內核頁表才可轉換為物理地址或物理頁。它們有可能尚未被映射,在發生缺頁時才真正分配物理頁面。
這里給出一個小程序幫助大家認清上面幾種分配函數所對應的區域。
#include<linux/mole.h>
#include<linux/slab.h>
#include<linux/vmalloc.h>
unsignedchar*pagemem;
unsignedchar*kmallocmem;
unsignedchar*vmallocmem;
intinit_mole(void)
{
pagemem = get_free_page(0);
printk("<1>pagemem=%s",pagemem);
kmallocmem = kmalloc(100,0);
printk("<1>kmallocmem=%s",kmallocmem);
vmallocmem = vmalloc(1000000);
printk("<1>vmallocmem=%s",vmallocmem);
}
voidcleanup_mole(void)
{
free_page(pagemem);
kfree(kmallocmem);
vfree(vmallocmem);
}
實例
內存映射(mmap)是Linux操作系統的一個很大特色,它可以將系統內存映射到一個文件(設備)上,以便可以通過訪問文件內容來達到訪問內存的目的。這樣做的最大好處是提高了內存訪問速度,並且可以利用文件系統的介面編程(設備在Linux中作為特殊文件處理)訪問內存,降低了開發難度。許多設備驅動程序便是利用內存映射功能將用戶空間的一段地址關聯到設備內存上,無論何時,只要內存在分配的地址范圍內進行讀寫,實際上就是對設備內存的訪問。同時對設備文件的訪問也等同於對內存區域的訪問,也就是說,通過文件操作介面可以訪問內存。Linux中的X伺服器就是一個利用內存映射達到直接高速訪問視頻卡內存的例子。
熟悉文件操作的朋友一定會知道file_operations結構中有mmap方法,在用戶執行mmap系統調用時,便會調用該方法來通過文件訪問內存——不過在調用文件系統mmap方法前,內核還需要處理分配內存區域(vma_struct)、建立頁表等工作。對於具體映射細節不作介紹了,需要強調的是,建立頁表可以採用remap_page_range方法一次建立起所有映射區的頁表,或利用vma_struct的nopage方法在缺頁時現場一頁一頁的建立頁表。第一種方法相比第二種方法簡單方便、速度快,但是靈活性不高。一次調用所有頁表便定型了,不適用於那些需要現場建立頁表的場合——比如映射區需要擴展或下面我們例子中的情況。
我們這里的實例希望利用內存映射,將系統內核中的一部分虛擬內存映射到用戶空間,以供應用程序讀取——你可利用它進行內核空間到用戶空間的大規模信息傳輸。因此我們將試圖寫一個虛擬字元設備驅動程序,通過它將系統內核空間映射到用戶空間——將內核虛擬內存映射到用戶虛擬地址。從上一節已經看到Linux內核空間中包含兩種虛擬地址:一種是物理和邏輯都連續的物理內存映射虛擬地址;另一種是邏輯連續但非物理連續的vmalloc分配的內存虛擬地址。我們的例子程序將演示把vmalloc分配的內核虛擬地址映射到用戶地址空間的全過程。
程序里主要應解決兩個問題:
第一是如何將vmalloc分配的內核虛擬內存正確地轉化成物理地址?
因為內存映射先要獲得被映射的物理地址,然後才能將其映射到要求的用戶虛擬地址上。我們已經看到內核物理內存映射區域中的地址可以被內核函數virt_to_phys轉換成實際的物理內存地址,但對於vmalloc分配的內核虛擬地址無法直接轉化成物理地址,所以我們必須對這部分虛擬內存格外「照顧」——先將其轉化成內核物理內存映射區域中的地址,然後在用virt_to_phys變為物理地址。
轉化工作需要進行如下步驟:
找到vmalloc虛擬內存對應的頁表,並尋找到對應的頁表項。
獲取頁表項對應的頁面指針
通過頁面得到對應的內核物理內存映射區域地址。
如下圖所示:
第二是當訪問vmalloc分配區時,如果發現虛擬內存尚未被映射到物理頁,則需要處理「缺頁異常」。因此需要我們實現內存區域中的nopaga操作,以能返回被映射的物理頁面指針,在我們的實例中就是返回上面過程中的內核物理內存映射區域中的地址。由於vmalloc分配的虛擬地址與物理地址的對應關系並非分配時就可確定,必須在缺頁現場建立頁表,因此這里不能使用remap_page_range方法,只能用vma的nopage方法一頁一頁的建立。
程序組成
map_driver.c,它是以模塊形式載入的虛擬字元驅動程序。該驅動負責將一定長的內核虛擬地址(vmalloc分配的)映射到設備文件上。其中主要的函數有——vaddress_to_kaddress()負責對vmalloc分配的地址進行頁表解析,以找到對應的內核物理映射地址(kmalloc分配的地址);map_nopage()負責在進程訪問一個當前並不存在的VMA頁時,尋找該地址對應的物理頁,並返回該頁的指針。
test.c它利用上述驅動模塊對應的設備文件在用戶空間讀取讀取內核內存。結果可以看到內核虛擬地址的內容(ok!),被顯示在了屏幕上。
執行步驟
編譯map_driver.c為map_driver.o模塊,具體參數見Makefile
載入模塊:insmodmap_driver.o
生成對應的設備文件
1在/proc/devices下找到map_driver對應的設備命和設備號:grepmapdrv/proc/devices
2建立設備文件mknodmapfilec 254 0(在我的系統里設備號為254)
利用maptest讀取mapfile文件,將取自內核的信息列印到屏幕上。
⑩ linux怎麼能跟蹤進程每一個vma區域建立的位置
Tracepath。
Tracepath指令可以追蹤數據到達目標主機的路由早液兆信息,同時還能夠發現MTU值。它跟蹤路徑到目的地,沿著這條路徑發現MTU。它使用UDP埠或一些隨機埠。
它類似陸租於Traceroute,只是不需要超級用戶特權,並且埋扒沒有花哨的選項。