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

news2024/11/24 16:20:37

嵌入式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.coloradmin.cn/o/1057099.html

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

相关文章

kafka客户端应用参数详解

一、基本客户端收发消息 Kafka提供了非常简单的客户端API。只需要引入一个Maven依赖即可&#xff1a; <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka_2.13</artifactId><version>3.4.0</version></depend…

阿里云OSS图片存储

阿里云对象存储 OSS&#xff08;Object Storage Service&#xff09;是一款海量、安全、低成本、高可靠的云存储服务&#xff0c;提供最高可达 99.995 % 的服务可用性。多种存储类型供选择&#xff0c;全面优化存储成本。 视频介绍 创建bucket 开发文档 上传文件demo &#x…

ios证书类型及其作用说明

ios证书类型及其作用说明 很多刚开始接触iOS证书的开发者可能不是很了解iOS证书的类型功能和概念。下面对iOS证书的几个方面进行介绍。 apple开发账号分类&#xff1a; 免费账号&#xff1a; 无需支付费用给apple&#xff0c;使用个人信息注册的账号 可以开发测试安装&…

Golang语法、技巧和窍门

Golang简介 命令式语言静态类型语法标记类似于C&#xff08;但括号较少且没有分号&#xff09;&#xff0c;结构类似Oberon-2编译为本机代码&#xff08;没有JVM&#xff09;没有类&#xff0c;但有带有方法的结构接口没有实现继承。不过有type嵌入。函数是一等公民函数可以返…

Scala第十章

Scala第十章 章节目标 1.数组 2.元组 3.列表 4.集 5.映射 6.迭代器 7.函数式编程 8.案例&#xff1a;学生成绩单 scala总目录 文档资料下载

【Python】time模块和datetime模块的部分函数说明

时间戳与日期 在说到这俩模块之前&#xff0c;首先先明确几个概念&#xff1a; 时间戳是个很单纯的东西&#xff0c;没有“时区”一说&#xff0c;因为时间戳本质上是经过的时间。日常生活中接触到的“日期”、“某点某时某分”准确的说是时间点&#xff0c;都是有时区概念的…

有时候,使用 clang -g test.c 编译出可执行文件后,发现 gdb a.out 进行调试无法读取符号信息,为什么?

经过测试&#xff0c;gdb 并不是和所有版本的 llvm/clang 都兼容的 当 gdb 版本为 9.2 时&#xff0c;能支持 9.0.1-12 版本的 clang&#xff0c;但无法支持 16.0.6 版本的 clang 可以尝试使用 LLVM 专用的调试器 lldb 我尝试使用了 16.0.6 版本的 lldb 调试 16.0.6 的 clan…

牛客题霸 -- DP41 【模板】01背包

解题步骤&#xff1a; 参考代码&#xff1a; 未优化的代码&#xff1a; int n; int V; const int N1010; int v[N]; int w[N]; int dp[N][N];int main() {cin>>n>>V;for(int i1;i<n;i){cin>>v[i]>>w[i];}//第一问//第一行全是0&#xff0c;不用初…

Ubuntu20 QT6.0 编译 ODBC 驱动

一、新建测试项目 新建一个控制台项目&#xff0c; // main.cpp #include <QCoreApplication> #include <QSqlDatabase> #include <QDebug>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);// 获取当前Qt支持的驱动列表QStringList driv…

IDEA2023 常用配置(JDK/系统设置等常用配置)

目录 一、JDK及编译目录设置 1 项目的JDK设置 2 out目录和编译版本 二、相关详细设置 1 打开详细配置界面 1、显示工具栏 2、默认启动项目配置 3、取消自动更新 2 设置整体主题 1、选择主题 2、设置菜单和窗口字体和大小 3、设置IDEA背景图 3 设置编辑器主题样式…

八、混合整数线性规划问题

文章目录 1、混合整数线性规划问题2、分枝定界算法2.1、分枝策略 THE END 1、混合整数线性规划问题 \qquad 混合整数线性规划问题的一般表示形式如下所示&#xff1a;假设现有 n n n个变量&#xff0c; m m m个约束&#xff0c;令最大化(或者最小化) c 1 x 1 c 2 x 2 . . . …

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

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…

VRRP配置案例(路由走向分析,端口切换)

以下配置图为例 PC1的配置 acsw下行为access口&#xff0c;上行为trunk口&#xff0c; 将g0/0/3划分到vlan100中 <Huawei>sys Enter system view, return user view with CtrlZ. [Huawei]sysname acsw [acsw] Sep 11 2023 18:15:48-08:00 acsw DS/4/DATASYNC_CFGCHANGE:O…

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

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石② 第十九章 驱动程序基石②19.3 异步通知19.3.1 适用场景19.3.2 使用流程19.3.3 驱动编程19.3.4 应用编程19.3.5 现场编程19.3.6 上机编程19.3.7 异步通知机制内核代码详解 19.4 阻塞与非阻塞19.4.1 应用编程19.4.2 驱动编程…

26-网络通信

网络通信 什么是网络编程&#xff1f; 可以让设备中的程序与网络上其他设备中的程序进行数据交互&#xff08;实现网络通信的&#xff09;。 java.net.包下提供了网络编程的解决方案&#xff01; 基本的通信架构有2种形式&#xff1a;CS架构&#xff08; Client客户端/Server服…

Python无废话-办公自动化Excel修改数据

如何修改Excel 符合条件的数据&#xff1f;用Python 几行代码搞定。 需求&#xff1a;将销售明细表的产品名称为PG手机、HW手机、HW电脑的零售价格分别修改为4500、5500、7500&#xff0c;并保存Excel文件。如下图 Python 修改Excel 数据&#xff0c;常见步骤&#xff1a; 1&…

又添十万字-CS的陋室2023年文章合集来袭

趁着国庆中秋双节&#xff0c;整理了“22年文章合集”以来的所有新文章&#xff0c;在此给大家带来我的文章合集2023版。 文章合集收录&#xff1a; 文章合集2022以来的所有文章&#xff0c;包括“前沿重器”和“心法利器”。 前沿重器28-34共7篇&#xff0c;约2.6万字。心法利…

《C和指针》笔记30:函数声明数组参数、数组初始化方式和字符数组的初始化

文章目录 1. 函数声明数组参数2. 数组初始化方式2.1 静态初始化2.2 自动变量初始化 2.2 字符数组的初始化 1. 函数声明数组参数 下面两个函数原型是一样的&#xff1a; int strlen( char *string ); int strlen( char string[] );可以使用任何一种声明&#xff0c;但哪个“更…

一文拿捏SpringMVC的调用流程

SpringMVC的调用流程 1.核心元素&#xff1a; DispatcherServlet(前端控制器)HandlerMapping(处理器映射器)HandlerAdapter(处理器适配器) ---> Handler(处理器)ViewResolver(视图解析器 )---> view(视图) 2.调用流程 用户发送请求到前端控制器前端控制器接收用户请求…

7.JavaScript-vue

1 JavaScript html完成了架子&#xff0c;css做了美化&#xff0c;但是网页是死的&#xff0c;我们需要给他注入灵魂&#xff0c;所以接下来我们需要学习JavaScript&#xff0c;这门语言会让我们的页面能够和用户进行交互。 1.1 介绍 通过代码/js效果演示提供资料进行效果演…