408 知识点笔记——操作系统(绪论、进程管理)

文章目录

1 绪论

【实时操作系统】

实时的含义是指计算机对于外来信息能够以足够快的速度进行处理,并在被控制对象允许的时间范围内做出快速反应

实时操作系统的主要特点是提供 及时响应高可靠性

【分时操作系统】

分时系统是必须有交互功能的,不存在不一定全部提供人机交互功能的情况。另外,分时系统只是给每个用户感觉好像是自己独占了一台计算机,而不存在真正的独占整个计算机。实时系统对响应的要求比分时系统高,两者对于响应的要求是不相似的

【微内核】

微内核的优点:可靠性好(某个服务器产生问题不会引起系统其它服务器的崩溃)、灵活(只要接口规范,可以方便地增删服务功能)、便于维护适合分分布式处理环境

缺点:效率不高(尤其通信频繁的系统)

△☼▽

在这里插入图片描述

分析:A,理论如上描述

【多道程序设计】

所谓多道程序设计,是指将一个以上的作业放入内存,并且同时处于运行状态。这些作业共享处理器的时间和外设及其他资源

多道程序设计下,宏观上进程是同时运行的,但是在微观上,单处理器某时刻只能处理一个进程

【批处理系统】

批处理系统中,将作业依次以脱机输入方式输入到磁带上,监督程序依次执行磁带上的作业,作业执行时用户无法干预其运行。批处理系统按照发展历程可分为单道批处理系统和多道批处理系统,主要区别为内存中同时存在单个或多个作业。多道批处理系统中的一道程序因 I/O 请求而暂停执行时,借助中断技术 CPU转而运行另一道程序

△☼▽(2016 年真题)
在这里插入图片描述

分析:A,理论如上所述

【中断处理程序】

中断处理程序只能是操作系统程序,不可能是应用程序

【接口方式】

操作系统有如下3种接口方式提供给用户使用:

  • 命令接口(交互式命令接口:控制台或终端;批处理命令接口:由一组作业控制命令组成,事先写成一份作业操作书,如类似DOS的批命令文件或者UNIX的shell文件)
  • 程序接口,也称为系统调用
  • 图形接口,也称为图形界面

【核心态 / 用户态执行】

1)只能在核心态执行

  • 缺页处理时钟中断都属于中断,要修改中断寄存器,因此只能在核心态执行
  • 进程调度(切换) 属于系统的一部分,也只能在核心态执行
  • 常见的特权指令:
    ① 有关对 I/O 设备使用的指令;
    ② 有关访问程序状态的指令(如对程序状态字PSW的指令);
    ③ 存取特殊寄存器指令(如存取中断寄存器、时钟寄存器等指令)
    (注,关中断指令 为特权指令)

△☼▽(2014 年真题)
在这里插入图片描述

分析:D,理论如上所述

2)可以在用户态执行

  • 命令解释程序属于命令接口,是操作系统提供给用户所使用的接口,因此可以在用户态执行
  • trap 指令跳转指令压栈指令 均可以在用户态执行,其中 trap 指令负责由用户态转换成为内核态

△☼▽(2011 年真题)
在这里插入图片描述

分析:A,理论如上所述

3)发生在用户态、处理在核心态(换言之,进程从用户态切换到内核态)

  • 系统调用是系统提供给用户程序调用内核函数的,虽然系统调用执行过程中 CPU 需要切换至核心态,但是系统调用是在用户态发生的, 如 read、write、open等
  • 外部中断是指由外部事件引起的中断,比如单击鼠标或键盘输入等操作引起的中断进程缺页产生的缺页中断

总结: 系统调用和中断的发生是在用户态,处理是在核心态

△☼▽(2012 年真题)
在这里插入图片描述

分析:C,理论如上所述

△☼▽(2013 年真题)
在这里插入图片描述

分析:B,理论如上所述

△☼▽(2015 年真题)

在这里插入图片描述

分析:C,A. 如果除数为 0 的话会引发中断;B. 软中断在内核态执行;D. 涉及访存,需要进入内核态

【中断处理和子程序调用】

中断处理子程序调用的区别:

中断的发生通常是突然的,往往是系统无法预知的。由于处理中断时 CPU 可能会切换状态(如果是核心态发生中断则始终为核心态,不需要切换),因此中断处理返回时就需要还原当时的程序状态,这就用到了程序状态字寄存器所存储的内容

子程序调用是系统能够预知的,而且子程序调用通常是在进程内部进行的,不会更改程序状态,即便更改程序状态,只要更新寄存器就行,而不需要保存,因为一切都是系统预料到的,不需要保护和恢复

故而,子程序调用只需保存程序断点,即该指令的下一条指令的地址;中断调用不仅要保护断点(PC 的内容),而且要保护程序状态字寄存器的内容 PSW,在中断处理中,最重要的两个寄存器是 PC 和 PSWR

△☼▽(2012 年真题)
在这里插入图片描述

分析:B,理论如上所述

【操作系统装载】

用户平时开机时首先启动的是存于主板上的 ROM 中的 BIOS 程序,其次再由它去调用硬盘中的操作系统,将操作系统的程序自动加载到内存中的系统区,这段区域就是 RAM

△☼▽(2013 年真题)
在这里插入图片描述

分析:D,理论如上所述

【外部中断处理过程】

外部中断处理过程,程序计数器的内容中断隐指令 自动保存,通用寄存器 的内容由操作系统保存

△☼▽(2015 年真题)
在这里插入图片描述

【系统调用】

1)基本概念

系统调用即程序接口或应用编程接口,是应用程序同系统之间的接口

△☼▽(2010 年真题)
在这里插入图片描述

分析:A,理论如上所述

内核提供了一系列具备预定功能的内核函数,通过一组称为系统调用的接口呈现给用户。系统调用把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,并将处理结果返回给应用程序

操作系统提供的系统调用通常包括:进程控制、文件系统控制、系统控制、内存管理、网络管理、socket 控制、用户管理以及进程间通信(信号、消息、管道、信号量和共享内存)

在这里插入图片描述

△☼▽
在这里插入图片描述

分析:C,用户可以在用户态调用操作系统的服务,但执行具体的系统调用服务程序处于内核态,故 Ⅰ 正确;显然,Ⅱ、Ⅳ 正确;操作系统不同,底层逻辑和实现方式均不同,为应用程序提供的系统调用接口也不同,Ⅲ 错误

2)执行系统调用过程

用户需要执行系统调用时,

  • 首先准备并传递系统调用所需的参数,通过陷入(trap)指令进入操作系统的系统内核,此时从用户态进入内核态
  • 之后执行相应的系统调用函数,使用特定的系统内核功能
  • 最后将处理结果返回给用户进程,此时将从内核态返回用户态

△☼▽(2017 年真题)
在这里插入图片描述

分析:C,理论如上所述

3)库函数和系统调用的区别和联系

区别:库函数是语言或应用程序的一部分,可以运行在用户空间中。而系统调用是操作系统的一部分,是内核提供给用户的程序接口,运行在内核空间中

联系: 许多库函数都会使用系统调用来实现功能。没有使用系统调用的库函数,执行的效率通常比系统调用高,因为使用系统调用时,需要上下问的切换以及状态的转换(从用户态转为核心态)

【操作系统主要功能】

操作系统的主要功能包括处理器管理存储器管理文件管理设备管理

注意,数据管理属于文件管理的范畴,网络管理 不是操作系统的功能

【并行执行与并发性】

并发性是指两个或两个以上的进程在执行时间上有重叠,即一个进程的第一个操作在另一个进程的最后一个操作完成之前开始。并发性是指宏观上在一段时间内有多道程序在同时运行,但在单处理器系统中,每个时刻仅有一道程序在执行,故微观上这些程序是交替执行的

△☼▽(2009 年真题)
在这里插入图片描述

分析:D,理论如上描述

两者的联系和区别,

  • 多重处理即并行执行,多任务处理即多个进程并发执行
  • 操作系统既可以支持并发执行也可以支持并行执行
  • 并行执行与并发执行不存在包含关系
  • 在同一时间间隔内,系统中同时运行多个进程是并发执行的基本概念
  • 一个 CPU 可以采用多核架构,可以实现并行执行

△☼▽
在这里插入图片描述

分析:B,理论如上所述

【层次结构划分操作系统】

在这里插入图片描述

【需要加以保护的指令】

△☼▽

在这里插入图片描述

分析:2)、4)两条指令需要加以保护,因为这两条操作是直接对操作系统本身的内容加以修改。其他几种指令在一般情况下也应该加以保护,但即使这些操作交由用户操作,也不会出现像 2)、4)两种操作那样的破坏性

【编译/链接】

核心的一句换: 编译后的模块需要经过链接才能装载,而链接后形成的地址才是整个程序的完整逻辑地址空间

以 C 语言为例:C 语言经过预处理(cpp) → \rightarrow 编译(ccl) → \rightarrow 汇编 (as) → \rightarrow 链接(ld)产生可执行文件

其中,链接的前一步,产生了可重定位的二进制的目标文件。C 语言采用源文件独立编译的方法,如程序 main.c,file1.c,file2.c,file1.h,file2.h,在链接的前一步也就是编译生成了 main.o,file1.o,file2.o ,这些目标模块采用的逻辑地址都是从 0 开始,但只是相对于该模块的逻辑地址。链接阶段主要完成了重定位,形成整个程序的完整逻辑地址空间

我们给出逻辑地址、线性地址和物理地址的概念:

  • 逻辑地址是指在程序各个模块中的偏移地址,它是相对于当前模块首地址的地址
  • 线性地址是指在分页式存储管理中单个程序所有模块集合在一起构成的地址
  • 物理地址是指出现在 CPU 外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址

那么,就能理解为什么说 “ 在虚拟内存管理中,地址变换机构将逻辑地址变换为物理地址,形成该逻辑地址的阶段是编译 ”。 逻辑地址指的就是段内的偏移量而不是链接后生成的整个程序全局的逻辑地址空间(即线性地址),所以逻辑地址是编译时产生的

△☼▽(2011 年真题)

在这里插入图片描述

分析:B,理论描述如上

【时钟中断】

时钟中断的主要工作是处理和时间有关的信息以及决定是否执行调度程序,和时间有关的所有信息包括系统时间、进程的时间片、延时、使用 CPU 的时间、各种定时器

△☼▽

在这里插入图片描述

分析:D,理论如所述

【有关 CPU 利用率、总运行时间的计算】

△☼▽

在这里插入图片描述

分析:单道,总运行时间为 260ms

在这里插入图片描述
多道,总运行时间为 190ms

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:只有当所有进程都在等待 I/O 时,CPU 才会空闲下来。因此 ,需要算出所有进程都在等待 I/O 这种情况发生的概率

设有 n n n 个进程,则 CPU 的利用率 u u u 可表示为 u = 1 − ( 0.8 ) n u=1-(0.8)^n u=1(0.8)n 在内存为 32 M B 32MB 32MB 时,可容纳 32 − 2 10 = 3 \frac{32-2}{10} = 3 10322=3 个用户进程,则   u = 1 − 0. 8 3 = 0.488 \\ \ \\ u = 1-0.8^3=0.488  u=10.83=0.488 内存再增加 32 M B 32MB 32MB 时,又多了 32 / 10 = 3 32/10 = 3 32/10=3 个用户进程,则   u = 1 − 0. 8 6 = 0.738 \\ \ \\ u=1-0.8^6=0.738  u=10.86=0.738

【操作系统设计】

△☼▽
在这里插入图片描述

分析:可以设计两个优先队列,分时作业进入高优先级队列,采用短时间片的时间轮转法调度。当高优先级队列空时,调度低优先级的成批作业,并给予较长的时间片

2 进程管理

【进程状态的相互转换】

在这里插入图片描述

  1. 执行状态 → \rightarrow 就绪状状态:时间片用完在抢占式调度中有更高优先级的进程变为就绪态
  2. 进程之间的状态转换并非都是主动的,很多情况下都是被动的,只有从执行状态到阻塞状态是程序的自我行为(因事件而主动调用阻塞原语),其他都是被动的
  3. 执行状态只能由就绪状态转换过来,而无法由阻塞状态直接转换
  4. 区分就绪状态和阻塞状态,抓住很关键的一句话:当进程处于阻塞状态时,即便把处理器分配给该进程,它也是无法运行的。所以区分就绪态和阻塞态的关键是当分配给该进程处理器时,是否能立即执行,若能立即执行,则处于就绪状态;反之,则为阻塞状态
  5. 进程被唤醒:进程被当进程被唤醒时,它从阻塞状态变为就绪态,即可以重新占用 CPU(不是直接占用 CPU,区别于运行状态)

考虑一个问题:若无进程处于运行状态,则就绪和等待队列均为空?

若无进程处于运行状态,则就绪和等待队列不一定为空,发生死锁时,无进程处于运行状态,而等待队列不为空

△☼▽

在这里插入图片描述

分析:C,Ⅲ. 转就绪态

△☼▽
在这里插入图片描述

分析:A,B 选项即是上题的 Ⅲ.

△☼▽

在这里插入图片描述

分析:C,当被阻塞进程等待的某资源(不包括)为可用时,进程将会被唤醒,显然 Ⅰ、Ⅱ 正确;对于 Ⅲ,当前进程的时间片用完后进入就绪队列等待重新调度,优先级最高的进程将获得处理机资源从就绪态变成执行态

【临界资源】

  • 临界资源与临界区是两个不同的概念:临界资源资源是一种系统资源,需要不同进程互斥访问,而临界区则是每个进程中访问临界资源的一段代码,是属于对应进程的
    (对不同的进程,临界区的代码可以不同,关键在于这个进程想怎么处理这个临界资源)
    在这里插入图片描述
  • 临界资源与共享资源的区别:临界资源与共享资源的区别在于,在一段时间内能否允许多个进程访问(并发使用),如磁盘即是一种共享设备,而公用队列则是临界资源

△☼▽

在这里插入图片描述

分析:B,理论如上所述

【不能自身修改的代码】

若代码可以被多个进程在任一时刻共享,则要求每个进程在调用此代码时都以同样的方式运行;而且进程在运行过程中被中断后继续执行,其结果也不受影响,这就要求代码不能修改自身,否则无法满足共享要求。这种代码就是可重入代码,也叫纯代码,即允许多个进程同时访问

【同步与互斥】

1)互斥的概念与要求

  • 空闲让进:当没有进程处于临界区时,可以允许一个请求进入临界区的进程立即进入到自己的临界区
  • 忙则等待:当已有进程进入其临界区时,其他试图进入临界区的进程必须等待
  • 有限等待:对要求访问临界资源的进程,应保证能在有限的时间内进入自己的临界区
  • 让权等待:当一个进程因为某些原因不能进入自己的临界区时,应释放处理器给其他进程

2)硬件方法

硬件方法的主要思想是用一条指令完成标志的检查和修改这两个操作,因为保证了检查操作与修改操作不被打断;或通过中断屏蔽的方式保证检查和修改作为一个整体执行。故硬件方法主要有两种:一种是中断屏蔽,另一种是硬件指令

优点:① 适用范围广;② 简单,硬件方法的标志设置简单,含义明确,容易验证其正确性;③ 支持多个临界区,当一个进程内有多个临界区时,只需为每个临界区设立一个布尔变量即可

缺点:① 进程在等待进入临界区时要耗费处理器时间,不能实现让权等待(需要软件噢诶和进行判断);② 进入临界区的进程的选择算法用硬件实现有一些缺陷,可能会使一些进程一直选不上,从而导致饥饿现象

3)信号量

信号量的分类

  1. 整型信号量:存在忙等,为遵循让权等待的准则
  2. 记录型信号量:为解决整型信号量忙等的问题,添加了链表结构,用于链接所有等待该资源的进程,从而遵循了让权等待的准则

△☼▽

在这里插入图片描述

分析:C,B、D 均属于硬件方法,根据上述理论,硬件方法不能实现让权等待;Peterson 算法满足有限等待但不满足让权等待;记录型信号量由于引入了阻塞机制,消除了不让权等待的情况

△☼▽

在这里插入图片描述

分析:B
显然根据上述第三点,当进程退出临界区时置 lock 为 FALSE,会负责唤醒处于就绪状态的进程,A)错误;让权等待要求当进程不能进入临界区时,应立即释放处理器,防止进程忙等,故而 B)对、C)错;若 while(TSL(&lock)) 在关中断状态下执行,当 TSL(&lock) 一直为 true 时,不再开中断,则系统可能会因此终止,故 D)错误

【消息缓冲】

△☼▽

在这里插入图片描述

(1)P、V 操作是指进程之间通过共享变量实现信息传递;而高级通信机制是由系统提供发送(Send)与接收(Receive)两个操作,进程间通过这两个操作进行通信,无需共享任何变量

(2)基本原理:操作系统管理一个用于进程通信的缓冲池,其中的每个缓冲区单元可存放一条消息。发送消息时,发送者从中申请一个可用缓冲区,接收者取出一条消息时再释放该缓冲区。每个进程均设置一条消息队列,任何发送给该进程的消息均暂存在其消息队列中

(3)下面给出的是教材上消息缓冲队列的发送原语和接收原语:

在这里插入图片描述
教材上对上述两个原语的解释如下:

在这里插入图片描述
在这里插入图片描述

【生产者-消费者问题】

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:

在这里插入图片描述

△☼▽(2009年真题)
在这里插入图片描述

分析:

在这里插入图片描述

△☼▽(2014 年真题)

在这里插入图片描述

分析:mutex1 用于多个消费者进程之间互斥,主要是考虑在一个消费者连续取出 10 件产品的过程中是不允许被其他的消费者打断的

在这里插入图片描述

【读者-写者问题】

读者优先

在这里插入图片描述

公平情况

在这里插入图片描述

写者优先

注意这里的 wmutex 不同于公平情况下的 wmutex,公平情况下的 wmutex 用于表示是否存在正在写或者等待的写者,这里的 wmutex 用于表示多个写者之间互斥访问 writecount

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:

在这里插入图片描述
我们对此题扩展一下,如果允许多个用户同时查询,当有订票者到达时,不允许后续查询者查询,且多个订票者互斥使用数据库(即写者优先)

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:为使 F 的并发度最高,P3 先看做读者,当其完成读操作之后再进行写操作

在这里插入图片描述

【哲学家就餐问题】

前提:5 个哲学家按照编号逆时针围桌而坐, 0 号哲学家左手筷子为 0 号筷子,右手为 1 号筷子,以此类推

下面的代码中已经对基础版的哲学家就餐算法存在的死锁问题作出改进,改进方式是奇数号的哲学家先那左边筷子,偶数号的哲学家先拿右边的筷子

在这里插入图片描述

另外一种改进方式是,至多允许 4 个哲学家就餐,实现如上图右边代码

△☼▽(2019年真题)

在这里插入图片描述

分析:传统的哲学家问题中可以采用限制只有 n − 1 n-1 n1 个哲学大家同时就餐的方法来避免死锁的发生(如上图右边代码所示),这里我们可以采用同样的方法。但是,我们不再单独引入信号量 s s s,而是利用对碗控制的信号量完成相应的工作。如果 m < n m<n m<n,则必然最多只有 m m m 个哲学家同时就餐,不会发生死锁问题;如果 m ⩾ n m\geqslant n mn,则可以令 m = n − 1 m = n-1 m=n1,这样就限制了最多只能有 n − 1 n-1 n1 个哲学家同时就餐

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:

在这里插入图片描述

【理发师问题】

两种思路:

  1. 将顾客人数、理发椅和等待用的凳子视为不同的资源

在这里插入图片描述

  1. 将顾客人数、理发椅和等待用的凳子视为统一的一个变量

在这里插入图片描述
△☼▽(2011年真题)
在这里插入图片描述

分析:

在这里插入图片描述

【其他的一些互斥与同步问题】

△☼▽

在这里插入图片描述

分析:

在这里插入图片描述

△☼▽
在这里插入图片描述

分析:

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:A:P(mutex),B:V(mutex),C:P(s),D:P(mutex);s 的初始值为 0,mutex 的初始值为 1

△☼▽

在这里插入图片描述

分析:我们抽象出一个机房管理员程序,当有两个学生到达且有机器空闲则通知学生进入机房(这整道题的细节实现上都很有特点,值得学习的)

在这里插入图片描述
△☼▽

在这里插入图片描述

分析:

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:

在这里插入图片描述

在这里插入图片描述

分析:

在这里插入图片描述

△☼▽(2013年真题)

在这里插入图片描述

分析:

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:考虑一个问题:能用一个互斥变量来控制对 y 的操作吗?肯定是不能的,1、2 对 y 同时可读,1、3 和 2、3 是读写互斥,如果只用一个互斥变量 mutex_y 控制对 y 的操作的话,就不能满足题意中最大程度并发的要求了

在这里插入图片描述

△☼▽(2015 年真题)

在这里插入图片描述
在这里插入图片描述

分析:

在这里插入图片描述

【死锁分析】

△☼▽

在这里插入图片描述

分析:D,如下图所示,左边为死锁发生的情况,右边为解决死锁的情况

在这里插入图片描述

在这里插入图片描述

分析:B,假设每个进程已经拿到了 2 台打印机,要求死锁不发生,至少还有 1 台打印机可分配,于是 N × 2 + 1 ⩽ 11 N\times2+1\leqslant11 N×2+111,故 N m a x = 5 N_{max} = 5 Nmax=5

在这里插入图片描述

分析:B,(2+3+4)+1 = 11

在这里插入图片描述

分析:C,注意要求处于死锁状态的进程数最少,于是,让 P4 先运行,之后:

在这里插入图片描述
则至少有 3 个进程处于死锁状态

在这里插入图片描述

分析:
(1)若不加以限制,会发生死锁,如下所示:

在这里插入图片描述
(2)方法一:要求进程一次性地全部申请到它所需要的全部资源;
方法二:要求进程按照资源号递增顺序申请资源;
方法三:当一个进程生情资源时,如果无该类资源可分配,但某个用于该资源的其他进程处于阻塞态时,则允许前者抢占后者资源

在这里插入图片描述

分析:A,死锁发生时,n 个进程由于争夺拿不到资源而全部阻塞

△☼▽

在这里插入图片描述

分析:会发生死锁,若某时有两个用户 A、B 同时向对方进行转账,P1 先对账户 A 进行加锁,再申请 B;P2 先对账户 B 加锁,在申请 A,此时发生死锁

解决方法时:可以采用资源顺序分配法,将 A、B 编号,用户转账时只能按照编号从小到大的顺序进行加锁

△☼▽

在这里插入图片描述

分析:
(1)有可能产生死锁,系统中只有 3 台 R1 设备,要被 4 个进程共享,且每个进程需要 2 台 R1 设备,明显优于 R1 设备不足会导致死锁的发生
(2)

在这里插入图片描述

【死锁处理策略】

死锁的处理采用三种策略:死锁预防、死锁避免、死锁检测和解除

防止死锁和避免死锁是两种方法,实际上都是通过施加某些限制条件的方法,来预防死锁的发生。两者的区别在于:防止死锁所施加的限制条件较严格,防止死锁的办法是破坏死锁产生的必要条件;死锁避免对系统所加的限制条件则相对宽松,银行家算法属于避免死锁的算法

破坏互斥条件、不可剥夺条件、请求与保持条件、环路等待条件属于预防死锁

对于死锁的检测和解除,在系统为进程分配资源时不采取任何措施,但提供死锁的检测和解除的手段

△☼▽

在这里插入图片描述

分析:B,显然 Ⅰ、Ⅳ 正确;死锁预防是破坏死锁产生的 4 个必要条件之一,以确保系统不会发生死锁,故 Ⅱ 正确;银行家算法是一种死锁避免算法,用于计算动态资源分配的完全性以避免系统进入死锁状态,不能用于判断系统是否处于死锁,故 Ⅲ 错误

【破坏死锁产生的必要条件】

  • 互斥条件: 允许多个进程同时访问资源
  • 不可剥夺条件: 对于一个已经获得了某些资源的进程,若新的资源请求不能立即得到满足,则它必须释放所有已经获得的资源,以后需要资源时再被重新申请
  • 请求与保持条件: 采用预先静态分配法,要求进程在其运行之前一次性申请所需要的全部资源,在它的资源未满足前,不投入运行
  • 环路等待条件: 可以采用有序资源分配法

△☼▽

在这里插入图片描述

分析:D,本题相当于破坏了死锁不可剥夺这一条件,故不会发生死锁。但由于某进程的资源可能不断地被剥夺,所以会出现饥饿现象

【安全状态与死锁的关系】

利用银行家算法,系统处于安全状态时就可以避免死锁(此时必然无死锁产生);当系统进入不安全状态后便可能进入死锁(但也不是必然的)

【银行家算法】

基本的关系式:

N e e d [ i ] [ j ] = M a x [ i ] [ j ] − A l l o c a t i o n [ i ] [ j ] Need[i][j] = Max[i][j]-Allocation[i][j] Need[i][j]=Max[i][j]Allocation[i][j]

银行机算法流程

在这里插入图片描述
安全性算法流程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

△☼▽

在这里插入图片描述

分析:D,一个一个带进去试吧

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:首先是死锁检测,其允许死锁出现,即允许进程最大限度地申请并分配资源,直至死锁出现,再由系统解决。其次是银行家算法,该方法允许进程自由申请资源,只是在某个进程申请时检测是否处于安全状态,若是,则可立即分配;若不是,则拒绝。最后是资源预分配,因为此方法要求进程在运行之前申请所需的全部资源,这样会使许多进程因申请不到资源而无法开始,得到资源的进程也并不是同时需要所占的全部资源,因此导致资源的浪费

【死锁与饿死】
在这里插入图片描述

【代码分析】

1)分析互斥进入临界区、饥饿等问题

△☼▽(模拟题一)

在这里插入图片描述

分析:B,若两个进程同时申请资源(左边为 P1,右边为 P1),此时 turn = 0,则可以顺序执行 ①②③④⑤⑥,显然无法保证互斥进入临界区;再看饥饿问题,turn = -1 时说明已经有程序进入临界区,退出后也有 turn = 0(满足 turn != -1),故不会产生饥饿

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

分析:D,本题是皮尔森算法的实际实现,能够保证进入临界区的进程合理安全。该算法为了防止两个进程为进入临界区而无限等待,设置变量 turn,表示是允许进入临界区的编号,每个进程在先设置自己的标志后再设置 turn 标志,让另一个进程进入,这时,再同时检测另一个进程状态标志和允许进入表示,这样可以保证当两个进程同时进入临界区时只允许一个进程进入临界区。turn 保存的是较晚的一次的赋值,因此较晚的进程等待,较早的进程进入。先到先入,后到等待,从而完成临界区访问的要求

其实这里可以想象为两个人进门,每个人进门之前都会和对方客气一句 “ 你先进 ”,如果进门时没别人,就当和空气说话,然后进门;如果两人同时进门,就互相请先,但各自只客套一次,所以先客套的人客套完,就等着对方客套自己,然后就直接进门

△☼▽

在这里插入图片描述

分析:上述并发执行的两个进程共享变量 x,且共享时没能做到互斥,因此它们的执行结果具有不确定性

若先执行 P1,并在 x:=1 后进行进程调度,执行完 P2 后再执行 P1,此时 x = y= z = 0,t = u = 2
若先执行 P1,并在 if 语句之后再调度 P2,此时 x = 0,y = z = 1,t = u = 2

在这里插入图片描述

2)分析并行程序运算结果

△☼▽

在这里插入图片描述

分析:C,注意 Begin 和 End之间的内容还是顺序执行的,Ⅱ:②③①②,Ⅲ:①③④②

在这里插入图片描述

分析:C,P1 和 P2 的变量是各自域内的变量,是不相关的,故互斥仅考虑在一个进程中的互斥,故 D 不必互斥;考虑每个线程中的变量 a,其生存域仅在该线程中,就像是两个循环中各声明了一个 i 变量,所以 P1 中对 a 的赋值并不影响最终的结果,故 A 不必互斥;P2 中将 x 赋给 a、b 执行顺序的先后不影响 a 与 b 的结果,即两个并发的线程同时取出 x 赋值给不同的变量,这个过程是不需要互斥的;P1 中的 x 为全局变量,Thread 1 和 Thread 2 中对 x 的操作需要互斥进行,C 正确

△☼▽

在这里插入图片描述

分析:可将上述两个进程分为如下的 6 个片段

在这里插入图片描述
于是,有前驱图

在这里插入图片描述
若先执行 PS3,再执行 PS6,结果为 x = 6,y = 7,z = 10
若先执行 PS6,再执行 PS3,结果为 x = 6,y = 13,z = 10

【进程与程序的联系】

  1. 进程是动态的,程序是静止的
  2. 进程是暂时的,程序是永久的
  3. 进程和程序的组成是不同的
  4. 通过多次执行,一个程序可以产生多个不同放入进程;通过调用关系,一个进程可以执行多个程序进程可以创建其他进程,而程序不能形成新的程序
  5. 进程具有并行特点,程序则没有

【并发进程的相对速度】

并发进程的相对速度受进程调度策略的影响,因为采取不同的调度策略会影响进程执行的相对速度

【中断扫描机构】

处理器执行完一条指令之后,硬件的中断装置(中断扫描机构)立即检查有无中断时间发生。若无中断事件发生,则处理器继续执行下面的指令;若有中断事件发生,则暂停现行进程的运行

【进程处于临界区时的调度问题】

进程在临界区时是不允许其他相关进程进入临界区的。但问题的关键在于系统中还存在着与这类进程无关的其他进程,其他进程的执行并不会受到这类进程是否处于临界区的影响。系统可以暂停该进程的执行,先去处理其他与之无关的紧急任务,处理完之后再返回来继续执行剩余的临界区代码

【调度算法】

1)各调度算法特性

FCFS算法比较有利于长作业进程,而不利于短作业进程;有利于 CPU 繁忙型的作业,而不利于 I/O 频繁行的作业。因为 CPU 繁忙型作业,是指该类作业需要大量的 CPU 事件进行计算,而很少请求 I/O,通常的科学计算便属于 CPU 繁忙作业。I/O 繁忙型作业是指 CPU 进行处理时又需要频繁地请求 I/O,而每次 I/O 的操作事件却很短

FCFS、SJF、优先级调度算法既可以用于作业调度也可以用于进程调度,时间片轮转、多级反馈队列用于进程调度,高响应比优先用于作业调度

FCFS 是非抢占式的;时间片轮转法是抢占式的;优先级调度算法既可以是非抢占式的,也可以是抢占式的

不会导致饥饿的调度算法:时间片轮转法;会导致饥饿的调度算法:静态优先级、非抢占式 SJF、抢占式 SJF

△☼▽

在这里插入图片描述

分析:
(1)如果就绪队列中总有优先数较小的进程时,优先数较大的进程则会一直处于等待状态,故可能会出现饥饿现象
(2) p r i o r i t y = n i c e + λ 1 c p u T i m e − λ 2 w a i t T i m e   ( λ 1 , λ 2 > 0 ) priority=nice+\lambda_1cpuTime-\lambda_2waitTime\ (\lambda_1,\lambda_2>0) priority=nice+λ1cpuTimeλ2waitTime (λ1,λ2>0)
这样设计的理由是:适当降低运行过的进程的优先级,提高长时间等待的进程的优先级。其中, w a i t T i m e waitTime waitTime 可使长时间等待的进程的优先数减小,从而避免出现饥饿现象

△☼▽

在这里插入图片描述

分析:
(1)FCFS:a → b → c → d → e;非抢占式的优先数:a → b → e → c → d
(2)

在这里插入图片描述
(3)

在这里插入图片描述

△☼▽

在这里插入图片描述

分析:C,P1、P2 被创建后依次插入 Q1 队列,先调度 P1,时间片用完后将 P1 插入 Q2,此时 P1 还需要 20ms;接着调度 P2,仍然时间片用完后插入 Q2,此时还需 10ms;在 Q2 队列中根据 SJF 先运行 P2,再运行 P1,整个过程如下图所示,虚线部分为两进程各自等待的时间

在这里插入图片描述

2)时间的计算

周转时间/(平均):作业从提交至完成的时间间隔,包括等待时间和执行时间, 作 业 i 的 周 转 时 间 T i = 作 业 i 的 完 成 时 间 − 作 业 i 的 提 交 时 间 作业 i 的周转时间 T_i = 作业 i 的完成时间-作业 i 的提交时间 iTi=ii

带权周转时间/(平均) W i = 作 业 i 的 周 转 时 间 / 作 业 i 的 运 行 时 间 W_i = 作业i的周转时间/作业i的运行时间 Wi=i/i

在这里插入图片描述

分析:D
P2:15+24+1 = 40
P3:18+24+1+36+1 = 80
P1:30+24+1+36+1+12+1 = 105
故平均周转时间为 (40+80+105)/3 = 75

在这里插入图片描述

分析:
FCFS:
平均等待时间 = (0+9.8+10.6+12.2+13)/5 = 9.12;
平均周转时间 = (10+10.8+12.6+13.2+18)/5 = 12.92;平均带权周转时间 = (10/10+10.8/1+12.6/2+13.2/1+18/5)/5 = 6.98

SJF: 1→2→4→3→5
平均等待时间 = (0+9.8+11.6+10.2+13)/5 = 8.92
平均周转时间 = (10+10.8+13.6+11.2+18)/5 = 12.72
平均带权周转时间 = (10/10+10.8/1+13.6/2+11.2/1+18/5)/5 = 6.68

非抢占式优先级:1→2→5→3→4
平均等待时间 = (0+9.8+15.6+17.2+10)/5 = 10.52
平均周转时间 = (10+10.8+17.6+18.2+15)/5 = 14.32
平均带权周转时间 = (10/10+10.8/1+17.6/2+18.2/1+15/5)/5 = 8.36

△☼▽

在这里插入图片描述

分析:FCFS、SJF 和非剥夺式优先级调度三个不说,重点看下时间片为 1 的RR 的执行情况

在这里插入图片描述
平均周转时间 = (19+2+7+4+14)/5 = 9.2s
平均带权周转时间 = (19/10+2/1+7/2+4/1+14/5) = 2.84s

【原语】

原语通常由若干条指令组成,用来实现某些特定的操作。通过一段不可分割的或不可中断的程序实现其功能。引进原语的主要目的就是为了实现进程的通信和控制。

【并发程序执行的特点】

  1. 间断性:并发程序具有 “执行——暂停——执行” 这种间断性的活动规律
  2. 失去封闭性:多个程序共享系统中的各种资源,因而这些资源的状态将由多个程序来改变,致使程序的运行失去了封闭性
  3. 不可再现性:由于失去了封闭性,程序的计算结果与并发程序的执行速度有关,从而使程序失去了可再现性

【导致一个进程去创建另外一个进程】

导致一个进程取得创建另一个进程的典型事件有 4 类:用户登录、作业调度、提供服务、应用请求

【互斥信号量】

互斥信号量仅能取值 1,0,-1,互斥信号量的初始值设为 1,表示同时只允许 1 个进程访问临界资源。当有 1 个进程提出访问临界资源请求时,执行 P 操作,互斥信号量 -1,变为 0,同时该进程进入临界区。如果另外一个进程此时也请求访问临界资源,则同样执行 P 操作。由于互斥信号量执行 P 操作之前的值为 0,执行 P 操作之后,信号量变为 -1

【信号量的值】

同步: 用 P、V 操作实现进程同步,信号量的初始值应根据具体情况来确定。若期望的消息未产生,则对应的初始值应设为 0;若期望的消息已经存在,则信号量应设为一个非 0 的正整数

互斥: 一般互斥信号量的初始值都设置为 1,P 操作成功则将其改成 0,V 操作成功则将其改为 1

△☼▽

在这里插入图片描述

分析:B,理论如上所述

△☼▽

在这里插入图片描述

分析:B,理论如上所述

△☼▽(模拟题一)

在这里插入图片描述

分析:B,先思考一个问题,之前的 28 次 P 操作和 18 次 V 操作对后面的计算有影响吗?没有,为什么?在 S ≤ 0 时,表示资源申请完毕,需要等待,|S| 表示等待该资源的进程数;S > 0 时,表示有空闲资源可申请,|S| 表示可申请的空闲资源数。显然,一对 PV 操作并不代表就有一个进程。于是,从 S = 0 时刻开始,3 次 V 操作后,S = 3 > 0 代表有 3 个空闲资源可申请,即没有进程处于等待状态

△☼▽

在这里插入图片描述

分析:A,理论如上所述

△☼▽

在这里插入图片描述

分析:B,理论如上所述

【管程】

管程定义了一个数据结构和能为并发进程所执行的一组操作,这组操作能同步进程和改变管程中的数据

管程由局部于管程的共享数据结构说明、操作这些数据结构的一组过程以及对局部于管程的数据结构设置初值的语句组成

管程有一下基本特征:

  1. 管程不仅能够实现进程间的互斥,而且能实现进程间的同步
  2. 管程的 V 操作不同于信号量机制中的 V 操作,前者必须在 P 操作之后,而后者则没有这个要求,只要和 P 操作配对出现即可
  3. 局部于管程的数据只能被局部于管程内的过程所访问
  4. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
  5. 每次仅允许一个进程在管程内执行某个内部过程,即进程互斥地通过调用内部过程进入管程

在管程定义中还包含以下支持同步的设施:

  1. 局限于管程并仅能从管程内进行访问的若干条件变量,用于区别不同的等待原因
  2. 在条件变量上进行操作的两个函数过程 wait 和 signal,若进程执行了 x.wait() 操作,那么该进程会阻塞,并挂到条件变量 x 对应的阻塞队列上。这样管程的使用权就被释放,就可以有另一个进程进入管程。如果进程执行了 x.signal() 操作,那么会唤醒 x 对应的阻塞队列对头进程。在 Pascal 语言的管程中,规定只有一个进程要离开管程时才调用 signal() 操作

(这里解释一下第 2 小点最后一句话什么意思:如果一个程序主动调用 signal 操作,表示其已经使用完共享数据,要离开管程,所以说一个进程要离开管程时才调用 signal 操作。那么上面说进程执行 wait 操作管程的使用权被释放是什么意思呢?进程执行 wait 操作被阻塞,没有获得共享数据的使用权,而管程中一次只能有一个进程在执行,那么被阻塞的进程不再使用管程,故其使用权被释放,允许其他进程进入管程使用共享数据)

【操作系统各功能组成部分中一定需要专门硬件配合支持的】

地址映射需要硬件机构来实现。当进程要访问某个逻辑地址中的数据时,分页地址变换机构(它是硬件)会自动将有效地址(相对地址)分为页号和业内地址两部分,再以页号为索引去检索页表。查找操作是有硬件执行的

中断系统需要硬件机构来实现。CPU 硬件中有一条中断请求线(IRL),CPU在执行完每条指令后,都将判断 IRL,当 CPU 检测到已经有中断控制器(即中断源)通过中断请求线发送了信号时,CPU 将保留少量转态,如当前指令位置,并且跳转到内存指定位置的中断处理程序。这里的中断控制器是硬件。

对于系统调用是否一定需要专门的硬件呢 ?我们需要清除系统调用的过程。C 编译程序采用一个预定义的函数库(C 的程序库),其中的函数具有系统调用放入名字,从而解决了在用户程序中请求系统调用的问题。这些库函数一般都执行一条指令,该指令将进程的运行方式变为核心态,然后使内核开始为系统执行代码,称这个指令为操作系统陷入。系统调用的接口是一个中断处理程序的特例。在处理操作系统陷入时:

  1. 内核根据系统调用号查系统调用入口表,找到相应的内核子程序的地址;
  2. 内核还要确定该系统调用所要求的参数个数;
  3. 从用户地址空间赋值参数到 U 区(UNIX V);
  4. 保存当前上下文,执行系统调用代码;
  5. 恢复处理器现场并返回

上述 1~3 过程和 5 过程都不需要专门的硬件,只有 4 过程可能需要专门的硬件,如显示器输出字符,但也可以不需要专门硬件,如打开一个已经在缓存中的文件

进程调度一般是通过使用一些调度算法来编程实现的

【线程】

1)线程的基本概念

线程是进程内一个相对独立的、可调度的执行单元。线程自己基本上不拥有资源,只拥有一点在运行时必不可少的资源(如用于控制线程运行的线程控制块 TCB,用于指示被执行指令序列的程序计数器,保留局部变量、少数状态参数和返回地址等的一组寄存器和栈),但它可以与同属于一个进程的其他线程共享进程拥有的全部资源

2)线程的实现

用户级线程:

  • 是指依赖于内核,由操作系统内核完成创建和撤销工作的线程
  • 在支持内核级线程的操作系统中,内核维护进程和线程上下文信息并完成线程切换工作
  • 一个内核级线程由于 I/O 操作而阻塞时,不会影响其他线程的运行(对比用户级线程来理解,当线程执行一个系统调用时不仅该线程被阻塞,而且进程内的所有线程会被阻塞)
  • 这时,处理器时间片的分配对象是线程,所以有多个线程的进程将获得更多处理器时间

用户级线程:

  • 是指不依赖于操作系统核心,由应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制的线程
  • 由于用户级线程的维护由应用进程完成,不需要操作系统内核了解用户级线程的存在,因此可用于不支持内核级线程的多进程操作系统,甚至是单用户操作系统
  • 用户级线程切换不需要内核特权,用户级线程调度算法可针对应用优化。由于用户级线程的调度在应用程序内部进行,通常采用非抢占式和更简单的规则,无需用户态/核心态切换,因此速度特别快
  • 由于操作系统内核不了解用户线程的存在,当一个线程阻塞时,整个进程都必须等待
  • 这时,处理器时间片是分配给进程的,当进程内有多个线程时,每个线程的执行时间相对减少

3)细节问题上的解释

① 对线程的时间片分配

由于用户级线程不依赖于操作系统内核,因此,操作系统内核是不知道用户线程的存在的,由于操作系统不知道用户级线程的存在,所以,操作系统把 CPU 的时间片分配给用户进程,再由用户进程的管理器将时间片分配给用户线程。那么,用户进程能得到的时间片即为所有用户线程共享。所以,该进程只占有 1 个时间片。若是内核级线程,由于内核级线程操作系统是知道的,它们与进程一样地分配处理器时间,所以,有多少个内核级线程就可以获得多少个时间片

② 线程可以共享进程的哪些内容?不共享哪些内容?

属于同一进程的各个线程有属于自己的栈空间,不允许共享

(为什么不允许共享呢?在线程运行时,经常会进行过程调用,而过程的调用通常会出现多重嵌套的情况,这样就必须将每次过程调用中所使用的局部变量以及返回地址保存起来。为此,应为每个线程设置一个堆栈,用来保存局部变量和返回地址

另外,在线程的 TCB 中,需设置两个指向堆栈的指针:一个指向用户自己堆栈的指针,当线程运行在用户态时,使用用户自己的用户栈来保存局部变量和返回地址;一个指向核心栈的指针,当线程运行在核心态时使用系统的核心栈)

线程共享的是进程的虚地址空间

③ 内核级线程的调度

在内核支持线程的情况下,在同一进程中,从一个线程切换到另一个线程时,需要从用户态转到核心态运行,这是因为用户进程的线程是在用户态运行,而线程调度和管理是在内核实现的

④ 在多处理机系统中内核级线程和用户级线程的不同

在多处理器系统中,内核能够同时调度同一进程中的多个线程并行执行;而在单纯的用户级线程实现方式中,多线程应用不能利用多处理机进行多重处理,内核每次分配给一个进程唯一的一个 CPU,因此,线程中仅有一个线程能执行

4)线程和进程的比较

  • 调度:
    ① 线程是独立调度的基本单位,进程是拥有资源的基本单位
    ② 同一进程中的线程切换不会引起进程切换
    ③ 不同进程中的线程切换换回引起进程切换
  • 并发性:
    同一进程内的多个线程可以并发执行,甚至不同进程内的多个线程也可以并发执行
  • 拥有资源:(没有新内容,便不再赘述)
  • 系统开销:
    ① 创建进程时,必须为它分配除处理机以外所必须的、所有资源;撤销进程时,必须先对其所占有资源执行收回操作,再撤销 PCB;进程切换时,需要保留当前进程的 CPU 环境,设置新调度进程的 CPU 环境。诸如此类下来,OS 对进程所付出的系统开销明显大于对线程所付出的系统开销
    ② 由于一个进程中的多个线程具有相同的地址空间,线程之间的同步和通信也比进程简单
  • 支持多处理机系统:
    ① 在多处理机系统中 ,对于传统进程,即单线程进程,不管有多少处理机,该进程只能在一个处理机上运行
    ② 对于多线程进程,可以将一个进程中的多个线程分配到多个处理机上并行执行

5)多线程模型

  • 多对一模型: 多对一模型将多个用户级线程映射到一个内核级线程上。对于这些多个映射的线程一般属于一个进程,运行在该进程的用户空间,对这些线程的调度和管理也是在该进程的用户空间中完成。仅当用户线程需要访问核心时,才将其映射到一个内核级线程上,且每次只允许一个线程进行映射。由于多个用户级线程映射到同一个内核级线程,只要一个用户级线程阻塞,就会导致整个进程阻塞。而且由于系统只能识别一个内核级线程,因此即使有多个处理器,该进程的若干个用户级线程也只能同时运行一个,不能并行执行
  • 一对一模型: 一对一模型将内核级线程与用户级线程一一对应。这样做的好处是当一个线程阻塞时,不影响其他线程的运行。而且这时,在多处理器上可以实现多线程并行。该模型的缺点是创建一个用户级线程时需要创建一个相应的内核级线程
  • 多对多模型: 多对多模型将多个用户级线程映射到多个内核级线程(内核级线程数不多于用户级线程数),这样的模型不仅可以使多个用户级线程真正意义上并行执行,而且不会限制用户级线程的数量。用户可以自由创建所需的用户级线程,多个内核级线程根据需要调用用户级线程,当一个用户级线程阻塞时,可以调度执行其他线程

△☼▽

在这里插入图片描述

分析:C,一般只有一个键盘,而且人为动作相对于计算机来说是十分缓慢的,故可以用一个线程来处理所有键盘输入

△☼▽

在这里插入图片描述

分析:B

对 A,强调内核级线程,故正确
对 B,在多线程模型中,用户级线程和内核级线程的连接方式有多对一、一对一、多对多,操作系统为每个用户线程建立一个线程控制块属于一对一模型,故错误
对 C,显然正确
对 D,用户级线程的管理工作可以只在用户空间中进行,故可以在不支持内核级线程的操作系统上实现,故正确

【进程优先级改变】

进程完成 I/O 后,进入就绪队列时,已经是优先级最低的进程,不能在降低其优先级,为了让其及时处理 I/O 结果,应该提升优先级

进程长期处于就绪队列,需要增加其优先级,使其不至于产生饥饿

当进程处于执行态时,不应该提高或降低其优先级

在时间片调度算法中,若进程时间片用完,则需要将其排到就绪队列的末尾,也就是优先级最低,所以降低优先级的合理时机是时间片用完时。另外,在多级反馈调度算法中,若时间片用完,但进程还没有结束,则放到下一级队列中,优先级也是被降低了

【处理器的三级调度】

处理器的三级调度是指一个作业在运行过程中要遇到的高级调度(作业调度)、中级调度(进程对换)和低级调度(进程调度)。不过,不是所有操作系统都有三级调度,有些只实现了其中的一级或者两级,但是每个操作系统都有进程调度

高级调度的主要工作是决定外存的后备队列中哪个进程被调入内存中,并给这个作业创建进程,给分配它必要的资源;中级调度的主要工作是在内存紧张时把就绪队列中暂时得不到执行的进程换到外存,也负责在内存比较空闲时把换到外存的进程调回内存;低级调度的主要工作是决定把 CPU 分配给就绪队列中的哪个进程

高级调度主要在需要从外存调入一个作业到内存中时发生;中级调度主要在内存紧张需要调出一些进程,或者内存空闲时需要把一些进程调回内存时发生;低级调度主要在正在执行的进程放弃 CPU 或者被其他优先级高的进程抢占 CPU 时发生

  • 21
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值