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.mfbz.cn/a/572667.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入浅出 Spring Boot 3.x:从原理到实战,全面解锁 Java 后端开发新潮流

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

一分钟教会你使用51cto网课视频下载工具下载51cto视频

想要学习技术知识&#xff0c;提升自己的职业能力&#xff0c;51cto是一个绝佳的选择。然而&#xff0c;有时候我们可能无法随时在线观看这些精彩的视频课程。别担心&#xff01;我将在一分钟内教您如何使用51cto视频下载工具&#xff0c;将这些宝贵的学习资源下载到您的设备上…

物联网鸿蒙实训解决方案

一、建设背景 在数字化浪潮汹涌的时代&#xff0c;华为鸿蒙系统以其前瞻的技术视野和创新的开发理念&#xff0c;成为了引领行业发展的风向标。 据华为开发者大会2023&#xff08;HDC. Together&#xff09;公布的数据&#xff0c;鸿蒙生态系统展现出了强劲的发展动力&#x…

钡铼IOy系列模块在无人值守智能仓库中的成功运用,提升仓储物流效率

随着科技的不断发展&#xff0c;无人值守智能仓库正成为现代物流行业的一个重要趋势。在这个快节奏的时代&#xff0c;提升仓储物流效率是企业追求的目标之一。钡铼IOy系列模块为无人值守智能仓库的成功运作提供了关键支持。本文将探讨钡铼IOy系列模块在无人值守智能仓库中的应…

子域名如何启用HTTPS,有免费泛域名SSL证书吗

如今HTTPS已经成为了网站标配&#xff0c;然而&#xff0c;对于一些刚刚起步的网站或是个人博客而言&#xff0c;如何自动跳转到HTTPS&#xff0c;以及免费SSL证书的获取&#xff0c;可能还是一个需要解决的问题。下面就来详细解答这两个问题。 我们需要明确HTTPS与SSL之间的关…

OpenAI 和 Moderna 合作,推进 mRNA 医学

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、关于 Moderna Moderna 是 mRNA 医学领域的佼佼者&#xff0c;其通过不断推动 mRNA 技术的发展&#xff0c;正在重塑药物的制造方式&#xff0c;并深刻地改变我们治疗和预防疾病的方法。凭借在科学、…

包装类简单认识泛型

文章目录 包装类基本数据类型和对应的包装类装箱和拆箱自动装箱和自动拆箱 什么是泛型引出泛型语法 泛型类的使用 包装类 在Java中&#xff0c;由于基本类型不是继承自Object&#xff0c;为了在泛型代码中可以支持基本类型&#xff0c;Java给每个基本类型都对应了一个包装类型…

骑砍2霸主MOD开发(6)-使用C#-Harmony修改本体游戏逻辑

一.C#-Harmony反射及动态注入 利用C#运行时环境的反射原理,实现对已加载DLL,未加载DLL中代码替换和前置后置插桩. C#依赖库下载地址:霸王•吕布 / CSharpHarmonyLib GitCodehttps://gitcode.net/qq_35829452/csharpharmonylib 根据实际运行.Net环境选择对应版本的0Harmony.dll…

为什么没有办法画圆角?

在AutoCAD里面画圆角不是单纯的在两条线之间点一下就好了&#xff0c; 主要要输入这个半径。

“浙大学报英文版”订阅号这篇文章,丢名校脸面

今天翻到“浙大学报英文版”订阅号分享的一篇文章&#xff0c;介绍了一篇奇文&#xff0c;该论文的摘要&#xff08;Abstract&#xff09;非常任性&#xff0c;仅有一个单词— “Yes”。 原文链接&#xff1a;https://mp.weixin.qq.com/s/riw_YU3caBf7E6rdCbLE-Q 该论文是由J. …

如何为Postgres数据库设置安全的访问控制和权限管理

文章目录 解决方案1. 使用角色和权限管理2. 配置认证方法3. 使用网络访问控制4. 定期审查和更新权限 示例代码1. 创建角色并分配权限2. 配置密码认证3. 配置网络访问控制 总结 PostgreSQL是一个功能强大的开源关系型数据库系统&#xff0c;提供了丰富的权限和访问控制机制&…

bit、进制、位、时钟(窗口)、OSI七层网络模型、协议、各种码

1.bit与进制 &#xff08;个人理解&#xff0c;具体电路是非常复杂的&#xff09; 物理层数据流&#xff0c;bit表示物理层数据传输单位&#xff0c; 一个电路当中&#xff0c;通过通断来表示数字1和0 两个电路要通讯&#xff0c;至少要两根线&#xff0c;一根作为电势参照…

C语言入门课程学习笔记2

C语言入门课程学习笔记2 第8课 - 四则运算与关系运算第9课 - 逻辑运算与位运算第10课 - 深度剖析位运算第11课 - 程序中的选择结构 本文学习自狄泰软件学院 唐佐林老师的 C语言入门课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学习记录 第8课 - 四则运算与关系…

Java | Leetcode Java题解之第48题旋转图像

题目&#xff1a; 题解&#xff1a; class Solution {public void rotate(int[][] matrix) {int n matrix.length;// 水平翻转for (int i 0; i < n / 2; i) {for (int j 0; j < n; j) {int temp matrix[i][j];matrix[i][j] matrix[n - i - 1][j];matrix[n - i - 1]…

【Camera KMD ISP SubSystem笔记】CAM SYNC与DRQ②

DRQ的作用&#xff1a; DRQ负责调度管理pipeline里的node处理逻辑(通过node之间的dependency依赖机制) 利用多线程并行处理Pipeline中并行的node&#xff0c;加快处理速度 DRQ运转流程&#xff1a; DRQ先告诉node fill dependency&#xff0c; 此时seq id 为0…

15.接口自动化学习-Mock(挡板/测试桩)

场景&#xff1a; 新需求还未开发时&#xff0c;使用mock提早介入测试&#xff0c;等后边开发后&#xff0c;进行调试 三方接口返回效率低&#xff0c;使用mock技术走通流程 1.mock方式 &#xff08;1&#xff09;如果会写django或flask,可以写简单对应的代码 &#xff08;…

小红书的影视剧泥土刷剧5天涨千粉7天接商单轻轻松松月入了万没脑子运送游戏玩法,新手也可以快速上手

大家好&#xff0c;今天我将为大家介绍一个项目&#xff1a;在小红书上通过观看和分享影视剧内容&#xff0c;五天涨千粉&#xff0c;七天接商业订单&#xff0c;轻松月入过万。这个项目的玩法简单易学&#xff0c;即使是新手也能快速上手。 下载 地 址 &#xff1a; laoa1.c…

【网络安全】系统0day分析

前言 起因看见通告&#xff0c;描述是通过/lfw/core/rpc接口访问到PortalSpecServiceImpl类中的createSkinFile方法。 补丁名称&#xff1a;patch_portal65_lfw任意文件上传漏洞 补丁编码&#xff1a;NCM_NC6.5_000_109902_20240301_GP_281362040 【386G《黑客&网络安全入…

基于STM32的蓝牙小车(虚拟串口模拟)的Proteus仿真

文章目录 一、前言二、仿真图1.要求2.思路3.画图3.1 电源部分3.2 超声波测距部分3.3 电机驱动部分3.4 按键部分3.5 蓝牙部分3.6 显示屏部分3.7 整体 4.仿真5.软件 三、总结 一、前言 proteus本身并不支持蓝牙仿真&#xff0c;这里我采用虚拟串口的方式来模拟蓝牙控制。 这里给…
最新文章