导读

本文通过分析Contiki的源码,梳理Contiki的定时器模型中一共5个定时器的工作机制和原理。

引入

从本文开始,我们开始探究Contiki的5个定时器模型,遵循从易到难的原则,我们先开始两个基本的内核定时器timer和stimer,两者唯一的区别就是timer以内核tick为单位,stimer以秒为单位。因此只需要了解timer即可类推到stimer。

在探究timer之前,我们先回顾一下在移植Contiki时在clock.c中做的一些工作,这些工作对Contiki的定时器模型至关重要。

在Contiki的clock.c中,有2个变量 clock_time_t count和clock_time_t seconds,它们分别记录了内核的tick和秒。很容易想到,它们分别提供给timer和stimer。

timer和stimer

简单看一下timer的类型定义

struct timer {clock_time_t start;  //计时开始时间clock_time_t interval;   //要计时的数量
};

基于这样的定时器类型定义,在Contiki中用 开始时间+时间间隔>=到期时间的方式,判定某个定时器是否到期。

timer提供了简单的定时器功能,包括timer_set,timer_reset,timer_restart,timer_expired,timer_remaining等等。这些功能最主要是提供给高级内核定时器使用,比如etimer,ctimer等等。

stimer和timer完全类似,只是计数单位变成了秒。

etimer

etimer可以说是Contiki中最重要的定时器了,是事件驱动机制的一部分。

 struct etimer {struct timer timer;    //基本timerstruct etimer *next;  //链接域struct process *p;    //etimer绑定的process
};

可以看到etimer是链式结构的,并且每个etimer绑定了一个process。

在etimer中,定义了timerlist和next_expiration两个变量,分别是etimer链表和最近一次到期时间。在前面我们或多或少的涉及了一些etimer的设计要点,现在就etimer几个关键的地方做进一步分析。

首先是我们知道有到期etimer时,硬件定时器中断服务函数会把etimer协程的poll置位,使etimer协程在下次调度时获得执行的机会。即下面这个函数。

void
etimer_request_poll(void)    //调用它意味着有etimer到期
{process_poll(&etimer_process);    //设置标志位,contiki将会poll etimer的process
}

那么在etimer协程中究竟做了什么工作,使得我们的etimer可以成为Contiki中事件驱动的一部分呢?我们看etimer协程的具体实现:

PROCESS_THREAD(etimer_process, ev, dataa)
{struct etimer *t, *u;PROCESS_BEGIN();timerlist = NULL;while(1) {PROCESS_YIELD(); //第一次调用时必然从这里放弃cpuif(ev == PROCESS_EVENT_EXITED) { //有process退出了,获得了该广播消息,删去和该process绑定的etimerstruct process *p = dataa;while(timerlist != NULL && timerlist->p == p) {timerlist = timerlist->next;}if(timerlist != NULL) {t = timerlist;while(t->next != NULL) {if(t->next->p == p) {t->next = t->next->next;} elset = t->next;}}continue;} else if(ev != PROCESS_EVENT_POLL) {  //不是poll事件,跳回去,放弃cpucontinue;}again://执行到这里说明有etimer的poll事件u = NULL;for(t = timerlist; t != NULL; t = t->next) {if(timer_expired(&t->timer)) { //有etimer到期if(process_post(t->p, PROCESS_EVENT_TIMER, t) == PROCESS_ERR_OK) { //post一个timer到期事件/* Reset the process ID of the event timer, to signal that theetimer has expired. This is later checked in theetimer_expired() function. */t->p = PROCESS_NONE;if(u != NULL) {u->next = t->next;} else {timerlist = t->next;}t->next = NULL;update_time();  //更新next_expiration变量goto again;        //直到所有的etimer都未到期才会结束} else {etimer_request_poll();   //post etimer到期事件失败,重新设置一个poll请求,等待下次poll}}u = t;}}PROCESS_END();
}

etimer协程主要分为2个部分,第一部分是接收协程退出的广播事件,当某个协程退出时,和该协程绑定的etimer也需要被回收。第二部分是处理etimer到期事件,etimer协程会遍历etimer链表,找到一个到期的etimer,然后通过异步post发送etimer到期事件,并设置该etimer的到期标志,之后更新下一个最近的到期时间,再次遍历etimer链表。如此反复直到所有的etimer均未到期时,结束etimer协程的执行,结合我在代码后面添加的注释,可以对etimer的协程理解更清晰。

etimer协程由于写法比较狂放,导致理解起来会有些困难,这里主要是goto语句的使用导致的,所以说迪杰斯特拉反对goto语句的声音是有一定道理的。不过goto语句恰当使用可以简化程序脉络,典型的是在出错处理时,通过goto语句直接跳转到出错处理位置。如果想使用goto处理出错,也可以使用do{}while(0);的方式,在出错处使用break跳出出错的位置。

rtimer

rtimer是real timer的意思,是为了满足在时间敏感的场景使用定时器的需求。在时间粒度上,etimer一般是10ms级的,rtimer一般在us级~100us级。因此rtimer的中断频率是远高于etimer的,为了避免频繁的中断损耗系统性能。rtimer并没有使用链表的形式,保存多个rtimer。换言之,rtimer在整个Contiki中仅有一个,是串行执行的,即某个rtimer定时任务完成,才会接受下一个rtimer定时任务,因此重复设置rtimer会导致rtimer任务覆盖的问题。

理论上rtimer可以使用任何一个硬件定时器,但是为了不和etimer产生影响,我们最好用单独的硬件定时器作为rtimer的硬件基础。简单看一下rtimer的类型定义。

struct rtimer {rtimer_clock_t time;//定时间隔,一般是us单位rtimer_callback_t func;//到期后执行的回调函数void *ptr;//传入回调函数的数据
};

在Contiki中,rtimer主要在2个文件中,rtimer-arch.c和rtimer.c。仅有一个rtimer定时器变量next_rtimer。在完成了移植和初始化的工作后,涉及到的函数也只有3个:rtimer_set,rtimer_run_next,rtimer_arch_schedule,下面逐个击破。

int
rtimer_set(struct rtimer *rtimer, rtimer_clock_t time,rtimer_clock_t duration,rtimer_callback_t func, void *ptr)
{int first = 0;if(next_rtimer == NULL) {first = 1;}rtimer->func = func;rtimer->ptr = ptr;rtimer->time = time;next_rtimer = rtimer;if(first == 1) {     //第一次设置,要调度一次,以后都在其他函数调度rtimer_arch_schedule(time);}return RTIMER_OK;
}

rtimer设置函数的入参比较多,主要是为了设置rtimer的参数,需要我们定义一个rtimer,然后定义好触发时间,触发后的回调函数,传入回调的参数等等。可以看到rtimer_set仅仅是设置好参数,然后把用户定义的rtimer挂到Contiki中唯一的rtimer定时器中,之后开启硬件定时器,等待rtimer到期。

void
rtimer_arch_schedule(rtimer_clock_t t)
{//TH0作为计数器,占据timer1的中断向量TH0=256-t;//t个间隔后产生timer1中断spin_timer_start(timer1);//打开中断
}

这里rtimer_arch_schedule主要的作用就是设置好一个rtimer后,通知硬件执行定时操作,并允许硬件定时器中断。在本例中,可以看出t的范围是0~255,单位是us。

那么rtimer定时到期会怎么办呢?这个在我们的硬件定时器中实现,并且代码是固定的。

void rtimer_isr(void) interrupt 3
{spin_timer_stop(timer1);//关闭中断rtimer_run_next();
}

首先是关闭rtimer的中断,然后运行rtimer的回调函数,并设置好下一个rtimer的运行参数。具体的实现我们看rtimer_run_next函数:

void
rtimer_run_next(void)
{struct rtimer *t;if(next_rtimer == NULL) {return;}t = next_rtimer;next_rtimer = NULL;t->func(t, t->ptr);//在设置的rtimer的回调函数里,用户要设置rtimer_set,确定下一个rtimer。否则rtimer会停止if(next_rtimer != NULL) {rtimer_arch_schedule(next_rtimer->time); //这里其实是有点重复了}return;
}

可以看出,rtimer_run_next会执行到期的rtimer的回调函数,该回调函数是由用户自己实现的,并且要注意,在该回调函数中,用户要设置好下一个rtimer运行的参数,否则rtimer会停止工作。对比rtimer的rtimer_run_next和rtimer_set,可以发现有一个小瑕疵,就是rtimer_arch_schedule被重复执行了,导致的结果就是rtimer的下次到期时间被重复设置了,换句话说,用户在rtimer的回调函数里面给rtimer_set传入的过期时间是无效的,rtimer回调函数返回,由rtimer模块自己设置的过期时间才是有效的。

如此,rtimer的工作方式和原理已经很清晰了。用户设置一个rtimer,到期后执行用户给定的回调函数,在回调函数中,用户可以选择再去设置一个rtimer,如此循环往复,顺序的执行一个个rtimer。rtimer在设置和执行回调时会禁止rtimer中断,在调度下一个时会允许rtimer中断。

ctimer

ctimer是Contiki中较为复杂的一个定时器,它是基于etimer,每个ctimer都会绑定一个etimer,但是比etimer多了一个回调函数,一般应用在周期执行的任务场景。ctimer挂接在ctimer的链表上面,因此允许有多个ctimer,ctimer和etimer都是使用协程管理的,并且ctimer可以自由的设置要绑定的process,这一点比etimer要更为强大。首先看一下ctimer的类型定义

struct ctimer {struct ctimer *next;//链接域struct etimer etimer;//绑定的etimerstruct process *p;//绑定的processvoid (*f)(void *);//到期的回调函数void *ptr;//传给回调函数的数据
};

类型定义结合注释浅显易懂,不再解释。继续看ctimer的协程都做了些什么。

PROCESS(ctimer_process, "Ctimer process");
PROCESS_THREAD(ctimer_process, ev, dataa)
{struct ctimer *c;PROCESS_BEGIN();for(c = list_head(ctimer_list); c != NULL; c = c->next) {etimer_set(&c->etimer, c->etimer.timer.interval);}initialized = 1;while(1) {PROCESS_YIELD_UNTIL(ev == PROCESS_EVENT_TIMER);for(c = list_head(ctimer_list); c != NULL; c = c->next) {if(&c->etimer == dataa) {list_remove(ctimer_list, c);PROCESS_CONTEXT_BEGIN(c->p);if(c->f != NULL) {c->f(c->ptr);}PROCESS_CONTEXT_END(c->p);break;}}}PROCESS_END();
}

ctimer的协程和我们自己编写用户协程没有什么区别,只是业务代码的区别。代码比起etimer协程来,浅显易懂得多,我们稍做分析即可。ctimer用了Contiki库中的list模块,因此可以看到ctimer调用的一些和list数据结构有关的api。

ctimer协程初始化部分。主要是设定所有和ctimer绑定的etimer,之后进入协程主体。在协程主体中等待etimer到期事件,之后执行etimer绑定的ctimer的回调函数。值得注意的是在执行回调函数之前,进行了上下文的切换,不过由于Contiki特殊的process机制,这里的上下文切换其实只是修改了当前执行process为ctimer绑定的process,并未做其他的切换。和etimer不同,ctimer执行完一个ctimer的回调函数,并不是继续遍历ctimer链表,而是跳出,等待下一次的etimer到期事件。另外注意的是,我们在定义ctimer时,要同时定义一个etimer,然后手动把etimer绑定到ctimer上,因为迄今为止,我们的Contiki并不支持动态内存。

再简单看一下ctimer的设置函数

void
ctimer_set_with_process(struct ctimer *c, clock_time_t t,void (*f)(void *), void *ptr, struct process *p);
void
ctimer_set(struct ctimer *c, clock_time_t t,void (*f)(void *), void *ptr);

两个函数的区别仅仅是给当前process设置ctimer,和给指定process设置ctimer的区别。ctimer其他的函数和etimer的函数大同小异,略过即可。

结束语

通过对Contiki中5个定时器的逐一梳理,我们理解了Contiki定时器的工作机制和编程方式,理解了Contiki定时器和Contiki的event-process模型的协同工作。从os内核的角度来说,event-process模型和定时器模型就是Contiki的全部,但是从实际应用来说,Contiki的内容远不止这么多。因此,Contiki的探究理应在这里设置一个里程碑,里程碑前,是狭义的Contiki内核;里程碑后,是Contiki在实际应用中的外围拓展。我们在立下这个里程碑前,再用一节内容,综合应用我们学到的Contiki的这些知识。下一篇见。

项目地址:https://github.com/zhangoneone/stc89c52

Contiki的内核分析-定时器模型相关推荐

  1. PG内核分析 Question and Answer

    PG内核分析 Question and Answer PG系统概述 为什么说PG是一种先进的对象-关系数据库系统 因为PG它不仅支持关系数据库的各种功能, 而且还具备类, 继承等对象数据库的特征. 面 ...

  2. linux内核分析(转自某位大哥网上的笔记)

    启动 当PC启动时,Intel系列的CPU首先进入的是实模式,并开始执行位于地址0xFFFF0处的代码,也就是ROM-BIOS起始位置的代码.BIOS先进行一系列的系统自检,然后初始化位于地址0的中断 ...

  3. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型

    在<朴素.Select.Poll和Epoll网络编程模型实现和分析--朴素模型>中我们分析了朴素模型的一个缺陷--一次只能处理一个连接.本文介绍的Select模型则可以解决这个问题.(转载 ...

  4. LINUX内核分析第二周学习总结——操作系统是如何工作的

    LINUX内核分析第二周学习总结--操作系统是如何工作的 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...

  5. Linux内核分析作业第二周

    操作系统是如何工作的 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.函数调用堆栈 1.计算机工作三 ...

  6. 《Linux内核分析》第一周笔记 计算机是如何工作的

    一.计算机是如何工作的? 1.存储程序计算机工作模型 1)冯诺依曼体系结构 学习研究计算机的基本概念.就是指存储程序计算机.所有的有计算功能的电子设备小到计算器,大到超级计算机核心部分都可以用这种体系 ...

  7. Linux内核分析— —计算机是如何工作的(20135213林涵锦)

    实验部分 (以下命令为实验楼64位Linux虚拟机环境下适用,32位Linux环境可能会稍有不同) 使用 gcc –S –o main.s main.c -m32 命令编译成汇编代码, int g(i ...

  8. ClickHouse内核分析-MergeTree的Merge和Mutation机制

    注:以下分析基于开源 v19.15.2.2-stable 版本进行 引言 ClickHouse内核分析系列文章,继上一篇文章 MergeTree查询链路 之后,这次我将为大家介绍MergeTree存储 ...

  9. 期末总结20135320赵瀚青LINUX内核分析与设计期末总结

    赵瀚青原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 对LINUX内核分析与设计这 ...

最新文章

  1. linux删除旧网卡,如何删除旧网卡驱动
  2. 对AI毫无了解?本文带你轻松了解AI
  3. 软设考试笔记--数据流图
  4. 值引用和引用问题分析
  5. 选择排序算法流程图_常用排序算法之选择排序
  6. gatsby_如何使用Gatsby和MDX从头开始构建编码博客
  7. android double转string_如何使用Java程序将Double转换为String
  8. UltraCompare 22 for Mac(文件比较工具)
  9. Oracle 归档模式的打开及关闭
  10. php 如何生成txt文件,PHP生成TXT文件
  11. CADD课程学习(7)-- 模拟靶点和小分子相互作用 (半柔性对接 AutoDock)
  12. 服务器的带宽与宽带有什么区别
  13. [转帖]CAPCOM的详细历史
  14. 南京艺术学院计算机作曲,南京艺术学院932主科(上机操作计算机作曲应用)考研复习经验...
  15. NISP一级知识点学习笔记总结
  16. 携手腾讯官方打造,微信(统信UOS版)首发
  17. chroom浏览器网页二维码生成功能的方法
  18. aqistudy真气网JS逆向 + 数据采集(20220801)
  19. js 获取浏览器高度和宽度值(兼容多浏览器)
  20. NFC的读写卡模式——前台调度系统

热门文章

  1. 串口 通讯 顶尖电子秤_顶尖通讯秤设置(os2 PS1)
  2. SOCKS5代理的四大应用场景
  3. linux cpu多核运行,LINUX在多核环境下,如果控制使用的CPU数目
  4. python爬虫淘宝登录_python爬虫实现模拟淘宝登录
  5. 如何取淘宝登录的完整cookies
  6. mtk camera移植
  7. 仿iPhone天气预报
  8. 深度学习框架-tensorflow进阶项目
  9. 网站中图片的显示和隐藏
  10. 鸿蒙os目前支持了哪些硬件,大佬终于把鸿蒙OS讲明白了,收藏了!