操作系统学习之进程管理

一、进程概念

系统运行过程中CPU的活动称为进程。(作业/用户程序/任务)

进程是执行中的程序。

进程:程序代码(文本段/代码段)+当前活动(PC和寄存器值表示)+堆栈段(临时数据、返回地址、局部变量)+数据段(全局变量)+堆

关于程序和进程:程序是被动实体,是静态的,固定的;

                             进程是活动实体,随着程序计数器的变化指示下一条执行的指令,所以进程是动态的,是不断变化的。

二、进程状态

进程状态可分为五种:

  • 新的(New):进程正在被创建
  • 运行(Running):指令正在被执行
  • 等待(Waiting):进程等待某个事件完成(I/O或特定事件)
  • 就绪(Ready):进程等待分配处理器
  • 终止(Terminated):进程完成执行

这里有一个进程状态转换图

通过这个图可以看到进程不可以由Ready状态直接变为Waiting状态,也不可以直接由Waiting直接变为running,需要通过一些中间态来完成。

三、进程控制块

这个进程控制块(PCB)就是在上一篇复习博客中提到的数据结构,用来存储进程的信息,包括进程的状态、进程编号(pid)、程序计数器(PC,用来指示下一条指令)、寄存器值、内存界限(基址+界限寄存器)、打开文件列表等。

四、进程调度

这部分的算法会在第五章的复习博客中提到,这里只进行大概的介绍。

当我们使用多道程序设计的时候,会使得CPU利用率达到最大化,当使用分时系统的时候是为了快速切换进程方便,为了达到这些目的需要对进程进行调度。

1.调度队列

这里大概可以分为两类,一种是就绪队列(Ready Queue)和等待队列(Waiting Queue)。

针对进程的五种状态,New状态肯定是不需要队列存储了,Running状态下每次只会存在一个进程,也就是每次只有一个进程正在执行,所以所有的等待被执行的Ready状态下的进程就需要进入一个队列存储,这里引入了就绪队列。Terminated状态同样不需要队列,而根据状态转换图可以知道,由Running回到Ready需要经过Waiting,Waiting状态是在等待I/O 或者特定事件完成,这个地方是需要一个队列将Waiting状态下的进程存储下来,事件完成则出队的,这里引入等待队列,也叫阻塞队列。

关于队列的实现:通常使用链表来实现,头节点指向第一个和最后一个PCB的指针,每个PCB包括指向下一个PCB的指针域。

针对进入CPU开始执行的进程,有几种情况:发生I/O事件导致进程进入I/O 等待队列,I/O完成后进入就绪队列;创建一个新的子进程,父进程进入等待队列,子进程结束返回父进程,父进程在就绪队列中等待被选择;产生中断,导致当前进程换出CPU,在PCB中保存当前进程的上下文信息,进入就绪队列,中断处理程序执行完成后从就绪队列中选择进程继续执行。

2.调度程序

  • 长期调度:控制内存中进程上限,将进程调入内存
  • 中期调度:将内存中进程保留并调入硬盘
  • 短期调度:将内存中的进程调入CPU

具体细节会在后面的第五章复习博客详细写明。

3.上下文切换

根据前面提到的,CPU中进行进程调度时需要将正在执行中的进程信息保存到PCB,然后换入将要在CPU中执行的进程,恢复这个进程PCB中保存的信息,这两个过程在书中称为“状态保存”和“状态恢复”。

上下文切换是一种额外开销,因为在这段时间内系统不能做什么有用的事情。

这种切换活动的速度依赖于内存读取速度、寄存器的数量、特殊指令等,一般是毫秒级的时间。

五、进程操作

1.进程创建

新进程是通过原来存在的进程创建的,创建其他进程的进程称为父进程,创建出的新进程称为子进程。

根据前面提到的,每个进程都有一个特定的唯一的标识符pid来识别进程,当系统中存在多个进程在队列中时,这种标识符的存在就显得特别必要。

当父进程创建子进程时,执行起来有两种可能:

  • 父进程、子进程并发执行
  • 父进程进入等待状态,等待子进程执行完返回

同时创建了子进程就需要一定的地址空间,这个地址空间也有两种可能:

  • 子进程和父进程共享程序和数据
  • 子进程创建一个新的地址空间

以Unix系统为例,创建进程函数fork(),新进程通过复制原来进程的地址空间形成,父进程和子进程都执行fork之后的指令,但是对于父子进程,子进程的返回值是0,父进程则是非0的。

2.进程终止

进程执行完最后的指令后会调用exit()函数,删除自身终止进程,释放所有资源。

进程通过适当的系统调用可以终止其他进程,一般来说是要父进程可以终止其子进程的。

父进程终止子进程的原因:

  • 子进程使用超过了分配的资源
  • 分配给子进程的任务不需要了
  • 其父进程被终止了

六、进程间通信

两种进程间通信机制:共享内存模型、消息传递模型。

共享内存模型:建立起一块供协作进程共享的内存区域,进程通过访问或修改这块内存区域中的内容完成通信。

消息传递模型:通过模型示意图看到是在内核中有一块区域供进程读或写信息,完成通信功能。

对比:传递较少数量的数据使用消息传递模型更好一点;共享内存的通信速度比消息传递模型更快,因为消息传递中是使用内核的,会涉及很多系统调用的操作,这些是额外开销,时间会相对于进行内存访问的共享内存模型更长一点。

1.共享内存模型

如果进程要使用一块共享内存的话,要将这块共享内存加入到自己的地址空间中,同时共同访问和修改的数据形式和位置是由进程本身来决定的而不是操作系统。

生产者-消费者问题:生产者进程产生信息以供消费者进程使用,使用一个缓冲来被生产者填充,被消费者使用,这个缓冲存在于两个进程共同使用的共享内存区域内,两者的内存必须是同步的,防止生产者向已经满的缓冲中添加信息或者消费者从空的缓冲中取信息。

缓冲分类:无限缓冲,不会出现上述生产者添加信息结果缓冲已满的情况;有限缓冲,可能会导致前面提到的情况,缓冲为空时消费者要等待,等待生产者向这个区域加入信息,缓冲满的时候生产者要等待,等待消费者从缓冲中取出信息。

代码实例:

针对这两个程序来看,允许缓冲的最大项数是BUFFER_SIZE-1,当时考虑了在生产者进程中把填充进去的语句和判断语句顺序换一下,这样确实是buffer中可以填满,但是取的时候也是只能取BUFFER_SIZE-1这么多,从利用率上来说是一样的。

2.消息传递模型

“消息传递提供一种机制以允许进程不必通过共享地址空间来实现通信和同步”

消息传递工具至少要可以发送消息和接收消息,消息可以是定长的或者是变长的,这是两种实现方法。

直接通信:进程仅需知道相互通信的标识符,一个线路只与两个进程有关,每对进程之间也只有一条线路。

                  send(P, message): 发送消息到进程 P 。
                  receive(id, message): 接收来自任何进程的消息,变量 id 设置成与其通信的进程名称。

间接通信:设置一个邮箱来存储消息,一个进程可能通过许多不同的邮箱与其他邮箱通信,但两个进程仅在其共享至少一个邮箱时可以通信。

                  send(A, message): 发送一个消息到邮箱 A 。
                  receive(A, message): 接收来自邮箱 A 的消息。

同步异步和阻塞非阻塞:这个地方没有理解得太好,查了一些资料比较喜欢这个作者的解释:

https://www.zhihu.com/question/19732473/answer/20851256

缓冲:通信进程交换的信息都驻留在临时队列中。队列的三种方法:①零容量,不能有任何处于等待的消息,阻塞发送,直到消费者接收到消息。②有限容量:最多n个消息等待,队列满时阻塞发送。③无限容量:不存在阻塞发送的情况。

七、关于套接字socket

这部分课上没怎么讲,但是听学姐说有的公司面试会问,就大概写一下吧。

Socket定义为通信的端点,由IP地址和端口号组成。

猜你喜欢

转载自blog.csdn.net/LieberVater/article/details/84075510