From c543b8173f8a914377c194d0bf6bea3ed2d1ef84 Mon Sep 17 00:00:00 2001
From: Shine wOng <1551885@tongji.edu.cn>
Date: Fri, 9 Aug 2019 09:08:40 +0800
Subject: [PATCH] modity some mistakes.
---
thu_os/chp5&6.md | 8 ++++----
thu_os/lab1_report.md | 13 +++++++++----
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/thu_os/chp5&6.md b/thu_os/chp5&6.md
index 7157796..756ac98 100644
--- a/thu_os/chp5&6.md
+++ b/thu_os/chp5&6.md
@@ -24,7 +24,7 @@
可见,从一开始完全没有内存管理的物理地址,到静态、动态重定位,内存管理使计算机可以更好地支持多道程序。可以说,内存管理就是为了运行多道程序。
-此外还有一些别的内存管理,比如计算机的存储空间时多级分层存储,各层次有寄存器,高速缓存,内存,硬盘等,需要对这些不同层次的存储器进行管理,比如高速缓存未命中时的操作等,这也是内存管理的一部分。
+此外还有一些别的内存管理,比如计算机的存储空间是多级分层存储,各层次有寄存器,高速缓存,内存,硬盘等,需要对这些不同层次的存储器进行管理,比如高速缓存未命中时的操作等,这也是内存管理的一部分。
## 连续的内存分配
@@ -36,7 +36,7 @@
> 连续内存分配的算法。
-+ 首要的是最先匹配算法(First Fit)。说来非常简单,就是在操作系统管理的空间内存中找到的第一个足以容纳新加入进程的区域,将其分配给新加入的进程。采用这样的算法可以方遍地合并被释放的分区,实现起来也比较简答,但是会产生外部碎片(但是所有连续内存分配都不可避免,所以应该不算缺点)
++ 首要的是最先匹配算法(First Fit)。说来非常简单,就是在操作系统管理的空间内存中找到的第一个足以容纳新加入进程的区域,将其分配给新加入的进程。采用这样的算法可以方便地合并被释放的分区,实现起来也比较简单,但是会产生外部碎片(但是所有连续内存分配都不可避免,所以应该不算缺点)
+ 最佳匹配算法(Best Fit)。最佳匹配算法的性能不见得是最佳。算法也非常简单,就是将管理的空闲的内存按照大小次序排列起来,找到刚好可以容下新加入的进程的那一个区域。这样的话可以保留一些较大的空闲空间供较大的程序使用,但是也会产生外部碎片,而且还是无用的小碎片。此外,在回收空间时比较麻烦(因为是按区域大小排序,而不是区域地址,因此合并操作会比较复杂)。
@@ -44,7 +44,7 @@
> 碎片整理办法。
-上面也说,凡是连续内存分配,都不可避免会出现外部碎片,这些碎片太多显然会影响到计算机的整体运行,因此需要对这些碎片进行整理。
+上面也说,凡是连续内存分配,都不可避免会出现外部碎片,这些碎片太多显然会影响到计算机的整体运行。当一个新的进程要求进入内存时,若内存中因为外部碎片的存在,而没有足够的空间来容纳这个新的进程,就需要对碎片进行整理,从而找到足够的空间放下这个进程。
一种方法是紧凑(compaction)。其实思想也很简单,就是既然你有外部碎片,
那我就把你压在一起,把中间碎片给填补掉。 想法虽然很简单,但是实现起来却很难。首先,程序需要在运行的时候更换位置,那么就要求所有的程序都支持动态重定位。其次,这样调整的开销极大。
@@ -54,7 +54,7 @@
## 非连续的内存分配
-所有连续的内存分配方法都会产生外部碎片,这是因为有些外部碎片极小,不足以分配给任何进程。此外,采用连续的内存分配方式,很容易产生具有足够的内存总空间,却因为这些空间都是离散的而无法给一个大进程分配空间。在这种情况下,就应该考虑非连续的内存分配。
+所有连续的内存分配方法都会产生外部碎片,这是因为有些外部碎片极小,不足以分配给任何进程。此外,采用连续的内存分配方式,很容易产生具有足够的内存总空间,却因为这些空间都是离散的而无法给一个大进程分配空间的情况。在这种情况下,就应该考虑非连续的内存分配。
非连续的内存分配实现起来其实非常复杂。因为要考虑很多问题,比如一个分区的大小为多大,一个字节?十个字节?还是1MB这么大?此外,应用进程需要保存所有这些不连续的分块的起始地址,才可以正确找到他们,而连续的内存分配应用进程只需要知道自己的起始地址。除此以外还有各种问题,让问题更加复杂。
diff --git a/thu_os/lab1_report.md b/thu_os/lab1_report.md
index 3108c5f..5a9e702 100644
--- a/thu_os/lab1_report.md
+++ b/thu_os/lab1_report.md
@@ -793,13 +793,18 @@ struct gatedesc {
首先来看中断的执行过程。这和我原来想象中的不太一样。按照我的想象,通过硬件判断出中断号后以后,通过查idt表,就可以获得服务程序的入口地址,进而转到中断服务程序去执行。
-但这个想法其实不太能经得起推敲。因为256个中断号,其中前32个(0~31)是系统保留的,用于异常和不可屏蔽中断,其它的都是可以留给用户定义的。如果这些用户定义的中断服务程序更换了地址,那么为了使它们生效,还需要重新加载中段描述符表,这种操作我反正是没有听说过。
-因此,比较好的方法就是固定各个中断服务程序的入口地址。实际上也的确是这么做的,这些入口地址也称为中断向量。但是这又会带来一个问题,应该给每个中断服务程序预留多少空间呢,因为每个中断服务程序理论上都是可以很大的,固定了入口地址带来了不便性。
+
+但这个想法其实不太能经得起推敲。因为256个中断号,其中前32个(0~31)是系统保留的,用于异常和不可屏蔽中断,其它的都是可以留给用户定义的。如果这些用户定义的中断服务程序更换了地址,那么为了使它们生效,还需要重新加载中段描述符表,这种操作我反正是没有听说过。
-实际的方法差不多是上面两者的折中,即中断向量里面进行的操作是固定的,无非是将一些信息压栈。然后所有中断向量都会跳转到同一个函数,在这个函数中再根据先前保存的各个信息(尤其是中断号),跳转到对应的实际的中断处理程序,这些处理程序可以是用户定义的,它们可以存在于内存的各个位置,就和普通的程序无异。
+因此,比较好的方法就是固定各个中断服务程序的入口地址。实际上也的确是这么做的,这些入口地址也称为中断向量。但是这又会带来一个问题,应该给每个中断服务程序预留多少空间呢,因为每个中断服务程序理论上都是可以很大的,固定了入口地址带来了不便性。
-这样,初始化`idt`表的工作,其实就是将中断向量里面的各个入口地址按格式填入的过程,问题就变得很简单了:
+实际的方法差不多是上面两者的折中,即中断向量里面进行的操作是固定的,无非是将一些信息压栈。然后所有中断向量都会跳转到同一个函数,在这个函数中再根据先前保存的各个信息(尤其是中断号),跳转到对应的实际的中断处理程序,这些处理程序可以是用户定义的,它们可以存在于内存的各个位置,就和普通的程序无异。
+
+
+上面写的我感觉有点不太对啊。中断服务程序肯定是预先就定义好的,只有操作系统的设计者才可以改变,因此通过idt表直接获得中断服务程序的入口地址的方案是完全可行的。但是问题在于,所有的中断都需要进行一些共同的操作,比如各个寄存器压栈,由用户栈到内核栈的切换等,如果由idt表直接跳转到中断服务程序的话,所有这些操作都需要在不同的中断服务程序里面出现,代码复用性差,而且也占用更多的内存空间。因此,一个方案就是idt表的所有项都跳转到同一个地址,来进行相同的中断准备工作,然后再进行实质上的中断服务操作。
+
+这样,初始化`idt`表的工作,其实就是将各个中断服务程序的入口地址逐个填入过程,问题就变得很简单了:
```c
static struct pseudodesc idt_pd = {