今天没有带书,那就只能记笔记了。
4.1 -4.2 基本的内存管理
4.3-4.5 页式管理技术
虚拟地址->虚页号->物理页号
缺页->缺页故障
页面替换算法:减少缺页故障的总数
PageSize的大小/工作集
教材版本的minix系统没有实现,没有页式,而是基本的管理。
概述
进程管理器:
- 进程管理
- 存储管理(存储管理器) 保持着一张按照内存地址排列的空洞列表,最先匹配算法
空洞列表:链表/位图 的空闲块管理
一旦有程序被装入内存,它将一直保持在原来的位置直到运行结束,它不会被患处或移动到内存的其他位置去。
机制和策略的分离
策略:哪个进程被放置在内存中的哪个位置是由存储管理器,具体如何放(上层)
机制:为具体的进程设置内存映像由内核中的系统任务;
算法:存储管理策略。
当a.c -> a.out时: 内存的布局是如何的?
内存的布局——组合的ID空间
组合的I(instructions)D (Data)空间:将代码、数据和栈共用一个内存块,作为整体来申请和释放。
创建进程的两种方式:
fork:为子进程分配所需要的空间,子进程是父进程的一个拷贝,对于这种情况没有保存两端父进程代码段的必要
Exec :修改它的内存映像,老得内存映像被释放,新的内存映像被分配内存。子进程运行新的程序。
独立的I/D空间
在fork时,只要为它分配堆栈段和数据段拷贝所需数量的内存。
子进程和父进程共享由父进程使用的执行代码,即父子进程共享代码。
在exec时:启动新的时候,看看进程表里有没有需要的代码,共享即可。
当我们终止进程释放内存的时候,因为数据和栈是独有的,可以删掉;但是对于代码段而言,要检查进程表看是否还被需要,代码段不再被需要时才应该被删掉。
程序文件与内存布局(重要)【from file-> memory layout】
如何给程序分配内存呢?
磁盘文件的头部包含了进程映像各部分的大小以及总的大小的信息。
为啥要包含了总的大小?
文件头中的total域说明总共分配的内存数量。
组合ID空间的程序布局
4K代码段
2K数据段
1K堆栈
文件头中说明的需要分配的内存总量是40K(total)
故堆栈段和数据段之间未用内存将是33K。
ID空间是组合的还是独立的判定由文件头中连接器设置的一位指出。
独立的ID空间
4K代码
2K数据
1K堆栈
Total域中被分配68K的空间(4K 指令空间,64K数据空间),留出61K空间供数据段和堆栈在运行时使用。
独立的与代码段无关,故就是64-3
消息处理
- 进程管理器也是消息驱动的。
- 系统初始化后,进入主循环,作为服务在工作。
- 等待消息,执行消息中的请求和发送应答。
- MINIX微内核架构转为相应的服务器进程。
- 内核与系统服务器之间的高优先级通信,采用系统通知消息。
- MINIX系统调用->消息->系统服务器进程
- 消息来源于类型:fork/exit/wait/brk
受到消息,解析消息,得到下标索引,数据结构为call_vec表,每个元素是函指针,指向相应的处理消息的函数。
数据结构与算法
MINIX3中:进程表:
- 内核kernel
- 进程管理器 Process management (存储管理)
- 文件管理器 File management
表项是精确对应的,进程管理器中的k和文件系统中的k对应的应该是同一个进程。
在进程创建或结束时,这三个部分都要更新它们的表以反映新的情况。
内存中的进程
PM管理器的进程表叫做mproc,mproc.h
mp_seg数组,它有三个表项,分别用于代码段/数据段和堆栈段。
他们都是用块而不是字节度量的。
1 | struct mem_map mp_seg[NR_LOCAL_SEGS]; |
内存中的记录
组合ID,代码段总是空的,数据段包含了代码和数据。
独立ID,代码段和数据段的长度都不为0;
Mm_seg数组记录了进程里面三个段的映射关系,
栈的虚拟地址起始和进程内存总量
- 栈的起始虚地址取决于分配给进程的内存总量,位于分配给他的内存的最高端。
- 应用chmem命令修改文件头,下一次文件执行时堆栈将从一个更高的虚地址开始。
- 0x8->0x7堆栈增长了一块的变化 向下
- 0xd0->0xcf 向下
- 0x2->0x3 向上
数据段于堆栈段越界(P4)
- MINIX中数据段描述符和堆栈段描述符是一样的,即硬件认为他们处于同一个段里, 硬件是无法检测这种越界的
- 数据和堆栈使用同一段的不同部分,两者都可以扩展进入位于他们的空隙之中。
- MINIX来管理这种越界,CPU硬件无法检测,若交错,可能有冲掉,导致程序出错。
共享代码段
只读代码段的共享。
exec要装入一个程序时,如果进程使用的是独立ID,那么就会搜索每个mproc表项,找到了以后,只要把新进程内存映射的mm_seg[T]初始化为指向已经装入代码段的位置。此时我们只需要为数据段和堆栈段分配相应的内存空间。
空闲链表
按照内存地址递增的顺序列出了内存中的各个空洞
- 最先匹配法
- 在进程结束后,他的数据和堆栈内存区将归还给空闲链表。I/D情况分为组合和独立的ID空间释放。
- 当被归还后,要看左右是否有gap空洞,进行空洞的合并操作。
创建进程的系统调用
在创建和撤销进程时必须分配或释放内存、必须更新进程表,包括由内核和FS保存的部分。这些操作都由进程管理器协调。
FORK
Check if PTable is full
allocate memory,为子进程非配空间
Copy ,将父进程的拷贝到子进程
find a free process slot and copy parent’s slot to it
enter the child’s memory map in process table
Choose a PID for a child
告诉kernel 和FS孩子来了
Report 孩子的memory马屁头kernel
send reply message to parent and child.
当子进程已经退出或被kill,但是父进程没有执行wait来回收,那么此时子进程会进入某种挂起状态,僵死状态,它将仍留在进程表中,它的内存被释放。
父进程执行wait时,释放进程表项,告诉kernel和FS被释放了。
exec
新的内存影响来替换当前的内存映像
- 检查要加载的文件permission bit是否为1(x)
- 检查要加载的文件头部获得segement和total sizes;
- 获取参数和环境变量
- 分配新的释放老的
- stack
- data
- 设置handle segued,setgid bits
- 更新process table entry
- 告诉kernel 和process它是可执行的了。
在执行过程中,需要考虑可执行文件是否可以放的进虚地址空间。
click
设置栈的初始状态:了解。
Brk和sbrk
库过程brk和sbrk用来调整数据段的上线。
前者的参数是绝对长度(bytes)后者的单位是相对增量。
检车各个部分是否仍在地址空间中、调整表、并通知内核。
信号处理系统调用
在系统中定义了一组信号,每个信号都有一个默认的动作,kill进程或忽略信号
sig_handler:将信号和动作绑定
- 信号处理3个阶段:
- 准备
- 响应
- 清理阶段
准备阶段
SIGACTION:忽略某些/捕获某些signal
SIGPROCMASK:阻塞信号
进程的signet_t变量表示可能的信号,定义忽略和捕获的信号的集合。
每个进程的sigaction的结构数组
1 | struct sigaction{ |
信号类型
MINIX3信号定义/usr/include/signal.h POSIX 标准
信号产生:KILL系统调用/内核产生
信号处理的清理阶段(介绍)
SIGRETURN系统调用
进程表由于空间有限,只能保存一份副本。
用户空间的定时器
内核空间定时器:系统进程
用户进程定时器:进程管理器
二者方法类似;
进程管理器维护一个定时器队列,每次根据队列头部的定时器向时钟任务发出警报请求。
在一个时钟中断后,若系统检测到一个时期的警报,进程管理器会收到一个通知,然后,它会检查自己的定时器队列,并向相应的用户进程发送信号。
其它的系统调用
time/stime/time;
Getuid/geteuid;
ptrace/rebbot
4.8 MINIX3进程管理器实现
头文件和数据结构
- pm.h
- glo.h
- mproc.h
- table.c –call_vec(PM消息)
- …
主程序
- 进程管理器是独立于内核和文件系统编译和连接的,所以它有自己的主程序。
- 它的主程序在内核初始化自己之后被启动。
fork,exit,wait的实现 forkexit.c
exec的实现
- 由exec.c中的do_exec函数实现
- 若映像是可执行文件,则用其替换当前的内存映像
- 若映像是脚本文件,则装入解释程序的二进制文件,并将该脚本文件的文件名作为其参数。
brk的实现
当进程创建时,将获得一块用于存放数据和栈的连续内存区域,该区域是固定的连续的内存区固定
数据段和栈段可以共享其中的间隙
brk用于调整数据段的大小,在break.c中do_brk实现
首先检查新的大小是否可能,然后更新相关的表。
数据和栈交错了咋办呢?–课程设计。
若交错了,是说明了什么呢?空间不够,要重新分配大的空间。
信号处理实现
与信号处理相关的系统调用8个,signal.c中实现