CPU管理 && 多进程图像

CPU管理 && 多进程图像

要管理CPU,先要学会使用CPU

CPU的工作方式在操作系统学习之初就已经提过:

  • 取值执行
    • 程序存放在内存中,每段指令对应一个地址
    • CPU发出取指命令,将想取地址通过地址总线传到PC
    • 内存根据地址取出对应地址的指令
    • 从总线传回,CPU解释执行

总之,就是自动的取指--执行

所以,管理CPU最直观的方法就是,设置PC的初值,CPU就能根据取指执行依次执行下去

好了现在CPU执行起来了,但这样的管理方式是否有什么问题?看下面一段程序:

int main(int argc,char* argv[]){
    int i,to,*fp,sum=0;
    to = atoi(agv[1]);
    for(i = 1; i <=to; i++){
        sum = sum + i;
        fprintf(fp,"%d",sum);
    }
}
  • 注意fprintf(),这是一条IO指令。如果将其替换成一条计算语句,整段程序运行时长前后比值约为10^6:1
  • 也就是说,IO指令特别特别慢

如果一段程序中IO指令的占比又很大,那么整个程序执行所耗时间中,IO指令会占非常大的比例。

  • 换句话说,CPU很多时间都在摸鱼,等待IO指令完成在继续干活,这样CPU利用率就太低了。这样管理就出现了问题

怎么压榨CPU?

CPU管理的核心:并发

并发概念

让CPU不再一次局限于单段程序,而是多道程序,交替执行

由此得出一个重要的概念:并发

  • 并发:一个CPU上交替地执行多个程序

注意并发与并行概念的不同:

并发(Concurrency):并发指的是一个系统能够同时处理多个任务。在一个单处理器系统中,多个任务交替执行,因为处理器实际上在一段时间内只能执行一个任务。这种情况下,因为多个任务交替执行的效果,看起来好像同时在运行。在一个多处理器系统中,多个任务可以真正同时执行,但是在并发系统中,即使没有多个处理器也能实现同时执行的效果。

并行(Parallelism):并行是指系统同时执行多个任务,每个任务都在不同的处理器核心上执行,因此它们真正同时进行。在一个拥有多核心处理器的系统中,可以实现真正的并行执行,每个核心处理器同时处理不同的任务。

并发与并行的区别:并发强调的是在单位时间内有多个任务同时进行,但是这些任务可能是交替执行的。而并行强调的是在同一时刻有多个任务同时进行。

通俗一点,一个系统可以同时处理多个任务,但这些任务可能只是交替执行,这是并发;而如果多个任务确实同时执行,那么这是并行。

这样,执行到像IO这样慢的步骤时,CPU就可以切换到另一个程序继续执行

如何实现并发?

要实现不同程序间的切换执行,首先想到修改寄存器PC。但只修改寄存器P C就可以实现吗?

很显然,还需要用栈来记录当前程序的一些信息。这和汇编模块化设计中用data段存数据一样。

  • 由此,每个程序有了一个存放信息的结构:PCB,Process Control Block,进程控制块

就像我们正在看书,突然被人叫走做别的事,我们就应当停下来,记录当前页码以及故事情节,然后离开,这样回来后才能继续阅读。

这样,这些运行的程序就和静态的程序不同了。具体不同就体现在PCB表中。

由此引出一个概念:进程

进程描述了运行中的程序,如上图中程序1和程序2就是两个进程。

进程的名字十分形象

  • 进程有开始、结束。程序没有;
  • 进程会走走停停,是动态的,有状态的。程序没有
  • 进程需要记录ax,bx......程序不用;

总结

由此,我们描述了CPU的管理:

  • 使用CPU:启动进程,执行进程
  • 更高效压榨使用CPU:启动多个进程,交替执行多个进程
  • 所谓多进程图像,便是CPU执行多个进程的过程,也是CPU执行的核心

多进程图像

前文讲到,为了让CPU更好的工作,我们需要让CPU执行多进程,而这个过程如何表征呢?

  • 对于用户而言
    • 就是一个个 PID 进程号;
    • 可供用户查看各进程运行情况;
  • 对于下层操作系统而言
    • 负责管理 各个进程;具体为记录情况、按照合理的次序推进;
    • 分配资源、进行调度;

多进程图像从开机一直存在到关机结束。

开机之后的多进程图像

  • 系统启动时,最后启动的 main.c 中最后执行了fork()

    if(!fork()){init();}
    // fork,启动进程的接口
    

    代码意思是:启动一个进程,执行init() ,即执行 shell,接下来就能再 shell 里操作,这就是计算机提供给用户使用的界面(初代版本)。

    可以理解为,操作系统要让用户使用计算机,需要创建一个初始化的进程。

    补充1:

    shell是一个子进程,父进程(main函数)因为成功创建子进程,所以fork()>0 不进init 而子进程fork()==0 进入init,启动shell

    补充2:

    fork()函数返回值是0或1, 返回0代表当前进程是新fork出来的子进程, 非零(也就是为1)代表当前进程为父进程, if条件里的就是父进程的逻辑,一直等待用户输入命令, 然后执行, 一直重复进行

  • shell 再根据用户输入启动其他进程,执行用户的命令也是在创建进程;

    // shell 的核心代码
    int main(){
        while(1){
            scanf("%s",cmd);
            if(!fork()){
                exec(cmd);
                wait();
            }
        }
    }
    

    image

  • 此后,计算机每执行一个任务,就开启一个进程。

操作系统如何组织实现多进程图像

为了实现多进程图像,操作系统需要解决哪些问题?

  1. 多进程如何组织、存储?
  2. 多进程如何切换
  3. 多进程交替时,如何相互配合、影响?

多进程如何组织、存储

  • 操作系统感知进程依赖于PCB,组织和存放进程也依赖于PCB,通过PCB结构体形成的一些数据结构(队列)组织多进程

  • 组织好多进程,才能合理推进多进程。

image.png

如何推进多进程

  • 一个进程正在执行

  • 另一些进程在排队(就绪队列)等待执行

  • 还有一些在等待触发事件,即使排到也不能调度执行

    比如上图中的第三列PCB,在等待磁盘操作。

    PCB是用来记录进程信息的数据结构

  • 总结:多进程对应的PCB分别放在不同的地方,执行不同的处理。

  • 把进程通过状态区分开来,通过操作系统对进程状态的转移控制,多进程就向前推进了。

    image.png

多进程如何交替、切换

例:一个进程启动磁盘读写,等待时进行进程切换

  • schedule()调度函数

  • getNext从就绪队列中选出CPU执行的下一个进程

    进程的选择涉及到进程调度问题,后续会详细展开

  • swithch_to()就是用 PCB 进行进程上下文的切换pCurpNew分别指当前进程的 PCB 和调度得到的下一个进程的 PCB ,即进行执行现场的更替。

交替的三部分:

  • 队列操作+调度+切换

image.png

进程如何调度?

  • 这里先讲两个基础调度算法。
    • FIFO,First In First Out.
      • 显然是比较公平的策略,但是没有考虑进程执行的任务轻重缓急;
    • Priority.
      • 对进程赋予优先级,但如何赋予也是个问题。

切换进程

  • 调度找到下一个占用CPU的进程后,就要进行切换;

    这个过程需要精细控制,所以需要 汇编代码,下图为伪代码;

  • 做的事情也不难想象,先把将要停下的进程信息保存到PCB1中(将当前CPU的各种信息(寄存器等)保存到pCur中),

    再从将要进行的进程的PCB2中取出信息赋到对应寄存器/位置(将pNew中的寄存器等信息恢复到CPU中

    image.png

多进程交替时,如何相互影响?

互斥、锁的概念。

  • 多进程看似不打照面,但实际上它们同时在一个内存来存放。

    多个进程交替执行会相互影响,包括正面的多进程合作,负面的内存地址冲突等等

    image.png

  • 比如,进程1中,修改了某个地址的值,而这个地址,正好是进程2包含的地址,这时就会引起进程2崩溃。

  • 如何解决进程间矛盾?

    限制对进程2地址的读写。即:内存映射

    其实涉及内存管理了,可见内存管理也服务于CPU管理的多进程图像。

    通过一个映射表,将真实物理地址转化为虚拟存储地址

    image.png

    两个进程的100内存地址,是虚拟逻辑地址,会映射到不同的物理内存;下图中展示了两个进程的100地址分别映射到了物理地址780和1260

    image.png

    还有一些时候,进程之间需要进行合作,如何进行进程间合作?

举例1(浅显):

不同的应用程序提交打印任务,打印任务会被放到“待打印文件队列”

打印进程会从“待打印文件队列”中一个接一个的取出打印任务,控制打印机打印

如果对存入打印进程的任务不进行管理,如任务1没放完,任务2就开始放,会出现打印的东西一半属于任务1一半属于任务2,引起错误

举例2(稍深):生产者-消费者实例

image.png

  • 生产者和消费者通过共享数据buffer[]进行合作

  • 如果缓冲区满了,就不应该再放了,

    counter记录,如果==buffer_size,说明满了,死循环;没满则counter++

    如果要避免缓冲区满而还向里放的情况,counter 这个信号量必须要保持正确(我突然感觉这是工程代码调试的一个关键)

  • 如果多个进程都在内存中交替执行,counter可能就会出错。

    下面是个具体的例子:

    初始counter=5,生产者执行counter++,消费者执行counter--,在寄存器层面将会是:

    // 生产者P
    register = counter;
    register = register+1;
    counter = register;
    
    // 消费者C
    register = counter;
    register = register -1;
    counter = register;
    

    当生产者的程序执行到中间切换到消费者,可能的代码序列如右上角所示,counter 直接乱了。后续合作就也会乱套。

    image.png

  • 解决合作问题(合作各方的合理推进顺序)的核心在于 :进程同步

    给 counter 上锁,即写 counter 时阻断其他进程访问 counter.

    image.png

posted @ 2024-07-01 20:37  _Elysia  阅读(1)  评论(0编辑  收藏  举报