一、定时器
加入内核定时器机制,使得任务可以在一定时间之后执行特定的功能。比如:任务睡眠、蜂鸣器超时、阻塞超时。 我们首先需要实现一下定时器机制,代码如下:
typedef struct timer_t
{
list_node_t node; // 链表节点
struct task_t *task; // 相关任务
u32 expires; // 超时时间
void (*handler)(struct timer_t *); // 超时处理函数
void *arg; // 参数
bool active; // 激活状态
} timer_t;
static timer_t *timer_get() // 从内核堆内存中分配一个定时器
{
timer_t *timer = (timer_t *)kmalloc(sizeof(timer_t));
return timer;
}
void timer_put(timer_t *timer) // 释放一个定时器
{
list_remove(&timer->node);
kfree(timer);
}
void default_timeout(timer_t *timer) // 默认超时函数,当超时解除进程阻塞
{
assert(timer->task->node.next);
task_unblock(timer->task, -ETIME);
}
timer_t *timer_add(u32 expire_ms, handler_t handler, void *arg) // 添加一个定时器
{
timer_t *timer = timer_get(); // 分配一个定时器
timer->task = running_task(); // 获取当前进程
timer->expires = jiffies + expire_ms / jiffy; // 设置超时时间
timer->handler = handler; // 设置超时处理函数
timer->arg = arg;
timer->active = false;
list_insert_sort(&timer_list, &timer->node, element_node_offset(timer_t, node, expires)); // 把定时器插入排序到链表
return timer;
}
u32 timer_expires() // 得到列表中最近一个定时器的超时时间
{
if (list_empty(&timer_list))
{
return EOF;
}
timer_t *timer = element_entry(timer_t, node, timer_list.head.next);
return timer->expires;
}
void timer_init() // 定时器初始化,即定时器链表的初始化
{
LOGK("timer init...\n");
list_init(&timer_list);
}
void timer_remove(task_t *task) // 从定时器链表中找到 task 任务的定时器,删除之,用于 task_exit
{
list_t *list = &timer_list;
for (list_node_t *ptr = list->head.next; ptr != &list->tail;)
{
timer_t *timer = element_entry(timer_t, node, ptr);
ptr = ptr->next;
if (timer->task != task)
continue;
timer_put(timer);
}
}
void timer_wakeup() // 轮询执行
{
while (timer_expires() <= jiffies) // 循环判断是否有超时的定时器, 如果没有定时器timer_expires()返回EOF即-1
{
timer_t *timer = element_entry(timer_t, node, timer_list.head.next);
timer->active = true;
assert(timer->expires <= jiffies);
if (timer->handler)
{
timer->handler(timer);
}
else
{
default_timeout(timer);
}
timer_put(timer);
}
}
如上所示:定时器按照超时时间顺序存放到一个链表中,在时钟中断的处理函数中调用 timer_wakeup 。 因此每次时钟中断都会去唤醒超时的进程。 如果设置了阻塞时间,那就需要生成一个定时器并将该定时器插入链表。统一当进程退出时应该移除该定时器。
二、定时器测试
我们这里写一个应用程序来进行定时器的测试,代码如下:
#include <onix/syscall.h>
#include <onix/stdio.h>
int main(int argc, char const *argv[])
{
int counter = 1;
while (counter)
{
printf("hello onix %d\a\n", counter++);
sleep(1000);
}
return 0;
}
如上所示,我们的程序会一直打印文字并响铃铛,并且每次打印完后睡眠1秒。我们来整理下运行流程。
- 系统启动时,此时没有定时器的。 timer_expires()返回EOF,即-1。-1属于有符号数,当它与无符号数jiffies进行比较,C规则会把-1转换为无符号数4294967295。此时判断为false直接结束。
- 当执行count程序时,先进行打印和响铃,响铃函数设置了 task_sleep(100),即进程睡眠100ms。此时产生了一个100ms的定时器,当系统时间没有达到时,判断会大于jiffies直接跳过。
- 当定时器时间到了,就会从链表中获取定时器,并结束休眠,此时响铃也就结束了。程序继续运行。执行 sleep(1000), 再次产生一个定时器,进程进入阻塞态。
- 等到时钟中断继续判断到阻塞时间到了之后,进行进程唤醒,继续运行。如果反复循环,我们就会再中断不停地看待打印和响铃了。
这里其实存在一个问题,当jiffies达到u32的最大值4294967295时,再次++就会出现回绕变为0。假设当jiffies已经到了4294967290,此时产生一个定时器延迟15个周期,计算后的定时器是一个比较小的数10。当下次时钟中断jiffies为4294967291判断时就会出现误判,提前超时。这里应该(s32)(jiffies - expires) >= 0)这样来判断,得到结果是u32 4294967281 转换为 s32 是 -15, 就不会提前超时了。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付