每一个支持多进程(线程)的系统都会有一个滴答时钟(系统时钟),这个时钟就好比系统的“心脏”,线程的休眠(延时)和时间片轮转调度都需要用到它。

Cortex-M系列的内核都有一个systick时钟,这个时钟就是设计用来支持操作系统的,是一个24位的自动重装载向下计数器,中断入口就位于中断向量表里面,定义在zephyr-zephyr-v1.13.0\arch\arm\core\cortex_m\vector_table.S:

 SECTION_SUBSEC_FUNC(exc_vector_table,_vector_table_section,_vector_table)

     /*
4 * setting the _very_ early boot on the main stack allows to use memset
5 * on the interrupt stack when CONFIG_INIT_STACKS is enabled before
6 * switching to the interrupt stack for the rest of the early boot
7 */
.word _main_stack + CONFIG_MAIN_STACK_SIZE .word __reset
.word __nmi .word __hard_fault
.word __mpu_fault
.word __bus_fault
.word __usage_fault
.word __reserved
.word __reserved
.word __reserved
.word __reserved
.word __svc
.word __debug_monitor .word __reserved
.word __pendsv
#if defined(CONFIG_CORTEX_M_SYSTICK)
.word _timer_int_handler
#else
.word __reserved
#endif

第27行,_timer_int_handler()就是systick时钟的中断入口函数。

那么问题来了,前面的启动过程随笔里并没有分析到systick时钟是何时被初始化的,事实上systick也是通过设备宏定义的方式进行初始化的,定义在zephyr-zephyr-v1.13.0\drivers\timer\sys_clock_init.c:

SYS_DEVICE_DEFINE("sys_clock", _sys_clock_driver_init, sys_clock_device_ctrl,
PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);

可知,系统时钟属于PRE_KERNEL_2类设备,同一类设备也是有分优先级的,优先级高的先初始化,初始化函数为_sys_clock_driver_init(),定义在zephyr-zephyr-v1.13.0\drivers\timer\cortex_m_systick.c:

 int _sys_clock_driver_init(struct device *device)
{
/* enable counter, interrupt and set clock src to system clock */
u32_t ctrl = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_CLKSOURCE_Msk; ARG_UNUSED(device); /*
10 * Determine the reload value to achieve the configured tick rate.
11 */ /* systick supports 24-bit H/W counter */
__ASSERT(sys_clock_hw_cycles_per_tick <= ( << ),
"sys_clock_hw_cycles_per_tick too large");
sysTickReloadSet(sys_clock_hw_cycles_per_tick - ); NVIC_SetPriority(SysTick_IRQn, _IRQ_PRIO_OFFSET); SysTick->CTRL = ctrl; SysTick->VAL = ; /* triggers immediate reload of count */ return ;
}

系统时钟不一定要使用systick,像Nordic的SOC用的是硬件RTC作为系统时钟的,只是不过systick是一个通用的时钟。

第16行,参数sys_clock_hw_cycles_per_tick的含义是多少个systick时钟计数产生一个中断,这里CPU时钟为72MHz(systick时钟源来自CPU),系统时钟中断周期为10ms(100Hz,1秒产生100个中断),所以sys_clock_hw_cycles_per_tick = 72000000 / 100 = 720000。sysTickReloadSet()函数定义在zephyr-zephyr-v1.13.0\drivers\timer\cortex_m_systick.c:

  static ALWAYS_INLINE void sysTickReloadSet(
u32_t count /* count from which timer is to count down */
)
{
/*
6 * Write the reload value and clear the current value in preparation
7 * for enabling the timer.
8 * The countflag in the control/status register is also cleared by
9 * this operation.
10 */
SysTick->LOAD = count;
SysTick->VAL = ; /* also clears the countflag */
}

第11行,设置重装载寄存器。

第12行,将计数值置0,在使能systick后就会马上触发中断。

回到_sys_clock_driver_init()函数,第18行,设置systick的中断优先级,这里_IRQ_PRIO_OFFSET的值为1,因此systick的中断优先级就为1。

第20行,使能systick。

第22行,马上触发systick中断,并自动重装计数值。

接下来看systick中断执行函数_timer_int_handler(),定义在zephyr-zephyr-v1.13.0\drivers\timer\cortex_m_systick.c:

  void _timer_int_handler(void *unused)
{
ARG_UNUSED(unused); sys_trace_isr_enter(); /* accumulate total counter value */
clock_accumulated_count += sys_clock_hw_cycles_per_tick; /*
11 * one more tick has occurred -- don't need to do anything special since
12 * timer is already configured to interrupt on the following tick
13 */
_sys_clock_tick_announce(); extern void _ExcExit(void);
_ExcExit();
}

第8行,累加系统启动后经历了多少个时钟计数,注意这里不是累加系统ticks的个数,因为累加时钟计数会更加精确。

第14行,调用_sys_clock_tick_announce()函数,定义在zephyr-zephyr-v1.13.0\include\drivers\ system_timer.h:

#define _sys_clock_tick_announce() \
_nano_sys_clock_tick_announce(_sys_idle_elapsed_ticks)

在没有使能TICKLESS_KERNEL配置的情况下参数_sys_idle_elapsed_ticks的值为1,实际上调用的是_nano_sys_clock_tick_announce()函数,定义在zephyr-zephyr-v1.13.0\kernel\ sys_clock.c:

 void _nano_sys_clock_tick_announce(s32_t ticks)
{
unsigned int key; K_DEBUG("ticks: %d\n", ticks); /* 64-bit value, ensure atomic access with irq lock */
key = irq_lock();
_sys_clock_tick_count += ticks;
irq_unlock(key); handle_timeouts(ticks); /* time slicing is basically handled like just yet another timeout */
handle_time_slicing(ticks);
}

第9行,累加系统启动后所经历的ticks个数。

在分析第12行的handle_timeouts()函数之前,先说一下线程加入到超时队列的过程。线程通过调用k_sleep()等函数后,系统会将该线程加入到超时队列里,然后调度其他线程。k_sleep()对应的实现函数为_impl_k_sleep(),定义在zephyr-zephyr-v1.13.0\kernel\ sched.c:

 void _impl_k_sleep(s32_t duration)
{
/* volatile to guarantee that irq_lock() is executed after ticks is
4 * populated
5 */
volatile s32_t ticks;
unsigned int key; __ASSERT(!_is_in_isr(), "");
__ASSERT(duration != K_FOREVER, ""); K_DEBUG("thread %p for %d ns\n", _current, duration); /* wait of 0 ms is treated as a 'yield' */
if (duration == ) {
k_yield();
return;
} ticks = _TICK_ALIGN + _ms_to_ticks(duration);
key = irq_lock(); _remove_thread_from_ready_q(_current);
_add_thread_timeout(_current, NULL, ticks); _Swap(key);
}

第15行,如果传进来的时参数为0,则直接调用k_yield()函数,切换到其他线程,具体实现的话在下一篇随笔里再分析。

第20行,_TICK_ALIGN的值为1,即将睡眠时间以tick为单位补齐。

第23行,调用_remove_thread_from_ready_q()函数,定义在zephyr-zephyr-v1.13.0\kernel\ sched.c:

  void _remove_thread_from_ready_q(struct k_thread *thread)
{
LOCKED(&sched_lock) {
if (_is_thread_queued(thread)) {
_priq_run_remove(&_kernel.ready_q.runq, thread);
_mark_thread_as_not_queued(thread);
update_cache(thread == _current);
}
}
}

第4行,线程能够运行,那它的状态必须是已经_THREAD_QUEUED了的。

第5行,将线程从运行队列移除,那么线程就不会参与线程调度了。

第6行,设置线程状态不为_THREAD_QUEUED。

第7行,调用update_cache()函数,在上一篇随笔已经分析过了,这里不再重复。

回到_impl_k_sleep()函数,第24行,调用_add_thread_timeout()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\timeout_q.h:

 static inline void _add_thread_timeout(struct k_thread *thread,
_wait_q_t *wait_q,
s32_t timeout_in_ticks)
{
_add_timeout(thread, &thread->base.timeout, wait_q, timeout_in_ticks);
}

实际上调用的是_add_timeout()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\timeout_q.h:

 static inline void _add_timeout(struct k_thread *thread,
struct _timeout *timeout,
_wait_q_t *wait_q,
s32_t timeout_in_ticks)
{
__ASSERT(timeout_in_ticks >= , ""); timeout->delta_ticks_from_prev = timeout_in_ticks;
timeout->thread = thread;
timeout->wait_q = (sys_dlist_t *)wait_q; K_DEBUG("before adding timeout %p\n", timeout); /* If timer is submitted to expire ASAP with
15 * timeout_in_ticks (duration) as zero value,
16 * then handle timeout immedately without going
17 * through timeout queue.
18 */
if (!timeout_in_ticks) {
_handle_one_expired_timeout(timeout);
return;
} s32_t *delta = &timeout->delta_ticks_from_prev;
struct _timeout *in_q; SYS_DLIST_FOR_EACH_CONTAINER(&_timeout_q, in_q, node) {
if (*delta <= in_q->delta_ticks_from_prev) {
in_q->delta_ticks_from_prev -= *delta;
sys_dlist_insert_before(&_timeout_q, &in_q->node,
&timeout->node);
goto inserted;
} *delta -= in_q->delta_ticks_from_prev;
} sys_dlist_append(&_timeout_q, &timeout->node); 40inserted:
K_DEBUG("after adding timeout %p\n", timeout);
}

第19行,很明显timeout_in_ticks的值不为0。

第27~38行, 按delta_ticks_from_prev的值由小到大插入到_timeout_q超时队列里。由此可知,超时队列里存放的是与前一个线程的时间的差值,而不是绝对值。

回到_impl_k_sleep()函数,第26行,调用_Swap()函数,把线程切换出去,这在下一篇随笔再分析。

好了,有了这些基础之后,现在回到_nano_sys_clock_tick_announce()函数,第12行,调用handle_timeouts()函数,定义在zephyr-zephyr-v1.13.0\kernel\ sys_clock.c:

  static inline void handle_timeouts(s32_t ticks)
{
sys_dlist_t expired;
unsigned int key; /* init before locking interrupts */
sys_dlist_init(&expired); key = irq_lock(); sys_dnode_t *next = sys_dlist_peek_head(&_timeout_q);
struct _timeout *timeout = (struct _timeout *)next; K_DEBUG("head: %p, delta: %d\n",
timeout, timeout ? timeout->delta_ticks_from_prev : -); if (!next) {
irq_unlock(key);
return;
} /*
23 * Dequeue all expired timeouts from _timeout_q, relieving irq lock
24 * pressure between each of them, allowing handling of higher priority
25 * interrupts. We know that no new timeout will be prepended in front
26 * of a timeout which delta is 0, since timeouts of 0 ticks are
27 * prohibited.
28 */ while (next) { /*
33 * In the case where ticks number is greater than the first
34 * timeout delta of the list, the lag produced by this initial
35 * difference must also be applied to others timeouts in list
36 * until it was entirely consumed.
37 */ s32_t tmp = timeout->delta_ticks_from_prev; if (timeout->delta_ticks_from_prev < ticks) {
timeout->delta_ticks_from_prev = ;
} else {
timeout->delta_ticks_from_prev -= ticks;
} ticks -= tmp; next = sys_dlist_peek_next(&_timeout_q, next); if (timeout->delta_ticks_from_prev == ) {
sys_dnode_t *node = &timeout->node; sys_dlist_remove(node); /*
57 * Reverse the order that that were queued in the
58 * timeout_q: timeouts expiring on the same ticks are
59 * queued in the reverse order, time-wise, that they are
60 * added to shorten the amount of time with interrupts
61 * locked while walking the timeout_q. By reversing the
62 * order _again_ when building the expired queue, they
63 * end up being processed in the same order they were
64 * added, time-wise.
65 */ sys_dlist_prepend(&expired, node); timeout->delta_ticks_from_prev = _EXPIRED; } else if (ticks <= ) {
break;
} irq_unlock(key);
key = irq_lock(); timeout = (struct _timeout *)next;
} irq_unlock(key); _handle_expired_timeouts(&expired);
}

代码有点多,但是原理比较简单。

第7行,初始化一个超时双向链表,用于后面存放已经超时(到期)的线程。

第11行,取出超时队列的头节点。

第17行,即如果超时队列为空(没有超时任务要处理),则直接返回。

第30行,遍历超时队列。

第41行,如果取出的线程剩余的超时时间小于ticks(这里是1),则说面线程到期了,第42行将线程的超时时间置为0。否则,第44行,将超时时间减ticks。

第47行,剩下的ticks个数,其值可能为负数。

第49行,取出下一个节点。

第51行,如果当前线程的超时时间已经到了,则if条件成立。

第54行,将当前线程从超时队列移除。

第67行,将当前线程加入到临时队列里,后面会统一处理这个队列里的线程。

第69行,将当前线程的超时时间置为_EXPIRED。

如此循环,直到ticks用完(其值小于等于0),然后跳出循环,调用83行的_handle_expired_timeouts()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\timeout_q.h:

  static inline void _handle_expired_timeouts(sys_dlist_t *expired)
{
struct _timeout *timeout; SYS_DLIST_FOR_EACH_CONTAINER(expired, timeout, node) {
_handle_one_expired_timeout(timeout);
}
}

即遍历临时队列,每次调用_handle_one_expired_timeout()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\timeout_q.h:

  static inline void _handle_one_expired_timeout(struct _timeout *timeout)
{
struct k_thread *thread = timeout->thread;
unsigned int key = irq_lock(); timeout->delta_ticks_from_prev = _INACTIVE; K_DEBUG("timeout %p\n", timeout);
if (thread) {
_unpend_thread_timing_out(thread, timeout);
_mark_thread_as_started(thread);
_ready_thread(thread);
irq_unlock(key);
} else {
irq_unlock(key);
if (timeout->func) {
timeout->func(timeout);
}
}
}

第6行,将超时时间置为_INACTIVE。

超时的方式有两种,一是线程调用k_sleep()等函数后将自己挂起导致的超时,二是线程调用软件定时器k_timer_start()函数导致的超时,线程本身不会挂起,只是开启了一个定时器。所以就有了第9行和第14行两种不同路径。

先看第一种方式,第10行,调用_unpend_thread_timing_out()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\timeout_q.h:

  static inline void _unpend_thread_timing_out(struct k_thread *thread,
struct _timeout *timeout_obj)
{
if (timeout_obj->wait_q) {
_unpend_thread_no_timeout(thread);
thread->base.timeout.wait_q = NULL;
}
}

第5行,调用_unpend_thread_no_timeout()函数,定义在zephyr-zephyr-v1.13.0\kernel\sched.c:

  void _unpend_thread_no_timeout(struct k_thread *thread)
{
LOCKED(&sched_lock) {
_priq_wait_remove(&pended_on(thread)->waitq, thread);
_mark_thread_as_not_pending(thread);
}
}

第4行,实际上调用的是_priq_dumb_remove()函数,定义在zephyr-zephyr-v1.13.0\kernel\sched.c:

void _priq_dumb_remove(sys_dlist_t *pq, struct k_thread *thread)
{
__ASSERT_NO_MSG(!_is_idle(thread)); sys_dlist_remove(&thread->base.qnode_dlist);
}

将线程从队列移除。

回到_unpend_thread_no_timeout()函数,第5行,将线程状态设置为不是_THREAD_PENDING。

回到_handle_one_expired_timeout()函数,第11~12行这两个函数在上一篇随笔里已经分析过了。第16~17行,如果定时器超时函数不为空,则调用定时器超时函数。

至此,handle_timeouts()函数分析完了。

回到_nano_sys_clock_tick_announce()函数,第15行,调用handle_time_slicing()函数,定义在zephyr-zephyr-v1.13.0\kernel\sys_clock.c:

 static void handle_time_slicing(s32_t ticks)
{
if (!_is_thread_time_slicing(_current)) {
return;
} _time_slice_elapsed += ticks;
if (_time_slice_elapsed >= _time_slice_duration) { unsigned int key; _time_slice_elapsed = ; key = irq_lock();
_move_thread_to_end_of_prio_q(_current);
irq_unlock(key);
}
}

第3行,调用_is_thread_time_slicing()函数,定义在zephyr-zephyr-v1.13.0\kernel\sched.c:

  int _is_thread_time_slicing(struct k_thread *thread)
{
int ret = ; /* Should fix API. Doesn't make sense for non-running threads
6 * to call this
7 */
__ASSERT_NO_MSG(thread == _current); if (_time_slice_duration <= || !_is_preempt(thread) ||
_is_prio_higher(thread->base.prio, _time_slice_prio_ceiling)) {
return ;
} LOCKED(&sched_lock) {
struct k_thread *next = _priq_run_best(&_kernel.ready_q.runq); if (next) {
ret = thread->base.prio == next->base.prio;
}
} return ret;
}

第10~13行,_time_slice_duration的值在系统启动时就设置了。_is_preempt()函数:

static inline int _is_preempt(struct k_thread *thread)
{
/* explanation in kernel_struct.h */
return thread->base.preempt <= _PREEMPT_THRESHOLD;
}

_PREEMPT_THRESHOLD的值为127。即如果线程的优先级小于128则_is_preempt()返回1。

_is_prio_higher()比较当前线程的优先级是否高于_time_slice_prio_ceiling的值(也是在系统启动时就设置了),如果这三个条件有一个成立了,则不会处理时间片相关的内容。

第17行,调用_priq_run_best()函数取出运行队列的头节点,即优先级最高的线程。只有运行队列的头节点的优先级与当前线程的优先级相等才会继续往下处理。

回到handle_time_slicing()函数,第7行,累加ticks个数。

第8行,如果累加的ticks个数大于等于配置的时间片数,则if条件成立。

第12行,将累加的ticks个数清0。

第15行,调用_move_thread_to_end_of_prio_q()函数,定义在zephyr-zephyr-v1.13.0\kernel\sched.c:

  void _move_thread_to_end_of_prio_q(struct k_thread *thread)
{
LOCKED(&sched_lock) {
_priq_run_remove(&_kernel.ready_q.runq, thread);
_priq_run_add(&_kernel.ready_q.runq, thread);
_mark_thread_as_queued(thread);
update_cache();
}
}

第4~7行,这几个函数前面都已经分析过了。

到这里就可以知道,要使用时间片轮转的调度方式,需要以下设置:

1.配置时间片大小(大于0)和优先级;

2.所有创建的线程的优先级要相同,并且优先级要比1中的优先级高;

仔细思考会发现目前这种超时处理机制对延时(休眠)的时间是不准确的,因此这种机制总是以tick为单位进行延时(休眠),也即时间只能精确到tick。那有没有其他方法可以准确延时(休眠)呢?肯定是有的,就是需要打开TICKLESS_KERNEL配置,其原理就是不以tick(假如10ms)为固定时间进行定时,而是每次根据需要延时(休眠)的最小时间进行定时,这样就能实现精确的延时(休眠),zephyr是支持这种精确定时方式的,感兴趣的可以去研究研究。

Zephyr学习(四)系统时钟的更多相关文章

  1. STM32学习笔记(六) SysTick系统时钟滴答实验(stm32中断入门)

    系统时钟滴答实验很不难,我就在面简单说下,但其中涉及到了STM32最复杂也是以后用途最广的外设-NVIC,如果说RCC是实时性所必须考虑的部分,那么NVIC就是stm32功能性实现的基础,NVIC的难 ...

  2. 【小梅哥SOPC学习笔记】系统时钟的使用

    给NIOS II CPU添加一颗澎湃的心——系统时钟的使用 本实验介绍如何在Qsys中添加一个定时器作为NIOS II的心跳定时器,并在NIOS II中软件编程使用该定时器. 将上一个实验watchd ...

  3. STM32学习笔记:系统时钟和SysTick定时器

    原文:http://blog.sina.com.cn/s/blog_49cb42490100s60d.html 1.     STM32的时钟系统 在STM32中,一共有5个时钟源,分别是HSI.HS ...

  4. LCD实验学习笔记(四):系统时钟

    一般CPU频率(FCLK)高于内存.网卡等设备频率(HCLK),而串口.USB.I2C等设备频率(PCLK)更低. 系统时钟: 系统时钟源为晶振,初始频率12MHz. 通过设置MPLLCON寄存器的M ...

  5. STM32F4_RCC系统时钟配置及描述

    Ⅰ.概述 对于系统时钟应该都知道它的作用,就是驱动整个芯片工作的心脏,如果没有了它,就等于人没有了心跳. 对于使用开发板学习的朋友来说,RCC系统时钟这一块知识估计没怎么去配置过,原因在于开发板提供的 ...

  6. LPC17XX 数据手册摘要之系统时钟与功率控制

    系统时钟与功率控制 一.系统时钟 LPC17XX有三个独立的时钟振荡器,分别是主振荡器(MIAN_OSC).内部RC振荡器(IRC_OSC).实时时钟振荡器(RTC_OSC).LPC17XX时钟框图如 ...

  7. 应届毕业生如何通过学习Linux系统选择一份高薪职业

    2017年全国高校毕业生人数795万,史上"更难就业季"大学生就业形势,再加上出国留学回来的约30万以及没有找到工作的往届毕业生,预计将有1000多万大学生同时竞争. 如果我们不是 ...

  8. 基于Systick系统时钟延时的LED闪烁灯

    1.回顾我们的51 单片机编程,当我们需要做系统延迟的时候,最常采用的一种方式就是使用for 循环的空语句等待来实现. 当然,在STM32 里面也可以这么实现.但是在STM32 的Cortex 内核里 ...

  9. 正确学习Linux系统的5个建议

    摘要: 最近几年Linux系统应用越来越广泛,以至于很多人开始热衷学习Linux.但是我们都是从小都是学习windows系统长大的,从windows 98到现在的windows 10,而根据学习win ...

随机推荐

  1. Mac下安装pyenv

    mac 用virtualenv安装py3.6的虚拟环境报错(virtualenv -p /usr/bin/python3 env36), 解决的话需要安装zlib包, 然后去掉configure配置里 ...

  2. C#静态代码检查工具StyleCode

    C#静态代码检查工具StyleCode -- 初探 最近我们Advent Data Service (ADS) 在项目上需要按照代码规范进行代码的编写工作,以方便将来代码的阅读与维护. 但是人工检查起 ...

  3. webview 向右滑动关闭时,怎么禁止此 webview 上下滚动?

    webview 向右滑动关闭时,怎么禁止此 webview 上下滚动?

  4. Go语言字典树定义及实现

    // trie 字典树实现 package Algorithm // 字典树节点 type TrieNode struct { children map[interface{}]*TrieNode i ...

  5. Spring Boot中使用Swagger2自动构建API文档

    由于Spring Boot能够快速开发.便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API.而我们构建RESTful API的目的通常都是由于多终端的原因,这 ...

  6. Android: Android Studio签名打包的两种方式(zz)

    注:给我们自己开发的app签名,就代表着我自己的版权,以后要进行升级,也必须要使用相同的签名才行.签名就代表着自己的身份(即keystore),多个app可以使用同一个签名. 如果不知道签名是啥意思, ...

  7. python之使用set对列表去重,并保持列表原来顺序(转)

    https://www.cnblogs.com/laowangball/p/8424432.html #原始方法,但是会打乱顺序 mylist = [1,2,2,2,2,3,3,3,4,4,4,4]m ...

  8. 一句话Javascript实现价格格式化

    //小数点后面如果超过3位则转换错误,如1.1234 正确的是1.1234但却错误的转换成了1.1,234 var test1 = '1234567890.123' var format = test ...

  9. 用于Spring Boot Jar部署的shell脚本

    用于在Jenkins将jar发送到目标节点之后的部署操作, 包含deploy, start, stop, restart功能. 在deploy时会自动备份原jar至指定目录 # Please defi ...

  10. 在Centos7下安装nghttp2

    如果是Ubuntu18.04, 系统本身已经带了nghttp2了, 直接apt安装就可以. 下载源代码 https://github.com/nghttp2/nghttp2 如果是在Ubuntu下编译 ...