Linux 内存管理浅析

来源:互联网 发布:如何查看淘宝宝贝类目 编辑:程序博客网 时间:2024/06/11 13:02

(4). 页表管理

下面我们来看下页表的建立过程。前面我说过,进程分为用户进程和内核进程。用户进程可以有多个,而内核进程则只有一个。因此,对于内核进程页表,我们只有一份。下面我以e6500平台为例说明内核进程页表和用户进程页表是怎么建立和管理的。

我们先来思考下,在什么时候,我们才需要真正的创建一个页表呢。前面我说过,页表是按需创建的,就是说在物理页面分配之后,我们才会创建相应的页表项。这里说下页表项和页表的区别。我这里说的页表是指PGD表,PUD表,PMD表,PTE表,在上一节,我画了一张图表示它们。它们包含了各自的页表项,如PGD,PUD,PMD和PTE。在创建相应页表项时,如果所在的页表还没有分配内存,那么需要调用页表alloc函数进行内存分配,然后再进行页表项的设置。那么页表的建立第一步就是需要分配各级页表内存存储空间。

因为PGD表对于每个进程来说都是必须的,所以PGD表的存储空间是在每个进程创建的时候就分配好的,虽然这个时候还不需要对PGD进行设置。

  • 内核进程PGD表的创建

    内核进程,就是init 0进程,是内核执行任务的空间,它有自己的进程存储空间,它负责处理像进程调度、内存管理、文件系统、异常处理、外设管理这样的计算机自身的问题。linux内核启动时,内核代码会对系统进行一些初始化的工作,在这之后,内核会建立内核进程的运行环境。我们说一个进程,对于系统来说,就是一个抽象实体,我们会用一些数据结构表示的数据来记录这样一个实体的行为。这个实体有自己的代码(行为),有自己的存储空间(虚拟地址),有自己的CPU等资源(通过分时复用)。那么我们为了表示这样一个实体,记录下这个实体的行为,我们需要一些结构体来存储数据。

    对于每个进程来说,我们都有一个结构体实体:struct task_struct。它记录了进程的行为,状态,存储空间,资源使用等。在这个结构体里,有个成员:struct mm_struct mm,它记录了进程的存储空间,PGD表就是这个结构体的成员:pgd_t pgd(inlcude\linux\mm_tyeps.h)。

    而对于内核进程来说,我们需要在内核启动时就能够知道内核进程的一些信息,所以内核进程所需要的这些数据结构实体,不能够动态分配内存存储,它需要静态的保存在内核的数据段里(.data..init_task)。

    init\init_task.c/* Initial task structure */struct task_struct init_task = INIT_TASK(init_task);

    同时定义了init_mm(mm\init-mm.c):

    struct mm_struct init_mm = {.mm_rb      = RB_ROOT,.pgd        = swapper_pg_dir,.mm_users   = ATOMIC_INIT(2),.mm_count   = ATOMIC_INIT(1),.mmap_sem   = __RWSEM_INITIALIZER(init_mm.mmap_sem),.page_table_lock =  __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),.mmlist     = LIST_HEAD_INIT(init_mm.mmlist),INIT_MM_CONTEXT(init_mm)};

    上面可看到,内核的PGD表是swapper_pg_dir。它是定义在内核的bss段的。

    arch\powerpc\kernel\head64.s/** We put a few things here that have to be page-aligned.* This stuff goes at the beginning of the bss, which is page-aligned.*/.section ".bss".align  PAGE_SHIFT.globl  empty_zero_pageempty_zero_page:.space  PAGE_SIZE.globl  swapper_pg_dirswapper_pg_dir:.space  PGD_TABLE_SIZE
  • 用户进程PGD表的创建

    用户进程PGD表是动态创建的,是在进程创建的时候通过动态分配内存来保存的。

    kernel\fork.cstatic inline int mm_alloc_pgd(struct mm_struct *mm){  mm->pgd = pgd_alloc(mm);  if (unlikely(!mm->pgd))      return -ENOMEM;  return 0;}

PGD页表的建立,在进程创建时,我们就已经分配好了存储空间。但其他几级页表,我们都是在需要的时候才创建的。这里说的需要的时候,就是指我们需要为某个物理页面建立页面映射的时候,如果没有相应的页表,需要一级一级的创建PUD,PMD和PTE表。由前面一节图我们可看出,我们只是创建了部分页表。当然,如果相应页表已经创建好了,我们只需要设置PTE就可以了。

页表的创建和页面映射的建立(设置PTE)是一个整体。下面是它们的一般流程:


这里写图片描述


  • alloc_page
    通过内核伙伴系统分配实际的物理页面。

  • mk_pte,set_pte_at
    这两个函数负责构建PTE页表项,具体和平台相关,前面小节已经介绍过。

上图同时给出了内核进入页表映射流程的一般代码路径。

对于用户进程来说,当其申请内存地址时,我们一般只是为其分配对应的虚拟地址,并没有分配实际的内存资源,因此相应的页表映射是不需要的。这样做的理由是,对于内存资源申请者来说,它申请内存资源为了存储数据,但申请之后并不总是立刻使用的,这样我们就可以在它真正需要用到内存资源的时候,再为其分配。在申请的时候,我们只需要记录下虚拟地址分配情况就可以了。这部分内容,我们会在用户进程地址空间管理部分看到。在用户进程需要实际访问这部分虚拟地址时,因为没有对应的物理地址页面,将会发生我们称之为缺页异常的过程。缺页异常发生在查找不到页表项的时候,页表查找过程,我们前面介绍过。所以,用户进程的页表映射基本上是在页面异常模块里处理的。

内核同时还为用户进程提供了mmap机制,使得用户进程可以手动映射到一段物理地址空间。因为物理地址是由用户进程提供的,所以上面页面映射流程,alloc_page是不需要的。

对于内核进程来说,vmalloc分配的的虚拟地址,是立刻就需要为其分配物理内存页面的,页面映射也会同时建立。ioremap是内核提供给驱动开发者手动映射到一段物理地址的工具。

原创粉丝点击