日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Linux進程的虛擬地址空間

 take_care 2012-06-21
在x86體系結構中分段機制是必選的,而分頁機制則可由具體的操作系統(tǒng)而選擇,Linux通過讓段的基地址為0而巧妙的繞過了基地址。因此,對于Linux來說,虛地址和線性地址是一致的。在32位的平臺上,線性地址的大小為固定的4GB。并且,由于采用了保護機制,Linux內(nèi)核將這4GB分為兩部分,虛地址較高的1GB(0xC0000000到0xFFFFFFFF)為共享的內(nèi)核空間;而較低的3GB(0x00000000到0xBFFFFFFF)為每個進程的用戶空間。由于每個進程都不能直接訪問內(nèi)核空間,而是通過系統(tǒng)調(diào)用間接進入內(nèi)核,因此,所有的進程都共享內(nèi)核空間。而每個進程都擁有各自的用戶空間,各個進程之間不能互相訪問彼此的用戶空間。因此,對于每一個具體的進程而言,都擁有4GB的虛擬地址空間?! ?div> 一個程序在經(jīng)過編譯、連接之后形成的地址空間是一個虛擬的地址空間,只有當程序運行的時候才會分配具體的物理空間。由此我們可以得知,程序的虛擬地址相對來說是固定的,而物理地址則隨著每一次程序的運行而有所不同。
  對于內(nèi)核空間而言,它與物理內(nèi)存之間存在一個簡單的線性關系,即存在3GB的偏移量。在Linux內(nèi)核中,這個偏移量叫做PAGE_OFFSET。如果內(nèi)核的某個物理地址為x,那么對應的內(nèi)核虛地址就為x+PAGE_OFFSET。
  對于用戶空間而言,它與物理內(nèi)存之間的映射遠不止這么簡單。與內(nèi)核空間和物理空間的線性映射不同的是,分頁機制將虛擬用戶空間和物理地址空間分成大小相同的頁,然后再通過頁表將虛擬頁和物理頁塊映射起來。
   內(nèi)核空間一般可以通過__get_free_page()、kmalloc()和vmalloc()來申請內(nèi)核空間。只不過__get_free_page函數(shù)每次申請的都是完整的頁;而后兩者則依據(jù)具體參數(shù)申請以字節(jié)為單位的內(nèi)存空間。此外,前兩個函數(shù)申請的虛擬地址空間和物理地址空間都是連續(xù)的;vmalloc函數(shù)申請的物理地址空間并不連續(xù)。vmalloc函數(shù)通過重新建立虛擬地址空間和物理地址空間之間的映射,即新建頁表項,將離散的物理地址空間映射到連續(xù)的虛擬地址空間。因此,使用該函數(shù)的開銷比較大。
  下面的程序簡單的演示了這三個函數(shù)的使用方法。從結果中可以看出,這些函數(shù)申請的地址都在3GB(0xBFFFFFFF)以上。完整代碼在如下。
   static int __init menroyshow_init(void)   {
   printk("mmshow module is working\n"); 
  pagemem = __get_free_page(GFP_KERNEL); 
  if(!pagemem)
     goto gfp_fail; 
  printk(KERN_INFO "pagemem = 0x%lx\n",pagemem); 
  kmallocmem = kmalloc(100 * sizeof(char),GFP_KERNEL); 
  if(!kmallocmem)
   goto kmalloc_fail; 
   printk(KERN_INFO "kmallocmem = 0x%p\n",kmallocmem);
   vmallocmem = vmalloc(1000000 * sizeof(char));
   if(!vmallocmem)
   goto vmalloc_fail; 
   printk(KERN_INFO "vmallocmem = 0x%p\n",vmallocmem); 
  return 0; 
  gfp_fail: 
   free_page(pagemem);
   kmalloc_fail: 
  kfree(kmallocmem); 
  vmalloc_fail: 
   vfree(vmallocmem); 
  return -1; 
  } 
  //運行結果:
   # pagemem = 0xf3211000   # kmallocmem = 0xd581e700   # vmallocmem = 0xf9251000
   每個進程夠擁有屬于自己的3GB的虛擬空間(用戶空間),那么這個3GB的空間是如何劃分的?通常,除了我們熟悉的代碼段和數(shù)據(jù)段,用戶空間還包括堆棧段和堆。我們可以通過下面的演示程序來了解這些區(qū)域到底負責存儲程序的那些內(nèi)容?! nt bss_var;   int data_var0 = 1;   int main(int argc,char **argv)   {   printf("The user space's address division of a process as follow:\n");   printf("Data segment:\n");   printf("address of \"main\" function:%p\n\n",main);   printf("Data segment:\n");   printf("address of data_var:%p\n",&data_var0);   static int data_var1 = 4;   printf("new end of data_var:%p\n\n",&data_var1);   printf("BSS:\n");   printf("address of bss_var:%p\n\n",&bss_var);   char *str = (char *)malloc(sizeof(char)*10);   printf("initial heap end:%p\n",str);   char *buf = (char *)malloc(sizeof(char)*10);   printf("new heap end:%p\n\n",buf);   int stack_var0 = 2;   printf("Stack segment:\n");   printf("initial end of stack:%p\n",&stack_var0);   int stack_var1 = 3;   printf("new end of stack:%p\n",&stack_var1);   return 0;   }   //運行結果:   The user space's address division of a process as follow:   Data segment:   address of "main" function:0x8048454   Data segment:   address of data_var:0x804a01c   new end of data_var:0x804a020   BSS:   address of bss_var:0x804a02c   initial heap end:0x8f77008   new heap end:0x8f77018   Stack segment:   initial end of stack:0xbfe0a3b4   new end of stack:0xbfe0a3b0   可以看到,代碼段存放程序的代碼;數(shù)據(jù)段存放全局變量和static類型的局部變量。此外,未初始化的全局變量雖然也存在于數(shù)據(jù)段,但是這些未初始化的變量都集中在靠近數(shù)據(jù)段上邊界的區(qū)域,這個區(qū)域稱為BSS段。以上這些空間是進程所必須擁有的,它們在進程運行之前就分配好了?! 〕绦蛑械木植孔兞恳话惚环峙湓诙褩6?,其位于用戶空間最頂部。與固定的代碼段和數(shù)據(jù)段不同的是,堆棧段存儲數(shù)據(jù)是從高低值往低地址延伸的。因此,在數(shù)據(jù)段到堆棧段之間,形成了一片空洞,這片空洞用于存儲malloc函數(shù)所動態(tài)分配的空間,這片空洞區(qū)域被稱為堆?! ⊥ㄟ^下面這個圖可以更進一步的了解到進程用戶空間的劃分情況?!   ?     以上是關于進程用戶空間劃分的大致分析,上述理論在內(nèi)核代碼中如何體現(xiàn)?它將涉及到mm_struct結構和vm_area_struct結構?! ∶恳粋€進程都擁有3GB大小的用戶空間,而連續(xù)用戶空間又按照存儲內(nèi)容的不同被劃分成若干個區(qū)域。在內(nèi)核中,主要通過mm_struct結構體和vm_area_struct結構體對進程用戶空間進行描述。前者是對進程的用戶空間進行整體的描述;而后者則是對用戶空間中的某個區(qū)域進行描述。顯然,每一個進程對應的有一個mm_struct結構和多個vm_area_struct結構?! ?.mm_struct結構  最新版本中的mm_struct結構字段比較多,接下來只對部分字段做以說明?! map:vm_area_struct結構體類型的指針。指向進程用戶空間中各區(qū)域所組成的雙鏈表。鏈表方式可以高效的遍歷所有元素;  mm_rb:rb_root結構體類型。同樣描述內(nèi)存區(qū)域塊,只不過采用紅黑樹來表示。用紅黑樹可以快速索引到指定的元素;  mm_users:atomic_t類型。用來記錄正在使用該地址空間的進程數(shù)目。比如,當前有3個進程正在共享該地址空間,那么其值為3;  mm_count:atomic_t類型。記錄mm_struct結構體被引用的次數(shù)。如果當前該地址空間只被兩個進程所共享,那么該值為1,mm_users為2;當這兩個進程都退出時,該值為0,mm_users也為0。另外,內(nèi)核線程并不需要訪問用戶的內(nèi)存空間,也并不需要創(chuàng)建頁表。內(nèi)核線程一般會直接使用前一個進程的mm_struct結構。因此該字段的計數(shù)還包括內(nèi)核線程對這個結構的引用?! ap_count:int類型。內(nèi)存區(qū)域的個數(shù);  pgd:pgd_t類型,該結構體類型內(nèi)部封裝的是unsigned long類型的數(shù)據(jù)。pgd表示的是頁目錄基址。當調(diào)度程序調(diào)度一個進程運行時,就將這個線性地址轉(zhuǎn)化為物理地址,并寫入CR3控制寄存器中;  start_code, end_code, start_data, end_data:unsigned long類型。進程代碼段和數(shù)據(jù)段的起始地址和終止地址;  start_brk, brk, start_stack:unsigned long類型。分別為堆的起始地址和終止地址,堆棧的起始地址。上文說過,進程的堆棧段是根據(jù)需求向下(朝低地址方向)延伸的,因此這里并沒有堆棧段的終止地址;  arg_start, arg_end, env_start, env_end:unsigned long類型。命令行參數(shù)所在內(nèi)存的起始地址和終止地址,環(huán)境變量所在內(nèi)存的起始地址和終止地址;  2.vm_area_struct結構  上面我們已經(jīng)知道,該結構體描述的是進程用戶空間中的一個虛擬內(nèi)存區(qū)間(Virtual Memory Area,VMA)?! m_mm:mm_struct結構體類型指針。指向該區(qū)域所屬的用戶空間對應的mm_struct結構體?! m_start,vm_end:unsigned long類型。該虛存區(qū)域的起始地址和終止地址?! m_next,vm_prev:vm_area_struct結構體類型指針。構成VMA雙聯(lián)表。  vm_flags:unsigned long類型。該虛存區(qū)的標志?! m_page_prot:pgprot_t結構體類型,內(nèi)部封裝了unsigned long類型。訪問控制權限?! m_ops:vm_operations_struct結構體類型。該虛存區(qū)域的操作函數(shù)接口,這些函數(shù)可以對虛存區(qū)中的頁進行操作。  3.數(shù)據(jù)結構的關系  了解了上述結構體的關鍵字段,它們與進程之間的邏輯關系便是我們接下來要關心的重點。我們知道,一個進程在內(nèi)核中使用task_struct結構對其進行描述。task_struct結構中有一個mm字段,它所指向的便是與該進程用戶空間所對應的mm_struct結構體。通過上述分析,我們知道m(xù)m_struct結構中有mmap字段,它指向VMA雙鏈表。因此,我們使用current->mm->mmap就可以獲得VMA鏈表的頭指針。那么current->mm->mmap->vm->next就可以獲得指向該VMA雙聯(lián)表的下一個結點的指針?! ?.動手查看內(nèi)存區(qū)域  上述我們從代碼角度分析了用戶地址空間和內(nèi)存區(qū)域。那么對于一個任意的進程,我們?nèi)绾尾榭此膬?nèi)存空間和所劃分的內(nèi)存區(qū)域?  我們先看一個簡單的測試程序:  int main(void)   {   int i=1;   char *str=NULL;   str=(char *)malloc(sizeof(char)*1119);   sleep(1000);   return 0;   }  這個程序中使用到了malloc函數(shù),因此str變量存儲于堆中。我們通過打印/proc/3530/maps文件,即可看到該進程的內(nèi)存空間劃分。其中3530是該進程的id?! ? cat /proc/3530/maps   0014a000-00165000 r-xp 00000000 08:07 398276 /lib/ld-2.11.1.so   00165000-00166000 r--p 0001a000 08:07 398276 /lib/ld-2.11.1.so   00166000-00167000 rw-p 0001b000 08:07 398276 /lib/ld-2.11.1.so   001d8000-0032b000 r-xp 00000000 08:07 421931 /lib/tls/i686/cmov/libc-2.11.1.so   0032b000-0032c000 ---p 00153000 08:07 421931 /lib/tls/i686/cmov/libc-2.11.1.so   0032c000-0032e000 r--p 00153000 08:07 421931 /lib/tls/i686/cmov/libc-2.11.1.so   0032e000-0032f000 rw-p 00155000 08:07 421931 /lib/tls/i686/cmov/libc-2.11.1.so   0032f000-00332000 rw-p 00000000 00:00 0   00441000-00442000 r-xp 00000000 00:00 0 [vdso]   08048000-08049000 r-xp 00000000 08:09 326401 /home/edsionte/test   08049000-0804a000 r--p 00000000 08:09 326401 /home/edsionte/test   0804a000-0804b000 rw-p 00001000 08:09 326401 /home/edsionte/test   08958000-08979000 rw-p 00000000 00:00 0 [heap]   b78ce000-b78cf000 rw-p 00000000 00:00 0   b78dd000-b78e0000 rw-p 00000000 00:00 0   bfa6a000-bfa7f000 rw-p 00000000 00:00 0 [stack] 每一行信息依次顯示的內(nèi)容為內(nèi)存區(qū)域其實地址-終止地址,訪問權限,偏移量,主設備號:次設備號,inode,文件?! ∩厦娴男畔⒉坏藅est可執(zhí)行對象的各內(nèi)存區(qū)域,而且還分別顯示了 /lib/ld-2.11.1.so(動態(tài)連接程序)文件和/lib/tls/i686/cmov/libc-2.11.1.so(C庫)文件的內(nèi)存區(qū)域信息。  從某個內(nèi)存區(qū)域的訪問權限上可以大致判斷該區(qū)域的類型。各個屬性符號的意義為:r-read,w-write,x-execute,s-shared,p-private。因此,r-x一般代表程序的代碼段,即可讀,可執(zhí)行。rw-可能代表數(shù)據(jù)段,BSS段和堆棧段等,即可讀,可寫。堆棧段從行信息的文件名就可以區(qū)分;如果某行信息的文件名為空,那么可能是BSS段。另外,上述test進程共享了內(nèi)核動態(tài)庫,所以在00441000-00442000行處文件名顯示為vdso(Virtual Dynamic Shared Object)。

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多