内核除了管理本身的内存外,还需要管理进程的地址空间,也就是系统中每个用户空间进程看到的内存。Linux采用虚拟内存方式进行管理,所以各个进程之间以虚拟方式共享内存。每个进程拥有一个平坦的(flat)地址空间,平坦用来描述的是地址空间范围是一个独立的连续区间,平坦描述的是地址空间范围是一个独立的连续空间。通常情况下,每个进程都有唯一的这种平坦地址空间,而且进程空间彼此互不干扰。进程空间也可以共享地址空间,这样的进程叫线程(线程对内核来说就是一个共享特定资源的进程。
进程只能访问有效范围的内的内存地址,如果一个进程访问了不在有效范围中的地址,或者以不正确的方式访问有效地址,内核就会终止该程序,返回“段错误”。
内存描述符
内核使用内存描述符结构体表示进程的地址空间(mm_struct)。
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 free_area_cache; /* first hole of size cached_hole_size or larger */
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 */
......
};
mm_users记录使用该地址的进程数目,mm_struct表示如果mm_users!=0,mm_struct=1。如果mm_struct=0,说明没有用到这个地址的进程了,它就会被释放。
mmap和mm_rb这两个不同的数据结构体描述的对象是相同的:该地址空间中的全部内存区域。
mmap是以链表形式存放的,这样利于简单高效地遍历所有元素;而mm_rb以红黑树形式存放,适合搜索指定元素。
在进程的描述符中存放着该进程使用的内存描述符,在分配内存描述符时候,fork函数复制父进程的内存描述符,这时候父子进程的描述符是一样的,而子进程通过会通过allocate_mm()宏分配得到。
如果父进程希望和子进程共享地址空间,可以调用clone(),我们把这样的进程称为线程。在进程退出时,内核会调用exit_mm()执行销毁工作。
内存区域
内存区域由vm_area_struct结构体描述,在内核中也经常被叫做虚拟内存区域或VMA。
vm_area_struct结构体描述了指定地址空间内连续区间上的一个独立内存范围。内核把每个内存区域作为一个独立的内存对象管理。
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 address
within 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, see mm.h. */
struct rb_node vm_rb;
};
每个内存描述符都对应于进程地址空间中的唯一区间。内存区域的位置就在vm_start-vm_end之中。
DMA标志
他包含在vm_flags中,标志了内存区域所包含的页面行为和信息。
查看实际使用的内存区域可以用下面命令。
$cat /proc/进程pid/maps
页表
虽然应用程序操作的对象是虚拟内存,但是处理器直接操作的是物理内存,所以当一个程序访问虚拟地址时候,首先必须将虚拟地址转化成物理地址,然后处理器才能解释地址访问请求。
地址的转换工作需要通过查页表完成。地址转换需要将虚拟地址分段,每段虚拟地址都作为一个索引指向页表,而页表则指向下一级别的页表,或者物理页。
Linux用三级页表完成地址转换,定级页表是页全局目录(PGD),PGD包含一个pgd_t数组,这个数组指向下一级目录表项(第二级目录表项):PMD。
二级页表是中间页目录(PMD),同理PMD有一个pmd_t数组,这个数组指向下一级目录表项PTE。
PTE简称页表,包含结构体pte_t,直接指向物理页面。
多数体系结构中,搜索页表的工作是由硬件完成的,但是只有在内核正确设置页表的前提下,硬件才能方便的操作。
由于几乎每次对虚拟内存中的页面访问都必须先解析页表,然后得到物理内存,这样页表操作的性能就比较关键。但是搜索物理内存的速度很有限,所以多数体系结构实现了一个缓冲器,叫翻译后缓冲器(TLB),TLB是一个将虚拟地址映射到物理地址的硬件缓存,当请求访问虚拟地址时候,先从TLB中找,如果有对应的物理地址,物理地址立即返回,没有再查页表。
阅读(1053) | 评论(0) | 转发(0) |