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 dumpable: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 几乎用处不大。


在内存分段系统中,一个程序的逻辑地址通过分段机制自动地映射(变换)到中间层的4GB(232B)线性地址空间中。程序每次对内存的引用都是对内存段中内存的引用。当程序引用一个内存地址时,通过把相应的段基址加到程序员看得见的逻辑地址上就形成了一个对应的线性地址。此时若没有启用分页机制,则该线性地址就被送到CPU的外部地址总线上,用于直接寻址对应的物理内存。如图5-6所示。

Linux内存管理_第1张图片 
图5-6  虚拟地址(逻辑地址)到物理地址的变换过程

CPU进行地址变换(映射)的主要目的是为了解决虚拟内存空间到物理内存空间的映射问题。虚拟内存空间的含义是指一种利用二级或外部存储空间,使程序能不受实际物理内存量限制而使用内存的一种方法。通常虚拟内存空间要比实际物理内存量大得多。

     标准Linux内核对于物理内存的管理采用1:3的分配比例,即物理内存的1/4为内核空间(kernel space),剩下的3/4为用户进程空间(user space),因此,在一台4G内存的服务器上,用户进程可使用的内存最大也就是3G。当进程被内核调入CPU运行时,相应的PAE寄存器也会被调入,即使它和别的进程都访问0x08344的地址,但由于PAE寄存器不同,不同的地址空间数据会被调入4G以内的标准用户进程空间(实际上也就是3G),这被称为PAE内存映射技术。

       每个进程都拥有4G(2的32次方)的虚拟地址空间。在实际编程过程中,指针中存放的地址也都是32位的线性地址(虚拟地址),经过页目录、页表等分页机制变换以后可以得到真正的物理地址,而这个物理地址也是32位的。对于32位的CPU来说没有任何问题,因为它的地址总线是32位的,寻址空间也就是2的32次方(4G)。那么问题就出来了:CPU的寻址空间是2的32次方(4G),程序要访问的物理地址(线性地址经过分页机制变换以后得到地址)也是32位的,这种情况下的内存应该至少是4G才对,而平时我们自己用的计算机才一两个G(比如说就1个G),那么这时候的内存是怎么编址的呢(1G内存只要30根地址线就够了,相应的的物理地址位数只要30位就行了,用不了32位啊)?          

       这是csdn网友的一个提问,的确1G的物理内存虚拟出4G的虚拟内存,进程在使用内存时额外的3G空间是哪来的呀?这时内存分页机制就发挥它的功效了。我们知道在内核空间kmalloc函数和__get_free_page函数使用的地址范围(虚拟地址)和物理内存的地址范围是一一对应的。两者只是相差一个偏移(offset)。这是一种线性地址。若我们用vmalloc函数则不然,它所分配的物理内存在地址上是不连续,对于这些不连续地址的管理linux hacker通过创建struct page数据结构来实现。也就是我们所说的页表。

       当需要使用不在物理内存中的页面时,会产生请求调页,然后操作系统负责内存页的换入换出。当线性地址转换到物理地址,而物理地址不足4G(比如1G),操作系统可以给你分配内存。你在线性地址空间得到了需要的内存,而物理内存上通过换页机制也可以满足。(假如你想使用3G开始的1页地址空间,操作系统会把任意空闲的1页内存给你使用)若真正意义上的内存只有1G。4G到1G,必须得借用硬盘空间。而一个线性地址对应一个物理地址。一个物理地址可能对应好几个线性地址。但是,使用中的那个会被放在内存,不使用的放在硬盘。由于程序的所谓“局部性”原理,一般这样做不会有问题。当同时使用内存需求大于1G时,机器就会“很卡”,因为操作系统不断地在换入换出内存页。


Linux内存管理_第2张图片

 利用两级页表转换地址

Linux内存管理_第3张图片

 虚存数据结构的关系



你可能感兴趣的:(数据结构,linux,struct,Semaphore,table,linux内核)