嵌入式Linux应用开发-基础知识-第十九章驱动程序基石③

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石③

  • 第十九章 驱动程序基石③
    • 19.5 定时器
      • 19.5.1 内核函数
      • 19.5.2 定时器时间单位
      • 19.5.3 使用定时器处理按键抖动
      • 19.5.4 现场编程、上机
      • 19.5.5 深入研究:定时器的内部机制
      • 19.5.6 深入研究:找到系统滴答
    • 19.6 中断下半部tasklet
      • 19.6.1 内核函数
        • 19.6.1.1 定义 tasklet
        • 19.6.1.2 使能/禁止 tasklet
        • 19.6.1.3 调度 tasklet
        • 19.6.1.4
      • 19.6.2
      • 19.6.3

第十九章 驱动程序基石③

在这里插入图片描述

19.5 定时器

使用 GIT命令载后,本节源码位于这个目录下:

01_all_series_quickstart\ 
05_嵌入式 Linux驱动开发基础知识\source\ 
06_gpio_irq\ 07_read_key_irq_poll_fasync_block_timer   

19.5.1 内核函数

所谓定时器,就是闹钟,时间到后你就要做某些事。有 2个要素:时间、做事,换成程序员的话就是:超时时间、函数。
在内核中使用定时器很简单,涉及这些函数(参考内核源码 include\linux\timer.h):
① setup_timer(timer, fn, data):
设置定时器,主要是初始化 timer_list结构体,设置其中的函数、参数。
② void add_timer(struct timer_list *timer):
向内核添加定时器。timer->expires表示超时时间。
当超时时间到达,内核就会调用这个函数:timer->function(timer->data)。
③ int mod_timer(struct timer_list *timer, unsigned long expires):
修改定时器的超时时间,
它等同于:del_timer(timer); timer->expires = expires; add_timer(timer);
但是更加高效。
④ int del_timer(struct timer_list *timer):
删除定时器。

19.5.2 定时器时间单位

编译内核时,可以在内核源码根目录下用“ls -a”看到一个隐藏文件,它就是内核配置文件。打开后可以看到如下这项:

CONFIG_HZ=100 

这表示内核每秒中会发生 100次系统滴答中断(tick),这就像人类的心跳一样,这是 Linux系统的心跳。每发生一次 tick中断,全局变量 jiffies就会累加 1。
CONFIG_HZ=100表示每个滴答是 10ms。
定时器的时间就是基于 jiffies的,我们修改超时时间时,一般使用这 2种方法:
① 在 add_timer之前,直接修改:

timer.expires = jiffies + xxx;   // xxx表示多少个滴答后超时,也就是 xxx*10ms 
timer.expires = jiffies + 2*HZ;  // HZ等于 CONFIG_HZ,2*HZ就相当于 2秒 

② 在 add_timer之后,使用 mod_timer修改:

mod_timer(&timer, jiffies + xxx);   // xxx表示多少个滴答后超时,也就是 xxx*10ms 
mod_timer(&timer, jiffies + 2*HZ);  // HZ等于 CONFIG_HZ,2*HZ就相当于 2秒 

19.5.3 使用定时器处理按键抖动

在实际的按键操作中,可能会有机械抖动:
在这里插入图片描述
按下或松开一个按键,它的 GPIO电平会反复变化,最后才稳定。一般是几十毫秒才会稳定。 如果不处理抖动的话,用户只操作一次按键,中断程序可能会上报多个数据。
怎么处理?
① 在按键中断程序中,可以循环判断几十亳秒,发现电平稳定之后再上报
② 使用定时器
显然第 1种方法太耗时,违背“中断要尽快处理”的原则,你的系统会很卡。
怎么使用定时器?看下图:
在这里插入图片描述

核心在于:在 GPIO中断中并不立刻记录按键值,而是修改定时器超时时间,10ms后再处理。 如果 10ms内又发生了 GPIO中断,那就认为是抖动,这时再次修改超时时间为 10ms。
只有 10ms之内再无 GPIO中断发生,那么定时器的函数才会被调用。
在定时器函数中记录按键值。

19.5.4 现场编程、上机

19.5.5 深入研究:定时器的内部机制

初学者会用定时器就行,本节不用看。
怎么实现定时器,逻辑上很简单:每发生一次硬件中断时,硬件中断处理完后就会看看有没有软件中断要处理。
定时器就是通过软件中断来实现的,它属于 TIMER_SOFTIRQ软中断。
对于 TIMER_SOFTIRQ软中断,初始化代码如下:

void __init init_timers(void) 
{ init_timer_cpus(); init_timer_stats(); open_softirq(TIMER_SOFTIRQ, run_timer_softirq); 
} 

当发生硬件中断时,硬件中断处理完后,内核会调用软件中断的处理函数。对于 TIMER_SOFTIRQ,会调用 run_timer_softirq,它的函数如下:

run_timer_softirq 
__run_timers(base); while (time_after_eq(jiffies, base->clk)) { …… 
expire_timers(base, heads + levels);     fn = timer->function; data = timer->data; call_timer_fn(timer, fn, data);         fn(data); 

简单地说,add_timer函数会把 timer放入内核里某个链表;
在 TIMER_SOFTIRQ的处理函数中,会从链表中把这些超时的 timer取出来,执行其中的函数。 怎么判断是否超时?jiffies大于或等于 timer->expires时,timer就超时。
内核中有很多 timer,如果高效地找到超时的 timer?这是比较复杂的,
我们以后如果要深入讲解 timer的话,会用视频来讲解。

19.5.6 深入研究:找到系统滴答

这只是一些笔记,初学者不用看。
在开发板执行以下命令,可以看到 CPU0下有一个数值变化特别快,它就是滴答中断:

# cat /proc/interrupts CPU0 16:       2532       GPC  55 Level     i.MX Timer Tick 19:         22       GPC  33 Level     2010000.ecspi 20:        384       GPC  26 Level     2020000.serial 21:          0       GPC  98 Level     sai 

以 xxxxxx_IMX6ULL为做,滴答中断名字就是“i.MX Timer Tick”。
在 Linux内核源码目录下执行以下命令:

$ grep "i.MX Timer Tick" * -nr 
drivers/clocksource/timer-imx-gpt.c:319:        act->name = "i.MX Timer Tick"; 

打开 timer-imx-gpt.c 319行左右,可得如下源码:

 act->name = "i.MX Timer Tick"; act->flags = IRQF_TIMER | IRQF_IRQPOLL; act->handler = mxc_timer_interrupt; act->dev_id = ced; 
return setup_irq(imxtm->irq, act); 
mxc_timer_interrupt应该就是滴答中断的处理函数,代码如下: static irqreturn_t mxc_timer_interrupt(int irq, void *dev_id) { struct clock_event_device *ced = dev_id; struct imx_timer *imxtm = to_imx_timer(ced); uint32_t tstat; 
tstat = readl_relaxed(imxtm->base + imxtm->gpt->reg_tstat); imxtm->gpt->gpt_irq_acknowledge(imxtm); 
ced->event_handler(ced); 
return IRQ_HANDLED; }

在上述代码中没看到对 jiffies的累加操作啊,应该是在 ced->event_handler(ced)中进行。
ced->event_handler(ced)是哪一个函数?不太好找,我使用QEMU来调试内核,在mxc_timer_interrupt中打断点跟踪代码(以后的课程会讲怎么用 QEMU调试内核),发现它对应 tick_handle_periodic。
tick_handle_periodic位于 kernel\time\tick-common.c中,它里面的调用关系如下:

tick_handle_periodic 
tick_periodic(cpu); do_timer(1); jiffies_64 += ticks;  // jiffies就是 jiffies_64 

你为何说 jiffies就是 jiffies_64?在 arch\arm\kernel\vmlinux.lds.S有如下代码:

#ifndef __ARMEB__ 
jiffies = jiffies_64; 
#else 
jiffies = jiffies_64 + 4; 
#endif 

上述代码说明了,对于大字节序的 CPU,jiffies指向 jiffies_64的高 4字节;对于小字节序的 CPU,jiffies指向 jiffies_64的低 4字节。
对 jiffies_64的累加操作,就是对 jiffies的累加操作。

19.6 中断下半部tasklet

使用 GIT命令载后,本节源码位于这个目录下:

01_all_series_quickstart\ 
05_嵌入式 Linux驱动开发基础知识\source\ 
06_gpio_irq\ 08_read_key_irq_poll_fasync_block_timer_tasklet 

在前面我们介绍过中断上半部、下半部。中断的处理有几个原则:
① 不能嵌套;
② 越快越好。
在处理当前中断时,即使发生了其他中断,其他中断也不会得到处理,所以中断的处理要越快越好。但是某些中断要做的事情稍微耗时,这时可以把中断拆分为上半部、下半部。
在上半部处理紧急的事情,在上半部的处理过程中,中断是被禁止的;
在下半部处理耗时的事情,在下半部的处理过程中,中断是使能的。
中断上半部、下半部的关系机制,请回顾第 18.2.5节。

19.6.1 内核函数

19.6.1.1 定义 tasklet

中断下半部使用结构体 tasklet_struct来表示,它在内核源码 include\linux\interrupt.h中定义: struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
其中的 state有 2位:
① bit0表示 TASKLET_STATE_SCHED
等于 1时表示已经执行了 tasklet_schedule把该 tasklet放入队列了;tasklet_schedule会判断该位,如果已经等于 1那么它就不会再次把 tasklet放入队列。
② bit1表示 TASKLET_STATE_RUN
等于 1时,表示正在运行 tasklet中的 func函数;函数执行完后内核会把该位清 0。
其中的 count表示该 tasklet是否使能:等于 0表示使能了,非 0表示被禁止了。对于 count非 0的tasklet,里面的 func函数不会被执行。
使用中断下半部之前,要先实现一个 tasklet_struct结构体,这可以用这 2个宏来定义结构体:

#define DECLARE_TASKLET(name, func, data) \ 
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data } 
#define DECLARE_TASKLET_DISABLED(name, func, data) \ 
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data } 

使用 DECLARE_TASKLET定义的 tasklet结构体,它是使能的;
使用 DECLARE_TASKLET_DISABLED定义的 tasklet结构体,它是禁止的;使用之前要先调用tasklet_enable使能它。
也可以使用函数来初始化 tasklet结构体:

extern void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); 
19.6.1.2 使能/禁止 tasklet
static inline void tasklet_enable(struct tasklet_struct *t); 
static inline void tasklet_disable(struct tasklet_struct *t); 

tasklet_enable把 count增加 1;tasklet_disable把 count减 1。

19.6.1.3 调度 tasklet
static inline void tasklet_schedule(struct tasklet_struct *t); 

把 tasklet放入链表,并且设置它的 TASKLET_STATE_SCHED状态为 1。

19.6.1.4
kill tasklet 
extern void tasklet_kill(struct tasklet_struct *t); 

如果一个 tasklet未被调度,tasklet_kill会把它的 TASKLET_STATE_SCHED状态清 0;
如果一个 tasklet已被调度,tasklet_kill会等待它执行完华,再把它的 TASKLET_STATE_SCHED状态清 0。
通常在卸载驱动程序时调用 tasklet_kill。

19.6.2

tasklet使用方法
先定义 tasklet,需要使用时调用 tasklet_schedule,驱动卸载前调用 tasklet_kill。
tasklet_schedule只是把 tasklet放入内核队列,它的 func函数会在软件中断的执行过程中被调用。

19.6.3

tasklet内部机制
作为初学者,可以不看本节。
tasklet属于 TASKLET_SOFTIRQ软件中断,入口函数为 tasklet_action,这在内核 kernel\softirq.c中设置:
在这里插入图片描述
当驱动程序调用 tasklet_schedule时,会设置 tasklet的 state为 TASKLET_STATE_SCHED,并把它放入某个链表:
在这里插入图片描述

当发生硬件中断时,内核处理完硬件中断后,会处理软件中断。对于 TASKLET_SOFTIRQ软件中断,会调用 tasklet_action函数。
执行过程还是挺简单的:从队列中找到 tasklet,进行状态判断后执行 func函数,从队列中删除 tasklet。
从这里可以看出:
① tasklet_schedule调度 tasklet时,其中的函数并不会立刻执行,而只是把 tasklet放入队列;
② 调用一次 tasklet_schedule,只会导致 tasklnet的函数被执行一次;
③ 如果 tasklet的函数尚未执行,多次调用 tasklet_schedule也是无效的,只会放入队列一次。
tasklet_action函数解析如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/93308.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud(一)Eureka、Nacos、Feign、Gateway

文章目录 概述微服务技术对比 Eureka服务远程调用服务提供者和消费者Eureka注册中心搭建注册中心服务注册服务发现Ribbon负载均衡负载均衡策略饥饿加载 NacosNacos与Eureka对比Nacos服务注册Nacos服务分集群存储NacosRule负载均衡服务实例权重设置环境隔离 Nacos配置管理配置热…

ESP32设备驱动-OLED-SSD1306(I2C)显示屏驱动

OLED-SSD1306(I2C)显示屏驱动 1、OLED介绍 OLED显示屏是指有机电激发光二极管(OrganicLight-EmittingDiode,OLED)由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一…

C++面试题准备

文章目录 一、线程1.什么是进程,线程,彼此有什么区别?2.多进程、多线程的优缺点3.什么时候用进程,什么时候用线程4.多进程、多线程同步(通讯)的方法5.父进程、子进程的关系以及区别6.什么是进程上下文、中断上下文7.一…

短期风速预测|LSTM|ELM|批处理(matlab代码)

目录 1 主要内容 LSTM-长短时记忆 ELM-极限学习机 2 部分代码 3 程序结果 4 程序链接 1 主要内容 该程序是预测类的基础性代码,程序对河北某地区的气象数据进行详细统计,程序最终得到pm2.5的预测结果,通过更改数据很容易得到风速预测结…

WSL2安装历程

WLS2安装 1、系统检查 安装WSL2必须运行 Windows 10 版本 2004 及更高版本(内部版本 19041 及更高版本)或 Windows 11。 查看 Windows 版本及内部版本号,选择 Win R,然后键入winver。 2、家庭版升级企业版 下载HEU_KMS_Activ…

Django模板加载与响应

前言 Django 的模板系统将 Python 代码与 HTML 代码解耦,动态地生成 HTML 页面。Django 项目可以配置一个或多个模板引擎,但是通常使用 Django 的模板系统时,应该首先考虑其内置的后端 DTL(Django Template Language,D…

【Flutter】Flutter Web 开发 如何从 URL 中获取参数值

【Flutter】Flutter Web 开发 如何从 URL 中获取参数值 文章目录 一、前言二、Flutter Web 中的 URL 处理三、如何从 URL 中获取参数四、实际业务中的用法五、完整示例六、总结 一、前言 大家好!我是小雨青年,今天我想和大家分享一下在 Flutter Web 开发…

UGUI交互组件Button

一.初识Button对象 从菜单中创建Button对象,Button的文本由子节点Text对象显示,Button对象的组件除了基础组件外,还有Image用来显示Button常规态的图片,还有Button组件用来控制点击过渡效果和点击事件的响应。 二.Button组件的属…

C#,数值计算——Ranq1的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// Recommended generator for everyday use.The period is 1.8E19. Calling /// conventions same as Ran, above. /// </summary> public class Ranq1 { …

【2023年11月第四版教材】第17章《干系人管理》(第二部分)

第17章《干系人管理》&#xff08;第二部分&#xff09; 4 过程1-识别干系人4.1 数据收集★★★4.3数据分析4.4 权力利益方格4.5 数据表现&#xff1a;干系人映射分析和表现★★★ 5 过程2-规划干系人参与5.1 数据分析5.2 数据表现★★★5.2.1 干系人参与度评估矩阵★★★ 5.3 …

git_SSL certificate problem: unable to get local issuer certificate解决办法

拉取问题 再拉取代码的时候&#xff0c;报这个错误 这是由于当你通过HTTPS访问Git远程仓库的时候&#xff0c;如果服务器上的SSL证书未经过第三方机构认证&#xff0c;git就会报错。原因是因为未知的没有签署过的证书意味着可能存在很大的风险。解决办法就是通过下面的命令将…

【算法分析与设计】回溯法(上)

目录 一、学习要点1.1 回溯法1.2 问题的解空间1.3 0-1背包问题的解空间1.4 旅行售货员问题的解空间1.5 生成问题状态的基本方法 二、回溯法的基本思想三、回溯算法的适用条件四、递归回溯五、迭代回溯六、子集树与排列树七、装载问题八、批处理作业调度问题 一、学习要点 理解回…

操作系统初探 - 进程的概念

目录 预备知识 冯诺依曼和现代计算机结构 操作系统的理解 进程和PCB的概念 PCB中的信息 查看进程信息的指令 - ps pid 进程状态 预备知识 在学习操作系统之前我们需要先了解一下如下的预备知识。 冯诺依曼和现代计算机结构 美籍匈牙利科学家冯诺依曼最先提出“程序存…

POJ 3109 Inner Vertices 离散化+树状数组

一、题目大意 围棋棋盘&#xff0c;如果某个坐标上下左右的四个方向都存在棋子&#xff0c;那么ans1&#xff0c;根据输入的棋子数量&#xff0c;求出ans的数量。 二、解题思路 题目中有说到如果程序不会结束&#xff0c;那么输出-1&#xff0c;这其实是无源之水&#xff0c…

【爬虫】用wget命令爬虫的简易教程

文章目录 1. 获取登录的请求2. 用postman模拟登录请求3. 用wget模拟登录请求并保存cookie4. 开始爬取网站5. 查看爬取结果6. 网站爬虫简易教程 爬取需要登录的网站的资源 背景&#xff1a;对于一些网站需要使用用户名和密码登录并且使用了https&#xff0c;我们如果不通过凭证将…

win10系统任务栏图标变成白色的解决办法

我平时都是用滴答清单进行管理这个自己的日程代办的&#xff0c;但是今天打开的时候发现这个快捷方式突然变成纯白色的了&#xff0c;重启电脑之后&#xff0c;这个图标的样式仍然没有变化。上网查找解决办法之后&#xff0c;终于搞好了&#xff0c;于是就有了下面的教程。 为什…

大数据Flink(九十五):DML:Window TopN

文章目录 DML:Window TopN DML:Window TopN Window TopN 定义(支持 Streaming):Window TopN 是一种特殊的 TopN,它的返回结果是每一个窗口内的 N 个最小值或者最大值。 应用场景

数据结构—归并排序-C语言实现

引言&#xff1a;归并排序跟快速排序一样&#xff0c;都运用到了分治的算法&#xff0c;但是归并排序是一种稳定的算法&#xff0c;同时也具备高效&#xff0c;其时间复杂度为O(N*logN) 算法图解&#xff1a; 然后开始归并&#xff1a; 就是这个思想&#xff0c;拆成最小子问题…

数据结构与算法----递归

1、迷宫回溯问题 package com.yhb.code.datastructer.recursion&#xffe5;5;public class MiGong {public static void main(String[] args) {// 先创建一个二维数组&#xff0c;模拟迷宫// 地图int[][] map new int[8][7];// 使用1 表示墙// 上下全部置为1for (int i 0; i…

BASH shell脚本篇4——函数

这篇文章介绍下BASH shell中的函数。之前有介绍过shell的其它命令&#xff0c;请参考&#xff1a; BASH shell脚本篇1——基本命令 BASH shell脚本篇2——条件命令 BASH shell脚本篇3——字符串处理 函数是代码重用的最重要方式。Bash函数可以定义为一组命令&#xff0c;在b…