• 进程管理(一)——进程


    前言-为什么需要进程?

    进程这个概念是在多道程序设计中引入的,在此之前的是批处理程序,所以进程主要是用来解决程序不能并发执行从而导致 CPU 利用率低下这个问题的。

    进程管理,就是在程序之上抽象出了进程的概念,然后通过进程状态、上下文切换、中断、调度等等手段,最终实现使程序在多道程序环境下能并发执行,并能对并发执行的程序加以控制和描述

    进程

    Process,一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。

    一个进程包含以下信息:

    • 代码
    • 数据
    • 状态寄存器,如 CPU 的状态,PC 指针等
    • 通用寄存器,如堆栈指针寄存器等
    • 进程占用系统资源,打开文件,已分配的内存信息等

    进程的特点

    • 动态性:可动态地创建、结束进程。
    • 并发性:进程可以被独立地调度并占用处理机运行。
    • 独立性:不同进程的工作不相互影响。
    • 制约性:因访问共享数据/资源或进程间同步而产生制约。

    进程与程序的联系

    • 程序是产生进程的基础;进程是程序功能的体现.
    • 程序的每次运行构成不同的进程。
    • 通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。

    进程与程序的区别

    • 进程是动态的,程序是静态的:程序是有序代码的集合;进程是程序的执行。
    • 进程是暂时的,程序是永久的:进程是一个状态变化的过程,程序可长久保存。
    • 组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)。
    进程表

    进程表存储在内核空间中,包含进程 ID 和进程控制块信息,以下是典型的进程表中的一些字段(都在 PCB 中):

    在这里插入图片描述

    操作系统在进程表中维护指向每个进程 PCB 的指针, 以便它可以快速访问 PCB。

    在这里插入图片描述

    PCB

    在操作系统中,是用 PCB(Process Control Block,进程控制块)数据结构来描述进程的基本情况以及运行变化的过程。PCB 是进程存在的唯一标识,这意味着一个进程的存在,必然会有一个 PCB,如果进程消失了,那么 PCB 也会随之消失。

    PCB 具体包含以下三类信息:

    进程标识信息:

    • 进程标识符:标识各个进程,每个进程都有一个并且唯一的标识符。
    • 用户标识符:进程归属的用户,用户标识符主要为共享和保护服务。

    进程控制信息:

    • 调度和状态信息,用于操作系统调度进程并占用处理机使用。
    • 进程间通信信息,为支持进程间的与通信相关的各种标识、信号、信件等,这些信息存在接收方的 PCB 中。
    • 存储管理信息,包含有指向本进程映像存储空间的数据结构。
    • 进程所用资源,说明由进程打开、使用的系统资源,如打开的文件等。
    • 有关数据结构连接信息,进程可以连接到一个进程队列中,或连接到相关的其他进程的 PCB。

    处理机状态信息保存区:

    • 用户可见寄存器,用户程序可以使用的数据,地址等寄存器。
    • 控制和状态寄存器,如程序计数器,程序状态字。
    • 栈指针,过程调用/系统调用/中断处理和返回时需要用到它。
    进程的状态

    基本的进程状态有五个:创建、就绪、阻塞、运行、结束。除此之外,还有两个挂起状态,共计七个状态。

    在这里插入图片描述

    • 创建状态:进程正在被创建时的状态;
    • 结束状态:进程正在从系统中消失时的状态;
    • 运行状态:正在占用 CPU 的状态;
    • 就绪状态:可运行的状态,由于其他进程处于运行状态而暂时停止运行;
    • 阻塞状态:正在等待某一事件发生(如等待输入/输出操作的完成)而暂时停止运行,这时,即使给它 CPU 控制权,它也无法运行;
    • 阻塞挂起状态:进程在外存并等待某个事件的出现;
    • 就绪挂起状态:进程在外存,但只要进入内存,即刻立刻运行;

    如果有大量处于阻塞状态的进程,进程可能会占用着物理内存空间。所以,在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换出到外存,等需要再次运行的时候,再从外存换入到物理内存。

    挂起状态就是用来描述进程没有占用实际的物理内存空间的情况。

    导致进程挂起的原因不只是因为进程所使用的内存空间不在物理内存,还包括如下情况:

    • 通过 sleep 让进程间歇性挂起,其工作原理是设置一个定时器,到期后唤醒进程。
    • 用户希望挂起一个程序的执行,比如在 Linux 中用 Ctrl+Z 挂起进程;

    进程的状态变迁:

    • NULL → 创建状态:一个新进程被创建时的第一个状态;
    • 创建状态 → 就绪状态:当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态,这个过程是很快的;
    • 就绪态 → 运行状态:处于就绪状态的进程被操作系统的进程调度器选中后,就分配给 CPU 正式运行该进程;
    • 运行状态 → 就绪状态:处于运行状态的进程在运行过程中,由于分配给它的运行时间片用完,操作系统会把该进程变为就绪态,接着从就绪态选中另外一个进程运行;
    • 运行状态 → 阻塞状态:当进程请求某个事件且必须等待时,例如请求 I/O 事件;
    • 阻塞状态 → 就绪状态:当进程要等待的事件完成时,它从阻塞状态变到就绪状态;
    • 运行状态 → 结束状态:当进程已经运行完成或出错时,会被操作系统作结束状态处理;

    进入挂起状态的方式:操作系统自动挂起、用户主动挂起、睡眠。

    离开挂起状态的方式:操作系统自动激活、用户主动激活、睡眠到期自动唤醒。

    状态队列

    通过链表的方式,把相同状态的进程的 PCB 链在一起,组成各种状态队列。比如就绪队列、阻塞队列、运行队列等。

    当一个进程的状态发生变化时,它的 PCB 从一个状态队列中脱离出来,加入到另一个状态队列中去。

    除了链接的组织方式,还有索引方式,它的工作原理:将同一状态的进程组织在一个索引表中,索引表项指向相应的 PCB,不同状态对应不同的索引表。

    一般会选择链表,因为可能面临进程创建,销毁等调度导致进程状态发生变化,所以链表能够更加灵活的插入和删除。

    进程的控制
    创建进程

    操作系统允许一个进程创建另一个进程,而且允许子进程继承父进程所拥有的资源(不可写的内存区域是共享的,如代码段),当子进程被终止时,其在父进程处继承的资源应当还给父进程。终止父进程时同时也会终止其所有的子进程。子进程可以视为父进程的副本,不共享地址空间。

    注意:Linux 操作系统对于终止有子进程的父进程,会把子进程交给 1 号进程接管。本文所指出的进程终止概念是宏观操作系统的一种观点,最后怎么实现当然是看具体的操作系统。

    创建进程的过程如下:

    • 申请一个空白的 PCB,并向 PCB 中填写一些控制和管理进程的信息,比如进程的唯一标识等;
    • 为该进程分配运行时所必需的资源,比如内存资源;
    • 将 PCB 插入到就绪队列,等待被调度运行;
    终止进程

    进程可以有 3 种终止方式:正常结束、异常结束以及外界干预(信号 kill 掉)。

    进程可以通过系统调用自愿终止,也可以让操作系统终止进程。

    终止进程的过程如下:

    • 查找需要终止的进程的 PCB;
    • 如果处于执行状态,则立即终止该进程的执行,然后将 CPU 资源分配给其他进程;
    • 如果其还有子进程,则应将其所有子进程终止;
    • 将该进程所拥有的全部资源都归还给父进程或操作系统;
    • 将其从 PCB 所在队列中删除;
    阻塞进程

    当进程需要等待某一事件完成时,它可以调用阻塞语句把自己阻塞等待。而一旦被阻塞等待,它只能由另一个进程唤醒。

    阻塞进程的过程如下:

    • 找到将要被阻塞进程标识号对应的 PCB;
    • 如果该进程为运行状态,则保护其现场,将其状态转为阻塞状态,停止运行;
    • 将该 PCB 插入到阻塞队列中去;
    唤醒进程

    进程由「运行」转变为「阻塞」状态是由于进程必须等待某一事件的完成,所以处于阻塞状态的进程是绝对不可能叫醒自己的。

    如果某进程正在等待 I/O 事件,需由别的进程发消息给它,则只有当该进程所期待的事件出现时,才由发现者进程用唤醒语句叫醒它。

    唤醒进程的过程如下:

    • 在该事件的阻塞队列中找到相应进程的 PCB;
    • 将其从阻塞队列中移出,并置其状态为就绪状态;
    • 把该 PCB 插入到就绪队列中,等待调度程序调度;

    进程的阻塞和唤醒是一对功能相反的语句,如果某个进程调用了阻塞语句,则必有一个与之对应的唤醒语句。

    进程的上下文切换

    进程是由内核管理和调度的,所以进程的切换只能发生在内核态

    进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。

    通常,会把交换的信息保存在进程的 PCB,当要运行另外一个进程的时候,我们需要从这个进程的 PCB 取出上下文,然后恢复到 CPU 中,这使得这个进程可以继续执行。

    常见的进程上下文切换场景:

    • 为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,进程就从运行状态变为就绪状态,系统从就绪队列选择另外一个进程运行;
    • 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行;
    • 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度;
    • 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行;
    • 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序;
    进程的层次结构
    父子进程

    在 UNIX 中,进程可以创新新的进程,且两者之间存在父子关系,每一个子进程都有一个父进程,父进程可以有零个或多个子进程。

    子进程的所有资源都继承父进程,相当于父进程的一个副本(除代码段共享以外),不共享地址空间

    除了进程 0(即 PID=0 的交换进程,Swapper Process)以外的所有进程都是由其他进程使用系统调用 fork 创建的,这里调用 fork 创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程 0 以外的进程都只有一个父进程,但一个进程可以有多个子进程。

    操作系统内核以进程标识符(Process Identifier,即 PID)来识别进程。进程 0 是系统引导时创建的一个特殊进程,在其调用 fork 创建出一个子进程(即 PID=1 的进程 1,又称 init)后,进程 0 就转为交换进程(有时也被称为空闲进程),而进程 1( init 进程)就是系统里其他所有进程的祖先。

    进程和它的所有子女和后裔进程共同组成一个进程组

    Windows 中没有父子进程的概念。

    孤儿进程

    父进程结束后仍在运行的子进程。孤儿进程将被 init 进程所收养。

    僵尸进程

    没有被父进程回收的子进程。僵尸进程会造成内存泄露。

    当一个子进程结束运行时,子进程的退出状态会返回给操作系统,系统则以 SIGCHLD 信号将子进程结束的事件告知父进程,此时子进程的 PCB 仍驻留在内存中。一般来说,收到 SIGCHLD 后,父进程会使用 wait/waitpid 系统调用以获取子进程的退出状态,然后内核就可以从内存中释放已结束的子进程的 PCB;而如果父进程没有这么做的话,子进程的 PCB 就会一直驻留在内存中,即成为僵尸进程。

    创建进程

    使用 fork 函数,函数的返回值:如果返回值大于零,表明处于父进程上下文环境中,返回值是子进程的ID.如果返回值是零,表明处于子进程上下文环境中.其他返回值(小于零)表明调用 fork 函数出错,仍处于父进程上下文环境中。fork 函数被调用一次,但返回两次,两次的返回值不同,子进程的返回值是 0,父进程的返回值是新进程的进程 ID。

    子进程在创建后和父进程同时执行,竞争系统资源,谁先谁后,取决于内核所使用调度算法。

    写时复制技术

    copy-on-write,当子进程刚刚被创建时,子进程和父进程的数据和代码是共享的,即父子进程的代码和数据通过页表映射到物理内存的同一块空间。只有当父进程或子进程需要修改数据时,才将父进程的数据在内存当中拷贝一份,然后再进行修改。

    好处:既能实现多进程资源的独立性,又能按需分配,提高内存空间的利用率。

    用户态和内核态

    操作系统设置了两种运行级别,即用户态和内核态,分别采用 CPU 的 R0 和 R3 两种特权级,来使用不同级别的指令集。

    • Ring0 相当于内核态,有对硬件的所有操作权限,可以执行所有CPU指令集,访问所有内存地址空间,且所占有的处理器允许被抢占。
    • Ring3 相当于用户态,没有对硬件的操作权限,只能执行部分CPU指令集,访问部分内存地址空间,且所占有的处理器不允许被抢占。

    当进程/线程运行在用户空间时就处于用户态,运行在内核空间时就处于内核态。

    用户进程不能直接执特权指令,只能发起使用特权指令的请求,然后由操作系统代替用户执行指令。

    从用户态切换到内核态的三种途径:系统调用、中断、异常。

    在 PSW(Program Status Word,程序状态字)中有1个二进制位用来控制 CPU 处于内核态还是用户态。

    进程不区分用户进程和内核进程,而是说进程运行在用户态和内核态。

    运行在用户态的进程不能直接访问内核空间的数据结构和程序。当我们在系统中执行一个进程时,大部分时间是运行在用户态的,在需要操作系统帮助完成某些没有权力完成的工作时就会切换到内核态执行(比如操作硬件)。

    总结

    进程是程序在一个数据集合上的一次动态执行过程。在程序之上抽象出进程的概念,既能够实现程序的并发执行,也方便进行资源的分配和管理。

    在内核空间中有一张进程表,包含进程ID和进程控制块信息。

    进程有创建、就绪、运行、阻塞、结束五个基本状态,以及就绪挂起和阻塞挂起两个状态,总共七个状态。

    不同状态的进程,通过链表的方式组织起来就成了状态队列,比如就绪队列、阻塞队列、运行队列等。

    CPU 往往会在多个进程中切换运行,进程需要将信息保存在 PCB 中,之后就可以继续运行了,即进程的上下文切换。

    进程运行在用户态或内核态,大部分时间都运行在用户态,但是不能直接访问内核空间,不具备对硬件的操作权限和部分 CPU 指令,所以在需要完成某些没有权利完成的工作时,需要切换到内核态由操作系统执行。

  • 相关阅读:
    bean实例化
    前端面试基础面试题——9
    java面向对象02——常见的设计模式
    Mysql 索引基数与选择性
    codeforces:C. Almost All Multiples【构造 + 贪心】
    六爻排盘神机
    客户数据成为营销必备!成功关键是挖掘数据价值
    含文档+PPT+源码等]精品基于NET实现的司库管理系统-金融理财管理系统[包运行成功]
    SpringBoot后端统计网站的IP访问次数及地区
    Springboot 之整合(Druid数据库连接池、PageHelper实现分页、Redis缓存)
  • 原文地址:https://blog.csdn.net/weixin_43844995/article/details/126441757