C++编程实现模拟中断处理

实验一 中断处理

实验题目

  1. 计算机系统工作过程中,若出现中断事件,硬件就把它记录在中断寄存器中。中断寄存器的每一位可与一个中断事件对应,当出现某中断事件后,对应的中断寄存器的某一位就被置成“1”。

    • 处理器每执行一条指令后,必须查中断寄存器,当中断寄存器内容不为“0”时,说明有中断事件发生。硬件把中断寄存器内容以及现行程序的断点存在主存的固定单元。操作系统分析保存在主存固定单元中的中断寄存器内容就可知道出现的中断事件的性质,从而作出相应的处理。
    • 本实验中,用从键盘读入信息来模拟中断寄存器的作用,用计数器加1来模拟处理器执行了一条指令。每模拟一条指令执行后,从键盘读入信息且分析,当读入信息=0时,表示无中断事件发生,继续执行指令;当读入信息=1时,表示发生了时钟中断事件,转时钟中断处理程序。
  2. 假定计算机系统有一时钟,它按电源频率(50Hz)产生中断请求信号,即每隔20毫秒产生一次中断请求信号,称时钟中断信号,时钟中断的间隔时间(20毫秒)称时钟单位。

    • 学生可按自己确定的频率在键盘上键入“0”或“1”来模拟按电源频率产生的时钟中断信号。
  3. 中断处理程序应首先保护被中断的现行进程的现场(通用寄存器内容、断点等),现场信息可保存在进程控制块中;然后处理出现的中断事件,根据处理结果修改被中断进程的状态;最后转向处理器调度,由处理器调度选择可运行的进程,恢复现场使其运行。

    • 本实验主要模拟中断事件的处理,为简单起见可省去保护现场和处理器调度的工作。
  4. 为模拟时钟中断的处理,先分析一下时钟中断的作用。利用时钟中断可计算日历时钟,也可作定时闹钟等。

    • 计算日历时钟——把开机时的时间存放在指定的称为“日历时钟”的工作单元中,用一计时器累计时钟中断次数。根据时钟中断的次数和时钟单位(20毫秒)以及开机时的日历时钟可计算出当前的精确的日历时钟。
    • 定时闹钟——对需要定时的场合,可把轮到运行的进程的时间片值送到称为“定时闹钟”的工作单元中,每产生一次时钟中断就把定时闹钟值减1,当该值为“0”时,表示确定的时间已到,起到定时的作用。
  5. 本实验的模拟程序可由两部分组成,一部分是模拟硬件产生时钟中断,另一部分模拟操作系统的时钟中断处理程序。模拟程序的算法如图。其中,保护现场和处理器调度的工作在编程序时可省去。约定处理器调度总是选择被中断进程继续执行。

    image-20211110141519912

程序中使用的数据结构及符号说明

  • 字符数组(数据结构:数组)

    • char *s_st; :开机时间的字符串形式。
    • char *s_now; :当前时间的字符串形式。
  • time_t(长整型):

    • time_t st;:开机时间自 1970 年 1 月 1 日以来经过的秒数。
    • time_t now; : 当前时间自 1970 年 1 月 1 日以来经过的秒数。
  • 整型(int):

    • int count;:(指令)计数器。
    • int timer; :(时间中断)计时器。
    • int wr;:键盘输入信息。
    • int time_alarm:定时闹钟(时钟单位为20毫秒)。
    • int p; :距离开机时间经过的时间(单位:毫秒)。

源程序及注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <ctime>
using namespace std;

int main()
{
time_t st = time(0); //基于当前系统的开机时间,自 1970 年 1 月 1 日以来经过的秒数。
char *s_st = ctime(&st); //将开机时间转化为字符串形式;
cout << "开机时间为:" << s_st;
//格式为:Www Mmm dd hh:mm:ss yyyy
//其中,Www 表示星期几,Mmm 是以字母表示的月份,dd 表示一月中的第几天,hh:mm:ss 表示时间,yyyy 表示年份。
int count = 0; //计数器
int timer = 0; //计时器
int wr;//键盘输入信息,“0”或“1”来模拟按电源频率产生的时钟中断信号
int time_alarm; //定时闹钟
cout << "请输入定时闹钟:";
cin >> time_alarm; //置定时闹钟 (毫秒)
while (time_alarm != 0) //当定时时钟不为0
{
cout << "执行了一条指令" << endl;
count++; //计数器加1
cout << "请输入模拟的时钟中断信号:";
cin >> wr; //读入键盘输入信息
if (wr == 1) //如果有时钟中断
{
cout << "时钟中断,保存现场" << endl;
timer++; //计时器加1;
time_alarm--; //定时闹钟减1;
cout << "处理器调度" << endl;
}
}
//定时闹钟为0,结束循环,接下来计算当前日历时钟。
int p; //距离开机时间经过的时间(单位:毫秒)
p = timer * 20;
time_t now = st + p / 1000; // 当前时间自 1970 年 1 月 1 日以来经过的秒数
char *s_now = ctime(&now); //将当前时间转化为字符串形式
cout << s_now;
return 0;
}
/*
//若要设置开机时间:将time_t st = time(0);替换如下:
struct tm timeptr;
int a;
cout << "请输入星期几(0-6):";
cin >> timeptr.tm_wday;
cout << "请输入月(0-11):";
cin >> timeptr.tm_mon;
cout << "请输入日(1-31):";
cin >> timeptr.tm_mday;
cout << "请输入时(0-6):";
cin >> timeptr.tm_hour;
cout << "请输入分(0-6):";
cin >> timeptr.tm_min;
cout << "请输入秒(0-6):";
cin >> timeptr.tm_sec;
cout << "请输入自1900年起的年数:";
cin >> timeptr.tm_year;
time_t st = mktime(&timeptr); //基于当前系统的开机时间,自 1970 年 1 月 1 日以来经过的秒数。
*/

程序运行时的初值和运行结果

初值:

image-20211113100649995

结果:

经过了50个定时闹钟,即1秒。

image-20211113101128598

思考题:

  • 将进程调度策略结合到本实验中,可选用时间片轮转的调度策略。给每个进程分配一个相同的时间片,每产生一次时钟中断经处理后,被中断进程时间片减1,时间片值!=0时,该进程优先运行,若时间片值=0且该进程尚未运行结束,则将它排入队尾,再给它分配一个时间片,直到所有的进程运行结束。应怎样设计进程控制块?各进程的状态怎样变化?在本实验的程序中加入处理器调度程序。

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#include <iostream>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stdlib.h>
#include <iomanip>
using namespace std;
#define N 1000000

int n, q, temp_q; //进程数,时间片大小,当前时间片大小
struct process
{
int id; //编号
int pos; //排序后的位置
int arrive; //到达时间
int work; //区间时间
int begin; //开始时间
int end; //完成时间
int turnaround; //周转时间
int wait; //等待时间
bool in; //是否进入过就绪队列
bool finish; //是否完成
int rest; //剩余区间时间
} proc[N];

queue <process> ready; //就绪队列
bool cmp_FCFS(process a, process b)
{
if (a.arrive != b.arrive)
return a.arrive < b.arrive;
else
return a.id < b.id;
}

void init()
{
cout << "请输入进程个数:";
cin >> n;
cout << "请输入时间片大小:";
cin >> q;
temp_q = q;
cout << "请按进程到达先后输入进程信息(1~n):到达时间 区间时间" << endl;
//要求按到达先后顺序输入,以便于到达时间相同时判断先后
for (int i = 1; i <= n; i++)
{
cout << "进程" << i << ":";
cin >> proc[i].arrive >> proc[i].work;
//信息初始化
proc[i].id = i;
proc[i].in = 0;
proc[i].begin = -1; //表示还没开始
proc[i].finish = 0;
proc[i].rest = proc[i].work;
}
sort(proc, proc + n + 1, cmp_FCFS);
for (int i = 1; i <= n; i++)
{
proc[i].pos = i;
}
}

bool cmp_id(process a, process b)
{
return a.id < b.id;
}

void RR_one(int timer)
{
for (int i = 1; i <= n; i++) //遍历所有进程
{
if (!proc[i].in && proc[i].arrive == timer) //若有进程还没有进入过就绪队列,且当前到达
{
proc[i].in = 1; //更改标记
ready.push(proc[i]); //进入就绪队列
}
}
if (!ready.empty())
{
process t = ready.front();
ready.pop();
if (t.begin == -1)
{
t.begin = timer;
}
temp_q--;
t.rest--;
cout << "进程" << t.id << "正在运行...";
if (t.rest == 0)
{
cout << "该进程已完成";
t.end = timer + 1; //完成时间=最新的当前时间
t.finish = 1; //更新标记:已完成
t.turnaround = t.end - t.arrive; //周转时间=完成时间-到达时间
t.wait = t.turnaround - t.work; //等待时间=周转时间-区间时间
proc[t.pos] = t; //更新(已完成的)进程信息
}
if (temp_q == 0) //当前时间片用完
{
if (t.rest > 0) //未完成则入就绪队列
{
ready.push(t);
}
temp_q = q;
}
cout << endl;
}
}

void display()
{
sort(proc, proc + n + 1, cmp_id);
cout << "所有进程当前状态如下:" << endl;
cout << setw(8) << "进程编号" << setw(10) << "到达时间" << setw(10) << "区间时间" << setw(10) << "开始时间" << setw(
10) << "完成时间" << setw(10) << "周转时间" << setw(10) << "等待时间";
cout << endl;
for (int i = 1; i <= n; i++)
{
cout << left << setw(10) << proc[i].id << setw(10) << proc[i].arrive << setw(10) << proc[i].work;
if (proc[i].begin != -1)
cout << left << setw(10) << proc[i].begin;
else
cout << left << setw(10) << "未开始";
if (proc[i].finish)
cout << left << setw(10) << proc[i].end << setw(10) << proc[i].turnaround << setw(10) << proc[i].wait;
else
cout << left << setw(10) << "未完成" << setw(10) << "未完成" << setw(10) << "未完成";
cout << endl;
}
return;
}

int main()
{
init();
time_t st = time(0); //基于当前系统的开机时间,自 1970 年 1 月 1 日以来经过的秒数。
char *s_st = ctime(&st); //将开机时间转化为字符串形式;
cout << "开机时间为:" << s_st;
//格式为:Www Mmm dd hh:mm:ss yyyy
//其中,Www 表示星期几,Mmm 是以字母表示的月份,dd 表示一月中的第几天,hh:mm:ss 表示时间,yyyy 表示年份。
int count = 0; //计数器
int timer = 0; //计时器
int wr;//键盘输入信息
int time_alarm; //定时闹钟
cout << "请输入定时闹钟:";
cin >> time_alarm; //置定时闹钟 (毫秒)
while (time_alarm != 0) //当定时时钟不为0
{
cout << "执行了一条指令..." << endl;
count++; //计数器加1
cout << "请输入模拟的时钟中断信号:";
cin >> wr; //读入键盘输入信息
if (wr == 1) //如果有时钟中断
{
cout << "时钟中断,保存现场..." << endl;
RR_one(timer);
timer++; //计时器加1;
time_alarm--; //定时闹钟减1;
}
}
//定时闹钟为0,结束循环,接下来计算当前日历时钟。
int p; //距离开机时间经过的时间(单位:毫秒)
p = timer * 20;
time_t now = st + p / 1000; // 当前时间自 1970 年 1 月 1 日以来经过的秒数
char *s_now = ctime(&now); //将当前时间转化为字符串形式
cout << "当前时间为:" << s_now;
display();
return 0;
}

程序运行时的初值和运行结果

初值

image-20211113102428868

运行结果

image-20211113102555653

实验心得

  • 本次实验让我通过编写模拟程序,进一步了解中断及中断处理程序的作用。在编程过程中,我接触到了C++中<ctime>这个时间相关库,学会通过time_t st = time(0)得到基于当前系统的开机时间(自 1970 年 1 月 1 日以来经过的秒数),再使用ctime(&st)将该时间转化为字符串形式(格式为:Www Mmm dd hh:mm:ss yyyy)。而对于中断的模拟是基于定时时钟的while循环。此外,我进行了拓展。一是,通过信息输入,再对时间的tm结构的处理,可以自己设定开机时间(见源程序末尾的注释)。二是完成了思考题,将处理器调度和时间中断相结合,刚好地了解中断的运行机制,并有效提高了编程能力。

本博客所有文章除特别声明外,均为博客作者本人编写整理,转载请联系作者!