银行家算法

一、死锁及银行家算法背景介绍

1.1 死锁

1.1.1 定义:

  • 指的是多个进程在运行过程中因为争夺资源而造成的一种僵局,当进程处于这种僵局状态时,若无外力作用,他们都将无法再向前推进的状态。

1.1.2 产生死锁的原因:

  • (1)竞争非可剥夺性资源;
  • (2)进程推进不当。

1.1.3 产生死锁的必要条件:

  • (1)互斥条件;
  • (2)请求和保持条件;
  • (3)不可剥夺条件;
  • (4)循环等待条件。

1.1.4处理死锁的基本发法

  (1)预防死锁:属于事前预防的策略,通过设置某些限制条件,去破坏产生死锁的四个必要条件或其中的几个条件。预防死锁比较容易实现,所以被泛使用,但是由于施加的限制条件过于严格可能会导致系统资源利用率和系统吞吐量降低。
  (2)避免死锁:属于事前预防的策略,但它并不需要事先采取各种限制措施去破坏产生死锁的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的产生。但实现有一定的难度。目前较完善的系统中常用此法来避免死锁。
  (3)检测死锁:这种方法不需要事前采取任何限制措施,也不用检查是否进入不安全状态,而是允许系统在运行的过程中发生死锁。但是通过系统所设置的检
测机构.及时的检测出死锁的发生,并精确的测出与死锁有关的进程和资源,然后,采取适当的措施,从系统中将已发生的死锁清楚掉。
  (4)解除死锁:这是与检测死锁相配套的一套措施。
当检测到系统已经产生死锁时,须将进程从死锁中解放出来。通常用到的实施方法是撤销或挂起些进程,以便收回一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源和吞吐量,但在现实上难度也最大。
  (5)预防死锁和避免死锁的区别:预防死锁和避免死锁实质上都是通过施加某种相知条件的方法,来预防发生死锁。两者的主要区别:为了预防死锁所施加的限制条件较为严格,这往往会影响到进程的并发执行,而避免死锁所施加的限制条件则较为宽松,有利于进程的并发执行。

1.2 银行家算法的由来

  首先银行家算法属于避免死锁的一个著名算法,由Dijkstra在1965年为T.H.E系统设计的一种避免死锁产生的算法。这是由于该算法用于银行系统现金贷款的发放而得名。

  Dijkstra的银行家算法是操作系统中最有代表性的死锁避免方案,该算法由于能够用于银行系统现金的发放而得名。在银行中,客户申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求时,客户应及时归还。银行家在客户申请的贷款数量不超过自己拥有的最大值时,都应尽量满足客户的需要。在这样的描述中,银行家就好比操作系统,资金就是资源,客户就相当于要申请资源的进程。
  操作系统作为裸机上安装的第一层软件, 起着控制和管理计算机内部软硬件资源,合理组织计算机工作流程,提高计算机工作效率,用户和计算机硬件接口的重要作用。因此操作系统即要保证系统资源的合理分配提高系统资源利用率,同时又要避免死锁等不安全状况的出现,如果这些不安全状况出现操作系统还要解决这些问题,让系统回到安全状态。银行家算法就是在这样的背景下应运而生的。

1.3 银行家算法的作用

  银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。为实现银行家算法,系统必须设置若干数据结构。要解释银行家算法,必须先解释操作系统安全状态和不安全状态。
  安全序列是指一个进程序列{P1,…,Pn}是安全的,即对于每一个进程Pi(1≤i≤n),它以后尚需要的资源量不超过系统当前剩余资源量与所有进程Pj (j < i )当前占有资源量之和。
  银行家算法的核心它通过自己特有的算法,在每次奉陪给进程系统资源前,先试探性的“假设”分配资源给进程Pi,再通过安全性算法检测此次分配是否会导致系统进入不安全状态,如果分配后系统依然安全,则系统将资源正是分配给进程Pi;如果此次分配导致系统进入不安全状态,则暂不分配资源给进程Pi。通过这种机制,系统可以有效的避免死锁的产生,确保系统时时刻刻都处在安全状态。

二、银行家算法的原理及实现过程

2.1 大概流程:

  (1)T0时刻系统通过算法检测当前系统是否安全,即是否可以找到一个安全序列使当前所有进程顺利完成,若找到则称系统安全。并按照序列对各个进程的执行,并执行步骤(2);若找不到使说明系统不安全,即系统已进入死锁状态,立刻停止进程的执行,通过挂起些进程来让系统重新进入安全态,当系统处于安全状态后转向执行步骤(2)
  (2)当T1时刻有进程对系统提出请求时,系统先先检测当前进程资源请求是否大于此进程最大需求,若大于则不执行;若不大于,再检测当前的资源要求是否大于系统当前可分配数,若大于则不子分配,若不大于则进行步骤(3)。
  (3)系统假设将进程Pi要求的资源分配给Pi,通过安全性算法检测将资源分配给进程后系统是否处于安全状态,若处于安全状态则分配资源,若不处于则不予分配

2.2 详细设计

2.2.1 银行家算法数据结构

  Avaliable[m]—可利用资源向量。这是一个含有m个元素的数组,Available[j]=k表示系统中第j类资源数为k个,其初值是系统中所配置的该类全部可用资源数目,其数值随该类资源的分配和回收而动态地改变。

  Max[n,m]—最大需求矩阵。这是一个n*m的矩阵,它定义了系统中n个进程中的每一个进程对m类资源的最大需求。n为系统中进程数,Max[i,j]=k表示进程i对j类资源的最大需求数为中k。
  Allocation[n,m]—分配矩阵。这是一个n*m的矩阵,它定义了系统中每一类资源当前已分配给每一进程资源数, Allocation[i,j] = k表示进程i已分得j类资源的数目为k个。
  Request—进程Pi的请求向量Request。这是一个含有m个元素的向量,它表示进程i对各类资源的需求情况。如果Request[j] = K,表示进程Pi需要K个Rj类型的资源。
  Need[n,m]—需求矩阵。这是一个n*m的矩阵,它定义了当前系统的n个进程要想完成工作,还需要各类资源的数目,它表示每个进程尚需的各类资源数,Need[i,j]=k 表示进程i 还需要j类资源k个。因此有以下关系:

1     Need[i,j] = Max[i,j]-Allocation[i,j]

2.2.2 安全性算法数据结构

  Work—工作向量。这是-一个含有m个元素的向量,表示系统可以提供给进程继续运行所需的各类资源数目,其初值Work=Avaliable。
  Finish—结束向量。表示系统是否有足够的资源分配给进程,使之运行完成。开始时Finish[i]=false,当有足够资源分配给进Finish[j]=ture。

2.2.3 银行家算法

  假设进程Pi提出资源请求Request[j] = k

    (1)若Request[i]<= Need[i,j]便转向执行步骤 (2) ;否则认为出错。
    (2)若Request[i]<= Available[i,j]便转 向执行步骤(3);否则表示系统尚无足够的资源分配,让Pi等待。
    (3)系统假设将Pi所要求的资源分配给Pi,并对数据结构做如下修改:

1      Avaliable[j] =Available[j] - Request[j]  
2      Allocation[i,j] = Allocation[i,j] - Request[j]  
3      Need[i,j] = Need[i,j]- Request[j]

    (4)系统执行安全性算法,检测此次资源分配后,系统是否处于安全状态。若安全才正式将资源分配给进程Pi,以完成本次分配;否则,本次试探分配废,恢复原来的资源分配状态,让Pi等待。

2.2.4 安全性算法

工作向量Work:

  它表示系统可提供给进程继续运行所需的各类资源数目,它含有m个元素

Finish:

  它表示系统是否有足够的资源分配给进程,使之运行完成。开始时先做Finish[i]:= False; 当有足够资源分配给进程时, 再令Finish[i]:= True。

  • (1) 设置两个向量: 在执行安全算法开始时。
    1 Work∶=Available; Finish[i]:= False;
  • (2)从进程集合中找到一个能满足以下条件的进程:
    1     Finish[i] = false;
    2     Need[i,j] <= Work[j]; 

若找到执行步骤(3)否则执行步骤(4)。

  • (3)进程Pi获得资源后,可以顺利执行,直至完成,并释放分配给它们的资源,故应执行:
    1     Work[j] = Work[j] +Allocation[i,j];
    2     Finish[i] = true; 

转向执行步骤(2);

  • (4)若所有的进程的Finish[i]=true都满足,则表示系统处于安全状态;否则系统处于不安全状态

  由此可见,安全性算法实际是银行家算法的一部分,为当前系统找到一个安全序列,确保此刻的系统的分配状态能够保证内存中的进程全部顺利完成。

三、测试与分析

  本次测试过程中共有6个测试用例,分别是:

3.1 测试1

  测试1:初始化各个矩阵,并调用安全性算法判断系统是否处于安全状态
  试结果:系统处于安全状态。

3.2 测试2

  测试2:P0发出请求Request0(1,2,3,1),系统按银行家算法检查:
    ①Request0(1,2,3,1) <= Need0(7,4,3,1);
    ②Request0(1,2,3,1) > Available(3,3,2,4);让P0等待!
  测试结果:分配出错!此进程请求资源超过系统资源,P0进程等待!!!

3.3 测试3

  测试3:P1发出请求Request1(1,2,2,1),系统按银行家算法检查:
    ①Request1(1,2,2,1) > Need1(1,2,2,0);分配出错!此进程请求资源超过它宣布的最大需求!!!
  测试结果:此进程请求资源超过它宣布的最大需求!!!

3.4 测试4

  测试4:P3发出请求Request3(0,1,0,1),系统按银行家算法检查:(有安全序列但不能回收)
    ①Request3(0,1,0,1) <= Need3(0,1,1,3);
    ②Request3(0,1,0,1) <= Available(3,3,2,4);
    ③资源预分配给此进程

  调用安全性算法:
  测试结果:安全序列:P1->P3->P4->P0->P2->
  此时,预分配成功,但是不能回收p3进程的资源,因为Request3 < Need3。

3.5 测试5

  测试5:P2发出请求Request2(3,0,0,2),系统按银行家算法检查:(若分配会形成死锁)
    ①Request1(3,0,0,2) <= Need3(6,0,0,2);
    ②Request1(3,0,0,2) <= Available(3,2,2,3);
    ③资源预分配给此进程
  经过系统检查,可用资源Available(0,2,2,1)已不能满足任何进程的需要,故系统若分配此请求会进入不安全状态,所以,此时不分配,本次试探分配作废,恢复原来的资源状态分配。
  测试结果:预分配失败!若分配会导致系统进入不安全序列。

3.6 测试6

  测试6:P3发出请求Request3(0,0,1,2),系统按银行家算法检查:(有安全序列且能回收)
    ①Request3(0,0,1,2) <= Need3(0,1,1,3);
    ②Request3(0,0,1,2) <= Available(3,3,2,4);
    ③资源预分配给此进程
  经过安全性算法检查,预分配不会导致系统进入不安全状态,可以分配,并且可以回收p3进程的资源,因为Request3 == Need3。
  测试结果:预分配成功!

  至此,所有测试用例结束,这6种用例包含了所有可能会出现的情况,从而保证系统在分配资源时能合理的避免死锁的发生。

四、代码实现

4.1 头文件

 1 #pragma once
 2 #ifndef 银行家算法_H_
 3 #define 银行家算法_H_
 4 
 5 #define _CRT_SECURE_NO_WARNINGS 1
 6 #include<iostream>
 7 #include<vector>
 8 #include<map>
 9 #include<algorithm>
10 #include<cstdio>
11 #include<cstdlib>
12 using namespace std;
13 
14 enum Status//系统可能出现的状态
15 {
16     NEED_REQUEST = 0,//所需资源超过此进程宣布的最大值
17     AVAILABLE_REQUEST,//所需资源超过此系统当前资源
18     SECURITY,//安全状态
19     UNSECURITY,//不安全状态
20     SUCCESS,//分配成功
21     FAIL//分配失败
22 };
23 
24 void Init();//初始化
25 void Display();//显示各个进程资源情况
26 void Dis_char(int i, char ch);//将ch字符显示i次
27 bool operator <= (vector<int>& Request, vector<int>& Need);//比较是否Request都小于等于Need
28 bool operator ==(vector<int>& Need, int n);//Need的每一项都等于0
29 void Display_Security(pair<vector<int>, Status>& val);//显示安全序列
30 void Display_Security2(pair<vector<int>, Status>& val);//显示各个进程安全情况
31 void Require();//进程请求资源
32 pair<vector<int>, Status> AlgoBanker(int Pid, vector<int>& Requset);//银行家算法
33 pair<vector<int>, Status> AlgoSecurity();//安全性算法
34 
35 
36 #endif

4.2 源文件

  1 #include "银行家算法.h"
  2 
  3 #define MAX_SOURCE 6 //可打印的最大资源数
  4 
  5 #define STATE 4 //表格中的四列
  6 vector<vector<int>> Allocation;//系统给此进程已分配资源
  7 vector<vector<int>> Need;//此进程还需分配资源
  8 vector<vector<int>> Max;//此进程需要最大资源
  9 vector<int> Available;//系统可用资源
 10 
 11 void Dis_char(int i, char ch)
 12 {
 13     while (i--)
 14     {
 15         cout << ch;
 16     }
 17 }
 18 
 19 void Display()
 20 {
 21     char Name[] = "ABCDEFGH";
 22     int n = Need.size();//进程数
 23     int m = Need[0].size();//资源数
 24     int i, j, tmp;
 25 
 26     //1.打印表头
 27     printf("|—————————————————————————————————————————-|
");
 28     printf("%-8s%-19s%-19s%-19s%-20s", "|进\资源", "|       Max        ", "|    Allocation    ", "|       Need       ", "|     Available    |
");
 29     printf("|   \情 |—————————|—————————|—————————|—————————|
");
 30     printf("|程  \况");
 31     for (i = 0; i < STATE; i++)
 32     {
 33         cout << "|";
 34         for (j = 0; j < m; j++)
 35         {
 36             printf(" %c ", Name[j]);
 37         }
 38         tmp = MAX_SOURCE - m;//超出的用空格弥补
 39         Dis_char(tmp * 3, ' ');
 40     }
 41     cout << "|
";
 42     printf("|-------|—————————|—————————|—————————|—————————|
");
 43 
 44 
 45 
 46     //2.打印每一行
 47     for (i = 0; i < n; i++)
 48     {
 49         printf("|  P%-2d  ", i);
 50         for (j = 0; j < m; j++)//Max数据
 51         {
 52             cout << "|";
 53             for (j = 0; j < m; j++)
 54             {
 55                 printf(" %d ", Max[i][j]);
 56             }
 57             tmp = MAX_SOURCE - m;//超出的用空格弥补
 58             Dis_char(tmp * 3, ' ');
 59         }
 60 
 61 
 62         for (j = 0; j < m; j++)//Allcoation数据
 63         {
 64             cout << "|";
 65             for (j = 0; j < m; j++)
 66             {
 67                 printf(" %d ", Allocation[i][j]);
 68             }
 69             tmp = MAX_SOURCE - m;//超出的用空格弥补
 70             Dis_char(tmp * 3, ' ');
 71         }
 72 
 73         for (j = 0; j < m; j++)//Need数据
 74         {
 75             cout << "|";
 76             for (j = 0; j < m; j++)
 77             {
 78                 printf(" %d ", Need[i][j]);
 79             }
 80             tmp = MAX_SOURCE - m;//超出的用空格弥补
 81             Dis_char(tmp * 3, ' ');
 82         }
 83 
 84 
 85         cout << "|";
 86         for (j = 0; j < m; j++)//Available数据
 87         {
 88             printf(" %d ", Available[j]);
 89         }
 90 
 91         tmp = MAX_SOURCE - m;//超出的用空格弥补
 92         Dis_char(tmp * 3, ' ');
 93 
 94         cout << "|
";
 95         printf("|-------|—————————|—————————|—————————|—————————|
");
 96     }
 97 }
 98 
 99 bool operator <= (vector<int>& Request, vector<int>& Need)//比较是否Request都小于等于Need
100 {
101     for (int i = 0; i < Request.size(); i++)
102     {
103         if (Request[i] > Need[i])
104         {
105             return false;
106         }
107     }
108     return true;
109 }
110 
111 bool operator ==(vector<int>& Need, int n)//Need的每一项都等于0,
112 {
113     for (auto& e : Need)
114     {
115         if (e != n)
116         {
117             return false;
118         }
119     }
120     return true;
121 }
122 
123 
124 void Display_Security(pair<vector<int>, Status>& tmp)//显示是否安全
125 {
126     if (tmp.second == SUCCESS)
127     {
128         cout << "安全序列为:";
129         for (auto& e : tmp.first)
130         {
131             cout << "P" << e << "->";
132         }
133         cout << endl;
134 
135     }
136     else
137     {
138         if (tmp.first.size() > 0)
139         {
140             for (auto& e : tmp.first)
141             {
142                 cout << "P" << e << "->";
143             }
144             cout << "找到P" << tmp.first[tmp.first.size() - 1] << "进程后再找不到安全序列!
";
145         }
146     }
147 }
148 
149 void Init()
150 {
151     pair<vector<int>, Status> retval;
152     int PcbNum, Source;
153     int i, j, tmp;
154     cout << "输入进程的个数:";
155     cin >> PcbNum;
156     cout << "输入资源的种类:";
157     cin >> Source;
158 
159     cout << "输入系统剩余资源(Available):";
160     for (i = 0; i < Source; i++)
161     {
162         cin >> tmp;
163         Available.push_back(tmp);
164     }
165     cout << endl;
166 
167     cout << "输入Allocation矩阵" << endl;
168 
169     for (i = 0; i < PcbNum; i++)
170     {
171         Allocation.push_back(vector<int>());
172         Max.push_back(vector<int>());
173 
174         for (j = 0; j < Source; j++)
175         {
176             cin >> tmp;
177             Allocation[i].push_back(tmp);
178             Max[i].push_back(tmp);
179         }
180     }
181     cout << endl;
182 
183     cout << "输入Need矩阵" << endl;
184 
185     for (i = 0; i < PcbNum; i++)
186     {
187         Need.push_back(vector<int>());
188 
189         for (j = 0; j < Source; j++)
190         {
191             cin >> tmp;
192             Need[i].push_back(tmp);
193             Max[i][j] += tmp;
194         }
195     }
196     cout << "
初始化完毕!

";
197     cout << "检测系统是否安全.......
";
198     retval = AlgoSecurity();
199     if (retval.second == SECURITY)
200     {
201         cout << "系统此时为安全状态
";
202         Display();
203     }
204 
205     else
206     {
207         cout << "系统此时处于不安全状态,请检查你的数据是否合理
";
208     }
209 
210 }
211 
212 void Require()//进程请求资源
213 {
214     int Pid, tmp;
215     vector<int>Requset;
216     pair<vector<int>, Status> retval;
217 
218     cout << "输入要想系统请求资源的进程Id:";
219     cin >> Pid;
220 
221     cout << "输入要想向系统请求资源的大小:";
222     for (int i = 0; i < Available.size(); i++)
223     {
224         cin >> tmp;
225         Requset.push_back(tmp);
226     }
227 
228     retval = AlgoBanker(Pid, Requset);
229 
230     switch (retval.second)
231     {
232     case NEED_REQUEST:cout << "
请求出错!此进程请求资源超过它宣布的最大需求!!!
"; cout << "当前时刻资源分配表
"; Display(); break;
233     case AVAILABLE_REQUEST:cout << "
请求出错!此请求所需资源超过系统资源,P" << Pid << "进程等待!!!
"; cout << "当前时刻资源分配表
"; Display(); ; break;
234     case SUCCESS:cout << "
预分配成功!
"; Display_Security(retval); cout << "当前时刻资源分配表
"; Display(); break;
235     case FAIL:cout << "
分配失败!此次分配会导致系统进出入不安全状态!!!
"; Display_Security(retval); cout << "当前时刻资源分配表
"; Display(); break;
236     }
237 }
238 
239 
240 pair<vector<int>, Status> AlgoBanker(int Pid, vector<int>& Requset)//银行家算法,传入进程Id和请求分配大小
241 {
242     pair<vector<int>, Status> retval;//first---安全序列,second---分配状态
243 
244     if (!(Requset <= Need[Pid]))//请求大于所需大小
245     {
246         retval.second = NEED_REQUEST;
247     }
248 
249     else if (!(Requset <= Available))//请求大于系统拥有资源
250     {
251         retval.second = AVAILABLE_REQUEST;
252     }
253 
254     else
255     {
256         for (int i = 0; i < Available.size(); i++)//试着分配给他
257         {
258             Available[i] -= Requset[i];
259             Allocation[Pid][i] += Requset[i];
260             Need[Pid][i] -= Requset[i];
261         }
262         cout << "预分配状态表:
";
263         Display();
264         retval = AlgoSecurity();
265 
266         if (retval.second == SECURITY)//若处于安全状态
267         {
268             if (Need[Pid] == 0)//不再需求资源,回收
269             {
270                 for (int i = 0; i < Available.size(); i++)
271                 {
272                     Available[i] += Allocation[Pid][i];
273                     Allocation[Pid][i] = 0;
274                 }
275             }
276             else//这次请求后还未完成(Need[Pid]!=0),不能回收
277             {
278                 //do nothing
279             }
280             retval.second = SUCCESS;//分配成功
281         }
282         else//不是安全状态,本次分配作废,回复原来状态。此进程等待
283         {
284             for (int i = 0; i < Available.size(); i++)
285             {
286                 Available[i] += Requset[i];
287                 Allocation[Pid][i] -= Requset[i];
288                 Need[Pid][i] += Requset[i];
289             }
290             retval.second = FAIL;
291         }
292     }
293     return retval;
294 }
295 
296 pair<vector<int>, Status> AlgoSecurity()//安全性算法
297 {
298     pair<vector<int>, Status> retval;//first---安全序列,second---分配状态
299 
300     //①初始化两个向量
301     vector<int> Work(Available);//工作向量
302     vector<bool> Finish(Need.size(), false);//可分配标志
303     int i, j;
304     int flag;//每找到一个进程满足安全状态,flag--,减到0说明系统出于安全状态
305     int tag;
306 
307     //②,查找
308     while (1)
309     {
310         flag = count(Finish.begin(), Finish.end(), false);//flag记录暂时不能分配的进程个数
311         tag = flag;//记录flag大小
312 
313         for (i = 0; i < Need.size(); i++)
314         {
315             if (Need[i] <= Work && Finish[i] == false)//找到一个进程满足分配需求且状态是未分配
316             {
317                 //
318                 for (j = 0; j < Work.size(); j++)
319                 {
320                     Work[j] += Allocation[i][j];
321                 }
322                 Finish[i] = true;
323                 flag--;
324                 retval.first.push_back(i);//将进程id  push 进安全序列中
325             }
326         }
327 
328         //④ ,所有进程Finish==true,表示系统安全
329         if (flag == 0)
330         {
331             retval.second = SECURITY;
332             break;
333         }
334 
335         //进入死锁,不安全
336         if (tag == flag)
337         {
338             retval.second = UNSECURITY;
339             break;
340         }
341         else //循环查找
342         {
343             //do nothing
344         }
345 
346     }
347     return retval;
348 }

4.3 测试主函数

 1 #include"Banker.h"
 2 
 3 int main()
 4 {
 5     int choose;
 6     while (1)
 7     {
 8         cout << "           银行家算法:" << endl<<endl;
 9         cout << "*****  1 - 初始化各矩阵        *****" << endl;
10         cout << "*****  2 - 进程提出请求        *****" << endl;
11         cout << "*****  3 - 显示各进程资源情况  *****" << endl;
12         cout << "*****  0 - 结束                *****" << endl << endl;
13         cout << "输入你的选择 : ";
14         cin >> choose;
15         switch (choose)
16         {
17         case 0:exit(0); break;
18         case 1:Init(); break;
19         case 2:Require(); break;
20         case 3:Display(); break;
21         default:cout << "请输入正确的序号:" << endl;
22         }
23     }
24     system("pause");
25     return 0;
26 }

五、参考文章

https://blog.csdn.net/Caoyang_He/article/details/80819411

https://www.cnblogs.com/wkfvawl/p/11929508.html

https://blog.csdn.net/weixin_43886592/article/details/107582347

本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/15186204.html

原文地址:https://www.cnblogs.com/MrLiuZF/p/15186204.html