文章目錄
- 1 地址分布
- 實際使用中的內存區域
- 2 進程的虛擬地址描述
- 用戶空間mmap
- 線程之間共享內存地址的實現機制
1 地址分布
現在采用虛擬內存的操作系統通常都使用平坦地址空間,平坦地址空間是指地址空間范圍是一個獨立的連續空間(比如,地址從0擴展到429496729位地址空間),對于32位的操作系統而言,每個進程的虛擬地址空間都是0x00000000~0xC0000000,合計3G大小。
進程的3G虛擬地址空間只有映射為物理地址空間,才能夠被使用,那么進程是如何管理和分配它的3G虛擬地址空間呢?
這就用到了分治思想,進程虛擬地址空間按照不同的訪問屬性和功能劃分為不同的內存區域,我們也叫虛擬內存區域(VMA)。
內存區域可以包含各種內存對象,比如:
- 代碼段(text section):可執行文件的內存映射
- 數據段:可執行文件的已初始化全局變量和靜態局部變量的內存映射
- bss段:未初始化的或者值為0的變量的內存映射
- lib庫的代碼段:(多個)
- lin庫的數據段:(多個)
- lib庫的bss段:(多個)
- 任何內存映射文件(有名mmap建立)
- 任何共享內存段(匿名mmap建立)
- 進程棧(stack)
- 進程堆(heap)
實際使用中的內存區域
可以使用/proc文件系統和pmap工具查看給定進程的內存空間和其中所包含的內存區域。
#include <stdio.h>
#include <unistd.h>int main(void)
{printf("PID=%d\n",getpid());while(1){sleep(2);}return 0;
}
運行該程序,輸入命令 cat /proc/<pid>/maps查看進程地址空間中的全部內存區域(我的機子是64位,所以使用的是64位虛擬地址空間)
進程的內存區域由vm_area_struct結構體描述,定義在文件linux/mm.h中
struct vm_area_struct {struct mm_struct * vm_mm; /* The address space we belong to. */unsigned long vm_start; /* Our start address within vm_mm. */unsigned long vm_end; /* The first byte after our end addresswithin vm_mm. *//* linked list of VM areas per task, sorted by address */struct vm_area_struct *vm_next;pgprot_t vm_page_prot; /* Access permissions of this VMA. */unsigned long vm_flags; /* Flags, listed below. */struct rb_node vm_rb;/** For areas with an address space and backing store,* linkage into the address_space->i_mmap prio tree, or* linkage to the list of like vmas hanging off its node, or* linkage of vma in the address_space->i_mmap_nonlinear list.*/union {struct {struct list_head list;void *parent; /* aligns with prio_tree_node parent */struct vm_area_struct *head;} vm_set;struct prio_tree_node prio_tree_node;} 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_node; /* Serialized by anon_vma->lock */struct anon_vma *anon_vma; /* Serialized by page_table_lock *//* Function pointers to deal with this struct. */struct vm_operations_struct * vm_ops;/* Information about our backing store: */unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZEunits, *not* PAGE_CACHE_SIZE */struct file * vm_file; /* File we map to (can be NULL). */void * vm_private_data; /* was vm_pte (shared mem) */#ifdef CONFIG_NUMAstruct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
};
上面的圖片中,輸出由六列,每一列都是vm_area_struct的一項
內核vm_area_struct中的項 | /proc/pid/maps中的項及其含義 |
---|---|
vm_start | 第一列’-'前的數字,如55c4b4d68000 ,表示該虛擬內存區域的開始地址 |
vm_end | 第一列’-'后的數字 ,如55c4b4d69000 ,表示該虛擬內存區域的結束地址 |
vm_flags | 第二列,如r-xp,表示該虛擬內存區域的屬性,每種屬性用一個字段表示,r表示可讀,w表示可寫,x表示可執行,p和s共用一個字段,p表示私有段,s表示共享段,如果沒有相應權限,用’-'代替 |
vm_pgoff | 第三列,如00001000,含義:對用有名映射,表示此虛擬內存起始地址在文件中以頁為單位的編譯,對匿名映射,它等于0或者vm_start/PAGE_SIZE |
vm_file->f_dentry->d_inode->i_sb->s_dev | 第四列,如08:01,表示映射文件所屬設備號,對匿名映射來說,因為沒有文件在磁盤上,所有沒有設備號,始終為00:00,對有名映射來說,是映射的文件所在設備的設備號 |
vm_file->f_dentry->d_inode->i_ino | 第五列,如1724853,含義:映射文件所屬節點號,對匿名文件來說,因為沒有節點號,所以時鐘是0,對有名映射來說,是映射文件的結點號 |
第六列,如/lib/x86_64-linux-gnu/libc-2.27.so,對有名映射來說,是映射的文件名,對匿名映射來說,是此段虛擬內存在進程中的角色,stack表示在進程中作為棧使用,heap表示堆 |
2 進程的虛擬地址描述
內核使用mm_struct來描述一個進程的地址空間,進程的地址空間由多個VMA組成,下面列舉幾個mm_struct管理內存的幾個重要域:
struct mm_struct {.../* 指向虛擬內存區域的鏈表 */struct vm_area_struct * mmap; /* list of VMAs *//* 指向最近找到的虛擬內存區域 */struct vm_area_struct * mmap_cache; /* last find_vma result *//* 指向該進程的頁目錄表 */pgd_t * pgd;...
};
VMA用struct vm_area_struct描述,內核將每個內存區域作為一個單獨的內存對象管理,每個內存區域都有一致的屬性,比如權限等。所以我們程序的代碼段、數據段和bss段在內核里都分別有一個struct vm_area_struct結構體來描述。
進程由結構體task_struct描述,task_struct里面的mm域用來管理進程的內存,它指向mm_struct結構體,mm_struct的mmap域指向VMA鏈表,用來管理進程虛擬內存,虛擬內存地址又通過頁表轉換為物理地址,怎么轉換的,由mm_struct的pgd頁目錄表來轉換,從頁目錄表中找到物理地址。
用戶空間mmap
#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);int munmap(void *addr, size_t length);
在用戶空間使用mmap就是給進程添加一個虛擬內存區域,即在VMA鏈表中添加一個vm_area_struct結構
線程之間共享內存地址的實現機制
在Linux中,如果clone()時設備CLONE_VM標志,我們把這樣的進程稱作為線程,線程之間共享同樣的虛擬內存空間。即將父進程的mm域復制給子進程。