段表页表

段式管理

段式管理是指将程序按照内容或函数关系分成多个段,每个段有自己的名字。一个用户作业或者进程包含一个二维虚拟存储器。段式管理程序以段为单位分配内存,然后通过地址映射机制把段式虚拟地址转换为实际物理内存地址。

段式管理的优点在于可以对各个段分别编写和编译,不同的段互不影响。同时可以通过动态链接进行代码共享。

段式管理的优点在于可以很方便对信息的共享和保护。

段式管理的缺点在于如果段过长,为其分配连续空间会很不方便,会产生外部碎片。

如何处理段式管理的缺点,有以下两种策略:

  • 将内存整理规整,合并不连续的内存:段表各个段互补关联,而且有的段长有的段短,整理内存比较耗时;
  • 将不常用的内存段换到磁盘上:内存和磁盘的交换速率为几何倍数,如果交换的段比较大,会非常消耗时间,效率也很低。

页式管理

页式管理类似于段式管理,不过页式管理将各个进程的虚拟内存空间划分为若干个长度相等的页。把内存空间按页大小划分为片或者页面,然后把页式虚拟地址与内存地址建立——对应的页表。页式管理采用请求调页和预调页技术实现内外存存储器的统一管理。

页式管理优点在于各个页大小是相等的(常规为4K),大小更小的页交换起来速度就比较快了,完美解决了磁盘和内存交换速率慢的问题。同时固定大小的内存页解决了外部碎片的问题,但出现了内部碎片问题(比如一个页4K,我只使用其中1K)。

页式管理的缺点也很明显,它不方便按照逻辑模块实现信息的共享和保护。

段页式管理

段页式管理集中了段式管理和页式管理的优点,基本思想为用户程序按照段式划分(段式划分指按照段的逻辑划分),方便用户对各个段管理。对于系统而言,按照页划分每一段,系统按照页式划分更方便进行空闲页交换,性能更高。

段页式管理的逻辑地址由段号和段内地址组成,段内地址又由页号和页内偏移量组成。

同时每个段对应一个段表项,每个段表项由段号、页表长度、页表存放号(页表起始地址)组成。每个段表项长度相等,段号是隐含的。


Mmap 原理

我们都知道 Mmap 可以实现进程间内存共享和减少用户态到内核态的数据拷贝次数,但是没有深入了解 Mmap 原理。

回顾段页式内存管理

作为程序员我们希望看到的内存按照段(栈段、代码段、数据段)的方式管理,而对于操作系统而言按照页方式管理更利于数据页交换,效率更高。因此产生了段页式内存管理,实际物理内存还是按照分页管理,程序员看到的是一块虚拟内存,虚拟内存上的地址通过某种方式映射到物理内存上的某一页的某块偏移地址上。

同时对于 Linux-0.11 而言,将虚拟内存设置为 0~4G,这块虚拟内存被多个进程共享,每个进程的代码段、数据段都是一个段,每个进程占 64MB虚拟地址空间,互不重叠。

但是现代 32位操作系统每个进程会独占 4G虚拟内存,各个进程对应的页表是会产生重叠的,因此每个进程需要有自己的段表和页表。

进程空间结构

mmap 是一种内存映射方法,它实现了磁盘地址和进程虚拟地址空间中一段虚拟地址的映射关系。这样的映射关系保证了我们可以通过指针的方式来读写这一段内存,系统会自动写回脏页到对应的文件磁盘上,完成对文件的操作而不需要调用 read/write 等系统调用函数。

操作系统内核为每个进程维护了一个 task_struct 任务结构体。任务结构体中的元素包含或者指向内核运行该进程所需的相关信息(PID、指向用户栈的指针、可执行目标文件名、程序计数器等)。

Linux 使用 vm_area_struct 结构来表示一个独立的虚拟内存区域,虚拟内存区域是指进程地址空间中的 代码段(text)、初始数据段、BSS数据段、堆、栈等部分。同一进程的各个虚拟内存区域通过 vm_area_struct 结构的内部指针相互连接形成链表。

mmap 函数就是创建了一个新的 vm_area_struct 结构,并将它与文件的物理磁盘地址相连。

mmap 内存映射原理

mmap 的运行可以分为三个阶段:进程启动映射过程、调用内核 mmap 函数、进程发起对映射空间访问阶段。

1、进程启动映射

  • 进程在用户态调用库函数 mmap,函数原型为 void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
  • 在当前进程的虚拟地址空间中,寻找到一段空闲的满足要求的连续的虚拟地址;
  • 为此虚拟地址分配一个 vm_area_struct 结构,并初始化该结构;
  • 将新建的结构加入到虚拟地址区域链表。

2、调用内核 mmap 函数

  • 完成新虚拟地址区域分配后,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核已打开文件集中该文件的文件结构体,每个文件结构体维护着和这个已打开文件的相关各项信息;
  • 通过该文件的文件结构体,调用内核函数 mmap,原型为 int mmap(struct file *filp, struct vm_area_struct *vma)
  • 内核 mmap 函数通过虚拟文件系统 inode 模块定位到文件磁盘物理地址;
  • 建立页表,实现文件地址和虚拟地址区域的映射关系。

3、进程发起对虚拟地址空间的访问

  • 前个阶段只是完成了虚拟地址空间的创建和银蛇,但是没有将任何文件数据拷贝到主存。真正的文件读取是当进程发起读或写操作时。
  • 进程通过读或写操作访问虚拟地址空间这一段的映射地址,通过查询页表发现这一段地址并不在物理页面上,从而引发中断异常。
  • 触发调页过程,将所需页从磁盘装入主存中;
  • 之后进程对这片主存进行读或写操作,如果写操作改变了内容,一段时间后系统会自动回写脏页到磁盘中。

对比 mmap 和常规文件操作的区别

1、常规文件读写过程

  • 用户态进程发起 read/write 系统调用;
  • 内核通过查找进程文件描述符表,定位到内核已打开文件集上的文件信息,从而找到此文件的 inode;
  • 判断 inode 对应的请求文件页是否已经存在页缓存中,若存在直接返回文件页缓存的内容;
  • 如果不存在,通过 inode 定位到文件磁盘地址,将数据从磁盘复制到页缓存,之后读取直接从页缓存返回用户进程。

总的来说,常规文件读写为了提高读写效率和保护磁盘,使用页缓存机制,先将文件页从磁盘拷贝到页缓存,页缓存处于内核态无法被用户态直接访问,所以需要再次将页缓存数据拷贝到用户空间中。两次拷贝操作才能完成进程对文件内容的获取。

2、mmap 文件读写过程

  • mmap 在创建新的虚拟内存区域、建立虚拟内存区域到磁盘地址映射这两步,并没有任何文件拷贝操作,真正读写操作发现文件页不存在时,才从磁盘中将数据传入内存的用户空间中;
  • mmap 由于直接建立了用户空间虚拟地址到物理页 Buffer地址的映射,从而实现了无需进入内核即可访问页 Buffer,因此效率更高;
  • 如果当前进程拥有的虚拟地址空间中,存在部分区域的虚拟地址空间是直接映射到内核空间中物理页 Buffer上,那么后续 DMA 从磁盘将文件数据加载到页 Buffer后,进程就可以直接访问了。

总的来说,普通访问文件方式需要先将文件从磁盘加载到内核空间的页缓存,再将数据从页缓存拷贝到用户空间,总共两次文件拷贝操作;而 mmap 内存映射用户虚拟地址空间直接映射到内核空间页 Buffer,只需要将数据从磁盘加载到内核页缓存,用户空间即可直接访问,减少了一次文件拷贝操作。

参考:

https://cloud.tencent.com/developer/article/2144681

posted @ 2024-01-04 00:30  Stitches  阅读(233)  评论(0)    收藏  举报