Linux驱动开发——(五)内核中断

目录

一、内核中断简介

1.1 中断号 

1.2 中断API函数

1.2.1 irq_of_parse_and_map函数

1.2.2 gpio_to_irq函数

1.2.3 request_irq函数

1.2.4 free_irq函数

1.2.5 中断处理函数

1.2.6 中断使能与禁止函数

二、上半部(顶半部)与下半部(底半部) 

2.1 上半部与下半部简介

2.2 软中断

2.3 tasklet

2.4 工作队列

三、驱动代码


一、内核中断简介

1.1 中断号 

每个中断都有一个中断号(中断线),通过中断号即可区分不同的中断。在 Linux内核中使用一个int变量表示中断号。

1.2 中断API函数

1.2.1 irq_of_parse_and_map函数

中断信息如果写到设备树里面,则可以通过irq_of_parse_and_map函数从interupts属性中提取对应的设备号

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

dev:设备节点。
index:索引号interrupts属性可能包含多条中断信息,通过index指定要获取的信息。
返回值:中断号。

1.2.2 gpio_to_irq函数

如果使用GPIO,则可以使用gpio_to_irq函数来获取gpio对应的中断号: 

int gpio_to_irq(unsigned int gpio)

gpio:要获取的 GPIO编号。
返回值:GPIO对应的中断号。

1.2.3 request_irq函数

在Linux内核中使用某个中断是需要申请的,request_irq函数用于申请中断

request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq函数。 request_irq函数会激活(使能)中断,所以不需要我们手动去使能中断:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

irq:要申请中断的中断号。

handler:中断处理函数,当中断发生以后就会执行此中断处理函数。

flags:中断标志,可以在文件include/linux/interrupt.h里面可以查看所有的中断标志,下表是常用的中断标志:

标志描述
IRQF_SHARED多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq函数的dev参数就是唯一区分他们的标志。
IRQF_ONESHOT单次中断,中断执行一次就结束 。
IRQF_TRIGGER_NONE无触发。
IRQF_TRIGGER_RISING上升沿触发。
IRQF_TRIGGER_FALLING下降沿触发。
IRQF_TRIGGER_HIGH高电平触发。
IRQF_TRIGGER_LOW低电平触发。

name:中断名字,设置以后可以在/proc/interrupts文件中看到对应的中断名字。建议搭配gpio_request函数赋予。

dev:如果将 flags设置为 IRQF_SHARED的话, dev用来区分不同的中断,一般情况下将dev设置为设备结构体, dev会传递给中断处理函数 irq_handler_t的第二个参数。

返回值: 0,中断申请成功,其他负值,中断申请失败,如果返回 -EBUSY的话表示中断已经被申请了。

1.2.4 free_irq函数

中断使用完成以后就要通过free_irq函数释放掉相应的中断如果中断不是共享的,那么 free_irq会删除中断处理函数并且禁止中断

void free_irq(unsigned int irq, void *dev)

irq:要释放的中断。

dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

返回值:无。

1.2.5 中断处理函数

函数申请中断的时候需要设置中断处理函数: 

irqreturn_t (*irq_handler_t) (int, void *)

第一个参数是要中断处理函数要相应的中断号;第二个参数是一个指向void的指针,也就是个通用指针,需要与request_irq函数的dev参数保持一致。用于区分共享中断的不同设备,dev也可以指向设备数据结构。中断处理函数的返回值为irqreturn_t类型,irqreturn_t类型定义如下所示:

enum irqreturn { IRQ_NONE = (0 << 0), IRQ_HANDLED = (1 << 0), IRQ_WAKE_THREAD = (1 << 1), 
}; typedef enum irqreturn irqreturn_t;

irqreturn_t是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED)
1.2.6 中断使能与禁止函数

常用的中断使用和禁止函数如下所示:

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)

irq:一个中断号。

disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:

void disable_irq_nosync(unsigned int irq)

disable_irq_nosync函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

如果需要操作当前处理器的整个中断系统(全局中断),则可以使用:

local_irq_enable()
local_irq_disable()

如果任务A关闭全局中断3s,在这3s内任务B打开全局中断,则系统容易崩溃。考虑到任务之间并发而竞争,此时就要用到下面两个函数:

local_irq_save(flags)
local_irq_restore(flags)

这两个函数一对使用。local_irq_save函数用于禁止中断,并且将中断状态保存在flags中。
local_irq_restore用于恢复中断,将中断到flags状态。 


二、上半部(顶半部)与下半部(底半部) 

2.1 上半部与下半部简介

在使用request_irq申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,那么中断处理函数就会执行。

中断处理函数一定是越快执行完毕越好,但有些中断处理过程就是比较费时间,我们必须要对其进行处理,缩小中断处理函数的执行时间。比如电容触摸屏通过中断通知SOC有触摸事件发生, SOC响应中断,然后通过IIC接口读取触摸坐标值并将其上报给系统。但是我们都知道IIC的速度最高也只有400Kbit/S,所以在中断中通过IIC读取数据就会浪费时间。我们可以将通过IIC读取触摸数据的操作暂后执行,中断处理函数仅仅相应中断,然后清除中断标志位即可。这个时候中断处理过程就分为了两部分:

上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。

下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出

哪些代码属于上半部,哪些代码属于下半部,并没有明确的规定,一切根据实际使用情况去判断。这里有一些可以借鉴的参考点:

①如果要处理的内容不希望被其他中断打断,那么可以放到上半部。

②如果要处理的任务对时间敏感,可以放到上半部。

③如果要处理的任务与硬件有关,可以放到上半部

④除了上述三点以外的其他任务,优先考虑放到下半部。

2.2 软中断

不推荐使用软中断!这里只是作为知识点简单介绍。

Linux内核使用结构体softirq_action表示软中断,softirq_action结构体定义在文件include/linux/interrupt.h中:

struct softirq_action 
{ void (*action)(struct softirq_action *); 
};

在kernel/softirq.c文件中一共定义了10个软中断:

static struct softirq_action softirq_vec[NR_SOFTIRQS];

NR_SOFTIRQS是枚举类型,定义在文件include/linux/interrupt.h中:

enum { HI_SOFTIRQ=0, /* 高优先级软中断 */ TIMER_SOFTIRQ, /* 定时器软中断 */ NET_TX_SOFTIRQ, /* 网络数据发送软中断 */ NET_RX_SOFTIRQ, /* 网络数据接收软中断 */ BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, /* tasklet软中断 */ SCHED_SOFTIRQ, /* 调度软中断 */ HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */ RCU_SOFTIRQ, /* RCU软中断 */ NR_SOFTIRQS 
};

softirq_action结构体中的action成员变量是软中断的服务函数,数组softirq_vec是个全局数组,所有的CPU(对于SMP系统而言)都可以访问到。

2.3 tasklet

tasklet是利用软中断来实现的另外一种下半部机制,相比起软中断,更建议使用tasklet。Linux内核使用tasklet_struct结构体来表示tasklet:

struct tasklet_struct 
{ struct tasklet_struct *next; /* 下一个tasklet */ unsigned long state; /* tasklet状态 */ atomic_t count; /* 计数器,记录对tasklet的引用数 */ void (*func)(unsigned long); /* tasklet执行的函数 */ unsigned long data; /* 函数func的参数 */ 
};

func函数就是tasklet要执行的处理函数,用户定义函数内容,类似于中断处理函数。如果要使用 tasklet,必须先定义一个 tasklet,然后使用tasklet_init函数初始化tasklet。taskled_init函数原型如下:

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

t:要初始化的tasklet。

func:tasklet的处理函数。

data:要传递给func函数的参数。

返回值:没有返回值。

也可以使用宏DECLARE_TASKLET来一次性完成tasklet的定义和初始化,DECLARE_TASKLET定义在include/linux/interrupt.h文件中:

DECLARE_TASKLET(name, func, data)

name:要定义的tasklet名字,为tasklet_struct类型的变量。

func:tasklet的处理函数。

data:传递给func函数的参数。 

在上半部(中断处理函数)中调用tasklet_schedule函数就能使tasklet在合适的时间运行:

void tasklet_schedule(struct tasklet_struct *t)

t:要调度的tasklet,也就是DECLARE_TASKLET宏里面的name。

返回值:没有返回值。

关于tasklet的参考使用示例:

/* 定义taselet */ 
struct tasklet_struct testtasklet; /* tasklet处理函数 */ 
void testtasklet_func(unsigned long data) 
{ /* tasklet具体处理内容 */ 
} /* 中断处理函数 */ 
irqreturn_t test_handler(int irq, void *dev_id) 
{ ...... /* 调度tasklet */ tasklet_schedule(&testtasklet); ...... 
} /* 驱动入口函数 */ 
static int __init xxxx_init(void) 
{ ...... /* 初始化tasklet */tasklet_init(&testtasklet, testtasklet_func, data); /* 注册中断处理函数 */ request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev); ...... 
}

2.4 工作队列

工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度

Linux内核使用work_struct结构体表示一个工作

struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; /* 工作队列处理函数 */ 
};

这些工作组织成工作队列,工作队列使用workqueue_struct结构体表示:

struct workqueue_struct { struct list_head pwqs; struct list_head list; struct mutex mutex; int work_color; int flush_color; atomic_t nr_pwqs_to_flush; struct wq_flusher *first_flusher; struct list_head flusher_queue; struct list_head flusher_overflow; struct list_head maydays; struct worker *rescuer; int nr_drainers; int saved_max_active; struct workqueue_attrs *unbound_attrs; struct pool_workqueue *dfl_pwq; char name[WQ_NAME_LEN]; struct rcu_head rcu; unsigned int flags ____cacheline_aligned; struct pool_workqueue __percpu *cpu_pwqs; struct pool_workqueue __rcu *numa_pwq_tbl[]; 
};

Linux内核使用工作者线程(worker thread)来处理工作队列中的各个工作, Linux内核使用worker结构体表示工作者线程

struct worker { union { struct list_head entry; struct hlist_node hentry; }; struct work_struct *current_work; work_func_t current_func; struct pool_workqueue *current_pwq; bool desc_valid; struct list_head scheduled; struct task_struct *task; struct worker_pool *pool; struct list_head node; unsigned long last_active; unsigned int flags; int id; char desc[WORKER_DESC_LEN]; struct workqueue_struct *rescue_wq; 
};

在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管

先定义一个work_struct结构体变量以创建工作,然后使用INIT_WORK宏来初始化工作:

#define INIT_WORK(_work, _func)

 _work:要初始化的工作。

_func:工作对应的处理函数。

也可以使用DECLARE_WORK一次性完成工作的创建和初始化:

#define DECLARE_WORK(n, f)

n:要定义的工作。

f:工作对应的处理函数。

工作的调度函数为schedule_work

bool schedule_work(struct work_struct *work)

work:要调度的工作。
返回值:0,成功;其他值,失败。

工作队列的参考使用示例:

/* 定义工作(work) */ 
struct work_struct testwork; /* work处理函数 */
void testwork_func_t(struct work_struct *work); 
{ /* work具体处理内容 */ 
} /* 中断处理函数 */ 
irqreturn_t test_handler(int irq, void *dev_id) 
{ ...... /* 调度work */ schedule_work(&testwork); ...... 
} /* 驱动入口函数 */ 
static int __init xxxx_init(void) 
{ ...... /* 初始化work */ INIT_WORK(&testwork, testwork_func_t); /* 注册中断处理函数 */ request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev); ...... 
}

三、驱动代码

由于此篇篇幅过长,驱动代码部分放在另一篇。 

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

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

相关文章

代码托管基础操作

在待上传代码文件夹中右键&#xff0c;打开Git Bash Here依次输入以下命令&#xff1a; git init(在本地初始化一个代码仓库&#xff0c;具体表现为会在你的文件夹里出现一个隐藏的.git文件夹) git add .&#xff08;先把代码放到本地的一个缓冲区&#xff09;添加当前目录下的…

【C++】从零开始认识泛型编程 — 模版

送给大家一句话&#xff1a; 尽管眼下十分艰难&#xff0c;可日后这段经历说不定就会开花结果。总有一天我们都会成为别人的回忆&#xff0c;所以尽力让它美好吧。 – 岩井俊二 &#xff3c;&#xff3c;\ ⱶ˝୧(๑ ⁼̴̀ᐜ⁼̴́๑)૭兯 //&#xff0f;&#xff0f; &#…

六、Java+FFmpeg,实战直播推流

目录 类 JavaFFmepegTest run() 方法 openFFmpegExe() 方法 main() 方法 总结 import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io

Unity 时间格式 12小时制与24小时制

using System; using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; using UniRx; public class DisplayTime : MonoBehaviour { //时间文本显示 [SerializeField] private TextMeshProUGUI _time; private int _timeType 0; enu…

AI大模型探索之路-训练篇3:大语言模型全景解读

文章目录 前言一、语言模型发展历程1. 第一阶段&#xff1a;统计语言模型&#xff08;Statistical Language Model, SLM&#xff09;2. 第二阶段&#xff1a;神经语言模型&#xff08;Neural Language Model, NLM&#xff09;3. 第三阶段&#xff1a;预训练语言模型&#xff08…

Ali-Sentinel-节点与度量

归档 GitHub: Ali-Sentinel-节点与度量 作用 保存资源的实时统计信息 节点 节点-类结构 com.alibaba.csp.sentinel.slots.statistic.metric.DebugSupport /** 调试支持 */ public interface DebugSupport {void debug(); // 打印统计信息 }com.alibaba.csp.sentinel.n…

Python基础知识(二)

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》 《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 1.输入和输出函数1.1输出函数1.2输入函数 2.常见运算符2.1赋值运算符2.2比较运算符2.3逻辑运算符2.4and逻辑与2.5or逻辑或2.6not逻…

nvidia-smi详解

nvidia-smi&#xff1a;控制你的 GPU 大多数用户都知道如何检查他们的 CPU 的状态&#xff0c;查看有多少系统内存可用&#xff0c;或者找出有多少磁盘空间可用。相比之下&#xff0c;从历史上看&#xff0c;密切关注 GPU 的运行状况和状态一直比较困难。如果您不知道去哪里寻…

c++二叉树的进阶--二叉搜索树

1. 二叉搜索树的概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值 它的左…

Swift-27-类的初始化与销毁

Swift的初始化是一个有大量规则的固定过程。初始化是设置类型实例的操作&#xff0c;包括给每个存储属性初始值&#xff0c;以及一些其他准备工作。完成这个过程后&#xff0c;实例就可以使用了。 简单来讲就是类的构造函数&#xff0c;基本语法如下&#xff1a; 注意&#xff…

C语言扫雷游戏完整实现(上)

文章目录 前言一、新建好头文件和源文件二、实现游戏菜单选择功能三、定义游戏函数四、初始化棋盘五、 打印棋盘函数六、布置雷函数七、玩家排雷菜单八、标记功能的菜单九、标记功能菜单的实现总结 前言 C语言从新建文件到游戏菜单&#xff0c;游戏函数&#xff0c;初始化棋盘…

JavaScript-4.正则表达式、BOM

正则表达式 正则表达式包含在"/"&#xff0c;"/"中 开始与结束 ^ 字符串的开始 $ 字符串的结束 例&#xff1a; "^The"&#xff1a;表示所有以"The"开始的字符串&#xff08;"There"、"The cat"等&#x…

数据结构(八)——排序

八、排序 8.1 排序的基本概念 排序(Sort)&#xff0c;就是重新排列表中的元素&#xff0c;使表少的元素满足按关键字有序的过程。 输入∶n个记录R1,R2...., Rn&#xff0c;对应的关键字为k1, k2,... , kn 输出:输入序列的一个重排R1,R2....,Rn&#xff0c;使得有k1≤k2≤...≤…

综合大实验

题目&#xff1a; 1、R4为ISP&#xff0c;其上只配置IP地址&#xff1b;R4与其他所直连设备间均使用公有IP&#xff1b; 2、R3-R5、R6、R7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个OSPF环境IP基于172.16.0.0/16划分&#xff1b;除了R12有两个环回&#xff0c;其…

VUE父组件向子组件传递值

创作灵感 最近在写一个项目时&#xff0c;遇到了这样的一个需求。我封装了一个组件&#xff0c;这个组件需要被以下两个地方使用&#xff0c;一个是搜索用户时用到&#xff0c;一个是修改用户信息时需要用到。其中&#xff0c;在搜索用户时&#xff0c;可以根据姓名或者账号进…

OllyDbg 快捷键及常用法

keywords: debug, ollydbg 快捷键 Ctrl --> C Shift --> S Alt --> M 功能快捷键设置/取消断点F2执行到光标所在行F4步过F8步进F7运行F9暂停F12回到应用层M-F9打开文件F3重新调试C-F2打开应用程序输入表C-n寻找表达式C-g打开断点窗口M-b切换断点状态空格添加备注…

[前端]NVM管理器安装、nodejs、npm、yarn配置

NVM管理器安装、nodejs、npm、yarn配置 NVM管理器安装 nvm(Node.js version manager) 是一个命令行应用&#xff0c;可以协助您快速地 更新、安装、使用、卸载 本机的全局 node.js 版本。 nvm下载地址&#xff1a;https://github.com/coreybutler/nvm-windows/releases 1.全部…

Unity面向切面编程

一直说面向AOP&#xff08;切面&#xff09;编程&#xff0c;好久直接专门扒出理论、代码学习过。最近因为某些原因&#x1f62d;还得再学学造火箭的技术。 废话不多说&#xff0c;啥是AOP呢&#xff1f;这里我就不班门弄斧了&#xff0c;网上资料一大堆&#xff0c;解释的肯定…

mybatis中<if>条件判断带数字的字符串失效问题

文章目录 一、项目背景二、真实错误原因说明三、解决方案3.1针对纯数字的字符串值场景3.2针对单个字符的字符串值场景 四、参考文献 一、项目背景 MySQL数据库使用Mybatis查询拼接select语句中进行<if>条件拼接的时候&#xff0c;发现带数字的或者带单个字母的字符串失效…

CPU资源控制

一、CPU资源控制定义 cgroups&#xff08;control groups&#xff09;是一个非常强大的linux内核工具&#xff0c;他不仅可以限制被namespace隔离起来的资源&#xff0c; 还可以为资源设置权重、计算使用量、操控进程启停等等。 所以cgroups&#xff08;control groups&#xf…