《Linux内核的设计与实现》第十五章笔记

第12章介绍了内核如何管理物理内存。其实内核除了管理本身的内存外,还必须管理用户空间中进程的内存。我们称这个内存为进程地址空间,也就是系统中每个用户空间进程所看到的内存。本章将集中讨论内核如何管理进程地址空间。

15.1 地址空间

进程地址空间由进程可寻址的虚拟内存组成,而且更为重要的特点是内核允许进程使用这种虚拟内存中的地址。每个进程都有一个32位或64位的平坦(fat)地址空间,空间的具体大小取决于体系结构。术语“平坦”指的是地址空间范围是一个独立的连续区间(比如,地址从0扩展到4294967295的32位地址空间)。现代采用虚拟内存的操作系统通常都使用平坦地址空间而不是分段式的内存模式。通常情况下,每个进程都有唯一的这种平坦地址空间。

虽然地址空间的范围很大,但是进程也不一定有权限访问全部的地址空间(一般都是只能访问地址空间中的一些地址区间),进程能够访问的那些地址区间也称为内存区域。进程如果访问了有效内存区域以外的内容就会报 “段错误” 信息。

以下列出的都可以作为独立的内存区域:

  • 可执行文件代码的内存映射,称为代码段(text section)
  • 可执行文件的已初始化全局变量的内存映射,称为数据段(data section)
  • 包含未初始化全局变量,也就是bss段的零页(页面中的信息全部为0值,所以可用于映射bss段等目的)的内存映射
  • 用于进程用户空间栈(不要和进程内核栈混淆,进程的内核栈独立存在并由内核维护)的零页的内存映射
  • 每一个诸如C库或动态连接程序等共享库的代码段、数据段和bss也会被载入进程的地址空间
  • 任何内存映射文件
  • 任何共享内存段
  • 任何匿名的内存映射,比如由malloc() 分配的内存

注意进程地址空间与内存区域的关系:进程地址空间中有多个内存区域

15.2 内存描述符

内核使用内存描述符结构体表示进程的地址空间,该结构包含了和进程地址空间有关的全部信息。内存描述符由mm-struct结构体表示,定义在文件<linux/sched.h>中。
在这里插入图片描述
在这里插入图片描述

所有的mm_struct结构体都通过自身的mm_list域连接在一个双向链表中,该链表的首元素是init_mm内存描述符,它代表init进程的地址空间。

15.2.1 分配内存描述符

在进程的进程描述符(在<linux/sched.h>中定义的task_struct结构体就表示进程描述符)中,mm域存放着该进程使用的内存描述符,所以current-> mm便指向当前进程的内存描述符。fork() 函数利用copy_mm() 函数复制父进程的内存描述符,也就是current_mm域给其子进程,而子进程中的mm_struct结构体实际是通过文件kernel/fork.c中的allocate_mm() 宏从mm_cachep slab缓存中分配得到的。通常,每个进程都有唯一的mm_struct结构体,即唯一的进程地址空间。

15.2.2 撤销内存描述符

当进程退出时,内核会调用定义在kerne/exit.c中的exit_mm() 函数,该函数执行一些常规的撤销工作,同时更新一些统计量。其中,该函数会调用mm_put()函数减少内存描述符中的mm_users用户计数,如果用户计数降到零,将调用mm_drop() 函数,减少mm_count使用计数。如果使用计数也等于零了,说明该内存描述符不再有任何使用者了,那么调用free_mm() 宏通过kmem_cache_free() 函数将mm_struct结构体归还到mm_cachep slab缓存中。

15.2.3 mm_struct 与内核线程

为何内核线程会使用用户空间的 mm_struct?

对Linux来说,用户进程和内核线程都是task_struct的实例,唯一的区别是内核线程是没有进程地址空间的(内核线程使用的内核地址空间),内核线程的mm描述符是NULL,即内核线程的task->mm域是空(NULL)。

内核调度程序在进程上下文的时候,会根据task->mm判断即将调度的进程是用户进程还是内核线程。但是虽然内核线程不用访问用户进程地址空间,但是仍然需要页表来访问内核自己的空间。

而任何用户进程来说,他们的内核空间都是100%相同的,所以内核会借用上一个被调用的用户进程的mm_struct中的页表来访问内核地址,这个mm_struct就记录在active_mm。

简而言之就是,对于内核线程,task->mm == NULL表示自己内核线程的身份,而task->active_mm是借用上一个用户进程的mm_struct,用mm_struct的页表来访问内核空间。

对于用户进程,task->mm == task->active_mm。

15.3 虚拟内存区域

内存区域在Linux中也被称为虚拟内存区域(VMA),它其实就是进程地址空间上一段连续的内存范围。内存区域由vm_area_struct结构体描述,定义在文件<linux/mm_types.h>中。

每个内存区域都对应于进程地址空间中的唯一区间。vm_start域指向区间的首地址(最低地址),vm_end域指向区间的尾地址(最高地址)之后的第一个字节,也就是说,vm_start是内存区间的开始地址(它本身在区间内),而vm_end是内存区间的结束地址(它本身在区间外)。

vm_mm域指向和VMA相关的mm_struct结构体,注意,每个VMA对其相关的mm_struct结构体来说都是唯一的,所以即使两个独立的进程将同一个文件映射到各自的地址空间,它们分别都会有一个vm_area_struct结构体来标志自己的内存区域;反过来,如果两个线程共享一个地址空间,那么它们也同时共享其中的所有vm_area_struct结构体。

mmap和mm_rb这两个不同数据结构体描述的对象是相同的:进程地址空间中的全部内存区城。但是前者以链表形式存放而后者以红一黑树的形式存放。

内核通常会避免使用两种数据结构组织同一种数据,但此处内核这样的冗余确实派得上用场。mmap结构体作为链表,利于简单、高效地遍历所有元素;而mm_tb结构体作为红一黑树,更适合搜索指定元素。

可以使用可以使用/proc文件系统和pmap(1)工具查看给定进程的内存空间和其中所含的内存区域。

15.3.1 VMA标志

VMA标志是一种位标志,其定义见<linux/mm.h>。它包含在vm_flags域内,标志了内存区域所包含的页面的行为和信息。VMA标志反映了内核处理页面所需要遵守的行为准则。而且,vm_flags同时也包含了内存区域中每个页面的信息,或内存区域的整体信息,而不是具体的独立页面。
在这里插入图片描述

15.3.2 VMA操作

vm_area_struct结构体中的vm_ops域指向与指定内存区域相关的操作函数表,内核使用表中的方法操作VMA。操作函数表由vm_operations_struct结构体表示,定义在文件<linux/mm.h>中。

15.4 页表

虽然应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存。所以当用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。地址的转换工作需要通过查询页表才能完成。

Linux中使用三级页表完成地址转换。利用多级页表能够节约地址转换需占用的存放空间。如果利用三级页表转换地址,即使是64位机器,占用的空间也很有限。

  1. 顶级页表是页全局目录(PGD),它包含了一个pgd_t类型数组,多数体系结构中pgd_t类型等同于无符号长整型类型。PGD中的表项指向二级页目录中的表项:PMD。

  2. 二级页表是中间页目录(PMD),它是个pmd_t类型数组,其中的表项指向PTE中的表项。

  3. 最后一级的页表简称页表,其中包含了pte_t类型的页表项,该页表项指向物理页面。

多数体系结构中,搜索页表的工作是由硬件完成的(至少某种程度上)。虽然通常操作中,很多使用页表的工作都可以由硬件执行,但是只有在内核正确设置页表的前提下,硬件才能方便地操作它们。

每个进程都有自己的页表(当然,线程会共享页表)。内存描述符的pgd域指向的就是进程的页全局目录。注意,操作和检索页表时必须使用page_table_lock锁,该锁在相应的进程的内存描述符中,以防止竞争条件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值