鸿蒙内核源码分析(时间管理篇) | Tick是操作系统的基本时间单位 | 百篇博客分析HarmonyOS源码 | v35.02

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding >

百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | csdn | 掘金 | harmony >


鸿蒙内核源码分析(时间管理篇) | Tick是操作系统的基本时间单位 | 百篇博客分析HarmonyOS源码 | v35.02_第1张图片

本篇说清楚时间概念

读本篇之前建议先读鸿蒙内核源码分析(总目录)其他篇.

时间概念太重要了,在鸿蒙内核又是如何管理和使用时间的呢?

时间管理以系统时钟 g_sysClock 为基础,给应用程序提供所有和时间有关的服务。

  • 用户以秒、毫秒为单位计时.
  • 操作系统以Tick为单位计时,这个认识很重要. 每秒的tick大小很大程度上决定了内核调度的次数多少.
  • 当用户需要对系统进行操作时,例如任务挂起、延时等,此时需要时间管理模块对Tick和秒/毫秒进行转换。

熟悉两个概念:

  • Cycle(周期):系统最小的计时单位。Cycle的时长由系统主时钟频率决定,系统主时钟频率就是每秒钟的Cycle数。
  • Tick(节拍):Tick是操作系统的基本时间单位,由用户配置的每秒Tick数决定,可大可小.

怎么去理解他们之间的关系呢?看几个宏定义就清楚了.

#ifndef OS_SYS_CLOCK	//HZ:是每秒中的周期性变动重复次数的计量
#define OS_SYS_CLOCK (get_bus_clk()) //系统主时钟频率 例如:50000000 即50微秒 
#endif
#ifndef LOSCFG_BASE_CORE_TICK_PER_SECOND
#define LOSCFG_BASE_CORE_TICK_PER_SECOND 100 //每秒Tick数,意味着正常情况下每秒调度100次
#endif
#define OS_CYCLE_PER_TICK (g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND) //每个tick多少机器周期

时钟周期(振荡周期)

在鸿蒙g_sysClock表示时钟周期,是CPU的赫兹,也就是上面说的Cycle,这是固定不变的,由硬件晶振的频率决定的.
OsMain是内核运行的第一个C函数,首个子函数就是 osRegister,完成对g_sysClock的赋值

LITE_OS_SEC_TEXT_INIT VOID osRegister(VOID)
{
     
    g_sysClock = OS_SYS_CLOCK; //获取CPU HZ 
    g_tickPerSecond =  LOSCFG_BASE_CORE_TICK_PER_SECOND;//每秒节拍数 默认100 即一个tick = 10ms
    return;
}

CPU周期也叫(机器周期)

在鸿蒙宏OS_CYCLE_PER_TICK表示机器周期,Tick由用户根据实际情况配置.
例如:主频为1G的CPU,其振荡周期为: 1吉赫 (GHz 109 Hz) = 1 000 000 000 Hz
当Tick为100时,则1 000 000 000/100 = 10000000 ,即一秒内可产生1千万个CPU周期.CPU就是用这1千万个周期去执行指令的.

指令周期

指令周期是执行一条指令所需要的时间,一般由若干个机器周期组成。指令不同,所需的机器周期数也不同。
对于一些简单的的单字节指令,在取指令周期中,指令取出到指令寄存器后,立即译码执行,不再需要其它的机器周期。
对于一些比较复杂的指令,例如转移指令、乘法指令,则需要两个或者两个以上的机器周期。
通常含一个机器周期的指令称为单周期指令,包含两个机器周期的指令称为双周期指令。

Tick硬中断函数

LITE_OS_SEC_BSS volatile UINT64 g_tickCount[LOSCFG_KERNEL_CORE_NUM] = {
     0};//tick计数器,系统一旦启动,一直在++, 为防止溢出,这是一个 UINT64 的变量
LITE_OS_SEC_DATA_INIT UINT32 g_sysClock;//系统时钟,是绝大部分部件工作的时钟源,也是其他所有外设的时钟的来源 
LITE_OS_SEC_DATA_INIT UINT32 g_tickPerSecond;//每秒Tick数,鸿蒙默认是每秒100次,即:10ms
LITE_OS_SEC_BSS DOUBLE g_cycle2NsScale;	//周期转纳秒级

/* spinlock for task module */
LITE_OS_SEC_BSS SPIN_LOCK_INIT(g_tickSpin); //节拍器自旋锁
#define TICK_LOCK(state)                       LOS_SpinLockSave(&g_tickSpin, &(state))
/*
 * Description : Tick interruption handler
 *///节拍中断处理函数 ,鸿蒙默认10ms触发一次
LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
{
     
    UINT32 intSave;

    TICK_LOCK(intSave);
    g_tickCount[ArchCurrCpuid()]++;//当前CPU核计数器
    TICK_UNLOCK(intSave);

#ifdef LOSCFG_KERNEL_VDSO
    OsUpdateVdsoTimeval();
#endif

#ifdef LOSCFG_KERNEL_TICKLESS
    OsTickIrqFlagSet(OsTicklessFlagGet());
#endif

#if (LOSCFG_BASE_CORE_TICK_HW_TIME == YES)
    HalClockIrqClear(); /* diff from every platform */
#endif

    OsTimesliceCheck();//时间片检查

    OsTaskScan(); /* task timeout scan *///任务扫描

#if (LOSCFG_BASE_CORE_SWTMR == YES)
    OsSwtmrScan();//定时器扫描,看是否有超时的定时器
#endif
}

#ifdef __cplusplus
#if __cplusplus
}

解读

  • g_tickCount记录每个CPU核tick的数组,每次硬中断都触发 OsTickHandler,每个CPU核单独计数.
  • OsTickHandler是内核调度的动力,其中会检查任务时间片是否用完,定时器是否超时.主动delay的任务是否需要被唤醒,其本质是个硬中断,在HalClockInit硬时钟初始化时创建的,具体在硬中断篇中会详细讲解.
  • TICK_LOCK是tick操作的自旋锁,宏原型LOS_SpinLockSave在自旋锁篇中已详细介绍.

功能函数

#define OS_SYS_MS_PER_SECOND   1000			//一秒多少毫秒
//获取自系统启动以来的Tick数
LITE_OS_SEC_TEXT_MINOR UINT64 LOS_TickCountGet(VOID)
{
     
    UINT32 intSave;
    UINT64 tick;

    /*
     * use core0's tick as system's timeline,
     * the tick needs to be atomic.
     */
    TICK_LOCK(intSave);
    tick = g_tickCount[0];//使用CPU core0作为系统的 tick数
    TICK_UNLOCK(intSave);

    return tick;
}
//每个Tick多少Cycle数
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID)
{
     
    return g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND;
}
//毫秒转换成Tick
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_MS2Tick(UINT32 millisec)
{
     
    if (millisec == OS_MAX_VALUE) {
     
        return OS_MAX_VALUE;
    }

    return ((UINT64)millisec * LOSCFG_BASE_CORE_TICK_PER_SECOND) / OS_SYS_MS_PER_SECOND;
}
//Tick转化为毫秒
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_Tick2MS(UINT32 tick)
{
     
    return ((UINT64)tick * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND;
}

说明

  • 在CPU篇中讲过,0号CPU核默认为主核,默认获取自系统启动以来的Tick数使用的是g_tickCount[0]
  • 因每个CPU核的tick是独立计数的,所以g_tickCount中各值是不一样的.
  • 系统的Tick数在关中断的情况下不进行计数,因为OsTickHandler本质是由硬中断触发的,屏蔽硬中断的情况下就不会触发OsTickHandler,自然也就不会有g_tickCount[ArchCurrCpuid()]++的计数,所以系统Tick数不能作为准确时间使用.
  • 追问下,什么情况下硬中断会被屏蔽?

编程示例

前提条件:

  • 使用每秒的Tick数LOSCFG_BASE_CORE_TICK_PER_SECOND的默认值100。
  • 配好OS_SYS_CLOCK系统主时钟频率。

时间转换

VOID Example_TransformTime(VOID)
{
     
    UINT32 ms;
    UINT32 tick;

    tick = LOS_MS2Tick(10000);    // 10000ms转换为tick
    dprintf("tick = %d \n",tick);
    ms = LOS_Tick2MS(100);        // 100tick转换为ms
    dprintf("ms = %d \n",ms);
}

时间转换结果

tick = 1000
ms = 1000

时间统计和时间延迟

LITE_OS_SEC_TEXT UINT32 LOS_TaskDelay(UINT32 tick);
VOID Example_GetTime(VOID)
{
     
    UINT32 cyclePerTick;
    UINT64 tickCount;

    cyclePerTick  = LOS_CyclePerTickGet();
    if(0 != cyclePerTick) {
     
        dprintf("LOS_CyclePerTickGet = %d \n", cyclePerTick);
    }

    tickCount = LOS_TickCountGet();
    if(0 != tickCount) {
     
        dprintf("LOS_TickCountGet = %d \n", (UINT32)tickCount);
    }

    LOS_TaskDelay(200);//延迟200个tick
    tickCount = LOS_TickCountGet();
    if(0 != tickCount) {
     
        dprintf("LOS_TickCountGet after delay = %d \n", (UINT32)tickCount);
    }
}

时间统计和时间延迟结果

LOS_CyclePerTickGet = 495000 //取决于CPU的频率
LOS_TickCountGet = 1 //实际情况不一定是1的
LOS_TickCountGet after delay = 201 //实际情况不一定是201,但二者的差距会是200

鸿蒙源码百篇博客 往期回顾

  • v44.03 (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | harmony | 掘金 >

  • v43.03 (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | harmony | 掘金 >

  • v42.03 (中断切换篇) | 中断切换到底在切换什么? < csdn | harmony | 掘金 >

  • v41.03 (任务切换篇) | 汇编逐行注解分析任务上下文 < csdn | harmony | 掘金 >

  • v40.03 (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | harmony | 掘金 >

  • v39.03 (异常接管篇) | 社会很单纯,复杂的是人 < csdn | harmony | 掘金 >

  • v38.03 (寄存器篇) | ARM所有寄存器一网打尽,不再神秘 < csdn | harmony | 掘金 >

  • v37.03 (系统调用篇) | 全盘解剖系统调用实现过程 < csdn | harmony | 掘金 >

  • v36.03 (工作模式篇) | CPU是韦小宝,有哪七个老婆? < csdn | harmony | 掘金 >

  • v35.03 (时间管理篇) | Tick是操作系统的基本时间单位 < csdn | harmony | 掘金 >

  • v34.03 (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | harmony | 掘金 >

  • v33.03 (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | harmony | 掘金 >

  • v32.03 (CPU篇) | 内核是如何描述CPU的? < csdn | harmony | 掘金 >

  • v31.03 (定时器篇) | 内核最高优先级任务是谁? < csdn | harmony | 掘金 >

  • v30.03 (事件控制篇) | 任务间多对多的同步方案 < csdn | harmony | 掘金 >

  • v29.03 (信号量篇) | 信号量解决任务同步问题 < csdn | harmony | 掘金 >

  • v28.03 (进程通讯篇) | 进程间通讯有哪九大方式? < csdn | harmony | 掘金 >

  • v27.03 (互斥锁篇) | 互斥锁比自旋锁可丰满许多 < csdn | harmony | 掘金 >

  • v26.03 (自旋锁篇) | 想为自旋锁立贞节牌坊! < csdn | harmony | 掘金 >

  • v25.03 (并发并行篇) | 怎么记住并发并行的区别? < csdn | harmony | 掘金 >

  • v24.03 (进程概念篇) | 进程在管理哪些资源? < csdn | harmony | 掘金 >

  • v23.02 (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | harmony | 掘金 >

  • v22.02 (汇编基础篇) | CPU在哪里打卡上班? < csdn | harmony | 掘金 >

  • v21.02 (线程概念篇) | 是谁在不断的折腾CPU? < csdn | harmony | 掘金 >

  • v20.02 (用栈方式篇) | 栈是构建底层运行的基础 < csdn | harmony | 掘金 >

  • v19.02 (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | harmony | 掘金 >

  • v18.02 (源码结构篇) | 内核500问你能答对多少? < csdn | harmony | 掘金 >

  • v17.02 (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | harmony | 掘金 >

  • v16.02 (内存规则篇) | 内存管理到底在管什么? < csdn | harmony | 掘金 >

  • v15.02 (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | harmony | 掘金 >

  • v14.02 (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | harmony | 掘金 >

  • v13.02 (源码注释篇) | 热爱是所有的理由和答案 < csdn | harmony | 掘金 >

  • v12.02 (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | harmony | 掘金 >

  • v11.02 (内存分配篇) | 内存有哪些分配方式? < csdn | harmony | 掘金 >

  • v10.02 (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | harmony | 掘金 >

  • v09.02 (调度故事篇) | 用故事说内核调度 < csdn | harmony | 掘金 >

  • v08.02 (总目录) | 百万汉字注解 百篇博客分析 < csdn | harmony | 掘金 >

  • v07.02 (调度机制篇) | 任务是如何被调度执行的? < csdn | harmony | 掘金 >

  • v06.02 (调度队列篇) | 就绪队列对调度的作用 < csdn | harmony | 掘金 >

  • v05.02 (任务管理篇) | 谁在让CPU忙忙碌碌? < csdn | harmony | 掘金 >

  • v04.02 (任务调度篇) | 任务是内核调度的单元 < csdn | harmony | 掘金 >

  • v03.02 (时钟任务篇) | 触发调度最大的动力来自哪里? < csdn | harmony | 掘金 >

  • v02.02 (进程管理篇) | 进程是内核资源管理单元 < csdn | harmony | 掘金 >

  • v01.09 (双向链表篇) | 谁是内核最重要结构体? < csdn | harmony | 掘金 >

参与贡献

  • 访问注解仓库地址

  • Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request

  • 新建 Issue

喜欢请大方 点赞+关注+收藏 吧

  • 公众号: 鸿蒙内核源码分析

  • 各大站点搜 “鸿蒙内核源码分析” .欢迎转载,请注明出处.

你可能感兴趣的:(鸿蒙内核源码分析,内核,操作系统,鸿蒙内核源码分析,百篇博客分析)