30天自制操作系统(day12)
第12天:定时器(1)
1、内容1:使用定时器
该设备具备定期提醒的功能作用,在规定的时间间隔内向中央处理器(CPU)发送一次中断信号以启动其计时过程。这种机制使得CPU无需自行计算时间间隔就可以正常运行程序系统。若未采用此功能将会使程序的时间管理变得更加复杂,并且无法再使用HLT指令来控制程序流程。因此采用该设备中的定时功能显得尤为必要:当启用此功能后不仅能够简化程序的时间处理逻辑而且还可以通过记录每次发生的时间间隔来实现精确的时间同步控制即使当CPU处于HLT状态时仍可通过相应的中断机制来恢复正常的运行状态。
那么该如何配置并管理这些定时功能呢?首先需要对PIT寄存器进行设定即可:其中PIT=可编程的间隔型定时器通过配置可以使每个定时间隔内产生相应的中断信号。

请确认IRQ0中断周期设置是否正确。我们的配置通道主要集中在D0至D7位。根据课本内容,我们需要向地址码存储空间中的位置...发送特定控制字。

写出的是^{34}H(即十进制76),其对应的二进制表示为^{} 8位数据中的低字节至高字节依次排列的结果。
采用的是计数器模式中的第^{} 一个设置选项,并按照低字节至高字节的顺序进行读取或写入操作。
采用的方式^{} 2来进行二进制计数过程。
将值写入至地址\text{Ox4C}的位置上用于中断周期配置。
所存储数值直接反映了对应的主频时钟周期数量。
当中断配置参数设为零时\text{(即等效于65536)};而若设定值为\text{1} \text{千}时,则可获得约\text{1.193} \text{千赫兹}的中断频率。
通过比例关系可灵活设定相应的参数组合以满足不同需求。

在这里,只需要执行3次OUT指令设定即可完成定时器的设定,如果将中断周期设为11932的话,中断频率即为100HZ,即1s发生100次中断。将11932换算为十六进制0x2e9c来进行设定。a的timer文件与bootpack.c中:
选择模式定义一个init_pit函数:

主函数中加入:

这样设定的话,在1秒内IQRO将触发100次中断


为了使中断处理程序成功注册至IDT中,该inlt_gdtidt函数还需增加若干处理中断的代码块。

2、内容2:计量时间
完成对时间的测量任务可以通过配置一个计数器来实现。每当定时器发生中断事件时, 计数值每次都会增加1. 每秒后会自动增加总计达100次, 因为在每秒钟内, 定时器会发生100次中断事件. 关注b目录下的time文件.

然后通过屏幕显示出来:

实验结果:

3、内容3:超时功能
在之前的阶段中已成功实现了对时间的计量。现可进一步尝试通过设置一个定时器使其在经过一段时间后发出提醒信号并实现超时功能。
书本上将定时视为一旦到定时间就会触发特定行动的功能并将此定义为超时行为。
为了记录相关超时信息 在结构体TIMERCTL中增加了三个字段 分别用于存储timeout 数据以及fifo相关信息。
当remaining_timeout降至零值 亦即系统计数值达到最大计数周期上限的时候 系统会将计数值存入FIFO缓冲区并通知主程序HariMain当前时间为已到状态。
data字段则存储着事件发生的时间戳 fifo字段则指示着当前使用的缓冲队列。


在int handler 20(中断处理函数)中实现了超时功能这一模块,在每次发生中断事件时将timeout计数器递减1计数单位,在timeout计数器降到零的时候就会向fifo队列中发送数据。对于set timer函数而言,在定时器设置尚未完全完成的情况下若出现IRQ 0中断事件可能会导致系统混乱状态因此应在定时器设置完成后及时恢复中断状态以保证系统的稳定运行。该文件中的默认位置是针对16位系统的特定配置参数设置区域




在没有中断的情况下,在线程中依次执行io_sti函数;当检测到定时器中断事件时,则首先从设备中读取数据以获取其地址之后调用io_sti函数;随后,在屏幕上显示'10[sec]'后立即刷新图像层。

4、内容4:使用多个定时器
在超时结束后再设定1000的话,就可以使显示变为10s依次,或是让其一闪一灭的显示,例如设定为0.5s的间隔可以用于文字输入时的光标闪烁。要实现多个这样的超时功能,便需要准备多个能设定超时的定时器。
首先是修改结构体,在e的bootpack.h文件中:

将最多可配置的超时定时器数量设置为500,并使用flag变量来标记各个定时器的状态。此外,在其他相关函数中也需要进行相应的优化工作,并主要针对bootpack.c文件中的相关内容进行优化。

在主函数中写入:

实验结果:

5、内容5:加快中断处理(1)
然而之前已经可以随意地使用多个定时器了

为了解决这一问题,作者提出了一种调整时间点的方法。具体而言,则是设定一个基准时间点t₀,并将其从各个时间点中减去该基准值。例如取t₀为1000秒时,在各个时间点上减去1 之后的结果是1个单位的时间间隔,在这种情况下相当于实际时间为基准时间点后的一个单位的时间长度。但具体的代码实现并未包含在内

6、内容6:加快中断处理(2)
虽然之前已经使程序的速度有所提升, 但速度仍显不足, 其原因何在? 在ithandler20函数中, 每次中断事件都会触发大量的if条件判断, 这会导致每秒将导致5万次的if条件判断, 这样的计算量实在令人担忧


首先对下一个定时器进行判断:若未达到预定时间(即next>count),则跳过不处理;否则需处理运行中的定时器:当到达预定时间时,则需检查当前运行状态并执行相应的操作:若当前时间为预定值,则将数据存入缓冲区;否则将next指向当前所有定时器中最早到达预定时间的那个,并选择比当前next值小的作为新的基准值以优化后续判断流程
7、内容7:加快中断处理(3)
前一阶段的优化带来了一个问题:那些在到达next时刻以及未到达该时刻(即已经超出了该时间段)的所有定时器中断都会被触发处理,并且这些操作之间的时间开销差异较大。具体来说,在完成这些操作后发现有一批特定位置上的定时器运行速度与预期存在不一致现象:这些是在完成某次特定循环之后才会被触发的操作对应的定点计时器运行得更快或者更慢了?因此为了进一步优化系统性能并解决这一问题:我们需要对那些在完成某次特定循环之后才会被触发的操作对应的定点计时器运行速度进行进一步优化:为此我们可以参考sheet.c中的做法:即在struct TIMERCTL结构体中增加一个变量用于存储按某种顺序排好的定点计时器地址列表

该变量旨在防止SHTCTL数据结构体中的top字段,并以统计当前定时器中活跃的计数器数量。

此处减少了对flags确认的判断步骤;直接针对using中的定时器执行循环检查以确定是否超时状态;一旦i超过时间限制,则将其移出标记为已处理;剩下的计数器将被平移以便填补空缺位置;具体来说就是将剩余计数器的位置j处的数据复制到i+j处的位置;这样做的好处是可以将空缺的位置从前向后依次填充;如果还有计数器处于运行状态,则将第一个可用计数器的时间值存入next变量中;这样就避免了每次都要重新搜索下一个可用时间点的操作

在settime函数中, 必须将timer注册到timers中. 首要任务是首先要找到注册的位置, 关闭中断, 然后依次向前移动一位, 腾出一个连续的位置块. 随后使用++操作符, 将该定时器插入到空闲的位置上. 两者的区别在于, settime过程中对定时器进行排序处理, 而采用next()方法在中断处理阶段进行排序处理. 这样对操作系统而言显然是先完成排序后再计时更为高效.
