一.CPU管理的直观想法
1.操作系统的核心
操作系统的核心是管理计算机硬件。显而易见,CPU是计算机系统中最核心的硬件。操作系统在管理CPU的时候引出了多进程图像,通过多进程图像操作系统管理好了CPU,由此也便管理好了其它硬件。
2.如何管理CPU?
管理CPU,先要使用CPU。
1).那么CPU如何工作呢?
当给了一个初始的PC地址后,CPU就自动的从内存中取指执行。
2).管理CPU的最直观方法就是设好PC初值就行了。但这样有没有什么问题呢?
举例一:
如下面这段代码,CPU执行这段代码的时候PC指针会先指向int main()的地址处,然后依次向下执行代码段。
int main(int argc,char *argv[]){
int i,to,*fp,sum=0;
to=atoi(argv[1]);
for(i=1;i<=to;i++){
sum=sum+i;
fprintf(fp,"%d",sum);
}
}
我们可以把fprintf()用一条其它的计算语句代替。这时这两段程序执行的速度如何呢?
第一段程序运行速度:0.015/10^7(秒/每次)
第二段程序运行速度:0.859/10^3(秒/每次)
0.015/107 : 0.859/103 就等于 5.7x105 : 1也就相当于106 : 1
那么就是说执行106次计算才相当于执行一次IO操作。IO操作需要经过磁盘等一系列处理,因此它的执行过程缓慢。
举例二:
有一个代码段如下所示。
如果按照CPU就自动的从内存中取指执行的思路就会这样实现:
在IO操作的过程中,无须CPU动手。如果按照计算速度:IO操作速度=106 : 1来看,整个代码段若总共需要4秒,CPU只会运行2秒。CPU的利用率为50%。而在实际写代码的时候可能计算20次就会需要进行一次IO操作,此时CPU的利用率仅
为(20 x 1/106)/ (20 x 1/106+106)。总之就是很低。
举例三:
平常我们烧热水的时候是怎么做的呢?
先把冷水倒进水壶中,再把水壶放在炉子上。这时我们就可以做别的事情了,当水壶发出嘟嘟的声响时,就相当于提醒我们水开了,然后我们处理,用热水去干其他事情。
我们人就相当CPU,而炉子就相当于别的设备。CPU想要完成一项工作时不用它动手,借助其他工具来进行工作的实现。这一整段时间内CPU可以去干其他的活儿,只等事情做完处理事情完成的结果。
3.多道程序交替执行
如下所示:
那么我们可以计算出各个硬件的利用率:
单道程序 | 多道程序 | |
---|---|---|
CPU利用率 | 40/80=50% | 40/45=89% |
DEV1利用率 | 15/80=18.75% | 15/45=33% |
DEV2利用率 | 25/80=31.25% | 25/45=56% |
4.一个CPU上交替的执行多个程序:并发。并发是如何做到的呢?
首先,需要明确的一点是:当由程序1到程序2时,OS修改PC指针,让它指向程序2的首地址。但只要修改寄存器PC就可以了吗?
如果只是进行PC的切换,那么在PC切出去的时候程序1的信息是这样的:
切回来的时候程序1的信息就变成了这样:
因此在PC切出去到程序二的时候还需要保存程序1中当前的信息,包括数据等。每个程序都有一个存放信息的结构:PCB。从而使这些信息呈现出这样的效果:
总结一下:运行的程序和静态的程序时不一样的,最大的不一样就是运行的程序需要记录运行过程中的数据等信息而静态程序不需要。所有的不一样的保存在PCB中。要描述这些不一样,便需要用一个概念来区分运行的程序和静态的程序。便给运行的程序起名叫进程,一个运行的程序就叫一个进程。
关于运行的程序和静态的程序还可以通过一个小故事来理解:
就像我们看一本书一样,在没看这本书之前就像静态的程序,不需要我们记录任何事情。而当我们看书看了几十页后,忽然有别的事情去做了,在去做别的事情之前一定得先记录我们现在读到了多少页,以便于我们做完别的事情后又从这一页开始阅读。这就变成了运行中的程序了。
二.多进程图像
为了让CPU更加高效的执行,一个进程跟着一个进程来执行是不够的,必须是跑多个进程,让它们可以交替的执行。
1.什么是多进程图像?
在上层用户的眼中,
多进程是用户启动了多个进程,它们按照自己的意愿或开或停的样子。
在操作系统的眼中,
多进程是OS需要管理,记录好各个进程的状态,并且根据PCB来推进多个进程。
总结:操作系统需要把多个进程的信息记录好,按照合理的次序推进(也就是分配资源,进行调度等),这就是多进程图像。
2.多进程图像的存在时间是从计算机启动到关机结束。那么多进程又是如何在计算机中执行的呢?
- (1)main中的fork()创建了第一个进程
if(!fork()) {init();}
- (2)init执行了shell
- (3)shell再启动了其他进程。
shell中的一段代码如下:
含义为输入一个命令启动了一个进程。返回shell再启动其他进程。int main(int argc,char *argv[]){ while(1){ scanf("%s",cmd); if(!fork()){ exec(cmd); } wait(); } }
3.在计算机中如何查看多进程呢?
打开Windows的任务管理器便可以看到各个进程占用的CPU,内存,磁盘,网络等。
我们可以发现:操作系统通过管理进程来管理用户对计算机的使用。计算机执行一项任务其实就是启动一个进程,一个进程的结束就是一项任务的完成。而计算机开始工作就是从启动一个进程开始。所以进一步说明了多进程图像是操作系统的核心图像。
4.如何组织多进程的?
(1)步骤:
- 有一个进程正在执行
- 有一些进程在就绪队列中等待执行
- 有一些进程在等待某事件比如一些进程在磁盘等待队列中等待从磁盘中读数据。
(2)进程状态图
多进程的组织:PCB(Process Control Block:用来记录进程信息的数据结构)+状态+队列
该图称为进程状态图。
它能给出进程生存期的清晰描述
它是认识操作系统进程管理的一个窗口。
5.多进程如何交替?
(1)步骤:
- 启动一个磁盘读写进程时,
- pCur.state = ‘W’;
- 将pCur放到阻塞队列中;
- 执行schedule();(切换)
schedule中的一段代码如下:
含义为pNew得到就绪队列中的下一个进程的PCB。再将pCur切换到pNew中,执行pNew所显示的进程。
schedule()
{
pNew = getNext(ReadyQueue);
(调度)
switch_to(pCur,pNew);
}
(2)交替的三个部分:队列操作+调度+切换
A.getNext()的简介
也就是进程的调度。
以下是两种简单的调度:
1.FIFO
- FIFO显然是公平的策略
- FIFO显然没有考虑进程执行的任务的区别
2.priority
- 优先级该怎么设定?可能会使某些进程饥饿
B.switch_to()是如何执行的?
伪代码:
switch_to(pCur,pNew) {
pCur.ax = CPU.ax;
pCur.bx = CPU.bx;
...
pCur.cs = CPU.cs;
pCur.retpc = CPU.pc;
CPU.ax = pNew.ax;
CPU.bx = pNew.bx;
...
CPU.cs = pNew.cs;
CPU.retpc = pNew.pc;
}
但若是要精确地描述这些信息,就必须使用汇编代码。
6.对进程如何影响?
假设多个进程同时存在于内存,可能会出现下面的问题:
解决的办法:多进程的地址空间分离。
多进程的地址空间分离是内存管理的主要内容。
如下所示:
这样一来:
进程1的映射表将访问限制在进程1范围内
进程1根本访问不到其他进程的内容
各个进程就可以在内存中共存。就便于更好的管理。
7.多进程如何合作?
(1)想一想打印工作过程这就是多进程合作:
- 应用程序提交打印任务
- 打印任务被放进打印队列
- 打印进程从队列中取出任务
- 打印进程控制打印机打印
(2)一个例子:
就拿打印工作过程来说。生产者进程就是应用程序的任务,消费者进程就是打印进程。
那么也有可能这样进行:生产者进程执行一半后消费者进程开始执行,执行一半后又切换到生产者进程去。那么两个合作的进程都要修改counter。如下所示:
P.register = counter;
P.register = P.register + 1;
C.register = counter;
C.register = C.register - 1;
counter = P.register;
counter = C.register;
最后counter=4。
但是如果按照常理来说,应该是生产者进程执行完毕后消费者进程再执行,counter最终等于5
如何解决这一矛盾呢?
我们需要明白,生产者进程执行过程中不能随意切换另一个进程,只有当它执行完毕后才会执行另一个进程。核心在于进程同步(即合理的推进顺序)。
方法就是:写counter时阻断其他进程访问counter。具体步骤如下: