Linux内存管理
Linux内存管理(一)Linux进程空间管理
一、用户态和内核态的空间划分
每个进程或者线程都是一个task_struct,它们都有一个mm_struct用于管理内存
struct mm_struct *mm;
在mm_struct中有这样的一个变量
unsigned long task_size; /* size of task vm space */
前面说过进程的空间被一分为二,一部分为用户空间,一部分为内核空间,那么这个分割线在哪呢?就是这个task_size定义的
内核里有TASK_SIZE的定义
#ifdef CONFIG_X86_32
/*
* User space process size: 3GB (default).
*/
#define TASK_SIZE PAGE_OFFSET
#define TASK_SIZE_MAX TASK_SIZE
/*
config PAGE_OFFSET
hex
default 0xC0000000
depends on X86_32
*/
#else
/*
* User space process size. 47bits minus one guard page.
*/
#define TASK_SIZE_MAX ((1UL << 47) - PAGE_SIZE)
#define TASK_SIZE (test_thread_flag(TIF_ADDR32) ? \
IA32_PAGE_OFFSET : TASK_SIZE_MAX)
......
当执行一个新进程的时候,会做以下操作
current->mm->task_size = TASK_SIZE;
对于32位,高1G位内核空间,低3G位用户空间
二、用户态的布局
用户虚拟空间有几类数据,代码,全局变量,堆,内存映射区,栈。在mm_struct中,有下面这些变量统计着这些信息
unsigned long mmap_base; /* base of mmap area,内存映射区起始地址 */
unsigned long total_vm; /* Total pages mapped,总共映射页的数目 */
unsigned long locked_vm; /* Pages that have PG_mlocked set,被锁定不能换出的页数目 */
unsigned long pinned_vm; /* Refcount permanently increased,不能换出也不能移动的页数目 */
unsigned long data_vm; /* VM_WRITE & ~VM_SHARED & ~VM_STACK,存放数据的页数目 */
unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE & ~VM_STACK,存放可运行程序的页数目 */
unsigned long stack_vm; /* VM_STACK,栈所占的页数目 */
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; //参数列表和环境变量的位置
除了mm_struct之外,还有一个专门的结构体vm_area_struct,来描述这些区域的属性
struct vm_area_struct *mmap; /* list of VMAs,链表 */
struct rb_root mm_rb; /* 红黑树树根 */
struct vm_area_struct {
/* The first cache line has the info for VMA tree walking. */
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, *vm_prev;
struct rb_node vm_rb;
struct mm_struct *vm_mm; /* The address space we belong to. */
struct list_head anon_vma_chain; /* Serialized by mmap_sem &
* page_table_lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */
/* Function pointers to deal with this struct. */
const struct vm_operations_struct *vm_ops;
struct file * vm_file; /* File we map to (can be NULL). */
void * vm_private_data; /* was vm_pte (shared mem) */
} __randomize_layout;
vm_start和vm_end指定了区域在用户空间的起始地址和结束地址。vm_next和vm_prev维护一个链表
vm_rb是红黑树节点,用于放置到红黑树中,vm_ops是这个区域的操作
vm_area_struct是如何一上面的区域关联起来的呢?
这个事情是在load_elf_binary中做的,加载内核是它,启动第一个用户态进程init是它,fork完调用exec执行一个程序也是它
当执行exec时,除了解析elf格式之外,还有一件重要的事情就是建立内存映射
static int load_elf_binary(struct linux_binprm *bprm)
{
......
setup_new_exec(bprm);
......
retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
executable_stack);
......
error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
elf_prot, elf_flags, total_size);
......
retval = set_brk(elf_bss, elf_brk, bss_prot);
......
elf_entry = load_elf_interp(&loc->interp_elf_ex,
interpreter,
&interp_map_addr,
load_bias, interp_elf_phdata);
......
current->mm->end_code = end_code;
current->mm->start_code = start_code;
current->mm->start_data = start_data;
current->mm->end_data = end_data;
current->mm->start_stack = bprm->p;
......
}
load_elf_binary会做下面的事
- 调用setup_new_exec设置内存映射区mmap_base
- 调用setup_arg_pages设置栈的vm_area_struct,这里设置mm->arg_start指向栈底
- elf_map会将elf文件中代码段映射到内存中
- set_brk设置堆的vm_area_struct,这里设置mm->start_brk=mm->brk,即堆位空
- load_elf_interp将依赖的so文件映射到内存中来
最终会形成下面形式
映射完后,什么情况会修改这些信息?
第一种情况是函数调用,涉及函数栈的改变,主要修改栈顶指针
第二种是调用malloc分配内存,底层要么调用brk,要么调用mmap,关于mmap后面再讲,这里先讨论brk
SYSCALL_DEFINE1(brk, unsigned long, brk)
{
unsigned long retval;
unsigned long newbrk, oldbrk;
struct mm_struct *mm = current->mm;
struct vm_area_struct *next;
......
/* 页对齐 */
newbrk = PAGE_ALIGN(brk);
oldbrk = PAGE_ALIGN(mm->brk);
/* 如果两个内存刚好在同一页内 */
if (oldbrk == newbrk)
goto set_brk;
/* Always allow shrinking brk. */
/* 如果新堆顶小于旧的堆顶,说明要释放内存 */
if (brk <= mm->brk) {
if (!do_munmap(mm, newbrk, oldbrk-newbrk, &uf))
goto set_brk;
goto out;
}
/* Check against existing mmap mappings. */
/* 在红黑树中找到当前当前堆顶处下一个vm_area_struct */
next = find_vma(mm, oldbrk);
/* 如果空闲区域不大于1页,那么就退出 */
if (next && newbrk + PAGE_SIZE > vm_start_gap(next))
goto out;
/* Ok, looks good - let it rip.否咋调用do_brk进行内存分配 */
if (do_brk(oldbrk, newbrk-oldbrk, &uf) < 0)
goto out;
/* 设置新的堆顶 */
set_brk:
mm->brk = brk;
......
return brk;
out:
retval = mm->brk;
return retval
static int do_brk(unsigned long addr, unsigned long len, struct list_head *uf)
{
return do_brk_flags(addr, len, 0, uf);
}
static int do_brk_flags(unsigned long addr, unsigned long request, unsigned long flags, struct list_head *uf)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma, *prev;
unsigned long len;
struct rb_node **rb_link, *rb_parent;
pgoff_t pgoff = addr >> PAGE_SHIFT;
int error;
len = PAGE_ALIGN(request);
......
find_vma_links(mm, addr, addr + len, &prev, &rb_link,
&rb_parent);
......
vma = vma_merge(mm, prev, addr, addr + len, flags,
NULL, NULL, pgoff, NULL, NULL_VM_UFFD_CTX);
if (vma)
goto out;
......
vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
INIT_LIST_HEAD(&vma->anon_vma_chain);
vma->vm_mm = mm;
vma->vm_start = addr;
vma->vm_end = addr + len;
vma->vm_pgoff = pgoff;
vma->vm_flags = flags;
vma->vm_page_prot = vm_get_page_prot(flags);
vma_link(mm, vma, prev, rb_link, rb_parent);
out:
perf_event_mmap(vma);
mm->total_vm += len >> PAGE_SHIFT;
mm->data_vm += len >> PAGE_SHIFT;
if (flags & VM_LOCKED)
mm->locked_vm += (len >> PAGE_SHIFT);
vma->vm_flags |= VM_SOFTDIRTY;
return 0;
find_vma_links找到将来的vm_area_struct节点在红黑树的位置,找到它的父节点、前序节点。接下来就是调用vma_merge,看新节点是否能够和现有树中的节点合并。如果地址是连着的,能够合并,不需要创建新的vm_area_struct。否则创建新的vm_area_struct,添加到链表中,也加到红黑树中
三、内核态的布局
32位的内核态的布局
32位的内核空间就1G,占据绝大部分的896G,称为直接映射区
所谓直接映射区,就是和物理内存的映射关系非常简单,它直接映射物理的内存的前896M,就是内存地址减去3G,得到物理地址
内核中有两个宏
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
#define __pa(x) __phys_addr((unsigned long)(x))
#define __phys_addr(x) __phys_addr_nodebug(x)
#define __phys_addr_nodebug(x) ((x) - PAGE_OFFSET)
即使这里映射非常简单,但是依然需要建立页表
在涉及内核栈的分配,内核的进程管理会讲内核栈放到3G-3G+896M地址中,当然存放在物理内存的前896M中,相应的页表也会被创建
896M这个被定义为高端内存,高端内存是物理内存的概念,是内核管理模块看待物理内存的概念
内核空间剩余的空间可以分为以下几类
- VMALLOC_START和VMALLOC_END之间称为内核的动态映射区,内核可以也可以通过vmlloac申请内存,这块区域可以映射物理内存中任何的位置
- PKMAP_BASE到FIXADDR_START的空间称为持久内核映射。使用alloc_pages()在物理内存的高端内存分配物理页,可以使用kmap将其映射到此区域
- FIXADDR_START到FIXADDR_TOP的空间称为固定映射区,主要用于满足特殊需求
64位的地址分布如下
下面两幅图总结
转载:https://blog.csdn.net/weixin_42462202/article/details/102511182