操作系统——进程

操作系统 专栏收录该内容
2 篇文章 3 订阅

一、进程

1、什么是进程

  • 进程的概念是操作系统中最基本、最重要的概念。它是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律而引进的一个新概念,所有多道程序设计的操作系统都建立在进程的基础上。操作系统专门引入进程的概念,从理论角度看,是对正在运行的程序活动规律的抽象;从实现角度看,则是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。
  • 进程(process)这个名词最早是 1960 年在 MIT 的 MULTICS 和 IBM 公司的 TSS/360系统中提出的,直到目前对进程的定义和名称均不统一,不同的系统中采用不同的术语名称,例如, MIT 称进程(process), IBM 公司称任务(task)和 Univac 公司称活动80(active)。可以说进程的定义多种多样,国内学术界较为一致的看法是: 进程是一个可并发执行的具有独立功能的程序关于某个数据集合的一次执行过程,也是操作系统进行资源分配和保护的基本单位(1978 年全国操作系统学术会议)
     

2、为什么引入进程

程序并发执行时具有如下特征:

  • 间断性:程序在并发执行时,由于它们共享资源或为完成同一项任务而相互合作,使在并发程序之间形成了相互制约的关系。相互制约将导致并发程序具有“执行-暂停-执行”这种间断性活动规律。
  • 失去封闭性 :程序在并发执行时,是多个程序共享系统中的各种资源,因而这些资源的状态将由多个程序来改变,致使程序的运行已失去了封闭性。
  • 不可再现性 :程序在并发执行时,由于失去了封闭性,也将导致失去结果的可再现性。即程序经过多次运行,虽然其各次的环境和初始条件相同,但得到的结果却各不相同。


由于程序在并发执行时,可能会造成执行结果的不可再现,所以用“程序”这个概念已无法描述程序的并发执行,所以必须引入新的概念—进程来描述程序的并发执行,并要对进程进行必要的管理,以保证进程在并发执行时结果可再现。 

  • 一是刻画系统的动态性,发挥系统的并发性,提高资源利用率。在多道程序环境下,程序可以并发执行,一个程序的任意两条指令之间都可能发生随机事件而引发程序切换。因而,每个程序的执行都可能不是连续的而是走走停停的。此外,程序的并发执行又引起了资源共享和竞争的问题,造成了各并发执行的程序间可能存在制约关系。并发执行的程序不再处在一个封闭的环境中,出现了许多新的特征,系统需要一个能描述程序动态执行过程的单位,这个基本单位就是进程。同静态的程序相比较,进程依赖于处理器和主存储器资源,具有动态性和暂时性,进程随着一个程序模块进入主存储器并获得一个数据块和一个进程控制块而创建,因等待某个事件发生或资源得不到满足而暂停执行,随着运行的结束退出主存储器而消亡,从创建到消亡期间,进程处于不断的动态变化之中。此外,由于程序的并发执行,使得处理器和 I/O 设备、I/O 设备和 I/O 设备能有效地并行工作, 提高了资源的利用率和系统的效率。 由此可见,进程是并发程序设计的一种有力工具,操作系统中引入进程概念能较好地刻画系统内部的“动态性”,发挥系统的“并发性”和提高资源的利用率。
  • 二是解决共享性,正确描述程序的执行状态。也可以从解决“共享性”来看操作系统中引入进程概念的必要性。首先,引入“可再入”程序的概念,所谓“可再入”程序是指能被多个程序同时调用的程序。另一种称“可再用”程序由于它被调用过程中具有自身修改,在调用它的程序退出以前是不允许其他程序来调用它的。“可再入”程序具有以下性质:它是纯代码,即它在执行中自身不被改变;调用它的各程序应提供工作区,因此,可再入程序可以同时被几个程序调用。


进程(Process)定义:“可并发执行的程序在一个数据集合上的运行过程”。进程具有如下特征:

  • 异步性:进程按各自独立的不可预知的速度向前推进,即进程按异步方式进行,正是这一特征,将导致程序执行的不可再现性,因此OS必须采用某种措施来限制各进程推进序列以保证各程序间正常协调运行。
  • 结构性:进程包含了数据集合和运行于其上的程序,为了描述和记录进程的动态变化过程使其能正确运行,还需配置一个进程控制块,所以,每个进程至少有三要素组成:程序块、数据块和进程控制块。
  • 共享性:同一程序同时运行于不同数据集合上时,构成不同的进程。或者说,多个不同的进程可以共享相同的程序,所以,进程和程序不是一一对应的。
  • 动态性:进程是程序在数据集合上的一次执行过程,是动态概念,同时,它还有生命周期,由创建而产生,由调度而执行,由撤销而消亡;而程序是一组有序指令序列,是静态概念,所以,程序作为一种系统资源是永久存在的。
  • 独立性:进程既是系统中资源分配和保护的基本单位,也是系统调度的独立单位(单线程进程)。凡是未建立进程的程序,都不能作为独立单位参与运行。通常,每个进程都可以各自独立的速度在 CPU 上推进。
  • 制约性:并发进程之间存在着制约关系,进程在进行的关键点上需要相互等待或互通消息,以保证程序执行的可再现性和计算结果的唯一性。
  • 并发性:进程可以并发地执行,进程的并发性能改进资源利用率和提高系统效率。对于一个单处理器的系统来说, m 个进程 P1, P2,…, Pm 是轮流占用处理器并发地执行。例如,可能是这样进行的:进程 P1 执行了 nl 条指令后让出处理器给 P2, P2 执行了 n2 条指令后让出处理器给 P3,…, Pm 执行了 nm 条指令后让出处理器给 P1,…。因此,进程的执行是可以被打断的,或者说,进程执行完一条指令后在执行下一条指令前,可能被迫让出处理器,由其他若干个进程执行若干条指令后才能再次获得处理器而执行。
     

3、进程的描述

一个进程从创建而产生至撤销而消亡的整个生命期间,有时占有处理器执行,有时虽可运行但分不到处理器、有时虽有空闲处理器但因等待某个事件的发生而无法执行,这一切都说明进程和程序不相同,它是活动的且有状态变化的,这可以用一组状态加以刻画。为了便于管理进程,一般来说,按进程在执行过程中的不同情况至少要定义三种不同的进程状态:
 

这里写图片描述

  • 运行态/执行态(Running):当一个进程在处理机上运行时,则称该进程处于运行状态。
  • 就绪态(Ready):一个进程获得了除处理机外的一切所需资源,一旦得到处理机即可运行,则称此进程处于就绪状态。
  • 阻塞态(Blocked):(又称挂起状态、等待状态):一个进程正在等待某一事件发生(例如请求I/O而等待I/O完成等)而暂时仃止运行,这时即使把处理机分配给进程也无法运行,故称该进程处于阻塞状态。

三个基本状态之间可能转换和转换原因如下:

  • 就绪态–>运行态:当处理机空闲时,进程调度程序必将处理机分配给一个处于就绪态的进程 ,该进程便由就绪态转换为运行态。
  • 运行态–>阻塞态:处于运行态的进程在运行过程中需要等待某一事件发生后(例如因I/O请求等待I/O完成后),才能继续运行,则该进程放弃处理机,从运行态转换为阻塞态。
  • 阻塞态–>就绪态:处于阻塞态的进程,若其等待的事件已经发生,于是进程由阻塞态转换为就绪态。
  • 运行态–>就绪态:处于运行状态的进程在其运行过程中,因分给它的处理机时间片已用完,而不得不让出(被抢占)处理机,于是进程由运行态转换为就绪态。
  • 而阻塞态–>运行态和就绪态–>阻塞态这二种状态转换不可能发生。
  • 处于运行态进程:如系统有一个处理机,则在任何一时刻,最多只有一个进程处于运行态。
  • 处于就绪态进程:一般处于就绪态的进程按照一定的算法(如先来的进程排在前面,或采用优先权高的进程排在前面)排成一个就绪队列RL。
  • 处于阻塞态进程:处于阻塞态的进程排在阻塞队列中。由于等待事件原因不同,阻塞队列也按事件分成几个队列WLi。
     

一个问题:假设一个只有一个处理机的系统中,OS的进程有运行、就绪、阻塞三个基本状态。假如某时刻该系统中有10个进程并发执行,在略去调度程序所占用时间情况下试问 
这时刻系统中处于运行态的进程数最多几个?最少几个 
这时刻系统中处于就绪态的进程数最多几个?最少几个 
这时刻系统中处于阻塞态的进程数最多几个?最少几个? 
解:

因为系统中只有一个处理机,所以某时刻处于运行态的进程数最多只有一个。而最少可能为0,此时其它10个进程一定全部排在各阻塞队列中,在就绪队列中没有进程。

而某时刻处于就绪态的进程数最多只有9个,不可能出现10个情况,因为一旦CPU有空,调度程序马上调度,当然这是在略去调度程序调度时间时考虑,处于就绪的进程最少0个,10个进程都阻塞则就绪态的就是0个。

处于阻塞态的最多有10个,比如是个进程都是在主动的sleep睡眠,处于阻塞态的进程数最少是0个。

 

操作系统作为资源管理和分配程序,其本质任务是自动控制程序的执行,并满足进程执行过程中提出的资源使用要求。因此,操作系统的核心控制结构是进程结构,资源管理的数据结构将围绕进程结构展开。
在研究进程的控制结构之前,首先介绍一下操作系统的控制结构。为了有效的管理进程和资源,操作系统必须掌握每一个进程和资源的当前状态。从效率出发,操作系统的控制结构及其管理方式必须是简明有效的,通常是通过构造一组表来管理和维护进程和每一类资源的信息。操作系统的控制表分为四类:进程控制表,存储控制表, I/O 控制表和文件控制表。

  • 进程控制表用来管理进程及其相关信息。
  • 存储控制表用来管理一级(主)存储器和二级(辅)存储器,主要内容包括:主存储器的分配信息,二级存储器的分配信息,存储保护和分区共享信息,虚拟存储器管理信息。
  • I/O 控制表用来管理计算机系统的 I/O 设备和通道,主要内容包括: I/O 设备和通道是否可用, I/O 设备和通道的分配信息, I/O 操作的状态和进展, I/O 操作传输数据所在的主存区。
  • 文件控制表用来管理文件,主要内容包括:被打开文件的信息,文件在主存储器和二级存储器中的位置信息,被打开文件的状态和其他属性信息。

4、进程上下文

当一个程序进入计算机的主存储器进行计算就构成了进程,主存储器中的进程到底是如何组成的?操作系统中把进程物理实体和支持进程运行的环境合称为进程上下文(process context) 。当系统调度新进程占有处理器时,新老进程随之发生上下文切换,因此,进程的运行被认为是在进程的上下文中执行的。在操作系统中,进程上下文包括三个组成部分:

  • 用户级上下文(user -level context):由用户进程的程序块、用户数据块(含共享数据块)和用户堆栈组成的进程地址空间。
  • 系统级上下文(system -level context):包括进程的标识信息、现场信息和控制信息,进程环境块,以及系统堆栈等组成的进程地址空间。
  • 寄存器上下文(register context):由程序状态字寄存器、各类控制寄存器、地址寄存器、通用寄存器、用户栈指针等组成。
     

进程是由程序、数据和进程控制块组成。进程上下文实际上是执行活动全过程的静态描述。具体说,进程上下文包括系统中与执行该进程有关的各种寄存器(例如:通用寄存器、程序计数器PC、程序状态寄存器PS等)的值,程序段在经编译之后形成的机器指令代码集(或称正文段)、数据集及各种堆栈值和PCB结构。一个进程的执行是在该进程的上下文中执行,而当系统调度新进程占有处理机时,新老进程的上下发生切换。UNIX 操作系统的进程上下文称为进程映象。

进程的内存映像可以很好地说明进程的组成。简单的说,一个进程映像(process Image) 包括:

  • 进程程序块:即被执行的程序,规定了进程一次运行应完成的功能。通常它是纯代码,作为一种系统资源可被多个进程共享。
  • 进程数据块:即程序运行时加工处理对象,包括全局变量、局部变量和常量等的存放区以及开辟的工作区,常常为一个进程专用。
  • 系统/用户堆栈:每一个进程都将捆绑一个系统/用户堆栈。用来解决过程调用或系统调用时的信息存储和参数传递。
  • 进程控制块:每一个进程都将捆绑一个进程控制块,用来存储进程的标志信息、现场信息和控制信息。进程创建时,建立进程控制块;进程撤销时,回收进程控制块,它与进程一一对应。
     

可见每个进程有四个要素组成:控制块、程序块、数据块和堆栈。
 

5、进程控制模块(PCB)

每一个进程都有一个也只有一个进程控制块 PCB(Process Control Block) ,进程控制块是操作系统用于记录和刻画进程状态及有关信息的数据结构,也是操作系统掌握进程的唯一资料结构,是操作系统控制和管理进程的主要依据。它包括了进程执行
时的情况,以及进程让出处理器后所处的状态、断点等信息。

一般说,进程控制块包含三类信息:

  • 标识信息。用于唯一地标识一个进程,常常分为由用户使用的外部标识符和被系统使用的内部标识号。几乎所有操作系统中进程都被赋予一个唯一的、内部使用的数值型的进程号,操作系统的其他控制表可以通过进程号来交叉引用进程控制表。常用的标识信息包括进程标识符、父进程的标识符、用户进程名、用户组名等。
  • 现场信息。用于保留一个进程在运行时存放在处理器现场中的各种信息,任何一个进程在让出处理器时必须把此时的处理器现场信息保存到进程控制块中,而当该进程重新恢复运行时也应恢复处理器现场。常用的现场信息包括:通用寄存器的内容、控制寄存器(如 PSW 寄存器)的内容、用户堆栈指针、系统堆栈指针等。
  • 控制信息。用于管理和调度一个进程。常用的控制信息包括: 1)进程调度相关信息,如进程状态、等待事件和等待原因、进程优先级、队列指引元等; 2)进程组成信息,如正文段指针、数据段指针; 3)进程间通信信息,如消息队列指针、信号量等互斥和同步机制; 4)进程在辅存储器内的地址; 5) CPU资源的占用和使用信息,如时间片余量、进程己占用 CPU 的时间、进程己执行的时间总和,记账信息; 6)进程特权信息,如在内存访问权限和处理器状态方面的特权; 7)资源清单,包括进程所需全部资源、已经分得的资源,如主存资源、 I/O 设备、打开文件表等。
     

进程控制块是操作系统中最为重要的数据结构,每个进程控制块包含了操作系统管理所需的所有进程信息,进程控制块的集合事实上定义了一个操作系统的当前状态。进程控制块使用权或修改权均属于操作系统程序,包括调度程序、资源分配程序、中断处理程序、性能监视和分析程序等。当系统创建一个进程时,就为它建立一个 PCB,当进程执行结束被撤销时,便回收它占用的 PCB。操作系统是根据 PCB 来对并发执行的进程进行控制和管理的,借助于进程控制块 PCB,进程才能被调度执行。
 

 

  • 进程标识符:它用于唯一地标识一个进程。它有外部标识符(由字母组成,供用户使用)和内部标识符(由整数组成,为方便系统管理而设置)二种。 
  • 进程调度信息:它包括进程状态(running、ready、blacked)、队列(就绪、阻塞队列)、队列指针,调度参数:进程优先级、进程已执行时间和已等待时间等。 
  • 处理机状态信息:它由处理机各种寄存器(通用寄存器、指令计数器、程序状态字PSW、用户栈指针等)的内容所组成,该类信息使进程被中断后重新执行时能恢复现场从断点处继续运行。 
  • 进程控制信息:它包括程序和数据的地址、I/O资源清单,保证进程正常运行的同步和通信机制等。 
  • 家族信息:它包括该进程的父、子进程标识符、进程的用户主等。 

UNIX的PCB由proc和user两个结构组成,proc常驻主存的系统区,是PCB中最基本和常用信息,而user可根据需要换进换出。

6、进程队列及其管理
 

并发系统中同时存在许多进程,有的处于就绪态,有的处于等待态,等待原因各不相同。进程的主要特征是由 PCB 来刻画的,为了便于管理和调度,常常把各个进程的 PCB 用某种方法组织起来。 用得较多的是用队列来组织 PCB, 下面先介绍这种方法。
一般说来,把处于同一状态(例如就绪态)的所有进程控制块链接在一起的数据结构称为进程队列(process queues) ,简称队列。同一状态进程的 PCB 既可按先来先到的原则排成队列;也可以按优先数或其他原则排成队列。对于等待态的进程队列可以进一步细分,每一个进程按等待的原因进入相应的等待队列,例如,如果一个进程要求使用某个设备,而该设备已经被占用时,此进程就链接到与该设备相关的等待态队列中去。
 

在一个队列中,链接进程控制块的方法可以是多样的,常用的是单向链接和双向链接。单向链接方法是在每个进程控制块内设置一个队列指引元,它指出在队列中跟随着它的下一个进程的进程控制块内队列指引元的位置。双向链接方法是在每个进程控制块内设置两个指引元,其中一个指出队列中该进程的上一个进程的进程控制块内队列指引元的位置,另一个指出队列中该进程的下一个进程的进程控制块的队列指引元的位置。为了标志和识别一个队列,系统为每一个队列设置一个队列标志,单向链接时,队列标志指引元指向队列中第一个进程的队列指引元的位置;双向链接时,队列标志的后向指引元指向队列中第一个进程的后向队列指引元的位置;队列标志的前向指引元指向队列中最后一个进程的前向队列指引元的位置。这两种链接方式如图所示。
 

当发生的某个事件使一个进程的状态发生变化时,这个进程就要退出所在的某个队列而排入到另一个队列中去。一个进程从一个所在的队列中退出的事件称为出队,相反,一个进程排入到一个指定的队列中的事件称为入队。处理器调度中负责入队和出队工作的功能模块称为队列管理模块,简称队列管理。下图给出了操作系统的队列管理和状态转换示意图。

7、进程切换与模式切换

中断是激活操作系统的唯一方法,它暂时中止当前运行进程的执行,把处理器切换到操作系统的控制之下。而当操作系统获得了处理器的控制权之后,它就可以实现进程切换,所以,进程切换必定在核心态而不是在用户态下发生。当发生中断事件,或进程执行系统调用后,有可能引发内核进行进程上下文切换,由于一个进程让出处理器时,其寄存器上下文将被保存到系统级上下文的相应的现场信息位置,这时内核就把这些信息压入系统栈的一个上下文层。当内核处理中断返回,或一个进程完成其系统调用返回用户态,或内核进行上下文切换时,内核就从系统栈弹出一个上下文层(context layer)。因此,上下文的切换总会引起上下文的压入和弹出堆栈。内核在四种情况下允许发生上下文切换:

  1. 当进程进入等待态时;
  2. 当进程完成其系统调用返回用户态但不是最有资格获得 CPU 时;
  3. 当内核完成中断处理,进程返回用户态但不是最有资格获得 CPU 时;
  4. 当进程执行结束时。

做一次进程上下文切换时,即保存老进程的状态而装入被保护了的新进程的状态,以便新进程运行。进程切换的步骤如下:

  • 保存被中断进程的处理器现场信息。
  • 修改被中断进程的进程控制块的有关信息,如进程状态等。
  • 把被中断进程的进程控制块加入有关队列。
  • 选择下一个占有处理器运行的进程。
  • 修改被选中进程的进程控制块的有关信息。
  • 根据被选中进程设置操作系统用到的地址转换和存储保护信息。
  • 根据被选中进程的信息来恢复处理器现场。

从上面介绍的切换工作可以看出,当进行上下文切换时,内核需要保存足够的信息,以便将来适当时机能够切换回原进程,并恢复它继续执行。类似地,当从用户态转到核心态时,内核保留足够信息以便后来能返回到用户态,并让进程从它的断点继续执行。用户态到核心态或者核心态到用户态的转变是 CPU 模式的改变,而不是进程上下文切换。为了进一步说明进程的上下文切换,下面来讨论模式切换。当中断发生的时候,暂时中断正在执行的用户进程,把进程从用户状态切换到内核状态,去执行操作系统例行程序以获得服务,这就是一次模式切换,注意,此时仍在该进程的上下文中执行,仅仅模式变了。内核在被中断了的进程的上下文中对这个中断事件作处理,即使该中断事件可能不是此进程引起的。另一点要注意的是被中断的进程可以是正在用户态下执行的,也可以是正在核心态下执行的,内核都要保留足够信息以便在后来能恢复被中断了的进程执行。内核在核心态下对中断事件进行处理时,决不会再产生或调度一个特殊进程来处理中断事件。模式切换的步骤如下:

  • 保存被中断进程的处理器现场信息。
  • 根据中断号置程序计数器。
  • 把用户状态切换到内核状态,以便执行中断处理程序。


注意模式切换不同于进程切换,它并不引起进程状态的变化,在大多数操作系统中,它也不一定引起进程的切换,在完成了中断调用之后,完全可以再通过一次逆向的模式切换来继续执行用户进程。显然,有效合理地使用模式切换和进程切换有利于操作系统效率和安全性的提高。为此,大多数现代操作系统存在两种进程:系统进程和用户进程。它们并不是指两个具体的进程实体,而是指一个进程的两个侧面,系统进程是在核心态下执行操作系统代码的进程,用户进程在用户态下执行用户程序的进程。用户进程因中断或系统调用进入内核态,系统进程就开始执行,这两个进程(用户进程和系统进程)使用同一个PCB,所以,实质上是一个进程实体。但是这两个进程所执行的程序不同,映射到不同物理地址空间、使用不同堆栈。一个系统进程的地址空间中包含所有的系统核心程
序和各进程的进程数据区,所以,各进程的系统进程除数据区不同外,其余部分全相同,但各进程的用户进程部分则各不相同。
 

下图清楚地给出了一个进程(未对换出主存)其生命周期中,可能出现的进程切换和模式切换的示意。其中,

  • 用户态运行表示进程在用户模式下执行;
  • 核心态运行表示进程在内核模式执行;
  • 就绪状态表示进程处于就绪态,但未正在执行;
  • 等待状态表示进程正处于等待态,由于发生某个事件(如等 I/O 完成)而进入此状态。
     

上面的状态给出了描述进程的静态观点,事实上进程状态是在连续动态地转换的,状态图中若有箭头指向的边,这种状态转换是合法的,通常进程完成且只完成一个合法状态转换。任何时刻一个处理器上仅能执行一个进程,所以,至多只有一个进程可以处在状态(1)或状态(2),这两个状态相应于用户模式和内核模式。当系统调用或中断发生时,进程通过一个模式切换从用户态运行转化为核心态运行,在核心态下运行的进程是不能被抢占的(现在比较新的内核是允许内核态抢占的);当进程在内核模式运行时,可以继续响应中断,当处理完中断或系统调用后,可以通过一个模式切换转回用户模式继续运行,也可以根据具体情况使进程等待一个事件(状态(4)),只有在此时内核才会允许发生进程切换,使原先运行的进程让出处理器;当等待事件完成后,进程会从等待状态被唤醒,进入就绪状态(状(3)),若被调度程序选中,进程会重新占有处理器运行。在多道程序设计系统中,有许多进程在并发执行,如果两个以上进程同时执行系统调用,并要求在内核模式下执行,则有可能破坏核心数据结构中的信息,通过禁止任意的上下文切换和控制中断的响应,就能保证数据的一致性和完整性。
 

二、进程控制

处理器管理的一个主要工作是对进程的控制,对进程的控制包括:创建进程、阻塞进程、唤醒进程、挂起进程、激活进程、终止进程和撤销进程等。这些控制和管理功能是由操作系统中的原语来实现的。 原语(Primitive)是在内核态下执行、完成系统特定功能的过程。原语和机器指令类似,其特点是执行过程中不允许被中断,是一个不可分割的基本单位,原语的执行是顺序的而不可能是并发的。系统对进程的控制如不使用原语,就会造成其状态的不确定性,从而,达不到进程控制的目的。原语和系统调用都使用访管指令实现,具有相同的调用形式;但原语由内核来实现,而系统调用由系统进程或系统服务器实现;原语不可中断,而系统调用执行时允许被中断,甚至有些操作系统中系统进程或系统服务器干脆在用户态运行;通常情况下,原语提供给系统进程或系统服务器使用(反之决不会形成调用关系), 系统进程或系统服务器提供系统调用给系统程序(和用户)使用,而系统程序提供高层功能给用户使用,例如,语言编译程序提供语句供用户解决应用问题。下面介绍部分进程控制原语。

 

1、内核

  • 核心态和用户态 :为了防止用户应用程序访问和/或更改重要的操作系统数据。UNIX使用两种处理器访问模式:核心态和用户态(又称管态和目态)。操作系统代码在核心态下运行,即在x86处理器Ring0运行,它有着最高的特权。而用户应用程序代码在用户态下运行,即在x86处理器Ring3中运行。 
  • 用户应用程序(在Windows 2000中以用户线程方式出现)运行用户程序一般代码时,它是在用户态下执行。但当程序要调用系统服务,例如要调用操作系统中负责从磁盘文件中读取数据的NT执行体例程时,它就要通过一条专门的指令(自陷指令/访管指令)来完成从用户态切换到核心态,操作系统根据该指令及有关参数,执行用户的请求服务。在服务完成后将处理器模式切换回用户态,并将控制返回用户线程。因此用户线程有时在核心态下执行,在核心态下执行的是调用操作系统有关功能模块的代码。
  • 原语是一种特殊的广义指令,它的功能是由系统通过一段不可分割的指令操作来完成,它又称原子操作,原语在核心态下完成。进程控制操作(创建、撤消、阻塞……)大都为原语操作。
  • 内核功能 :内核是计算机硬件上的第一层扩充软件,它是OS中关键部分,它是管理控制中心。内核在核心态下运行,常驻内存,内核通过执行各种原语操作来实现各种控制和管理功能。

2、进程状态的细化

“挂起”、 “激活”操作的引入 
系统管理员有时需要暂停某个进程,以便排除系统故障或暂时减轻系统负荷,用户有时也希望暂停自己的进程以便检查自己作业的中间结果。这就希望系统提供“挂起”操作,暂停进程运行,同是也要提供“激活”的操作,恢复被挂起的进程。由于被挂起前进程的状态有三种,挂起后的进程就分为二种状态:静止就绪态和静止阻塞态(有的称挂起就绪态和挂起阻塞态)。挂起前的进程就绪态和阻塞态也改为活动就绪态和活动阻塞态。 
这里写图片描述

  • 当进程处于运行态和活动就绪态时,执行挂起操作,进程状态转换为静止就绪态。当进程处于活动阻塞态时,执行挂起操作,进程状态转换为静止阻塞态。对被挂起的进程施加“激活”操作,则处于静止就绪的进程转换为活动就绪态,处于静止阻塞态的进程转换为活动阻塞态。被挂起的处于静止阻塞态的进程当它等待的事件发生后,它就由静止阻塞态转换为静止就绪态。

 

3、进程控制原语

  • 创建原语(Create) 
  • 一个进程可借助创建原语来创建一个新进程,该新进程是它的子进程,创建一个进程主要是为新进程创建一个PCB。创建原语首先从系统的PCB表中索取一个空白的PCB表目,并获得其内部标识,然后将调用进程提供的参数:如外部名、正文段、数据段的首址、大小、所需资源、优先级等填入这张空白PCB表目中。并设置新进程状态为活动/静止就绪态,并把该PCB插入到就绪队列RQ中,就可进入系统并发执行。
  • 撤消原语(Destroy)/ 终止(Termination)
  • 对于树型层次结构的进程系统撤消原语采用的策略是由父进程发出,撤消它的一个子进程及该子进程所有的子孙进程,被撤消进程的所有资源(主存、I/O资源、PCB表目)全部释放出来归还系统,并将它们从所有的队列中移去。如撤消的进程正在运行,则要调用进程调度程序将处理器分给其它进程。
  • 阻塞原语(block) 
  • 当前进程因请求某事件而不能执行时(例如请求I/O而等待I/O完成时),该进程将调用阻塞原语阻塞自己,暂时放弃处理机。进程阻塞是进程自身的主动行为。阻塞过程首先立即停止原来程序的执行,把PCB中的现行状态由运行态改为活动阻塞态,并将PCB插入到等待某事件的阻塞队列中,最后调用进程调度程序进行处理机的重新分配。
  • 唤醒原语(wakeup) 
  • 当被阻塞的进程所期待的事件发生时(例如I/O完成时),则有关进程和过程(例如I/O设备处理程序或释放资源的进程等)调用wakeup原语,将阻塞的进程唤醒,将等待该事件的进程从阻塞队列移出,插入到就绪队列中,将该进程的PCB中现行状态,如是活动阻塞态改为活动就绪态,如是静止阻塞态改为静止就绪态。
  • 挂起原语(suspend) 
  • 调用挂起原语的进程只能挂起它自己或它的子孙,而不能挂起别的族系的进程。挂起原语的执行过程是:检查要挂起进程PCB的现行状态,若正处于活动就绪态,便将它改为静止就绪态;如是活动阻塞态则改为静止阻塞态。如是运行态,则将它改为静止就绪态,并调用进程调度程序重新分配处理机。为了方便用户或父进程考察该进程的运行情况,需把该进程的PCB复制到内存指定区域。
  • 激活原语(active) 
  • 用户进程或父进程通过调用激活原语将被挂起的进程激活。激活原语执行过程是:检查被挂起进程PCB中的现行状态,若处于静止就绪态,则将它改为活动就绪态,若处于静止阻塞态,则将它改为活动阻塞态。

4、进程结构和组

它由三部分组成: proc 结构、数据段和正文段,它们合称为进程映像, UNIX 中把进程定义为映像的执行。其中, PCB由基本控制块 proc 结构和扩充控制块 user 结构两部分组成。在 proc 结构里存放着关于一个进程的最基本、最必需的信息,因此,它常驻内存;在 user 结构里存放着只有进程运行时才用到的数据和状态信息,为了节省内存空间,当进程暂时不在处理机上运行时,就把它放在磁盘上的对换区中,进程的 user 结构总和进程的数据段一起,在主存和磁盘对换区之间换进/换出。系统中维持一张名叫 proc 的结构数组,每个表目为一个 proc 结构,供一个进程使用。创建进程时,在 proc 表中找一个空表目,以建立起相应于该进程的 proc 结构。所有进程的 proc 结构集中形成 proc 结构数组存放在操作系统的核心数据区中。
 

  • proc 结构:存放的信息包括进程标识符、父进程标识符、进程用户标识符、进程状态、等待的事件、指向 user 结构和进程存储区(text/data/stack)的指针、软中断信息、有关进程执行时间/核心资源使用/用户设置示警信号等的计时器、进程的大小、调度优先数、就绪队列指针等。进程即使不运行,核心也需要访问有关信息,所以 proc结构常驻内存。
  • user 结构:存放的信息包括本进程 proc 结构表项的指针、保护现场、本进程正文段/数据区/栈区长度、用户标识、用户组标识、用户打开文件表、当前目录和当前根、计时信息、文件 I/O 参数、限制字段、错误码字段、返回值字段和信号处理数组等。为了节省内存空间,当进程暂时不在处理器上运行时,就把它放在磁盘上的对换区中,进程的 user 结构没有集中存放,而是分散存放在进程的数据段内,在主存和磁盘对换区之间换进/换出。进程的 user 结构和系统栈组成了系统数据区,虽然放在每个进程的数据块内,以便于与其他数据一起换进换出内存,但却与其他数据不属于同一地址空间。系统数据区是 PCB 的一部分属核心态空间,用户程序不能访问它。
  • 系统数据结构:是进程系统数据区,它位于数据段的前面,进程 proc 结构中有指针指向这个区域的首址。该区通常有 1024 个字节,由两块内容组成:最前面的若干字节为进程的扩充控制块 user 结构,剩下的字节为核心栈,当进程运行在核心态时,这里是它的工作区,用来保存过程调用和中断访问时用到的地址和参数。
  • 用户数据区:用于存放程序运行时用到的数据,如果进程运行的程序是非共享的,那么这个程序也放于此地。
  • 用户栈区:当进程运行在用户态时,这里是它的工作区。
  • text 结构:正文段在磁盘上和主存中的位置和大小、访问正文段进程数、在主存中访问正文段进程数、标志信息、地址转换信息。由于共享正文段在进程映像中的特殊性,为了便于对它们的管理, UNIX 系统在内存中设置了一张正文段表。每一个表目都是一个 text 结构,用来记录一个共享正文段的属性(磁盘和主存中的位置、尺寸、共享的进程数、正文段文件节点指针等),有时也把这种结构称为正文段控制(信息)块。这是可以被多个进程共享的可重入程序和常数,如果一个进程的程序是不被共享的,那么,它的映像中就不出现这一部分。若一个进程有共享正文段,那么,当把该进程的非常驻内存部分调入内存时,应该关注共享正文段是否也在内存,如果发现不
  • 在内存,则要将它调入;当把该进程的非常驻内存部分调出内存时,同样要关注它的共享正文段目前被共享的情况,只要还有一个别的共享该正文段的进程映像全部在内存,那么,这个共享正文段就不得调出去。如果一个进程有共享正文段,该共享正文段在正文段表里一定有一个 text 结构与之相对应,而在该进程的基本控制块 proc 里,有专门指针指向这一个 text 结构。综上所述,在 UNIX 进程映像的三个组成部分中,proc、 user 和 text 这三个数据结构是最为重要的角色。
  • 系统区表和进程区表:用于实现地址转换。系统区表用于记录进程虚拟地址空间的连续区域,包括正文区、数据区、栈区等。这些区是可被共享和保护的独立实体,并允许多个进程共享一个区。为了对区进行管理,系统区表记录了区的类型、大小、状态、位置、引用计数、以及相应的文件索引节点指针。系统为每个进程建立一张进程区表 PPRT(Per Process Region Table)由存储管理系统使用,它定义了物理地址与虚拟地址之间的对应关系,还定义了进程对存储区域的访问权限。其中含有正文段、数据段和堆栈的区域表的指针和各区域的逻辑起始地址; 区域表中含有该区域属性(正文/数据,可以共享)的信息和页表的指针;而每个页表中含有相应区域的页面在内存的起始地址。
     

 

linux3.15.57内核的进程控制块(表)


struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	void *stack;
	atomic_t usage;
	unsigned int flags;	/* per process flags, defined below */
	unsigned int ptrace;

#ifdef CONFIG_SMP
	struct llist_node wake_entry;
	int on_cpu;
	struct task_struct *last_wakee;
	unsigned long wakee_flips;
	unsigned long wakee_flip_decay_ts;

	int wake_cpu;
#endif
	int on_rq;

	int prio, static_prio, normal_prio;
	unsigned int rt_priority;
	const struct sched_class *sched_class;
	struct sched_entity se;
	struct sched_rt_entity rt;
#ifdef CONFIG_CGROUP_SCHED
	struct task_group *sched_task_group;
#endif
	struct sched_dl_entity dl;

#ifdef CONFIG_PREEMPT_NOTIFIERS
	/* list of struct preempt_notifier: */
	struct hlist_head preempt_notifiers;
#endif

#ifdef CONFIG_BLK_DEV_IO_TRACE
	unsigned int btrace_seq;
#endif

	unsigned int policy;
	int nr_cpus_allowed;
	cpumask_t cpus_allowed;

#ifdef CONFIG_PREEMPT_RCU
	int rcu_read_lock_nesting;
	char rcu_read_unlock_special;
	struct list_head rcu_node_entry;
#endif /* #ifdef CONFIG_PREEMPT_RCU */
#ifdef CONFIG_TREE_PREEMPT_RCU
	struct rcu_node *rcu_blocked_node;
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
#ifdef CONFIG_RCU_BOOST
	struct rt_mutex *rcu_boost_mutex;
#endif /* #ifdef CONFIG_RCU_BOOST */

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
	struct sched_info sched_info;
#endif

	struct list_head tasks;
#ifdef CONFIG_SMP
	struct plist_node pushable_tasks;
	struct rb_node pushable_dl_tasks;
#endif

	struct mm_struct *mm, *active_mm;
#ifdef CONFIG_COMPAT_BRK
	unsigned brk_randomized:1;
#endif
	/* per-thread vma caching */
	u32 vmacache_seqnum;
	struct vm_area_struct *vmacache[VMACACHE_SIZE];
#if defined(SPLIT_RSS_COUNTING)
	struct task_rss_stat	rss_stat;
#endif
/* task state */
	int exit_state;
	int exit_code, exit_signal;
	int pdeath_signal;  /*  The signal sent when the parent dies  */
	unsigned int jobctl;	/* JOBCTL_*, siglock protected */

	/* Used for emulating ABI behavior of previous Linux versions */
	unsigned int personality;

	unsigned in_execve:1;	/* Tell the LSMs that the process is doing an
				 * execve */
	unsigned in_iowait:1;

	/* Revert to default priority/policy when forking */
	unsigned sched_reset_on_fork:1;
	unsigned sched_contributes_to_load:1;

	unsigned long atomic_flags; /* Flags needing atomic access. */

	pid_t pid;
	pid_t tgid;

#ifdef CONFIG_CC_STACKPROTECTOR
	/* Canary value for the -fstack-protector gcc feature */
	unsigned long stack_canary;
#endif
	/*
	 * pointers to (original) parent process, youngest child, younger sibling,
	 * older sibling, respectively.  (p->father can be replaced with
	 * p->real_parent->pid)
	 */
	struct task_struct __rcu *real_parent; /* real parent process */
	struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
	/*
	 * children/sibling forms the list of my natural children
	 */
	struct list_head children;	/* list of my children */
	struct list_head sibling;	/* linkage in my parent's children list */
	struct task_struct *group_leader;	/* threadgroup leader */

	/*
	 * ptraced is the list of tasks this task is using ptrace on.
	 * This includes both natural children and PTRACE_ATTACH targets.
	 * p->ptrace_entry is p's link on the p->parent->ptraced list.
	 */
	struct list_head ptraced;
	struct list_head ptrace_entry;

	/* PID/PID hash table linkage. */
	struct pid_link pids[PIDTYPE_MAX];
	struct list_head thread_group;
	struct list_head thread_node;

	struct completion *vfork_done;		/* for vfork() */
	int __user *set_child_tid;		/* CLONE_CHILD_SETTID */
	int __user *clear_child_tid;		/* CLONE_CHILD_CLEARTID */

	cputime_t utime, stime, utimescaled, stimescaled;
	cputime_t gtime;
#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
	struct cputime prev_cputime;
#endif
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
	seqlock_t vtime_seqlock;
	unsigned long long vtime_snap;
	enum {
		VTIME_SLEEPING = 0,
		VTIME_USER,
		VTIME_SYS,
	} vtime_snap_whence;
#endif
	unsigned long nvcsw, nivcsw; /* context switch counts */
	struct timespec start_time; 		/* monotonic time */
	struct timespec real_start_time;	/* boot based time */
/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
	unsigned long min_flt, maj_flt;

	struct task_cputime cputime_expires;
	struct list_head cpu_timers[3];

/* process credentials */
	const struct cred __rcu *ptracer_cred; /* Tracer's credentials at attach */
	const struct cred __rcu *real_cred; /* objective and real subjective task
					 * credentials (COW) */
	const struct cred __rcu *cred;	/* effective (overridable) subjective task
					 * credentials (COW) */
	char comm[TASK_COMM_LEN]; /* executable name excluding path
				     - access with [gs]et_task_comm (which lock
				       it with task_lock())
				     - initialized normally by setup_new_exec */
/* file system info */
	int link_count, total_link_count;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
	struct sysv_sem sysvsem;
#endif
#ifdef CONFIG_DETECT_HUNG_TASK
/* hung task detection */
	unsigned long last_switch_count;
#endif
/* CPU-specific state of this task */
	struct thread_struct thread;
/* filesystem information */
	struct fs_struct *fs;
/* open file information */
	struct files_struct *files;
/* namespaces */
	struct nsproxy *nsproxy;
/* signal handlers */
	struct signal_struct *signal;
	struct sighand_struct *sighand;

	sigset_t blocked, real_blocked;
	sigset_t saved_sigmask;	/* restored if set_restore_sigmask() was used */
	struct sigpending pending;

	unsigned long sas_ss_sp;
	size_t sas_ss_size;
	int (*notifier)(void *priv);
	void *notifier_data;
	sigset_t *notifier_mask;
	struct callback_head *task_works;

	struct audit_context *audit_context;
#ifdef CONFIG_AUDITSYSCALL
	kuid_t loginuid;
	unsigned int sessionid;
#endif
	struct seccomp seccomp;

/* Thread group tracking */
   	u32 parent_exec_id;
   	u32 self_exec_id;
/* Protection of (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed,
 * mempolicy */
	spinlock_t alloc_lock;

	/* Protection of the PI data structures: */
	raw_spinlock_t pi_lock;

#ifdef CONFIG_RT_MUTEXES
	/* PI waiters blocked on a rt_mutex held by this task */
	struct rb_root pi_waiters;
	struct rb_node *pi_waiters_leftmost;
	/* Deadlock detection and priority inheritance handling */
	struct rt_mutex_waiter *pi_blocked_on;
	/* Top pi_waiters task */
	struct task_struct *pi_top_task;
#endif

#ifdef CONFIG_DEBUG_MUTEXES
	/* mutex deadlock detection */
	struct mutex_waiter *blocked_on;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
	unsigned int irq_events;
	unsigned long hardirq_enable_ip;
	unsigned long hardirq_disable_ip;
	unsigned int hardirq_enable_event;
	unsigned int hardirq_disable_event;
	int hardirqs_enabled;
	int hardirq_context;
	unsigned long softirq_disable_ip;
	unsigned long softirq_enable_ip;
	unsigned int softirq_disable_event;
	unsigned int softirq_enable_event;
	int softirqs_enabled;
	int softirq_context;
#endif
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 48UL
	u64 curr_chain_key;
	int lockdep_depth;
	unsigned int lockdep_recursion;
	struct held_lock held_locks[MAX_LOCK_DEPTH];
	gfp_t lockdep_reclaim_gfp;
#endif

/* journalling filesystem info */
	void *journal_info;

/* stacked block device info */
	struct bio_list *bio_list;

#ifdef CONFIG_BLOCK
/* stack plugging */
	struct blk_plug *plug;
#endif

/* VM state */
	struct reclaim_state *reclaim_state;

	struct backing_dev_info *backing_dev_info;

	struct io_context *io_context;

	unsigned long ptrace_message;
	siginfo_t *last_siginfo; /* For ptrace use.  */
	struct task_io_accounting ioac;
#if defined(CONFIG_TASK_XACCT)
	u64 acct_rss_mem1;	/* accumulated rss usage */
	u64 acct_vm_mem1;	/* accumulated virtual memory usage */
	cputime_t acct_timexpd;	/* stime + utime since last update */
#endif
#ifdef CONFIG_CPUSETS
	nodemask_t mems_allowed;	/* Protected by alloc_lock */
	seqcount_t mems_allowed_seq;	/* Seqence no to catch updates */
	int cpuset_mem_spread_rotor;
	int cpuset_slab_spread_rotor;
#endif
#ifdef CONFIG_CGROUPS
	/* Control Group info protected by css_set_lock */
	struct css_set __rcu *cgroups;
	/* cg_list protected by css_set_lock and tsk->alloc_lock */
	struct list_head cg_list;
#endif
#ifdef CONFIG_FUTEX
	struct robust_list_head __user *robust_list;
#ifdef CONFIG_COMPAT
	struct compat_robust_list_head __user *compat_robust_list;
#endif
	struct list_head pi_state_list;
	struct futex_pi_state *pi_state_cache;
#endif
#ifdef CONFIG_PERF_EVENTS
	struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts];
	struct mutex perf_event_mutex;
	struct list_head perf_event_list;
#endif
#ifdef CONFIG_DEBUG_PREEMPT
	unsigned long preempt_disable_ip;
#endif
#ifdef CONFIG_NUMA
	struct mempolicy *mempolicy;	/* Protected by alloc_lock */
	short il_next;
	short pref_node_fork;
#endif
#ifdef CONFIG_NUMA_BALANCING
	int numa_scan_seq;
	unsigned int numa_scan_period;
	unsigned int numa_scan_period_max;
	int numa_preferred_nid;
	unsigned long numa_migrate_retry;
	u64 node_stamp;			/* migration stamp  */
	u64 last_task_numa_placement;
	u64 last_sum_exec_runtime;
	struct callback_head numa_work;

	struct list_head numa_entry;
	struct numa_group *numa_group;

	/*
	 * Exponential decaying average of faults on a per-node basis.
	 * Scheduling placement decisions are made based on the these counts.
	 * The values remain static for the duration of a PTE scan
	 */
	unsigned long *numa_faults_memory;
	unsigned long total_numa_faults;

	/*
	 * numa_faults_buffer records faults per node during the current
	 * scan window. When the scan completes, the counts in
	 * numa_faults_memory decay and these values are copied.
	 */
	unsigned long *numa_faults_buffer_memory;

	/*
	 * Track the nodes the process was running on when a NUMA hinting
	 * fault was incurred.
	 */
	unsigned long *numa_faults_cpu;
	unsigned long *numa_faults_buffer_cpu;

	/*
	 * numa_faults_locality tracks if faults recorded during the last
	 * scan window were remote/local. The task scan period is adapted
	 * based on the locality of the faults with different weights
	 * depending on whether they were shared or private faults
	 */
	unsigned long numa_faults_locality[2];

	unsigned long numa_pages_migrated;
#endif /* CONFIG_NUMA_BALANCING */

	struct rcu_head rcu;

	/*
	 * cache last used pipe for splice
	 */
	struct pipe_inode_info *splice_pipe;

	struct page_frag task_frag;

#ifdef	CONFIG_TASK_DELAY_ACCT
	struct task_delay_info *delays;
#endif
#ifdef CONFIG_FAULT_INJECTION
	int make_it_fail;
#endif
	/*
	 * when (nr_dirtied >= nr_dirtied_pause), it's time to call
	 * balance_dirty_pages() for some dirty throttling pause
	 */
	int nr_dirtied;
	int nr_dirtied_pause;
	unsigned long dirty_paused_when; /* start of a write-and-pause period */

#ifdef CONFIG_LATENCYTOP
	int latency_record_count;
	struct latency_record latency_record[LT_SAVECOUNT];
#endif
	/*
	 * time slack values; these are used to round up poll() and
	 * select() etc timeout values. These are in nanoseconds.
	 */
	unsigned long timer_slack_ns;
	unsigned long default_timer_slack_ns;

#ifdef CONFIG_FUNCTION_GRAPH_TRACER
	/* Index of current stored address in ret_stack */
	int curr_ret_stack;
	/* Stack of return addresses for return function tracing */
	struct ftrace_ret_stack	*ret_stack;
	/* time stamp for last schedule */
	unsigned long long ftrace_timestamp;
	/*
	 * Number of functions that haven't been traced
	 * because of depth overrun.
	 */
	atomic_t trace_overrun;
	/* Pause for the tracing */
	atomic_t tracing_graph_pause;
#endif
#ifdef CONFIG_TRACING
	/* state flags for use by tracers */
	unsigned long trace;
	/* bitmask and counter of trace recursion */
	unsigned long trace_recursion;
#endif /* CONFIG_TRACING */
#ifdef CONFIG_MEMCG /* memcg uses this to do batch job */
	struct memcg_batch_info {
		int do_batch;	/* incremented when batch uncharge started */
		struct mem_cgroup *memcg; /* target memcg of uncharge */
		unsigned long nr_pages;	/* uncharged usage */
		unsigned long memsw_nr_pages; /* uncharged mem+swap usage */
	} memcg_batch;
	unsigned int memcg_kmem_skip_account;
	struct memcg_oom_info {
		struct mem_cgroup *memcg;
		gfp_t gfp_mask;
		int order;
		unsigned int may_oom:1;
	} memcg_oom;
#endif
#ifdef CONFIG_UPROBES
	struct uprobe_task *utask;
#endif
#if defined(CONFIG_BCACHE) || defined(CONFIG_BCACHE_MODULE)
	unsigned int	sequential_io;
	unsigned int	sequential_io_avg;
#endif
};

 

此外, Linux 为进程队列的调度定义了以下重要的全局变量:
current 当前正在运行的进程的指针,在 SMP 中则指向 CPU 组中正被调度的CPU 的当前进程。
init_task 即 0 号进程的 PCB,是进程树的根。
*task[NR_TASKS] 进程 PCB 数组,规定系统可同时运行的最大进程数,每个进程占一个数组元素(元素下标不一定就是进程 pid)。task[0]必须指向 0 号进程 init-task,可以通过 tasks[]数组遍历所有进程的 PCB,另外,还提供了宏 for-each-task(),它通过next-task 遍历所有进程的 PCB。
jiffies 是 Linux 的基准时间,系统初始化时清 0,每隔定时基准时间由时钟中断处理程序 do-timer()增 1。
need_resched()重新调度标志位,当需要系统调度时置位,在系统调用返回前或其他情况下,判别标志位是否为 1,以决定是否schedule()进行 CPU 调度。
intr_count 记录中断服务程序的嵌套重数。

本文主要来自孙钟秀老师主编的《操作系统教程》书本

  • 1
    点赞
  • 0
    评论
  • 4
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页

打赏作者

奔跑的小刺猬

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值