操作系统存储器管理课程设计报告.docx
流程图任务执行路径进程地址间的治理模型页表治理内存虚拟空间3. 相关代码设计linux/memory.c 程序代码#include <signal.h>/ 信号头文件。定义信号符号常量,信号构造以及信号操作函数原型。#include <asm/system.h> / 系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏。#include <linux/sched.h> / 调度程序头文件,定义了任务构造 task_struct、初始任务 0 的数据,/ 还有一些有关描述符参数设置和猎取的嵌入式汇编函数宏语句。#include <linux/head.h>/ head 头文件,定义了段描述符的简洁构造,和几个选择符常量。#include <linux/kernel.h> / 内核头文件。含有一些内核常用函数的原形定义。volatile void do_exit(long code);/ 进程退出处理函数,在 kernel/exit.c。/ 显示内存已用完出错信息,并退出。static inline volatile void oom(void)printk(“out of memorynr“);do_exit(SIGSEGV);/do_exit 应当使用退出代码, 这里用了信号值SIGSEGV(11)/ 一样值的出错码含义是“资源临时不行用”,正好同义。/ 刷页变换高速缓冲宏函数。/ 为了提高地址转换的效率,CPU 将最近使用的页表数据存放在芯片中高速缓冲中。在修改正页表/ 信息之后,就需要刷该缓冲区。这里使用重加载页名目基址存放器cr3 的方法来进展刷。/ 下面 eax = 0,是页名目的基址。#define invalidate asm(“movl %eax,%cr3“:“a“ (0)/* these are not to be changed without changing head.s etc */* 下面定义假设需要改动,则需要与 head.s 等文件中的相关信息一起转变 */ linux 0.11 内核默认支持的最大内存容量是 16M,可以修改这些定义以适合更多的内存。#define LOW_MEM 0x100000/ 内存低端1MB。#define PAGING_MEMORY (15*1024*1024)/ 分页内存 15MB。主内存区最多15M。#define PAGING_S (PAGING_MEMORY>>12)/ 分页后的物理内存页数。#define MAP_NR(addr) (addr)-LOW_MEM)>>12)/ 指定内存地址映射为页号。#define USED 100/ 页面被占用标志/ CODE_SPACE(addr) (addr)+0xfff)&0xfff) < current->start_code + current->end_code。)/ 该宏用于推断给定地址是否位于当前进程的代码段中。#define CODE_SPACE(addr) (addr)+4095)&4095) < current->start_code + current->end_code)static long HIGH_MEMORY = 0;/ 全局变量,存放实际物理内存最高端地址。/ 复制 1 页内存4K 字节。#define copy_(from,to) asm(“cld ; rep ; movsl“:“S“ (from),“D“ (to),“c“ (1024):“cx“,“di“,“si“)/ 内存映射字节图,每个页面对应的字节用于标志页面当前被引用占用次数。static unsigned char mem_map PAGING_S = 0,;* Get physical address of first (actually last :-) free , and mark it* used. If no free s left, return 0.* 猎取首个(实际上是最终 1 个:-)空闲页面,并标记为已使用。假设没有空闲页面,* 就返回 0。/ 取空闲页面。假设已经没有可用内存了,则返回 0。/输 入 : %1(ax=0)-0; %2(LOW_MEM); %3(cx=PAGINGS) ;%4(edi=mem_map+PAGING_S-1)。/ 输出:返回%0(ax=页面起始地址)。/ 上面%4 存放器实际指向 mem_map内存字节图的最终一个字节。本函数从字节图末端开头向前扫描/ 全部页面标志页面总数为 PAGING_S,假设有页面空闲其内存映像字节为 0则返回页面地址。/ 留意!本函数只是指出在主内存区的一页空闲页面,但并没有映射到某个进程的线性地址去。后面/ 的 put_函数就是用来作映射的。unsigned long get_free_(void)register unsigned longres asm(“ax“); asm(“std ; repne ; scasbnt“/ 方向位置位,将 al(0)与对应每个页面的(di)内容比较, “jne 1fnt“/ 假设没有等于 0 的字节,则跳转完毕返回 0。movb $1,1(%edi)nt/ 将对应页面的内存映像位置 1。sall $12,%ecxnt“/ 页面数*4K = 相对页面起始地址。“addl %2,%ecxnt“/ 再加上低端内存地址,即获得页面实际物理起始地址。“movl %ecx,%edxnt“/ 将页面实际起始地址edx 存放器。“movl $1024,%ecxnt“/ 存放器 ecx 置计数值 1024。leal 4092(%edx),%edint“/ 将 4092+edx 的位置edi(该页面的末端)。rep ; stoslnt“/ 将 edi 所指内存清零反方向,也马上该页面清零。“movl %edx,%eaxn“/ 将页面起始地址eax返回值。“1:“:“=a“ (res): “ (0),“i“ (LOW_MEM),“c“ (PAGING_S), “D“ (mem_map+PAGING_S-1):“di“,“cx“,“dx“);return res;/ 返回空闲页面地址假设无空闲也则返回 0。* Free a of memory at physical address ”addr”. Used by* ”free_tables”* 释放物理地址”addr”开头的一页内存。用于函数”free_tables”。/ 释放物理地址 addr 开头的一页面内存。/ 1MB 以下的内存空间用于内核程序和缓冲,不作为安排页面的内存空间。void free_(unsigned long addr)if (addr < LOW_MEM) return;/ 假设物理地址 addr 小于内存低端1MB,则返回。if (addr >= HIGH_MEMORY)/ 假设物理地址 addr> 内存最高端,则显示出错信息。panic(“trying to free nonexistent “);addr -= LOW_MEM;/ 物理地址减去低端内存位置,再除以 4KB,得页面号。addr >>= 12;if (mem_mapaddr-) return; / 假设对应内存页面映射字节不等于 0,则减 1 返回。死机。mem_mapaddr=0;/ 否则置对应页面映射字节为 0,并显示出错信息,panic(“trying to free free “);* This function frees a continuos block of tables, as needed* by ”exit”. As does copy_tables, this handles only 4Mb blocks.* 下面函数释放页表连续的内存块,”exit”需要该函数。与 copy_tables* 类似,该函数仅处理 4Mb 的内存块。/ 依据指定的线性地址和限长页表个数,释放对应内存页表所指定的内存块并置表项空闲。/ 页名目位于物理地址 0 开头处,共 1024 项,占 4K 字节。每个名目项指定一个页表。/ 每个页表项对应一页物理内存4K。/ 参数:from - 起始基地址;size - 释放的长度。int free_tables(unsigned long from,unsigned long size)unsigned long *pg_table; unsigned long * dir, nr;if (from & 0x3fffff)/ 要释放内存块的地址需以 4M 为边界。panic(“free_tables called with wrong alignment“);if (!from)/ 出错,试图释放内核和缓冲所占空间。panic(“Trying to free up swapper memory space“);/ 计算所占页名目项数(4M 的进位整数倍),也即所占页表数。size = (size + 0x3fffff) >> 22;/ 下面一句计算起始名目项。对应的名目项号=from>>22,因每项占 4 字节,并且由于页名目是从/ 物理地址 0 开头,因此实际的名目项指针=名目项号<<2,也即(from>>20)。与上 0xffc 确保/ 名目项指针范围有效。dir = (unsigned long *) (from>>20) & 0xffc); /* _pg_dir = 0 */for ( ; size->0 ; dir+) / size 现在是需要被释放内存的名目项数。if (!(1 & *dir)/ 假设该名目项无效(P 位 0),则连续。continue;/ 名目项的位 0(P 位)表示对应页表是否存在。pg_table = (unsigned long *) (0xfffff000 & *dir);/ 取名目项中页表地址。for (nr=0 ; nr<1024 ; nr+) / 每个页表有 1024 个页项。if (1 & *pg_table)/ 假设该页表项有效(P 位=1),则释放对应内存页。free_(0xfffff000 & *pg_table);*pg_table = 0;/ 该页表项内容清零。pg_table+;/ 指向页表中下一项。free_(0xfffff000 & *dir);/ 释放该页表所占内存页面。但由于页表在句什么都不做。/ 物理地址 1M 以内,所以这*dir = 0;/ 对相应页表的名目项清零。invalidate;/ 刷页变换高速缓冲。return 0;int copy_tables(unsigned long from,unsigned long to,long size)unsigned long * from_table; unsigned long * to_table; unsigned long this_;unsigned long * from_dir, * to_dir; unsigned long nr;/ 源地址和目的地址都需要是在 4Mb 的内存边界地址上。否则出错,死机。if (from&0x3fffff) | (to&0x3fffff)panic(“copy_tables called with wrong alignment“);/ 取得源地址和目的地址的名目项(from_dir 和 to_dir)。from_dir = (unsigned long *) (from>>20) & 0xffc); /* _pg_dir = 0 */ to_dir = (unsigned long *) (to>>20) & 0xffc);/ 计算要复制的内存块占用的页表数也即名目项数。size = (unsigned) (size+0x3fffff) >> 22;/ 下面开头对每个占用的页表依次进展复制操作。for( ; size->0 ; from_dir+,to_dir+) / 假设目的名目项指定的页表已经存在(P=1),则出错,死机。if (1 & *to_dir)panic(“copy_tables: already exist“);/ 假设此源名目项未被使用,则不用复制对应页表,跳过。if (!(1 & *from_dir)continue;/ 取当前源名目项中页表的地址from_table。from_table = (unsigned long *) (0xfffff000 & *from_dir);/ 为目的页表取一页空闲内存,假设返回是 0 则说明没有申请到空闲内存页面。返回值=-1,退出。if (!(to_table = (unsigned long *) get_free_) return -1;/* Out of memory, see freeing */ 设置目的名目项信息。7 是标志信息,表示(Usr, R/W, Present)。*to_dir = (unsigned long) to_table) | 7;/ 针对当前处理的页表,设置需复制的页面数。nr = (from0)?0xA0:1024;/ 对于当前页表,开头复制指定数目 nr 个内存页面。for ( ; nr- > 0 ; from_table+,to_table+) this_=*from_table;/取 源 页 表 项 内 容 。if (!(1 & this_)/ 假设当前源页面没有使用,则不用复制。continue;/ 复位页表项中 R/W 标志(置 0)。(假设 U/S 位是 0,则 R/W 就没有作用。假设 U/S 是 1, 而 R/W 是 0,/ 那么运行在用户层的代码就只能读页面。假设 U/S 和 R/W 都置位,则就有写的权限。) this_ &= 2;*to_table = this_; / 将该页表项复制到目的页表中。/ 假设该页表项所指页面的地址在 1M 以上,则需要设置内存页面映射数组 mem_map, 于是计算/ 页面号,并以它为索引在页面映射数组相应项中增加引用次数。if (this_ > LOW_MEM) / 下面这句的含义是令源页表项所指内存页也为只读。由于现在开头有两个进程共用内存区了。/ 假设其中一个内存需要进展写操作,则可以通过页特别的写保护处理,为执行写操作的进程安排/ 一页的空闲页面,也即进展写时复制的操作。*from_table = this_; / 令源页表项也只读。this_ -= LOW_MEM; this_ >>= 12;mem_mapthis_+;invalidate;/ 刷页变换高速缓冲。return 0;* This function puts a in memory at the wanted address.* It returns the physical address of the gotten, 0 if* out of memory (either when trying to access -table or* .)* 下面函数将一内存页面放置在指定地址处。它返回页面的物理地址,假设* 内存不够(在访问页表或页面时),则返回 0。/ 把一物理内存页面映射到指定的线性地址处。/ 主要工作是在页名目和页表中设置指定页面的信息。假设成功则返回页面地址。unsigned long put_(unsigned long ,unsigned long address)unsigned long tmp, *_table;/* NOTE ! This uses the fact that _pg_dir=0 */* 留意!这里使用了页名目基址_pg_dir=0 的条件 */如 果 申 请 的 页 面 位 置 低 于 LOW_MEM(1Mb) 或 超 出 系 统 实 际 含 有 内 存 高 端HIGH_MEMORY,则发出警告。if ( < LOW_MEM | >= HIGH_MEMORY) printk(“Trying to put %p at %pn“,address);/ 假设申请的页面在内存页面映射字节图中没有置位,则显示警告信息。if (mem_map(-LOW_MEM)>>12 != 1)printk(“mem_map disagrees with %p at %pn“,address);/ 计算指定地址在页名目表中对应的名目项指针。_table = (unsigned long *) (address>>20) & 0xffc);/ 假设该名目项有效 (P=1)(也即指定的页表在内存中 ),则从中取得指定页表的地址_table。if (*_table)&1)_table = (unsigned long *) (0xfffff000 & *_table);else / 否则,申请空闲页面给页表使用,并在对应名目项中置相应标志 7User, U/S, R/W。然后将/ 该页表的地址_table。if (!(tmp get_free_)return 0;*_table = tmp|7;_table = (unsigned long *) tmp;/ 在页表中设置指定地址的物理内存页面的页表项内容。每个页表共可有 1024 项(0x3ff)。_table(address>>12) & 0x3ff = | 7;/* no need for invalidate */* 不需要刷页变换高速缓冲 */return ;/ 返回页面地址。/ 取消写保护页面函数。用于页特别中断过程中写保护特别的处理写时复制。/ 输入参数为页表项指针。/ un_wp_ 意思是取消页面的写保护:Un-Write Protected。 void un_wp_(unsigned long * table_entry)unsigned long old_,new_;old_ = 0xfffff000 & *table_entry;/ 取原页面对应的名目项号。/ 假设原页面地址大于内存低端 LOW_MEM(1Mb),并且其在页面映射字节图数组中值为1表示仅/ 被引用 1 次,页面没有被共享,则在该页面的页表项中置 R/W 标志可写,并刷页变换/ 高速缓冲,然后返回。if (old_ >= LOW_MEM && mem_mapMAP_NR(old_)1) *table_entry |= 2;invalidate; return;/ 否则,在主内存区内申请一页空闲页面。if (!(new_ get_free_)oom;/ Out of Memory。内存不够处理。/ 假设原页面大于内存低端则意味着 mem_map>1,页面是共享的,则将原页面的页面映射/ 数组值递减 1。然后将指定页表项内容更为页面的地址,并置可读写等标志 (U/S, R/W, P)。/ 刷页变换高速缓冲。最终将原页面内容复制到页面。if (old_ >= LOW_MEM)mem_mapMAP_NR(old_)-;*table_entry = new_ | 7; invalidate; copy_(old_,new_);/ 页特别中断处理调用的 C 函数。写共享页面处理函数。在.s 程序中被调用。/ 参数 error_code 是由 CPU 自动产生,address 是页面线性地址。/ 写共享页面时,需复制页面写时复制。void do_wp_(unsigned long error_code,unsigned long address)#if 0/* we cannot do this yet: the estdio library writes to code space */* stupid, stupid. I really want the libc.a from GNU */* 我们现在还不能这样做:由于 estdio 库会在代码空间执行写操作 */if (CODE_SPACE(address)/ 假设地址位于代码空间,则终止执行程序。do_exit(SIGSEGV);#endif/ 处理取消页面保护。参数指定页面在页表中的页表项指针,其计算方法是:/ (address>>10) & 0xffc):计算指定地址的页面在页表中的偏移地址;/ (0xfffff000 &(address>>20) &0xffc):取名目项中页表的地址值,/ 其中(address>>20) &0xffc)计算页面所在页表的名目项指针;/ 两者相加即得指定地址对应页面的页表项指针。这里对共享的页面进展复制。un_wp_(unsigned long *)(address>>10) & 0xffc) + (0xfffff000 &*(unsigned long *) (address>>20) &0xffc);/ 写页面验证。/ 假设页面不行写,则复制页面。 void write_verify(unsigned long address)unsigned long ;/ /推断指定地址所对应页名目项的页表是否存在(P),假设不存在(P=0)则返回。if (!( ( = *(unsigned long *) (address>>20) & 0xffc) )&1)return;/ 取页表的地址,加上指定地址的页面在页表中的页表项偏移值,得对应物理页面的页表项指针。 &= 0xfffff000; += (address>>10) & 0xffc);/ 假设该页面不行写(标志 R/W 没有置位),则执行共享检验和复制页面操作写时复制。if (3 & *(unsigned long *) )1)/* non-writeable, present */ un_wp_(unsigned long *) );return;/ 取得一页空闲内存并映射到指定线性地址处。/ 与 get_free_不同。get_free_仅是申请取得了主内存区的一页物理内存。而该函数/ 不仅是猎取到一页物理内存页面,还进一步调用 put_,将物理页面映射到指定的线性地址处。void get_empty_(unsigned long address)unsigned long tmp;/ 假设不能取得一空闲页面,或者不能将页面放置到指定地址处,则显示内存不够的信息。/ 279 行上英文注释的含义是:即使执行 get_free_返回 0 也无所谓,由于 put_/ 中还会对此状况再次申请空闲物理页面的,见 210 行。if (!(tmp get_free_) | !put_(tmp,address) free_(tmp);/* 0 is ok - ignored */oom; * try_to_share checks the at address “address“ in the task “p“,* to see if it exists, and if it is clean. If so, share it with the current* task.* NOTE! This assumes we have checked that p != current, and that they* share the same executable.* try_to_share在任务“p“中检查位于地址“address“处的页面,看页面是否存在,是否干净。* 假设是干净的话,就与当前任务共享。* 留意!这里我们已假定 p !=当前任务,并且它们共享同一个执行程序。/ 尝试对进程指定地址处的页面进展共享操作。/ 同时还验证指定的地址处是否已经申请了页面,假设是则出错,死机。/ 返回 1-成功,0-失败。static int try_to_share(unsigned long address, struct task_struct * p)unsigned long from; unsigned long to; unsigned long from_; unsigned long to_; unsigned long phys_addr;/ 求指定内存地址的页名目项。from_ = to_ = (address>>20) & 0xffc);/ 计算进程 p 的代码起始地址所对应的页名目项。from_ += (p->start_code>>20) & 0xffc);/ 计算当前进程中代码起始地址所对应的页名目项。to_ += (current->start_code>>20) & 0xffc);/* is there a -directory at from? */* 在 from 处是否存在页名目?*/ * 对 p 进程页面进展操作。/ 取页名目项内容。假设该名目项无效(P=0),则返回。否则取该名目项对应页表地址from。from = *(unsigned long *) from_; if (!(from & 1)return 0; from &= 0xfffff000;/ 计算地址对应的页表项指针值,并取出该页表项内容phys_addr。from_ = from + (address>>10) & 0xffc);phys_addr = *(unsigned long *) from_;/* is the clean and present? */* 页面干净并且存在吗?*/ 0x41 对应页表项中的 Dirty 和 Present 标志。假设页面不干净或无效则返回。if (phys_addr & 0x41) != 0x01)return 0;/ 取页面的地址phys_addr。假设该页面地址不存在或小于内存低端(1M)也返回退出。phys_addr &= 0xfffff000;if (phys_addr >= HIGH_MEMORY | phys_addr < LOW_MEM)return 0;/ * 对当前进程页面进展操作。/ 取页名目项内容 to。假设该名目项无效(P=0),则取空闲页面,并更to_ 所指的工程。to = *(unsigned long *) to_; if (!(to & 1)if (to = get_free_)*(unsigned long *) to_ = to | 7;elseoom;/ 取对应页表地址to,页表项地址to_。假设对应的页面已经存在,则出错,死机。to &= 0xfffff000;to_ = to + (address>>10) & 0xffc); if (1 & *(unsigned long *) to_)panic(“try_to_share: to_ already exists“);/* share them: write-protect */* 对它们进展共享处理:写保护 */ 对 p 进程中页面置写保护标志(置 R/W=0 只读)。并且当前进程中的对应页表项指向它。*(unsigned long *) from_ &= 2;*(unsigned long *) to_ = *(unsigned long *) from_;/ 刷页变换高速缓冲。invalidate;/ 计算所操作页面的页面号,并将对应页面映射数组项中的引用递增 1。phys_addr -= LOW_MEM;phys_addr >>= 12; mem_mapphys_addr+; return 1;* share_ tries to find a process that could share a with* the current one. Address is the address of the wanted relative* to the current data space.* We first check if it is at all feasible by checking executable->i_count.* It should be >1 if there are other tasks sharing this inode.* share_试图找到一个进程,它可以与当前进程共享页面。参数 address 是* 当前数据空间中期望共享的某页面地址。* 首先我们通过检测 executable->i_count 来查证是否可行。假设有其它任务已共享* 该 inode,则它应当大于 1。/ 共享页面。在缺页处理时看看能否共享页面。/ 返回 1 - 成功,0 - 失败。static int share_(unsigned long address)struct task_struct * p;/ 假设是不行执行的,则返回。excutable 是执行进程的内存 i 节点构造。if (!current->executable)return 0;/ 假设只能单独执行(executable->i_count=1),也退出。if (current->executable->i_count < 2) return 0;/ 搜寻任务数组中全部任务。查找与当前进程可共享页面的进程,并尝试对指定地址的页面进展共享。for (p = &LAST_TASK ; p > &FIRST_TASK ; -p) if (!*p)/ 假设该任务项空闲,则连续查找。continue;if (current*p)/ 假设就是当前任务,也连续查找。continue;if (*p)->executable != current->executable) / 假设 executable 不等,也继continue;if (try_to_share(address,*p) / 尝试共享页面。return 1;return 0;/ 页特别中断处理调用的函数。处理缺页特别状况。在.s 程序中被调用。/ 参数 error_code 是由 CPU 自动产生,address 是页面线性地址。void do_no_(unsigned long error_code,unsigned long address)int nr4;unsigned long tmp; unsigned long ; int block,i;address &= 0xfffff000;/ 页面地址。/ 首先算出指定线性地址在进程空间中相对于进程基址的偏移长度值。tmp = address - current->start_code;/ 假设当前进程的 executable 空,或者指定地址超出代码+数据长度,则申请一页物理内存, 并映射/ 影射到指定的线性地址处。executable 是进程的 i 节点构造。该值为 0,说明进程刚开头设置,/ 需要内存;而指定的线性地址超出代码加数据长度,说明进程在申请的内存空间,也需要赐予。/ 因此就直接调用 get_empty_函数,申请一页物理内存并映射到指定线性地址处即可。/ start_code 是进程代码段地址,end_data 是代码加数据长度。对于 linux 内核,它的代码段和/ 数据段是起始基址是一样的。if (!current->executable | tmp >= current->end_data)