正点原子【第四期】Linux之驱动开发学习笔记-10.1 Linux 内核定时器实验
前言:
本文是根据哔哩哔哩网站上“正点原子【第四期】手把手教你学Linux系列课程之 Linux驱动开发篇”视频的学习笔记,该课程配套开发板为正点原子alpha/mini Linux开发板。在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。
引用:
正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com
正点原子【第四期】手把手教你学 Linux之驱动开发篇_哔哩哔哩_bilibili
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》
正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档
正点原子imx6ull-mini-Linux驱动之Linux I2C 驱动实验(21)-CSDN博客
uboot移植(4)--在NXP官方uboot适配ALPHA开发板网络_uboot sr8201f-CSDN博客
正文:
本文是 “正点原子【第四期】手把手教你学 Linux之驱动开发篇-1.1 Linux驱动开发与裸机开发的区别”。本节将参考正点原子的视频教程和配套的正点原子开发指南文档进行学习。
0. 概述
定时器是我们最常用到的功能,一般用来完成定时功能,本章我们就来学习一下 Linux内核提供的定时器 API函数,通过这些定时器 API函数我们可以完成很多要求定时的应用。 Linux内核也提了短延时函数,比如微秒、纳秒、毫秒延时函数,本章我们就来学习一下这些和时间有关的功能。
1 Linux时间管理和内核定时器简介
1.1 内核时间管理简介
学习过 UCOS或 FreeRTOS的同学应该知道, UCOS或 FreeRTOS是需要一个硬件定时器提供系统时钟,一般使用 Systick作为系统时钟源。同理, Linux要运行,也是需要一个系统时钟的,至于这个系统时钟是由哪个定时器提供的,笔者没有去研究过 Linux内核,但是在 Cortex-A7内核中个通用定时器,在《 Cortex-A7 Technical ReferenceManua.pdf》的“ 9:Generic Timer章节有简单的讲解,关于这个通用定时器的详细内容,可以参考《 ARM ArchitectureReference Manual ARMv7A and ARMv7-R edition.pdf》的“ chapter B8 The Generic Timer”章节。这个通用定时器是可选的,按照笔者学习 FreeRTOS和 STM32的经验,猜测 Linux会将这个通用定时器作为 Linux系统时钟源 (前提是 SOC得选配这个通用定时器 )。具体是怎么做的笔者没有深入研究过,这里仅仅是猜测!不过对于我们 Linux驱动编写者来说,不需要深入研究这些具体的实现,只需要掌握相应的 API函数即可,除非你是内核编写者或者内核爱好者。
Linux内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、对于我们驱动编写者来说最常用的定时器。硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率,也叫做节拍率 (tick rate)(有的资料也叫系统频率 ),比如 1000Hz 100Hz等等说的就是系统节拍率。系统节拍率是可以设置的,单位是 Hz,我们在编译 Linux内核的时候可以通过图形化界面设置系统节拍率,按照如下路径打开配置界面:
-> Kernel Features
-> Timer frequency (<choice> [=y])
选中“ Timer frequency”,打开以后如图 50.1.1.1所示:

从图 50.1.1.1可以看出,可选的系统节拍率为 100Hz、 200Hz、 250Hz、 300Hz、 500Hz和1000Hz,默认情况下选择 100Hz。设置好以后打开 Linux内核源码根目录下的 .config文件,在此文件中有如图 50.1.1.2所示定义:

图 50.1.1.2中的 CONFIG_HZ为 100 Linux内核会使用 CONFIG_HZ来设置自己的系统时钟。打开文件 include/asm-generic/param.h,有如下内容
include/asm-generic/param.h
# undef HZ 
# define HZ CONFIG_HZ 
# define USER_HZ 100 
# define CLOCKS_PER_SEC (USER_HZ) 
第 7行定义了一个宏 HZ,宏 HZ就是 CONFIG_HZ,因此 HZ=100,我们后面编写 Linux驱动的候会常常用到 HZ,因为 HZ表示一秒的节拍数,也就是频率。
大多数初学者看到系统节拍率默认为100Hz的时候都会有疑问,怎么这么小? 100Hz是可选的节拍率里面最小的。为什么不选择大一点的呢?这里就引出了一个问题:高节拍率和低节拍率的优缺点:
①、高节拍率会提高系统时间精度,如果采用 100Hz的节拍率,时间精度就是 10ms,采用1000Hz的话时间精度就是 1ms,精度提高了 10倍。高精度时钟的好处有很多,对于那些对时间要求严格的函数来说,能够以更高的精度运行,时间测量也更加准确。
②、高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担, 1000Hz和 100Hz的统节拍率相比,系统要花费 10倍的“精力”去处理中断。中断服务函数占用处理器的时间增加,但是现在的处理器性能都很强大,所以采用 1000Hz的系统节拍率并不会增加太大的负载压力。根据自己的实际情况,选择合适的系统节拍率,本教程我们全部采用默认的 100Hz系统节拍率。
Linux内核使用全局变量 jiffies来记 录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies初始化为 0 jiffies定义在文件 include/linux/jiffies.h中,定义如下:
include/linux/jiffies.h
extern u64 __cacheline_aligned_in_smp jiffies_64;
extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies;
 
第 76行,定义了一个 64位的 jiffies_64。
 第 77行,定义了一个 unsigned long类型的 32位的 jiffies
jiffies_64和 jiffies其实是同一个东西, jiffies_64用于 64位系统,而 jiffies用于 32位系统。为了兼容不同的硬件, jiffies其实就是 jiffies_64的低 32位, jiffies_64和 jiffies的结构如图50.1.1.3所示:

当我们访问jiffies 的时候其实访问的是jiffies_64 的低32 位,使用get_jiffies_64 这个函数可以获取jiffies_64 的值。在32 位的系统上读取jiffies 的值,在64 位的系统上jiffes 和jiffies_64表示同一个变量,因此也可以直接读取jiffies 的值。所以不管是32 位的系统还是64 位系统,都可以使用jiffies。
前面说了HZ 表示每秒的节拍数,jiffies 表示系统运行的jiffies 节拍数,所以jiffies/HZ 就是系统运行时间,单位为秒。不管是32 位还是64 位的jiffies,都有溢出的风险,溢出以后会重新从0 开始计数,相当于绕回来了,因此有些资料也将这个现象也叫做绕回。假如HZ 为最大值1000 的时候,32 位的jiffies 只需要49.7 天就发生了绕回,对于64 位的jiffies 来说大概需要5.8 亿年才能绕回,因此jiffies_64 的绕回忽略不计。处理32 位jiffies 的绕回显得尤为重要,Linux 内核提供了如表50.1.1.1 所示的几个API 函数来处理绕回。
| 函数 | 描述 | 
| timer_after(unknown, known) | unknown 通常为 jiffies 值,known通常是需要对比的值 | 
| time_before(unknown, known) | |
| time_after_eq(unknown, known) | |
| time_before_eq(unknown, known) | 
如果unkown 超过known 的话,time_after 函数返回真,否则返回假。如果unkown 没有超过known 的话time_before 函数返回真,否则返回假。time_after_eq 函数和time_after 函数类似,只是多了判断等于这个条件。同理,time_before_eq 函数和time_before 函数也类似。比如我们要判断某段代码执行时间有没有超时,此时就可以使用如下所示代码:
