task_struct->mm_struct->mmap:一个进程虚拟空间各个虚拟区的vm_area_struct链表。
task_struct->mm_struct->pgd: 一个进程的页表目录。
1、甚么是mm_struct???
内存描写符也用1个结构体表示,这个结构体的名字叫做mm_struct(内存描写符),linux就是通过mm_struct这个结构体来实现内存管理。 1个进程的虚拟地址空间主要由两个数据结构来描写,1个是最高层次的mm_struct,1个是较高层次的vm_ares_struct。最高层次的mm_struct结构描写了1个进程的全部虚拟地址空间。较高层次的结构vm_area_struct描写了虚拟地址空间的1个区间(简称虚拟区)。每一个进程只有1个mm_struct结构,在每一个进程的task_struct结构体中,有1个指向该进程的结构。可以说,mm_struct结构是对全部用户空间的描写。
在进程的task_struct结构体中包括1个指向mm_struct结构的指针,mm_struct用来描写1个进程的虚拟地址空间。进程的mm_struct则包括装入的可履行映像信息和进程的页目录指针pgd。该结构还包括有指向vm_area_struct结构的几个指针,每一个vm_area_struct代表进程的1个虚拟地址区间。vm_area_struct结构含有指向vm_operations_struct结构的1个指针,vm_operations_struct描写了在这个区间的操作。vm_operations_struct结构中包括的是函数指针,其中open、close分别用于虚拟区间的打开、关闭,而nopage用于当虚拟页面不再物理内存而引发的”缺页异常”时所调用的函数,当linux处理这1缺页异常时,就能够为新的虚拟内存分配实际的物理内存。
2、mm_struct
每一个进程都只有1个内存描写符mm_struct。在每一个进程的task_struct结构中,有1个指向mm_struct的变量,这个变量常常是mm。
mm_struct是对进程的地址空间(虚拟内存)的描写。1个进程的虚拟空间中可能有多个虚拟区间,对这些虚拟空间的组织方式有两种,当虚拟区较少时采取单链表,由mmap指针指向这个链表,当虚拟区间多时采取红黑树进行管理,由mm_rb指向这棵树。由于程序中用到的地址常常具有局部性,因此,最近1次用到的虚拟区间极可能下1次还要用到,因此把最近用到的虚拟区间结构放到高速缓存,这个虚拟区间就由mmap_cache指向。
指针pgt指向该进程的页目录(每一个进程都有自己的页目录),当调度程序调度1个程序运行时,就将这个地址转换成物理地址,并写入控制寄存器。
由于进程的虚拟空间及下属的虚拟区间有可能在不同的上下文中遭到访问,而这些访问又必须互斥,所以在该结构中设置了用于P,V操作的信号量mmap_sem。另外,page_table_lock也是为类似的目的而设置。
虽然每一个进程只有1个虚拟空间,但是这个虚拟空间可以被别的进程来同享。如:子进程同享父进程的地址空间,而mm_user和mm_count就对其计数。
另外,还描写了代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。
struct mm_struct
{
struct vm_area_struct *mmap; //指向虚拟区间(VMA)链表
struct rb_root mm_rb; //指向red_black树
struct vm_area_struct *mmap_cache; //找到最近的虚拟区间
unsigned long(*get_unmapped_area)(struct file *filp,unsigned long addr,unsigned long len,unsigned long pgoof,unsigned long flags);
void (*unmap_area)(struct mm_struct *mm,unsigned long addr);
unsigned long mmap_base;
unsigned long task_size; //具有该结构体的进程的虚拟地址空间的大小
unsigned long cached_hole_size;
unsigned long free_area_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; //所有活动mm的链表
mm_counter_t _file_rss;
mm_counter_t _anon_rss;
unsigned long hiwter_rss;
unsigned long hiwater_vm;
unsigned long total_vm,locked_vm,shared_vm,exec_vm;
usingned long stack_vm,reserved_vm,def_flags,nr_ptes;
unsingned long start_code,end_code,start_data,end_data; //代码段的开始start_code ,结束end_code,数据段的开始start_data,结束end_data
unsigned long start_brk,brk,start_stack; //start_brk和brk记录有关堆的信息,start_brk是用户虚拟地址空间初始化,brk是当前堆的结束地址,start_stack是栈的起始地址
unsigned long arg_start,arg_end,env_start,env_end; //参数段的开始arg_start,结束arg_end,环境段的开始env_start,结束env_end
unsigned long saved_auxv[AT_VECTOR_SIZE];
struct linux_binfmt *binfmt;
cpumask_t cpu_vm_mask;
mm_counter_t context;
unsigned int faultstamp;
unsigned int token_priority;
unsigned int last_interval;
unsigned long flags;
struct core_state *core_state;
}
3、vm_area_struct
虚拟地址区间vm_area_struct是虚拟内存的1部份,内存描写符mm_struct指向全部虚拟空间,而vm_area_struct只是指向了虚拟空间的1段。较高层次的结构vm_area_struct是由双向链表链接起来的,它们是依照虚拟地址降序排序的,每一个这样的结构都对应描写1个相邻的地址空间范围。之所以这样分隔是由于每一个虚拟区间可能来源不同,有的可能来自可履行映像,有的可能来自同享库,而有的多是动态内存分配的内存区,所以对每一个由vm_area_struct结构所描写的区间的处理操作和它前后范围的处理操作不同,因此linux把虚拟内存分割管理,并利用了虚拟内存处理例程vm_ops来抽象对不同来源虚拟内存的处理方法。不同的虚拟区间其处理操作可能不同,linux在这里利用了面向对象的思想,即把1个虚拟区间看成是1个对象,用vm_area_struct描写这个对象的属性,其中的vm_operation结构描写了在这个对象上的操作。
struct vm_area_struct
{
struct mm_struct *vm_mm; //虚拟地址区间所在的地址空间
unsigned long vm_start; //在vm_mm中的起始地址
unsigned long vm_end; //在vm_mm中的结束地址
struct vm_area_struct *vm_next; //指向下1个虚拟区间
pgprot_t vm_page_prot; //虚拟区间的存取极限
unsigned long vm_flags; //描写对虚拟区间进行操作的标志
struct rb_node vm_rb;
union
{
struct
{
struct list_head list;
void *parent;
struct vm_area_struct *head;
} vm_set;
struct raw_prio_tree_node prio_tree_node;
} shared;
struct list_head anon_vma_node;
struct anon_vma *anon_vma;
const struct vm_operations_struct *vm_ops; //对这个区间进行操作的函数
unsigned long vm_pgoff;
struct file* vm_file;
void *vm_private_data;
unsigned long vm_truncate_count;
}
4、vm_operations_struct
vm_operations_struct结构中包好的是函数指针,其中open、close分别用于虚拟内存的打开、关闭,而nopage用于当虚拟内存页面没有实际的物理内存映照而引发的”缺页异常”时所调用的函数指针。
struct vm_operations_struct
{
void (*open)(struct vm_area_strucr *area);
void (*close)(struct vm_area_struct *area);
struct page *(nopage)(struct vm_area_struct *area,unsigned long address,int unused);
}
5、linux虚拟内存管理数据结构图(本来想自己画的,结果发现这个图真的不错,所以直接粘过来):
内核页表和进程页表
内核页表和进程页表-humjb_1983-ChinaUnix博客
https://segmentfault.com/a/1190000012269249
Linux内存管理--基本概念_MyArrow的专栏-CSDN博客
Linux内存组织结构和页面布局-电子发烧友网
当物理内存出现不足时,Linux内存管理子系统需要释放部分物理内存页。这一任务由内核的交换守护进程kswapd完成,该内核守护进程实际是一 个内核线程,它在内核初始化时启动,并周期性地运行。它的任务就是保证系统中具有足够的空闲页,从而使内存管理子系统能够有效运行。
原文地址:https://www.cnblogs.com/alantu2018/p/8446887.html
深入理解Linux内存分配 - AlanTu - 博客园
Linux分页机制之分页机制的实现详解--Linux内存管理(八)_OSKernelLAB(gatieme)-CSDN博客_linux pte
-----------------------------------------------------------------------------------------------------------------------------------------
以下讨论仅适用于32位ARM Linux
在alloc PGD(Page Global Directory)的时候,mm->pgd的值应该是虚拟地址:
static inline int mm_alloc_pgd(struct mm_struct *mm)
{
mm->pgd = pgd_alloc(mm);
if (unlikely(!mm->pgd))
return -ENOMEM;
return 0;
}
而在context switch的时候,这个pgd的值又被放到了TTBR里面:
cpu_switch_mm(mm->pgd, mm);
--> mcr p15, 0, r0, c2, c0, 0 @ set TTB 0
但是在ARM手册里面又写到:
The Translation Table Base Register (TTBR in CP15 register 2) holds the physical
address of the base of the first-level table.
这个是没有问题的。
mm->pgd = pgd_alloc(mm);
mm->pgd 的确是虚拟地址,但pgd表里存放的pte表地址,是物理地址。
然后进程调进来的时,将mm->pgd的物理地址写到寄存器上即可。
可以看看代码(下面是2.6.34内核的代码,有点老,但原理是一样的):
static inline void switch_new_context(struct mm_struct *mm)
{
unsigned long flags;
__new_context(mm);
local_irq_save(flags);
cpu_switch_mm(mm->pgd, mm);
local_irq_restore(flags);
}
#define cpu_switch_mm(pgd,mm) cpu_do_switch_mm(virt_to_phys(pgd),mm) // 注意virt_to_phys
最终CPU根据寄存器找到页表时,以及二级页表,都是根据物理地址来找VA->PA的映射。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Linux内存的工作(malloc,brk系统调用和mmap系统调用)_lwj的博客-CSDN博客_brk系统调用和mmap系统调用
malloc 的实现原理 内存池 mmap sbrk 链表 - 知乎