<section data-role="outer" label="Powered by 135editor.com">git
<section data-role="outer" label="Powered by 135editor.com">程序员
点击上方“程序员江湖”,选择“置顶或者星标”github
你关注的就是我关心的!面试
做者:CyC2018算法
连接:https://github.com/CyC2018/CS...浏览器
<section>缓存
并发是指宏观上在一段时间内能同时运行多个程序,而并行则指同一时刻能运行多个指令。安全
并行须要硬件支持,如多流水线、多核处理器或者分布式计算系统。服务器
操做系统经过引入进程和线程,使得程序可以并发运行。网络
共享是指系统中的资源能够被多个并发进程共同使用。
有两种共享方式:互斥共享和同时共享。
互斥共享的资源称为临界资源,例如打印机等,在同一时间只容许一个进程访问,须要用同步机制来实现对临界资源的访问。
虚拟技术把一个物理实体转换为多个逻辑实体。
主要有两种虚拟技术:时分复用技术和空分复用技术。
多个进程能在同一个处理器上并发执行使用了时分复用技术,让每一个进程轮流占有处理器,每次只执行一小个时间片并快速切换。
虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每一个进程都有各自的地址空间。地址空间的页被映射到物理内存,地址空间的页并不须要所有在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。
异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推动。
进程控制、进程同步、进程通讯、死锁处理、处理机调度等。
内存分配、地址映射、内存保护与共享、虚拟内存等。
文件存储空间的管理、目录管理、文件读写管理和保护等。
完成用户的 I/O 请求,方便用户使用各类设备,并提升设备的利用率。
主要包括缓冲管理、设备分配、设备处理、虛拟设备等。
若是一个进程在用户态须要使用内核态的功能,就进行系统调用从而陷入内核,由操做系统代为完成。
<figure></figure>
Linux 的系统调用主要有如下这些:
Task | Commands |
---|---|
进程控制 | fork(); exit(); wait(); |
进程通讯 | pipe(); shmget(); mmap(); |
文件操做 | open(); read(); write(); |
设备操做 | ioctl(); read(); write(); |
信息维护 | getpid(); alarm(); sleep(); |
安全 | chmod(); umask(); chown(); |
大内核是将操做系统功能做为一个紧密结合的总体放到内核。
因为各模块共享信息,所以有很高的性能。
因为操做系统不断复杂,所以将一部分操做系统功能移出内核,从而下降内核的复杂性。移出的部分根据分层的原则划分红若干服务,相互独立。
在微内核结构下,操做系统被划分红小的、定义良好的模块,只有微内核这一个模块运行在内核态,其他模块运行在用户态。
由于须要频繁地在用户态和核心态之间进行切换,因此会有必定的性能损失。
<figure></figure>
由 CPU 执行指令之外的事件引发,如 I/O 完成中断,表示设备输入/输出处理已经完成,处理器可以发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。
由 CPU 执行指令的内部事件引发,如非法操做码、地址越界、算术溢出等。
在用户程序中使用系统调用。
进程是资源分配的基本单位。
进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的建立进程和撤销进程,都是指对 PCB 的操做。
下图显示了 4 个程序建立了 4 个进程,这 4 个进程能够并发地执行。
<figure></figure>
线程是独立调度的基本单位。
一个进程中能够有多个线程,它们共享进程资源。
QQ 和浏览器是两个进程,浏览器进程里面有不少线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新连接从而发起 HTTP 请求时,浏览器还能够响应用户的其它事件。
<figure></figure>
Ⅰ 拥有资源
进程是资源分配的基本单位,可是线程不拥有资源,线程能够访问隶属进程的资源。
Ⅱ 调度
线程是独立调度的基本单位,在同一进程中,线程的切换不会引发进程切换,从一个进程中的线程切换到另外一个进程中的线程时,会引发进程切换。
Ⅲ 系统开销
因为建立或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于建立或撤销线程时的开销。相似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少许寄存器内容,开销很小。
Ⅳ 通讯方面
线程间能够经过直接读写同一进程中的数据进行通讯,可是进程通讯须要借助 IPC。
<figure></figure>
应该注意如下内容:
不一样环境的调度算法目标不一样,所以须要针对不一样环境来讨论调度算法。
批处理系统没有太多的用户操做,在该系统中,调度算法目标是保证吞吐量和周转时间(从提交到终止的时间)。
1.1 先来先服务 first-come first-serverd(FCFS)
按照请求的顺序进行调度。
有利于长做业,但不利于短做业,由于短做业必须一直等待前面的长做业执行完毕才能执行,而长做业又须要执行很长时间,形成了短做业等待时间过长。
1.2 短做业优先 shortest job first(SJF)
按估计运行时间最短的顺序进行调度。
长做业有可能会饿死,处于一直等待短做业执行完毕的状态。由于若是一直有短做业到来,那么长做业永远得不到调度。
1.3 最短剩余时间优先 shortest remaining time next(SRTN)
按估计剩余时间最短的顺序进行调度。
交互式系统有大量的用户交互操做,在该系统中调度算法的目标是快速地进行响应。
2.1 时间片轮转
将全部就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程能够执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便中止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。
时间片轮转算法的效率和时间片的大小有很大关系:
<figure></figure>
2.2 优先级调度
为每一个进程分配一个优先级,按优先级进行调度。
为了防止低优先级的进程永远等不到调度,能够随着时间的推移增长等待进程的优先级。
2.3 多级反馈队列
一个进程须要执行 100 个时间片,若是采用时间片轮转调度算法,那么须要交换 100 次。
多级队列是为这种须要连续执行多个时间片的进程考虑,它设置了多个队列,每一个队列时间片大小都不一样,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,以前的进程只须要交换 7 次。
每一个队列优先权也不一样,最上面的优先权最高。所以只有上一个队列没有进程在排队,才能调度当前队列上的进程。
能够将这种调度算法当作是时间片轮转调度算法和优先级调度算法的结合。
<figure></figure>
实时系统要求一个请求在一个肯定时间内获得响应。
分为硬实时和软实时,前者必须知足绝对的截止时间,后者能够容忍必定的超时。
对临界资源进行访问的那段代码称为临界区。
为了互斥访问临界资源,每一个进程在进入临界区以前,须要先进行检查。
// entry section// critical section;// exit section
信号量(Semaphore)是一个整型变量,能够对其执行 down 和 up 操做,也就是常见的 P 和 V 操做。
down 和 up 操做须要被设计成原语,不可分割,一般的作法是在执行这些操做的时候屏蔽中断。
若是信号量的取值只能为 0 或者 1,那么就成为了 互斥量(Mutex) ,0 表示临界区已经加锁,1 表示临界区解锁。
typedef int semaphore;semaphore mutex = 1;void P1() { down(&mutex); // 临界区 up(&mutex);}void P2() { down(&mutex); // 临界区 up(&mutex);}
使用信号量实现生产者-消费者问题
问题描述:使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才能够放入物品;只有缓冲区不为空,消费者才能够拿走物品。
由于缓冲区属于临界资源,所以须要使用一个互斥量 mutex 来控制对缓冲区的互斥访问。
为了同步生产者和消费者的行为,须要记录缓冲区中物品的数量。数量可使用信号量来进行统计,这里须要使用两个信号量:empty 记录空缓冲区的数量,full 记录满缓冲区的数量。其中,empty 信号量是在生产者进程中使用,当 empty 不为 0 时,生产者才能够放入物品;full 信号量是在消费者进程中使用,当 full 信号量不为 0 时,消费者才能够取走物品。
注意,不能先对缓冲区进行加锁,再测试信号量。也就是说,不能先执行 down(mutex) 再执行 down(empty)。若是这么作了,那么可能会出现这种状况:生产者对缓冲区加锁后,执行 down(empty) 操做,发现 empty = 0,此时生产者睡眠。消费者不能进入临界区,由于生产者对缓冲区加锁了,消费者就没法执行 up(empty) 操做,empty 永远都为 0,致使生产者永远等待下,不会释放锁,消费者所以也会永远等待下去。
#define N 100typedef int semaphore;semaphore mutex = 1;semaphore empty = N;semaphore full = 0;void producer() { while(TRUE) { int item = produce_item(); down(&empty); down(&mutex); insert_item(item); up(&mutex); up(&full); }}void consumer() { while(TRUE) { down(&full); down(&mutex); int item = remove_item(); consume_item(item); up(&mutex); up(&empty); }}
使用信号量机制实现的生产者消费者问题须要客户端代码作不少控制,而管程把控制的代码独立出来,不只不容易出错,也使得客户端代码调用更容易。
c 语言不支持管程,下面的示例代码使用了类 Pascal 语言来描述管程。示例代码的管程提供了 insert() 和 remove() 方法,客户端代码经过调用这两个方法来解决生产者-消费者问题。
monitor ProducerConsumer integer i; condition c; procedure insert(); begin // ... end; procedure remove(); begin // ... end;end monitor;
管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在没法继续执行的时候不能一直占用管程,否者其它进程永远不能使用管程。
管程引入了 条件变量 以及相关的操做:wait() 和 signal() 来实现同步操做。对条件变量执行 wait() 操做会致使调用进程阻塞,把管程让出来给另外一个进程持有。signal() 操做用于唤醒被阻塞的进程。
使用管程实现生产者-消费者问题
// 管程monitor ProducerConsumer condition full, empty; integer count := 0; condition c; procedure insert(item: integer); begin if count = N then wait(full); insert_item(item); count := count + 1; if count = 1 then signal(empty); end; function remove: integer; begin if count = 0 then wait(empty); remove = remove_item; count := count - 1; if count = N -1 then signal(full); end;end monitor;// 生产者客户端procedure producerbegin while true do begin item = produce_item; ProducerConsumer.insert(item); endend;// 消费者客户端procedure consumerbegin while true do begin item = ProducerConsumer.remove; consume_item(item); endend;
生产者和消费者问题前面已经讨论过了。
容许多个进程同时对数据进行读操做,可是不容许读和写以及写和写操做同时发生。
一个整型变量 count 记录在对数据进行读操做的进程数量,一个互斥量 count_mutex 用于对 count 加锁,一个互斥量 data_mutex 用于对读写的数据加锁。
typedef int semaphore;semaphore count_mutex = 1;semaphore data_mutex = 1;int count = 0;void reader() { while(TRUE) { down(&count_mutex); count++; if(count == 1) down(&data_mutex); // 第一个读者须要对数据进行加锁,防止写进程访问 up(&count_mutex); read(); down(&count_mutex); count--; if(count == 0) up(&data_mutex); up(&count_mutex); }}void writer() { while(TRUE) { down(&data_mutex); write(); up(&data_mutex); }}
如下内容由 @Bandi Yugandhar 提供。
The first case may result Writer to starve. This case favous Writers i.e no writer, once added to the queue, shall be kept waiting longer than absolutely necessary(only when there are readers that entered the queue before the writer).
int readcount, writecount; //(initial value = 0)semaphore rmutex, wmutex, readLock, resource; //(initial value = 1)//READERvoid reader() {<ENTRY Section> down(&readLock); // reader is trying to enter down(&rmutex); // lock to increase readcount readcount++; if (readcount == 1) down(&resource); //if you are the first reader then lock the resource up(&rmutex); //release for other readers up(&readLock); //Done with trying to access the resource<CRITICAL Section>//reading is performed<EXIT Section> down(&rmutex); //reserve exit section - avoids race condition with readers readcount--; //indicate you're leaving if (readcount == 0) //checks if you are last reader leaving up(&resource); //if last, you must release the locked resource up(&rmutex); //release exit section for other readers}//WRITERvoid writer() { <ENTRY Section> down(&wmutex); //reserve entry section for writers - avoids race conditions writecount++; //report yourself as a writer entering if (writecount == 1) //checks if you're first writer down(&readLock); //if you're first, then you must lock the readers out. Prevent them from trying to enter CS up(&wmutex); //release entry section<CRITICAL Section> down(&resource); //reserve the resource for yourself - prevents other writers from simultaneously editing the shared resource //writing is performed up(&resource); //release file<EXIT Section> down(&wmutex); //reserve exit section writecount--; //indicate you're leaving if (writecount == 0) //checks if you're the last writer up(&readLock); //if you're last writer, you must unlock the readers. Allows them to try enter CS for reading up(&wmutex); //release exit section}
We can observe that every reader is forced to acquire ReadLock. On the otherhand, writers doesn’t need to lock individually. Once the first writer locks the ReadLock, it will be released only when there is no writer left in the queue.
From the both cases we observed that either reader or writer has to starve. Below solutionadds the constraint that no thread shall be allowed to starve; that is, the operation of obtaining a lock on the shared data will always terminate in a bounded amount of time.
int readCount; // init to 0; number of readers currently accessing resource// all semaphores initialised to 1Semaphore resourceAccess; // controls access (read/write) to the resourceSemaphore readCountAccess; // for syncing changes to shared variable readCountSemaphore serviceQueue; // FAIRNESS: preserves ordering of requests (signaling must be FIFO)void writer(){ down(&serviceQueue); // wait in line to be servicexs // <ENTER> down(&resourceAccess); // request exclusive access to resource // </ENTER> up(&serviceQueue); // let next in line be serviced // <WRITE> writeResource(); // writing is performed // </WRITE> // <EXIT> up(&resourceAccess); // release resource access for next reader/writer // </EXIT>}void reader(){ down(&serviceQueue); // wait in line to be serviced down(&readCountAccess); // request exclusive access to readCount // <ENTER> if (readCount == 0) // if there are no readers already reading: down(&resourceAccess); // request resource access for readers (writers blocked) readCount++; // update count of active readers // </ENTER> up(&serviceQueue); // let next in line be serviced up(&readCountAccess); // release access to readCount // <READ> readResource(); // reading is performed // </READ> down(&readCountAccess); // request exclusive access to readCount // <EXIT> readCount--; // update count of active readers if (readCount == 0) // if there are no readers left: up(&resourceAccess); // release resource access for all // </EXIT> up(&readCountAccess); // release access to readCount}
<figure></figure>
五个哲学家围着一张圆桌,每一个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,须要先拿起本身左右两边的两根筷子,而且一次只能拿起一根筷子。
下面是一种错误的解法,考虑到若是全部哲学家同时拿起左手边的筷子,那么就没法拿起右手边的筷子,形成死锁。
#define N 5void philosopher(int i) { while(TRUE) { think(); take(i); // 拿起左边的筷子 take((i+1)%N); // 拿起右边的筷子 eat(); put(i); put((i+1)%N); }}
为了防止死锁的发生,能够设置两个条件:
#define N 5#define LEFT (i + N - 1) % N // 左邻居#define RIGHT (i + 1) % N // 右邻居#define THINKING 0#define HUNGRY 1#define EATING 2typedef int semaphore;int state[N]; // 跟踪每一个哲学家的状态semaphore mutex = 1; // 临界区的互斥semaphore s[N]; // 每一个哲学家一个信号量void philosopher(int i) { while(TRUE) { think(); take_two(i); eat(); put_two(i); }}void take_two(int i) { down(&mutex); state[i] = HUNGRY; test(i); up(&mutex); down(&s[i]);}void put_two(i) { down(&mutex); state[i] = THINKING; test(LEFT); test(RIGHT); up(&mutex);}void test(i) { // 尝试拿起两把筷子 if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) { state[i] = EATING; up(&s[i]); }}
进程同步与进程通讯很容易混淆,它们的区别在于:
进程通讯是一种手段,而进程同步是一种目的。也能够说,为了可以达到进程同步的目的,须要让进程进行通讯,传输一些进程同步所须要的信息。
管道是经过调用 pipe 函数建立的,fd[0] 用于读,fd[1] 用于写。
#include <unistd.h>int pipe(int fd[2]);
它具备如下限制:
<figure></figure>
也称为命名管道,去除了管道只能在父子进程中使用的限制。
#include <sys/stat.h>int mkfifo(const char *path, mode_t mode);int mkfifoat(int fd, const char *path, mode_t mode);
FIFO 经常使用于客户-服务器应用程序中,FIFO 用做汇聚点,在客户进程和服务器进程之间传递数据。
<figure></figure>
相比于 FIFO,消息队列具备如下优势:
它是一个计数器,用于为多个进程提供对共享数据对象的访问。
容许多个进程共享一个给定的存储区。由于数据不须要在进程之间复制,因此这是最快的一种 IPC。
须要使用信号量用来同步对共享存储的访问。
多个进程能够将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件,而是使用使用内存的匿名段。
与其它通讯机制不一样的是,它可用于不一样机器间的进程通讯。
<figure></figure>
主要有如下四种方法:
把头埋在沙子里,伪装根本没发生问题。
由于解决死锁问题的代价很高,所以鸵鸟策略这种不采起任务措施的方案会得到更高的性能。
当发生死锁时不会对用户形成多大影响,或发生死锁的几率很低,能够采用鸵鸟策略。
大多数操做系统,包括 Unix,Linux 和 Windows,处理死锁问题的办法仅仅是忽略它。
不试图阻止死锁,而是当检测到死锁发生时,采起措施进行恢复。
<figure></figure>
上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。
图 a 能够抽取出环,如图 b,它知足了环路等待条件,所以会发生死锁。
每种类型一个资源的死锁检测算法是经过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,若是访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。
<figure></figure>
上图中,有三个进程四个资源,每一个数据表明的含义以下:
进程 P1 和 P2 所请求的资源都得不到知足,只有进程 P3 能够,让 P3 执行,以后释放 P3 拥有的资源,此时 A = (2 2 2 0)。P2 能够执行,执行后释放 P2 拥有的资源,A = (4 2 2 1) 。P1 也能够执行。全部进程均可以顺利执行,没有死锁。
算法总结以下:
每一个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。
在程序运行以前预防发生死锁。
例如假脱机打印机技术容许若干个进程同时输出,惟一真正请求物理打印机的进程是打印机守护进程。
一种实现方式是规定全部进程在开始执行前请求所须要的所有资源。
给资源统一编号,进程只能按编号顺序来请求资源。
在程序运行时避免发生死锁。
<figure></figure>
图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共须要的资源数,Free 表示还有可使用的资源数。从图 a 开始出发,先让 B 拥有所需的全部资源(图 b),运行结束后释放 B,此时 Free 变为 5(图 c);接着以一样的方式运行 C 和 A,使得全部进程都能成功运行,所以能够称图 a 所示的状态时安全的。
定义:若是没有死锁发生,而且即便全部进程忽然请求对资源的最大需求,也仍然存在某种调度次序可以使得每个进程运行完毕,则称该状态是安全的。
安全状态的检测与死锁的检测相似,由于安全状态必需要求不能发生死锁。下面的银行家算法与死锁检测算法很是相似,能够结合着作参考对比。
一个小城镇的银行家,他向一群客户分别承诺了必定的贷款额度,算法要作的是判断对请求的知足是否会进入不安全状态,若是是,就拒绝请求;不然予以分配。
<figure></figure>
上图 c 为不安全状态,所以算法会拒绝以前的请求,从而避免进入图 c 中的状态。
<figure></figure>
上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还须要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。
检查一个状态是否安全的算法以下:
若是一个状态不是安全的,须要拒绝进入这个状态。
虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序得到更多的可用内存。
为了更好的管理内存,操做系统将内存抽象成地址空间。每一个程序拥有本身的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不须要映射到连续的物理内存,也不须要全部页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并从新执行失败的指令。
从上面的描述中能够看出,虚拟内存容许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不须要所有调入内存就能够运行,这使得有限的内存运行大程序成为可能。例若有一台计算机能够产生 16 位地址,那么一个程序的地址空间范围是 0~64K。该计算机只有 32KB 的物理内存,虚拟内存技术容许该计算机运行一个 64K 大小的程序。
<figure></figure>
内存管理单元(MMU)管理着地址空间和物理内存的转换,其中的页表(Page table)存储着页(程序地址空间)和页框(物理内存空间)的映射表。
一个虚拟地址分红两个部分,一部分存储页面号,一部分存储偏移量。
下图的页表存放着 16 个页,这 16 个页须要用 4 个比特位来进行索引定位。例如对于虚拟地址(0010 000000000100),前 4 位是存储页面号 2,读取表项内容为(110 1),页表项最后一位表示是否存在于内存中,1 表示存在。后 12 位存储偏移量。这个页对应的页框的地址为 (110 000000000100)。
<figure></figure>
在程序运行过程当中,若是要访问的页面不在内存中,就发生缺页中断从而将该页调入内存中。此时若是内存已无空闲空间,系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。
页面置换算法和缓存淘汰策略相似,能够将内存当作磁盘的缓存。在缓存系统中,缓存的大小有限,当有新的缓存到达时,须要淘汰一部分已经存在的缓存,这样才有空间存放新的缓存数据。
页面置换算法的主要目标是使页面置换频率最低(也能够说缺页率最低)。
OPT, Optimal replacement algorithm
所选择的被换出的页面将是最长时间内再也不被访问,一般能够保证得到最低的缺页率。
是一种理论上的算法,由于没法知道一个页面多长时间再也不被访问。
举例:一个系统为某进程分配了三个物理块,并有以下页面引用序列:
开始运行时,先将 7, 0, 1 三个页面装入内存。当进程要访问页面 2 时,产生缺页中断,会将页面 7 换出,由于页面 7 再次被访问的时间最长。
LRU, Least Recently Used
虽然没法知道未来要使用的页面状况,可是能够知道过去使用页面的状况。LRU 将最近最久未使用的页面换出。
为了实现 LRU,须要在内存中维护一个全部页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。
由于每次访问都须要更新链表,所以这种方式实现的 LRU 代价很高。
<figure></figure>
NRU, Not Recently Used
每一个页面都有两个状态位:R 与 M,当页面被访问时设置页面的 R=1,当页面被修改时设置 M=1。其中 R 位会定时被清零。能够将页面分红如下四类:
当发生缺页中断时,NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。
NRU 优先换出已经被修改的脏页面(R=0,M=1),而不是被频繁使用的干净页面(R=1,M=0)。
FIFO, First In First Out
选择换出的页面是最早进入的页面。
该算法会将那些常常被访问的页面也被换出,从而使缺页率升高。
FIFO 算法可能会把常用的页面置换出去,为了不这一问题,对该算法作一个简单的修改:
当页面被访问 (读或写) 时设置该页面的 R 位为 1。须要替换的时候,检查最老页面的 R 位。若是 R 位是 0,那么这个页面既老又没有被使用,能够马上置换掉;若是是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的同样,而后继续从链表的头部开始搜索。
<figure></figure>
Clock
第二次机会算法须要在链表中移动页面,下降了效率。时钟算法使用环形链表将页面链接起来,再使用一个指针指向最老的页面。
<figure></figure>
虚拟内存采用的是分页技术,也就是将地址空间划分红固定大小的页,每一页再与内存进行映射。
下图为一个编译器在编译过程当中创建的多个表,有 4 个表是动态增加的,若是使用分页系统的一维地址空间,动态增加的特色会致使覆盖问题的出现。
<figure></figure>
分段的作法是把每一个表分红段,一个段构成一个独立的地址空间。每一个段的长度能够不一样,而且能够动态增加。
<figure></figure>
程序的地址空间划分红多个拥有独立地址空间的段,每一个段上的地址空间划分红大小相同的页。这样既拥有分段系统的共享和保护,又拥有分页系统的虚拟内存功能。
<figure></figure>
读写一个磁盘块的时间的影响因素有:
其中,寻道时间最长,所以磁盘调度的主要目标是使磁盘的平均寻道时间最短。
FCFS, First Come First Served
按照磁盘请求的顺序进行调度。
优势是公平和简单。缺点也很明显,由于未对寻道作任何优化,使平均寻道时间可能较长。
SSTF, Shortest Seek Time First
优先调度与当前磁头所在磁道距离最近的磁道。
虽然平均寻道时间比较低,可是不够公平。若是新到达的磁道请求老是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来讲,两端的磁道请求更容易出现饥饿现象。
<figure></figure>
SCAN
电梯老是保持一个方向运行,直到该方向没有请求为止,而后改变运行方向。
电梯算法(扫描算法)和电梯的运行过程相似,老是按一个方向来进行磁盘调度,直到该方向上没有未完成的磁盘请求,而后改变方向。
由于考虑了移动方向,所以全部的磁盘请求都会被知足,解决了 SSTF 的饥饿问题。
<figure></figure>
如下是一个 hello.c 程序:
#include <stdio.h>int main(){ printf("hello, world"); return 0;}
在 Unix 系统上,由编译器把源文件转换为目标文件。
gcc -o hello hello.c
这个过程大体以下:
<figure></figure>
静态连接器以一组可重定向目标文件为输入,生成一个彻底连接的可执行目标文件做为输出。连接器主要完成如下两个任务:
<figure></figure>
静态库有如下两个问题:
共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中一般用 .so 后缀来表示,Windows 系统上它们被称为 DLL。它具备如下特色:
<figure></figure>
</section>
<section data-role="outer">
文能码字,武能coding,是我黄小斜,不是黄老邪噢。
推荐阅读:
</section>
<section data-tools="135编辑器" data-id="94248">
<section>
<section></section>
<section>
<section>
<section data-brushtype="text">喜欢本文的话,就点一下“在看”吧</section>
</section>
</section>
</section>
</section>
</section>
</section>