操作系统(11) 段页结合

236 阅读3分钟

本文引用代码及图片均来自 李治军: 操作系统32讲

段页结合

我们知道程序载入内存后是分段存储的,用户以段的形式访问内存,比如我们熟悉的CS:IP形式,但物理内存是按页分配的,为了将段页结合起来,操作系统在用户访问的逻辑地址和物理内存对应的物理地址中间增加了一个虚拟地址

image.png

程序载入内存时操作系统在虚拟地址空间中为程序的每一段都划分出一段连续的空间,然后将每一段的虚拟地址空间按页划分对应到不同的物理页

image.png

程序载入时会建立段表页表,访问内存时先根据逻辑地址中的段号在段表找到虚拟地址空间中段的起始地址,起始地址+逻辑地址中的偏移形成虚拟地址,然后根据虚拟地址查页表找到对应的物理内存页

image.png

代码实现

本节探讨Linux 0.11中创建子进程时内存时如何分配的,进程通过fork系统调用创建子进程,在 操作系统 (6) 线程与进程的创建 中讲到fork最终会调用copy_processcopy_process调用copy_mem复制父进程的内存

image.png image.png

copy_memnew_data_base就是新分配的虚拟地址空间的起始地址,可以看到Linux 0.11中系统为每一个进程分配64M的空间,进程的段表中代码段数据段指向同一个虚拟地址空间

image.png

给进程分配了虚拟地址空间后需要给虚拟地址空间建立页表,页表的建立调用copy_page_table来完成,此时页表的内容是直接复制父进程的

image.png

copy_page_table的入参分别是父进程和子进程的虚拟地址空间起始地址,对应形式参数fromto

Linux 0.11中,页目录表被放在物理内存的起始位置,所有进程共享该表。在 操作系统(10) 内存的使用 中讲到多级页表中32位虚拟地址前10位是页目录号,每个页目录项大小4个字节。所以from >> 22即定位到父进程对应的起始页目录号,然后再<< 2,也就是页目录号乘以页目录项大小,即可得到父进程起始页目录项的地址。在代码中写作(from >> 20) & 0xffc, 0xffc是为了把最后面两位置零

然后要做的就是把父进程页目录项对应的页表复制一份到子进程页目录项中,父进程对应的页目录项有多个所以有一个for循环控制

先给子进程的每一个页目录项申请一块内存用于建立页表然后建立页目录项和页表的关联|7类似chmod 777 file的用法,此处表示页目录项对应的页表可读写

image.png

然后把父进程页目录项对应页表里的每一项复制到子进程页目录项对应的页表里,此时父子进程页表对应的物理内存都会被设置为只读,具体是操作物理内存对应的页表项中的状态位

image.png

此时父进程和子进程各自有自己的虚拟地址空间,但是虚拟地址空间的页表是一样的,所以指向同一块物理内存

image.png

在前面复制页表项的时候设置了内存为只读,当父子进程中任意一个写内存时会发生错误,此时操作系统会为进程重建页表使其指向新的物理内存并设置写权限,此操作称为写时复制

image.png

至此,子进程和父进程的物理内存就分开了

参考文献