【IMX6ULL驱动开发学习】18.中断下半部(tasklet、工作队列、中断线程化)

下图表述了Linux内核的中断处理机制,为了在中断执行时间尽量短和中断处理需完成的工作尽量大之间找到一
个平衡点, Linux将中断处理程序分解为两个半部: 顶半部(Top Half) 和底半部(Bottom Half) 。
在这里插入图片描述
顶半部: 用于完成尽量少的比较紧急的功能, 它往往只是简单地读取寄存器中的中断状态, 并在清除中断标志后就
进行“登记中断”的工作。 “登记中断 意味着将底半部处理程序挂到该设备的底半部执行队列中去。 这样, 顶半部
执行的速度就会很快, 从而可以服务更多的中断请求。

底半部: 几乎做了中断处理程序所有的事情, 而且可以被新的中断打断, 这也是底半部和顶半部的最大不同, 因为顶半部往往被设计成不可中断。 底半部相对来说并不是非常紧急的, 而且相对比较耗时, 不在硬件中断服务程序中执行。


Linux实现底半部的机制主要有tasklet、 工作队列、 软中断和线程化irq

先来个比较:

中断下半部机制上下文是否可被打断是否可睡眠实时性
tasklet运行于软中断上下文可被硬中断打断,不会被定时器中断打断不可睡眠会阻塞应用程序
工作队列运行于内核线程上下文可被硬中断、定时器中断打断可睡眠同一个链表中可能挂多个work
所以实时性会降低
threaded_irq运行于内核线程上下文可被硬中断、定时器中断打断可睡眠对于每个中断都有独立的内核线程
实时性高

1. tasklet

tasklet的使用较简单, 它的执行上下文是软中断, 执行时机通常是顶半部返回的时候。 我们只需要定义tasklet及
其处理函数, 并将两者关联则可, 例如:

/* 定义一个处理函数 */
void my_tasklet_func(unsigned long); 
/* 定义一个 tasklet 结构 my_tasklet , 与 my_tasklet_func(data) 函数相关联 */
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);/* 定义一个 tasklet_struct 结构体 */
struct tasklet_struct tasklet;
/* 定义一个处理函数 */
void my_tasklet_func(unsigned long); 
/* 关联 tasklet 与 处理函数 */
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)

在需要调度tasklet的时候引用一个tasklet_schedule() 函数就能使系统在适当的时候进行调度运行:

tasklet_schedule(struct tasklet_struct *tasklet);

销毁 tasklet

tasklet_kill(struct tasklet_struct *tasklet);

taskelet 运行于软中断上下文,所以不能睡眠,会阻塞应用程序,可被外部中断打断,不会被内核定时器中断打断

/* 中断下半部处理函数 不能睡眠可以被外部中断打断,但不会被定时中断打断 */
static void my_tasklet_func(unsigned long arg)
{int i = 0;printk("my_tasklet_func gpio = %d\n", (int)arg);/* 循环期间,APP程序得不到执行 */while(i < 500){printk("%d ",i++);if(i % 20 == 0)printk("\n");}printk("\n");
}

测试结果:
tasklet 可以被按键中断,但不能被定时器中断,也不能睡眠(测试了添加msleep后,加载驱动直接一直输出报错)

在这里插入图片描述

tasklet 使用模板

/* 定义 tasklet 和底半部函数并将它们关联 */
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);/* 中断处理底半部 */
void xxx_do_tasklet(unsigned long)
{...
}/* 中断处理顶半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{...tasklet_schedule(&xxx_tasklet);...
}/* 设备驱动模块加载函数 */
int __init xxx_init(void){.../* 申请中断 */result = request_irq(xxx_irq, xxx_interrupt,0, "xxx", NULL);...return IRQ_HANDLED;
}/* 设备驱动模块卸载函数 */
void __exit xxx_exit(void)
{.../* 释放中断 */free_irq(xxx_irq, xxx_interrupt);...
}

2. 工作队列

工作队列的使用方法和tasklet非常相似, 但是工作队列的执行上下文是内核线程, 因此可以调度和睡眠。 下面的
代码用于定义一个工作队列和一个底半部执行函数:

struct work_struct my_wq; /* 定义一个工作队列 */void my_wq_func(struct work_struct *work); /* 定义一个处理函数 */

通过INIT_WORK() 可以初始化这个工作队列并将工作队列与处理函数绑定:

/* 初始化工作队列并将其与处理函数绑定 */
INIT_WORK(&my_wq, my_wq_func);

与tasklet_schedule() 对应的用于调度工作队列执行的函数为schedule_work() , 如:

/* 调度工作队列执行 */
schedule_work(&my_wq);

在这里插入图片描述
假设有两个cpu,当workqueue有新任务时,会将任务放进cpu0和cpu1左侧的work链表中(所有cpu的work链表都放),调度任务时从两个cpu的work_pool中选择一个普通优先级线程放入执行(总共只选一个)

工作队列使用模板

/* 定义工作队列和关联函数 */
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);/* 中断处理底半部 */
void xxx_do_work(struct work_struct *work)
{...
}/* 中断处理顶半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{...schedule_work(&xxx_wq);...return IRQ_HANDLED;
}/* 设备驱动模块加载函数 */
int xxx_init(void)
{.../* 申请中断 */result = request_irq(xxx_irq, xxx_interrupt,0, "xxx", NULL);.../* 初始化工作队列 */INIT_WORK(&xxx_wq, xxx_do_work);...
}/* 设备驱动模块卸载函数 */
void xxx_exit(void)
{.../* 释放中断 */free_irq(xxx_irq, xxx_interrupt);...
}

测试结果:
工作队列可以被按键和定时器中断,也可以睡眠

在这里插入图片描述
在这里插入图片描述


3. 软中断(Softirq)

  • 软中断(Softirq) 也是一种传统的底半部处理机制, 它的执行时机通常是顶半部返回的时候, tasklet是基于软中断实现的, 因此也运行于软中断上下文。在Linux内核中, 用 softirq_action 结构体表征一个软中断, 这个结构体包含软中断处理函数指针和传递给该函数的参数。 使用 open_softirq() 函数可以注册软中断对应的处理函数, 而 raise_softirq() 函数可以触发一个软中断。
  • 软中断和tasklet运行于软中断上下文, 仍然属于原子上下文的一种, 而工作队列则运行于进程上下文。 因此, 在
    软中断和tasklet处理函数中不允许睡眠, 而在工作队列处理函数中允许睡眠
  • local_bh_disable() 和local_bh_enable() 是内核中用于禁止和使能软中断及tasklet底半部机制的函数。
    内核中采用softirq的地方包括HI_SOFTIRQ、 TIMER_SOFTIRQ、 NET_TX_SOFTIRQ、 NET_RX_SOFTIRQ、
    SCSI_SOFTIRQ、 TASKLET_SOFTIRQ等, 一般来说, 驱动的编写者不会也不宜直接使用softirq
  • 总结一下硬中断、 软中断和信号的区别: 硬中断是外部设备对CPU的中断软中断是中断底半部的一种处理机制, 而信号则是由内核(或其他进程) 对某个进程的中断
  • 在涉及系统调用的场合, 人们也常说通过软中断(例如ARM为swi) 陷入内核, 此时软中断的概念是指由软件指令引发的中断, 和我们这个地方说的softirq是两个完全不同的概念, 一个是software, 一个是soft。
  • 需要特别说明的是, 软中断以及基于软中断的tasklet如果在某段时间内大量出现的话, 内核会把后续软中断放入
    ksoftirqd内核线程中执行。 总的来说, 中断优先级高于软中断软中断又高于任何一个线程。 软中断适度线程
    化, 可以缓解高负载情况下系统的响应。

4. threaded_irq(中断线程化)

在内核中, 除了可以通过request_irq() 、 devm_request_irq() 申请中断以外, 还可以通过
request_threaded_irq() 和devm_request_threaded_irq() 申请。 这两个函数的原型为:

int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn,unsigned long flags, const char *name, void *dev);int devm_request_threaded_irq(struct device *dev, unsigned int irq,irq_handler_t handler, irq_handler_t thread_fn,unsigned long irqflags, const char *devname,void *dev_id);
  • 由此可见, 它们比request_irq() 、 devm_request_irq() 多了一个参数thread_fn。 用这两个API申请中断的时
    候, 内核会为相应的中断号分配一个对应的内核线程。 注意这个线程只针对这个中断号, 如果其他中断也通过
    request_threaded_irq() 申请, 自然会得到新的内核线程。
  • 参数handler对应的函数执行于中断上下文thread_fn参数对应的函数则执行于内核线程。 如果handler结束的时候, 返回值是IRQ_WAKE_THREAD, 内核会调度对应线程执行thread_fn对应的函数。
  • request_threaded_irq() 和devm_request_threaded_irq() 支持在irqflags中设置IRQF_ONESHOT标记, 这样内核会
    自动帮助我们在中断上下文中屏蔽对应的中断号而在内核调度thread_fn执行后, 重新使能该中断号。 对于我们
    无法在上半部清除中断的情况, IRQF_ONESHOT特别有用, 避免了中断服务程序一退出, 中断就洪泛的情况。
  • handler参数可以设置为NULL, 这种情况下, 内核会用默认的irq_default_primary_handler() 代替handler, 并会使
    用IRQF_ONESHOT标记。 irq_default_primary_handler() 定义为:
/*
* Default primary interrupt handler for threaded interrupts. Is
* assigned as primary handler when request_threaded_irq is called
* with handler == NULL. Useful for oneshot interrupts.
*/
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{return IRQ_WAKE_THREAD;
}

threaded_irq 使用案例 (可通过<asm/current.h>头文件的current对象获取线程名和pid)

/* 定义工作队列和关联函数 */
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);/* 中断线程化:存在于内核线程 */
static irqreturn_t key_threaded_func_handler(int irq, void *dev)
{int i = 0;printk("the process is\"%s\"(pid %d)\n",current->comm, current->pid);  //打印出线程名和pidwhile(i < 5){printk("%d ",i++);msleep(500);}printk("\n");return IRQ_HANDLED;
}/* 中断处理顶半部 */
static irqreturn_t key_irq_handler(int irq, void *dev)
{struct gpioirq *girq = dev;...return IRQ_WAKE_THREAD;	 
}/* 设备驱动模块加载函数 */
int xxx_init(void)
{.../* 申请中断 *//* 中断下半部:threaded_irq        IRQF_ONESHOT:在上下文中屏蔽对应中断号 */request_threaded_irq(xxx_irq, key_irq_handler, key_threaded_func_handler, IRQF_TRIGGER_FALLING, "hc_key_irq", xxx_arg);...
}/* 设备驱动模块卸载函数 */
void xxx_exit(void)
{.../* 释放中断 */free_irq(xxx_irq, xxx_arg);...
}

测试结果:
threaded_irq可以被中断、定时器打断,可以睡眠

在这里插入图片描述

PS:扩展一个 container_of 函数,它可以倒推导一个结构体的起始地址
在这里插入图片描述

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

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

相关文章

C语言a---b

C语言的编译遵循贪心读法&#xff0c;也就是说&#xff0c;对于有歧义的符号&#xff0c;编译器会一直读取&#xff0c;直到它的意思完结&#xff1b; a---b&#xff0c;是a-- -b还是a- --b&#xff0c;根据贪心法则&#xff0c;读到第二个减号&#xff0c;意思完结&#xff0c…

你知道mp3转换器怎么用吗?分享在线音频转换mp3怎么弄

飒飒&#xff1a;嘿&#xff0c;你有没有想过如何将在线音频转换为mp3格式&#xff1f; 潇潇&#xff1a;是的&#xff0c;我确实有过这个需求。在网上找到了一些工具和方法&#xff0c;可以帮助我们完成这个任务。 飒飒&#xff1a;那太好了&#xff01;你能告诉我一些详细的…

【新版系统架构】系统架构设计师教程全篇知识点提炼

第一章-绪论 架构的定义&#xff1a; 1、架构体现在组件中的一个系统的基本组织、彼此的关系和环境的关系及指导它的设计和发展的原则 2、系统是组织起来完成某一特定功能或一组功能的组件集 3、环境或者上下文决定了对这个系统的开发、运作、政策以及会对系统造成其他影响的…

开放式耳机哪个好?开放式耳机选购推荐

相比入耳式耳机&#xff0c;近几年流行的开放式耳机似乎更受大众欢迎&#xff0c;不入耳的设计&#xff0c;佩戴稳固舒适不容易掉落&#xff0c;也不伤耳&#xff0c;不仅能够提升幸福感还能听到周围环境声&#xff0c;避免在长期佩戴后舒适度下降的问题&#xff0c;对于产生的…

nginx日志分析,实时可视化工具goaccess

一款可以实时分析NGINX访问日志&#xff0c;并且支持可视化的软件 GoAccess - Visual Web Log Analyzer github如下&#xff1a;GitHub - allinurl/goaccess: GoAccess is a real-time web log analyzer and interactive viewer that runs in a terminal in *nix systems or th…

使用npm和nrm查看源和切换镜像

一、使用npm查看当前源、切换淘宝镜像、切换官方源 &#xff08;1&#xff09;npm查看当前源&#xff1a; npm get registry &#xff08;2&#xff09;npm设置淘宝镜像源&#xff1a; npm config set registry http://registry.npm.taobao.org &#xff08;3&#xff09;n…

mysql函数练习

创建表sch 向表中加入数据 1、创建一个可以统计表格内记录条数的存储函数 &#xff0c;函数名为count_sch() CREATE DEFINERroot% FUNCTION count_sch() RETURNS int(11) BEGINDECLARE total INT DEFAULT 0;#Routine body goes here...SELECT count(1) into total from sch;IN…

Openlayers实战:加载高德地图

在国内地图市场上,高德地图占据了重要的地位。Openlayers可以加载高德地图,这其中根据不同的参数,可以显示遥感地图,矢量地图等,可以显示中文,英文。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhuanlan),还是大剑师兰特(CSDN) * @此源代码版权归大剑师兰特所有…

2023西安电子科技大学杭州研究院|实验室介绍|夏令营

招募通知 西安电子科技大学杭州研究院2023年优秀大学生夏令营招募通知 西电杭收集表 https://docs.qq.com/form/page/DR0Faa2NKQ0Rqc0pC#/fill-detail 西电杭今年太火爆了 1200个人申请 123人入营 嘎嘎被拒 往年双非rank1 进西电还是比较容易 我认识的两个双非…

The 2022 ICPC Asia Xian Regional Contest(C/E/F/G/J/L)

原题链接&#xff1a;Dashboard - The 2022 ICPC Asia Xian Regional Contest - Codeforces 目录 J. Strange Sum F. Hotel C. Clone Ranran G. Perfect Word E. Find Maximum L. Tree J. Strange Sum 题意&#xff1a;思路&#xff1a;当我们选择in时&#xff0c;我们则可以…

Idea 修改默认 Maven 为自己的

每次我们打开新项目时,都要去配置一遍 maven,很麻烦,其实可以去修改 idea 里面默认的 maven 配置,这样后面不管是打开新项目还是老项目,就都是用的自己的 maven 了. 1.文件->新项目设置->新项目的设置 File->Other Settings -> Settings for New Project 2.然后和…

Meteor code must always run within a Fiber 报错解决办法

报错&#xff1a; 这样的写法会出现这个报错 大概的意思就是说&#xff0c;目前你这个函数不是运行在meteor的环境中&#xff0c;所以要使用Meteor.bindEnvironment&#xff0c;来改变函数运行的上下文 解决办法&#xff1a;